| # Copyright 2021 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Tests for pw_build.create_python_tree""" |
| |
| import io |
| import os |
| from pathlib import Path |
| import tempfile |
| from typing import List |
| import unittest |
| |
| from parameterized import parameterized # type: ignore |
| |
| from pw_build.python_package import PythonPackage |
| from pw_build.create_python_tree import ( |
| build_python_tree, |
| copy_extra_files, |
| load_common_config, |
| update_config_with_packages, |
| ) |
| from pw_build.generate_python_package import PYPROJECT_FILE |
| |
| |
| def _setup_cfg(package_name: str, install_requires: str = '') -> str: |
| return f''' |
| [metadata] |
| name = {package_name} |
| version = 0.0.1 |
| author = Pigweed Authors |
| author_email = pigweed-developers@googlegroups.com |
| description = Pigweed swiss-army knife |
| |
| [options] |
| packages = find: |
| zip_safe = False |
| {install_requires} |
| |
| [options.package_data] |
| {package_name} = |
| py.typed |
| ''' |
| |
| |
| def _create_fake_python_package(location: Path, |
| package_name: str, |
| files: List[str], |
| install_requires: str = '') -> None: |
| for file in files: |
| destination = location / file |
| destination.parent.mkdir(parents=True, exist_ok=True) |
| text = f'"""{package_name}"""' |
| if str(destination).endswith('setup.cfg'): |
| text = _setup_cfg(package_name, install_requires) |
| elif str(destination).endswith('pyproject.toml'): |
| # Make sure pyproject.toml file has valid syntax. |
| text = PYPROJECT_FILE |
| destination.write_text(text) |
| |
| |
| class TestCreatePythonTree(unittest.TestCase): |
| """Integration tests for create_python_tree.""" |
| def setUp(self): |
| self.maxDiff = None # pylint: disable=invalid-name |
| # Save the starting working directory for returning to later. |
| self.start_dir = Path.cwd() |
| # Create a temp out directory |
| self.temp_dir = tempfile.TemporaryDirectory() |
| |
| def tearDown(self): |
| # cd to the starting dir before cleaning up the temp out directory |
| os.chdir(self.start_dir) |
| # Delete the TemporaryDirectory |
| self.temp_dir.cleanup() |
| |
| def _check_result_paths_equal(self, install_dir, expected_results) -> None: |
| # Normalize path strings to posix before comparing. |
| expected_paths = set(Path(p).as_posix() for p in expected_results) |
| actual_paths = set( |
| p.relative_to(install_dir).as_posix() |
| for p in install_dir.glob('**/*') if p.is_file()) |
| self.assertEqual(expected_paths, actual_paths) |
| |
| def test_update_config_with_packages(self) -> None: |
| """Test merging package setup.cfg files.""" |
| temp_root = Path(self.temp_dir.name) |
| common_config = temp_root / 'common_setup.cfg' |
| common_config.write_text(''' |
| [metadata] |
| name = megapackage |
| version = 0.0.1 |
| author = Pigweed Authors |
| author_email = pigweed-developers@googlegroups.com |
| description = Pigweed swiss-army knife |
| |
| [options] |
| zip_safe = False |
| |
| [options.package_data] |
| megapackage = |
| py.typed |
| ''') |
| config = load_common_config(common_config=common_config, |
| append_git_sha=False, |
| append_date=False) |
| config_metadata = dict(config['metadata'].items()) |
| self.assertIn('name', config_metadata) |
| |
| pkg1_root = temp_root / 'pkg1' |
| pkg2_root = temp_root / 'pkg2' |
| _create_fake_python_package(pkg1_root, |
| 'mars', [ |
| 'planets/BUILD.mars_rocket', |
| 'planets/mars/__init__.py', |
| 'planets/mars/__main__.py', |
| 'planets/mars/moons/__init__.py', |
| 'planets/mars/moons/deimos.py', |
| 'planets/mars/moons/phobos.py', |
| 'planets/hohmann_transfer_test.py', |
| 'planets/pyproject.toml', |
| 'planets/setup.cfg', |
| ], |
| install_requires=''' |
| install_requires = |
| coloredlogs |
| coverage |
| cryptography |
| graphlib-backport;python_version<'3.9' |
| httpwatcher |
| ''') |
| |
| os.chdir(pkg1_root) |
| pkg1 = PythonPackage.from_dict( |
| **{ |
| 'generate_setup': { |
| 'metadata': { |
| 'name': 'mars', |
| 'version': '0.0.1' |
| }, |
| }, |
| 'inputs': [], |
| 'setup_sources': [ |
| 'planets/pyproject.toml', |
| 'planets/setup.cfg', |
| ], |
| 'sources': [ |
| 'planets/mars/__init__.py', |
| 'planets/mars/__main__.py', |
| 'planets/mars/moons/__init__.py', |
| 'planets/mars/moons/deimos.py', |
| 'planets/mars/moons/phobos.py', |
| ], |
| 'tests': [ |
| 'planets/hohmann_transfer_test.py', |
| ], |
| }) |
| |
| _create_fake_python_package(pkg2_root, |
| 'saturn', [ |
| 'planets/BUILD.saturn_rocket', |
| 'planets/hohmann_transfer_test.py', |
| 'planets/pyproject.toml', |
| 'planets/saturn/__init__.py', |
| 'planets/saturn/__main__.py', |
| 'planets/saturn/misson.py', |
| 'planets/saturn/moons/__init__.py', |
| 'planets/saturn/moons/enceladus.py', |
| 'planets/saturn/moons/iapetus.py', |
| 'planets/saturn/moons/rhea.py', |
| 'planets/saturn/moons/titan.py', |
| 'planets/setup.cfg', |
| 'planets/setup.py', |
| ], |
| install_requires=''' |
| install_requires = |
| graphlib-backport;python_version<'3.9' |
| httpwatcher |
| ''') |
| os.chdir(pkg2_root) |
| pkg2 = PythonPackage.from_dict( |
| **{ |
| 'inputs': [], |
| 'setup_sources': [ |
| 'planets/pyproject.toml', |
| 'planets/setup.cfg', |
| 'planets/setup.py', |
| ], |
| 'sources': [ |
| 'planets/saturn/__init__.py', |
| 'planets/saturn/__main__.py', |
| 'planets/saturn/misson.py', |
| 'planets/saturn/moons/__init__.py', |
| 'planets/saturn/moons/enceladus.py', |
| 'planets/saturn/moons/iapetus.py', |
| 'planets/saturn/moons/rhea.py', |
| 'planets/saturn/moons/titan.py', |
| ], |
| 'tests': [ |
| 'planets/hohmann_transfer_test.py', |
| ] |
| }) |
| |
| update_config_with_packages(config=config, |
| python_packages=[pkg1, pkg2]) |
| |
| setup_cfg_text = io.StringIO() |
| config.write(setup_cfg_text) |
| expected_cfg = ''' |
| [metadata] |
| name = megapackage |
| version = 0.0.1 |
| author = Pigweed Authors |
| author_email = pigweed-developers@googlegroups.com |
| description = Pigweed swiss-army knife |
| |
| [options] |
| zip_safe = False |
| packages = find: |
| install_requires = |
| coloredlogs |
| coverage |
| cryptography |
| graphlib-backport;python_version<'3.9' |
| httpwatcher |
| |
| [options.package_data] |
| megapackage = |
| py.typed |
| mars = |
| py.typed |
| saturn = |
| py.typed |
| |
| [options.entry_points] |
| ''' |
| result_cfg_lines = [ |
| line.rstrip().replace('\t', ' ') |
| for line in setup_cfg_text.getvalue().splitlines() if line |
| ] |
| expected_cfg_lines = [ |
| line.rstrip() for line in expected_cfg.splitlines() if line |
| ] |
| self.assertEqual(expected_cfg_lines, result_cfg_lines) |
| |
| |
| @parameterized.expand([ |
| ( |
| # Test name |
| 'working case', |
| # Package name |
| 'mars', |
| # File list |
| [ |
| 'planets/BUILD.mars_rocket', |
| 'planets/mars/__init__.py', |
| 'planets/mars/__main__.py', |
| 'planets/mars/moons/__init__.py', |
| 'planets/mars/moons/deimos.py', |
| 'planets/mars/moons/phobos.py', |
| 'planets/hohmann_transfer_test.py', |
| 'planets/pyproject.toml', |
| 'planets/setup.cfg', |
| ], |
| # Extra_files |
| [], |
| # Package definition |
| { |
| 'generate_setup': { |
| 'metadata': { |
| 'name': 'mars', |
| 'version': '0.0.1' |
| }, |
| }, |
| 'inputs': [ |
| ], |
| 'setup_sources': [ |
| 'planets/pyproject.toml', |
| 'planets/setup.cfg', |
| ], |
| 'sources': [ |
| 'planets/mars/__init__.py', |
| 'planets/mars/__main__.py', |
| 'planets/mars/moons/__init__.py', |
| 'planets/mars/moons/deimos.py', |
| 'planets/mars/moons/phobos.py', |
| ], |
| 'tests': [ |
| 'planets/hohmann_transfer_test.py', |
| ], |
| }, |
| # Output file list |
| [ |
| 'mars/__init__.py', |
| 'mars/__main__.py', |
| 'mars/moons/__init__.py', |
| 'mars/moons/deimos.py', |
| 'mars/moons/phobos.py', |
| 'mars/tests/hohmann_transfer_test.py', |
| ], |
| ), |
| |
| ( |
| # Test name |
| 'with extra files', |
| # Package name |
| 'saturn', |
| # File list |
| [ |
| 'planets/BUILD.saturn_rocket', |
| 'planets/hohmann_transfer_test.py', |
| 'planets/pyproject.toml', |
| 'planets/saturn/__init__.py', |
| 'planets/saturn/__main__.py', |
| 'planets/saturn/misson.py', |
| 'planets/saturn/moons/__init__.py', |
| 'planets/saturn/moons/enceladus.py', |
| 'planets/saturn/moons/iapetus.py', |
| 'planets/saturn/moons/rhea.py', |
| 'planets/saturn/moons/titan.py', |
| 'planets/setup.cfg', |
| 'planets/setup.py', |
| ], |
| # Extra files |
| [ |
| 'planets/BUILD.saturn_rocket > out/saturn/BUILD.rocket', |
| ], |
| # Package definition |
| { |
| 'inputs': [ |
| ], |
| 'setup_sources': [ |
| 'planets/pyproject.toml', |
| 'planets/setup.cfg', |
| 'planets/setup.py', |
| ], |
| 'sources': [ |
| 'planets/saturn/__init__.py', |
| 'planets/saturn/__main__.py', |
| 'planets/saturn/misson.py', |
| 'planets/saturn/moons/__init__.py', |
| 'planets/saturn/moons/enceladus.py', |
| 'planets/saturn/moons/iapetus.py', |
| 'planets/saturn/moons/rhea.py', |
| 'planets/saturn/moons/titan.py', |
| ], |
| 'tests': [ |
| 'planets/hohmann_transfer_test.py', |
| ] |
| }, |
| # Output file list |
| [ |
| 'saturn/BUILD.rocket', |
| 'saturn/__init__.py', |
| 'saturn/__main__.py', |
| 'saturn/misson.py', |
| 'saturn/moons/__init__.py', |
| 'saturn/moons/enceladus.py', |
| 'saturn/moons/iapetus.py', |
| 'saturn/moons/rhea.py', |
| 'saturn/moons/titan.py', |
| 'saturn/tests/hohmann_transfer_test.py', |
| ], |
| ), |
| ]) # yapf: disable |
| def test_build_python_tree( |
| self, |
| _test_name, |
| package_name, |
| file_list, |
| extra_files, |
| package_definition, |
| expected_file_list, |
| ) -> None: |
| """Check results of build_python_tree and copy_extra_files.""" |
| temp_root = Path(self.temp_dir.name) |
| _create_fake_python_package(temp_root, package_name, file_list) |
| |
| os.chdir(temp_root) |
| install_dir = temp_root / 'out' |
| |
| package = PythonPackage.from_dict(**package_definition) |
| build_python_tree(python_packages=[package], |
| tree_destination_dir=install_dir, |
| include_tests=True) |
| copy_extra_files(extra_files) |
| |
| # Check expected files are in place. |
| self._check_result_paths_equal(install_dir, expected_file_list) |
| |
| @parameterized.expand([ |
| ( |
| # Test name |
| 'everything in correct locations', |
| # Package name |
| 'planets', |
| # File list |
| [ |
| 'BUILD.mars_rocket', |
| ], |
| # Extra_files |
| [ |
| 'BUILD.mars_rocket > out/mars/BUILD.rocket', |
| ], |
| # Output file list |
| [ |
| 'mars/BUILD.rocket', |
| ], |
| # Should raise exception |
| None, |
| ), |
| ( |
| # Test name |
| 'missing source files', |
| # Package name |
| 'planets', |
| # File list |
| [ |
| 'BUILD.mars_rocket', |
| ], |
| # Extra_files |
| [ |
| 'BUILD.venus_rocket > out/venus/BUILD.rocket', |
| ], |
| # Output file list |
| [], |
| # Should raise exception |
| FileNotFoundError, |
| ), |
| ( |
| # Test name |
| 'existing destination files', |
| # Package name |
| 'planets', |
| # File list |
| [ |
| 'BUILD.jupiter_rocket', |
| 'out/jupiter/BUILD.rocket', |
| ], |
| # Extra_files |
| [ |
| 'BUILD.jupiter_rocket > out/jupiter/BUILD.rocket', |
| ], |
| # Output file list |
| [], |
| # Should raise exception |
| FileExistsError, |
| ), |
| ]) # yapf: disable |
| def test_copy_extra_files( |
| self, |
| _test_name, |
| package_name, |
| file_list, |
| extra_files, |
| expected_file_list, |
| should_raise_exception, |
| ) -> None: |
| """Check results of build_python_tree and copy_extra_files.""" |
| temp_root = Path(self.temp_dir.name) |
| _create_fake_python_package(temp_root, package_name, file_list) |
| |
| os.chdir(temp_root) |
| install_dir = temp_root / 'out' |
| |
| # If exceptions should be raised |
| if should_raise_exception: |
| with self.assertRaises(should_raise_exception): |
| copy_extra_files(extra_files) |
| return |
| |
| # Do the copy |
| copy_extra_files(extra_files) |
| # Check expected files are in place. |
| self._check_result_paths_equal(install_dir, expected_file_list) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |