def test_source_variant(self, run_command, has_documentation): """Create a source (non-built) Rez package that has 1+ variants and run a command on it. Args: run_command (:class:`mock.MagicMock`): A replacement for the function that would normally run as part of the commands that run on a Rez package. If this function gets run, we know that this test passes. """ has_documentation.side_effect = [False, True] packages = self._setup_test( run_command, package_common.make_source_variant_python_package) roots = list( set( inspection.get_packages_path_from_package(package) for package in packages)) directory = tempfile.mkdtemp(suffix="_some_build_location") self.delete_item_later(directory) build_package = package_common.make_build_python_package( textwrap.dedent("""\ name = "project_b" version = "2.0.0" revision = { "push_url": "fake_git_repo", } """), "project_b", "2.0.0", directory, ) build_package = finder.get_nearest_rez_package( os.path.join(build_package)) build_root = inspection.get_packages_path_from_package(build_package) paths = roots + [build_root] with rez_configuration.patch_packages_path(paths): self._test( ( set(), [], [ # Important note: "project_b" doesn't actually already # have documentation. It's a result of `side_effect`, above. # worker.Skip( build_package, finder.get_package_root(build_package), "Python package already has documentation.", ) ], ), paths, ) self.assertEqual(1, run_command.call_count)
def _make_installed_package(self, name, text, packages_path=None): """Create a Rez source package and install it into a temporary directory. All temporary directories will be marked for clean-up. Clean-up occurs after each test is run. Args: name (str): The name of the source Rez packge to create. text (str): The text that will be used for a "package.py" file. packages_path (list[str], optional): The paths that will be used to search for Rez packages while building. This is usually to make it easier to find package dependencies. If `packages_path` is not defined, Rez will use its default paths. Default is None. Returns: :class:`rez.developer_package.DeveloperPackage`: The package the represents the newly-built package. """ directory = make_fake_source_package(name, text) package = packages_.get_developer_package(directory) new_package = creator.build(package, tempfile.mkdtemp(), packages_path=packages_path, quiet=True) self.delete_item_later(os.path.dirname(directory)) self.delete_item_later( inspection.get_packages_path_from_package(new_package)) return new_package
def make_symlinks(paths, directory, force=False): """Make symlinks for all of the given Rez-specific paths and place them under `directory`. Args: paths (iter[str]): Each installed Rez package path to generate symlinks for. directory (str): The absolute path to a directory on-disk to place the symlinks under. This path can later be fed directly into REZ_PACKAGES_PATH to reproduce the environment. force (bool, optional): If True, delete any symlinks that may already exist. If False and symlinks exist, normal exceptions will be raised. Default is False. """ for path in paths: package = finder.get_nearest_rez_package(path) root = inspection.get_packages_path_from_package(package) relative_directory = os.path.relpath(path, root) destination_directory = os.path.join(directory, relative_directory) destination_root = os.path.dirname(destination_directory) if not os.path.isdir(destination_root): os.makedirs(destination_root) if force and os.path.exists(destination_directory): os.remove(destination_directory) os.symlink(path, destination_directory)
def test_installed_package_no_variants(self): """Create a Rez package with no variants and get the folders affecting PYTHONPATH.""" dependencies = self._make_test_dependencies() package, root = self._make_fake_rez_install_package( "some_fake_package", "1.0.0", textwrap.dedent("""\ name = "some_fake_package" version = "1.0.0" def commands(): import os env.PYTHONPATH.append(os.path.join("{root}", "python")) """), ) context = resolved_context.ResolvedContext( ["{package.name}==1.0.0".format(package=package)], package_paths=[inspection.get_packages_path_from_package(package)] + dependencies + config.packages_path, # pylint: disable=no-member ) python_files = inspection.get_package_python_paths( package, context.get_environ().get("PYTHONPATH", "").split(os.pathsep)) self.assertEqual( {os.path.join(root, "some_fake_package", "1.0.0", "python")}, python_files)
def test_no_remote(self): """Check that a fix will not run if the package's repository has no git remote.""" root = os.path.join(tempfile.mkdtemp(), "test_folder") os.makedirs(root) self.delete_item_later(root) package = package_common.make_package("project_a", root, self._make_source_with_no_remote) invalids = [ exceptions.NoRepositoryRemote( package, os.path.join(root, "project_a"), "No remote origin could be found for", ) ] paths = [inspection.get_packages_path_from_package(package)] with rez_configuration.patch_packages_path(paths): unfixed, invalids, skips = self._test_unhandled() self.assertEqual(set(), unfixed) self.assertEqual([], skips) invalid = next(iter(invalids)) self.assertTrue( str(invalid).startswith( "Generic error: No remote origin could be found for"))
def test_partial_errors(self, find_api_documentation): """Allow other keys in the intersphinx mapping that may not be in the found requirements.""" dependency1 = "foo_bar" dependency2 = "another_one" url = "https://some_path.com" find_api_documentation.return_value = url existing_intersphinx = { "fake_environment": ("http:/some_url.com", None) } package = self._make_fake_package_with_intersphinx_mapping( textwrap.dedent("""\ name = "thing" version = "1.0.0" requires = [ "another_one", "foo_bar", ] """), existing_intersphinx, ) dependency1 = self._make_dependency_package("foo_bar", "1.0.0") dependency2 = self._make_dependency_package("another_one", "2.0.0") config.packages_path.append( # pylint: disable=no-member inspection.get_packages_path_from_package(dependency1)) config.packages_path.append( # pylint: disable=no-member inspection.get_packages_path_from_package(dependency2)) root = finder.get_package_root(package) self.assertEqual(existing_intersphinx, cli.get_existing_intersphinx_links(root)) expected = { dependency1.name: (url, None), dependency2.name: (url, None), } self.assertEqual(expected, cli.find_intersphinx_links(package.requires or []))
def test_installed_package_with_variants(self): """Create a Rez package with variants and get the folders affecting PYTHONPATH.""" dependencies = self._make_test_dependencies() package = self._make_fake_rez_source_package( "some_fake_package", textwrap.dedent("""\ name = "some_fake_package" version = "1.0.0" variants = [["some_dependency-1", "another_one-2.3"], ["another_one-2.3"]] build_command = "echo 'foo'" def commands(): import os env.PYTHONPATH.append(os.path.join("{root}", "python")) """), ) install_path = tempfile.mkdtemp(suffix="_install_path") self.delete_item_later(install_path) build_package = creator.build( package, install_path, packages_path=dependencies + config.packages_path, # pylint: disable=no-member quiet=True, ) context = resolved_context.ResolvedContext( [ "{build_package.name}==1.0.0".format( build_package=build_package) ], package_paths=[ inspection.get_packages_path_from_package(build_package) ] + dependencies + config.packages_path, # pylint: disable=no-member ) python_files = inspection.get_package_python_paths( build_package, context.get_environ().get("PYTHONPATH", "").split(os.pathsep)) self.assertEqual( { os.path.join( install_path, "some_fake_package", "1.0.0", "another_one-2.3", "python", ) }, python_files, )
def _make_test_dependencies(self): """Create a couple of generic, installed Rez packages to use for unittesting.""" # To actually make this test work, "some_fake_package" needs some variants. # # It may be overkill, but we're going to make some quick Rez packages # that can be used for variants. # dependency1, _ = self._make_fake_rez_install_package( "some_dependency", "1.1.0", textwrap.dedent("""\ name = "some_dependency" version = "1.1.0" def commands(): import os env.PYTHONPATH.append(os.path.join("{root}", "python")) """), ) dependency2, _ = self._make_fake_rez_install_package( "another_one", "2.3.0", textwrap.dedent("""\ name = "another_one" version = "2.3.0" def commands(): import os env.PYTHONPATH.append(os.path.join("{root}", "python")) """), ) return [ inspection.get_packages_path_from_package(dependency1), inspection.get_packages_path_from_package(dependency2), ]
def _make_test_case(self, name, text, extra_paths=None): """Create a Rez package that has some Python files inside of it. This method exists to make unittesting a bit easier to understand. Note: This method creates Python files whether `text` actually appends to PYTHONPATH or not. They just get ignored by unittests, in that case. Args: name (str): The name of the Rez package family to make. text (str): The source code used for the package.py file. extra_paths (list[str], optional): A list of paths used to search for Rez packages during the Rez resolve that this function calls. If no paths are given, Rez will default to :attr:`rez.config.packages_path` instead. Default is None. Returns: tuple[set[str], str]: The found Python files (excluding __init__.py files) and the parent folder where the created package.py goes. """ if not extra_paths: extra_paths = [] package = self._make_fake_rez_source_package(name, text) root = finder.get_package_root(package) python_root = os.path.join(root, "python") os.makedirs(python_root) open(os.path.join(python_root, "thing.py"), "w").close() open(os.path.join(python_root, "another.py"), "w").close() context = resolved_context.ResolvedContext( ["{package.name}==".format(package=package)], package_paths=[inspection.get_packages_path_from_package(package)] + extra_paths + config.packages_path, # pylint: disable=no-member ) paths = inspection.get_package_python_paths( package, context.get_environ().get("PYTHONPATH", "").split(os.pathsep)) return paths, root
def test_multiple_errors(self, find_api_documentation): """Check that the tool contains more than one error.""" dependency1 = "foo_bar" dependency2 = "another_one" url = "https://some_path.com" find_api_documentation.return_value = url package = self._make_fake_package_with_intersphinx_mapping( textwrap.dedent("""\ name = "thing" version = "1.0.0" requires = [ "another_one", "foo_bar", ] """), dict(), ) dependency1 = self._make_dependency_package("foo_bar", "1.0.0") dependency2 = self._make_dependency_package("another_one", "2.0.0") config.packages_path.append( # pylint: disable=no-member inspection.get_packages_path_from_package(dependency1)) config.packages_path.append( # pylint: disable=no-member inspection.get_packages_path_from_package(dependency2)) root = finder.get_package_root(package) self.assertEqual(dict(), cli.get_existing_intersphinx_links(root)) expected = { dependency1.name: (url, None), dependency2.name: (url, None), } self.assertEqual(expected, cli.find_intersphinx_links(package.requires or []))
def test_get_context(self): """Check that Rez can resolve a context correctly and return it.""" directory = testing_packaging.make_fake_source_package( "some_package", textwrap.dedent("""\ name = "some_package" version = "1.0.0" requires = [ "some_dependency-1", ] """), ) self.delete_item_later(os.path.dirname(directory)) package = packages_.get_developer_package(directory) dependency_directory = testing_packaging.make_fake_source_package( "some_dependency", textwrap.dedent("""\ name = "some_dependency" version = "1.1.0" build_command = "echo 'foo'" """), ) self.delete_item_later(os.path.dirname(dependency_directory)) dependency_package = packages_.get_developer_package( dependency_directory) dependency_build_path = tempfile.mkdtemp( suffix="_dependency_build_path") self.delete_item_later(dependency_build_path) dependency_package = creator.build(dependency_package, dependency_build_path, quiet=True) dependency_path = inspection.get_packages_path_from_package( dependency_package) context = dict() with testing_packaging.override_packages_path([dependency_path], prepend=True): packaging.SourceResolvedContext.run(package, context) self.assertTrue(lint_constant.RESOLVED_SOURCE_CONTEXT in context) rez_resolved = context[lint_constant.RESOLVED_SOURCE_CONTEXT] self.assertEqual(os.path.join(directory), rez_resolved.get_environ()["REZ_SOME_PACKAGE_ROOT"]) self.assertEqual(set(), context[lint_constant.DEPENDENT_PACKAGES])
def test_build_variant(self, run_command): """Create a build package that has 1+ Rez package variants and run a command on it. Args: run_command (:class:`mock.MagicMock`): A replacement for the function that would normally run as part of the commands that run on a Rez package. If this function gets run, we know that this test passes. """ packages = self._setup_test(run_command, package_common.make_build_python_package) path_root = inspection.get_packages_path_from_package(packages[0]) self._test((set(), [], []), [path_root]) self.assertEqual(1, run_command.call_count)
def _setup_test(self, run_command, builder, variants=None): """Build an environment that tests in this class can use. Args: run_command (:class:`mock.MagicMock`): A replacement for the function that would normally run as part of the commands that run on a Rez package. If this function gets run, we know that this test passes. builder (callable[str, str, str, str] -> str): The function that is used to generate the "contents" of the Rez package. This function is only responsible for creating the package.py/rezbuild.py files, it doesn't create a Python package, for example. This parameter is responsible for creating the rest of the files of the package. variants (list[list[str]]): The extra Rez package build configurations for this package. No variants will be installed if nothing is given. Default is None. Returns: list[:class:`rez.developer_package.Package`]: The generated Rez packages that are inside of a git repository and will be used for unittesting. """ run_command.return_value = "" root = os.path.join(tempfile.mkdtemp(), "test_folder") os.makedirs(root) self.delete_item_later(root) packages = [ package_common.make_package("project_a", root, builder, variants=variants) ] path_root = inspection.get_packages_path_from_package(packages[0]) repository, packages, remote_root = package_common.make_fake_repository( packages, path_root) self.delete_item_later(repository.working_dir) self.delete_item_later(remote_root) return packages
def test_source_no_variant(self, run_command): """Create a source (non-built) Rez package and run a command on it. Args: run_command (:class:`mock.MagicMock`): A replacement for the function that would normally run as part of the commands that run on a Rez package. If this function gets run, we know that this test passes. """ packages = self._setup_test(run_command, package_common.make_source_python_package) path_root = inspection.get_packages_path_from_package(packages[0]) paths = [path_root] with rez_configuration.patch_packages_path(paths): self._test((set(), [], []), [path_root]) self.assertEqual(1, run_command.call_count)
def test_one_error_002(self, find_api_documentation): """Check that the tool contains a single missing intersphinx mapping. This test assumes that the user is calling ``rez_documentation_check`` along with the other package that they're calling from. In other words, every imported dependency is already known before ``rez-documentation-check`` is ever run. """ dependency = "foo_bar" url = "https://some_path.com" find_api_documentation.return_value = url package = self._make_fake_package_with_intersphinx_mapping( textwrap.dedent("""\ name = "thing" version = "1.0.0" requires = [ "foo_bar", ] """), dict(), ) dependency = self._make_dependency_package("foo_bar", "1.0.0") path = inspection.get_packages_path_from_package(dependency) config.packages_path.append(path) # pylint: disable=no-member root = finder.get_package_root(package) self.assertEqual(dict(), cli.get_existing_intersphinx_links(root)) self.assertEqual( {dependency.name: (url, None)}, cli.find_intersphinx_links(package.requires or []), ) self.assertEqual( {dependency.name: (url, None)}, cli.get_missing_intersphinx_mappings(package), )
def test_egg(self, _create_pull_request): """Bump a released Rez package which only contains a single zipped .egg file.""" def _create_package(root, name, version, requirements=None): text = textwrap.dedent("""\ name = "{name}" version = "{version}" description = "A package.py Rez package that won't be converted." build_command = "python {{root}}/rezbuild.py" def commands(): import os env.PYTHONPATH.append(os.path.join("{{root}}", "python.egg")) """) text = text.format(name=name, version=version) if requirements: text += "\nrequires = {requirements!r}".format( requirements=requirements) with open(os.path.join(root, "package.py"), "w") as handler: handler.write(text) with open(os.path.join(root, "rezbuild.py"), "w") as handler: handler.write( textwrap.dedent("""\ #!/usr/bin/env python # -*- coding: utf-8 -*- import os import shutil import zipfile def main(): source = os.environ["REZ_BUILD_SOURCE_PATH"] build = os.environ["REZ_BUILD_PATH"] python_directory = os.path.join(source, "python") with zipfile.ZipFile(os.path.join(build, "python.egg"), "w") as handler: for root, folders, files in os.walk(python_directory): relative_root = os.path.relpath(root, python_directory) for folder in folders: handler.write( os.path.join(root, folder), os.path.join(relative_root, folder), ) for file_ in files: handler.write( os.path.join(root, file_), os.path.join(relative_root, file_), ) shutil.copy2( handler.filename, os.path.join( os.environ["REZ_BUILD_INSTALL_PATH"], os.path.basename(handler.filename) ), ) if __name__ == "__main__": main() """)) python_root = os.path.join(root, "python") os.makedirs(python_root) common.make_files( { "something": { "__init__.py": None, "inner_folder": { "__init__.py": None, }, }, "some_other_package_folder": { "__init__.py": None, "some_module.py": None, }, }, python_root, ) def _make_package_with_contents(root, name, version, create_package): directory = os.path.join(root, name) os.makedirs(directory) create_package(directory, name, version) return finder.get_nearest_rez_package(directory) root = tempfile.mkdtemp(suffix="_test_is_definition_build_package") self.delete_item_later(root) packages = [ _make_package_with_contents(root, "another_package", "1.2.0", _create_package), _make_package_with_contents( root, "some_package", "1.2.0", functools.partial(_create_package, requirements=["another_package-1"]), ), ] repository, packages, remote_root = testify.make_fake_repository( packages, root) self.delete_item_later(repository.working_dir) self.delete_item_later(remote_root) release_path = tempfile.mkdtemp( suffix="_a_release_location_for_testing") self.delete_item_later(release_path) options, parser = _make_fake_release_data() for package in packages: creator.release( finder.get_package_root(package), options, parser, release_path, search_paths=[repository.working_dir], quiet=True, ) text = 'PR github-token --packages another_package-1.1 --instructions "Do it!" --new minor' text = shlex.split(text) sys.argv[1:] = text # Simulate the act of a user with a locally built package that # they want to test against other existing releases. # source_root = tempfile.mkdtemp(suffix="_another_source_root") source_package = _make_package_with_contents(source_root, "another_package", "1.3.0", _create_package) install_path = tempfile.mkdtemp(suffix="_install_path") local_built_package = creator.build(source_package, install_path) arguments = _BumpArguments( additional_paths=[ inspection.get_packages_path_from_package(local_built_package) ], instructions="Do something!", new="minor", packages=["another_package-1.3"], pull_request_name="PR", token="github-token", ssl_no_verify=False, cached_users="", fallback_reviewers=None, base_url="", ) with rez_configuration.patch_release_packages_path(release_path): ran_packages, unfixed, invalids, skips = _get_test_results( "bump", arguments=arguments, paths={release_path}) package_file = next(iter(ran_packages)).filepath with open(package_file, "r") as handler: text = handler.read() expected = textwrap.dedent("""\ name = "some_package" version = "1.3.0" description = "A package.py Rez package that won't be converted." build_command = "python {root}/rezbuild.py" def commands(): import os env.PYTHONPATH.append(os.path.join("{root}", "python.egg")) requires = [ "another_package-1.3", ]""") self.assertEqual(expected, text) self.assertEqual((set(), [], []), (unfixed, invalids, skips)) self.assertEqual(1, _create_pull_request.call_count)
def _make_absolute(items, package): """Change `items` from relative paths to absolute paths. Args: items (iter[str]): A collection of files or folders on-disk. These paths can be absolute or relative. package (:class:`rez.developer_package.DeveloperPackage`): The package that will be used to resolve any relative paths in `items` into absolute paths. This works because the package is A. assumed as already built and B. resolves against ``rez_documentation_check`` into an installed variant, which each item in `items` should be inside of. Raises: RuntimeError: If a string in `items` cannot be linked to a file or folder on-disk. Returns: set[str]: The absolute file(s)/folder(s) on-disk. Every path must exist. """ def _get_package_root(package, package_paths): root = os.getenv(inspection.get_root_key(package.name), "") if root: return root context = resolved_context.ResolvedContext( ["{package.name}=={package.version}".format(package=package)], package_paths=package_paths, ) environment = context.get_environ() return environment[inspection.get_root_key(package.name)] output = set() package_root = inspection.get_packages_path_from_package(package) package_paths = [package_root] + config.packages_path # pylint: disable=no-member try: root = _get_package_root(package, package_paths) except rez_exceptions.PackageNotFoundError: _LOGGER.error('Package could not be found in paths "%s".', package_paths) raise for item in items: path = item if not os.path.isabs(path): path = os.path.normcase(os.path.join(root, path)) if not os.path.exists(path): raise RuntimeError( 'Item "{item}" resolved to "{path}" but this path does not exist.' .format(item=item, path=path)) output.add(path) return output
def _test_release(self, run_command, builder): """Build a Rez package, release it, and then test it. This is a convenience function to keep unittests a bit more concise. Args: run_command (:class:`mock.MagicMock`): A replacement for the function that would normally run as part of the commands that run on a Rez package. If this function gets run, we know that this test passes. builder (callable[str, str, str, str] -> str): The function that is used to generate the "contents" of the Rez package. This function is only responsible for creating the package.py/rezbuild.py files, it doesn't create a Python package, for example. This parameter is responsible for creating the rest of the files of the package. """ packages = self._setup_test(run_command, builder, variants=[["project_b-1"]]) directory = tempfile.mkdtemp(suffix="_some_build_location") self.delete_item_later(directory) build_package = package_common.make_build_python_package( textwrap.dedent("""\ name = "project_b" version = "2.0.0" revision = { "push_url": "fake_git_repo", } """), "project_b", "2.0.0", directory, ) build_package = finder.get_nearest_rez_package( os.path.join(build_package)) build_root = inspection.get_packages_path_from_package(build_package) build_paths = [build_root] release_path = _release_packages( packages, search_paths=build_paths + config.packages_path, # pylint: disable=no-member ) self.delete_item_later(release_path) packages = [ packages_.get_developer_package( os.path.join(release_path, package.name, str(package.version))) for package in packages ] paths = build_paths + [release_path] with rez_configuration.patch_packages_path(paths): self._test( ( set(), [], [ worker.Skip( build_package, finder.get_package_root(build_package), "Python package already has documentation.", ) ], ), paths, ) self.assertEqual(1, run_command.call_count)