예제 #1
0
    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)
예제 #2
0
    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
예제 #3
0
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)
예제 #5
0
    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])
예제 #12
0
    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)
예제 #13
0
    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
예제 #14
0
    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),
        )
예제 #16
0
    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)
예제 #17
0
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
예제 #18
0
    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)