diff --git a/piptools/repositories/base.py b/piptools/repositories/base.py index 6ac06bad9..0343fe7d7 100644 --- a/piptools/repositories/base.py +++ b/piptools/repositories/base.py @@ -45,6 +45,7 @@ def allow_all_wheels(self): Monkey patches pip.Wheel to allow wheels from all platforms and Python versions. """ + @abstractmethod def copy_ireq_dependencies(self, source, dest): """ Notifies the repository that `dest` is a copy of `source`, and so it diff --git a/piptools/repositories/local.py b/piptools/repositories/local.py index ec3a7963b..b2356d811 100644 --- a/piptools/repositories/local.py +++ b/piptools/repositories/local.py @@ -92,3 +92,6 @@ def get_hashes(self, ireq): def allow_all_wheels(self): with self.repository.allow_all_wheels(): yield + + def copy_ireq_dependencies(self, source, dest): + self.repository.copy_ireq_dependencies(source, dest) diff --git a/tests/conftest.py b/tests/conftest.py index 7cbb6814c..876d1fe7d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -88,6 +88,10 @@ def allow_all_wheels(self): # No need to do an actual pip.Wheel mock here. yield + def copy_ireq_dependencies(self, source, dest): + # No state to update. + pass + class FakeInstalledDistribution(object): def __init__(self, line, deps=None): diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py index 6140efed5..5079ac2f1 100644 --- a/tests/test_cli_compile.py +++ b/tests/test_cli_compile.py @@ -1308,3 +1308,28 @@ def test_prefer_binary_dist_even_there_is_source_dists( assert out.exit_code == 0, out assert "test-package==2.0" in out.stderr.splitlines(), out.stderr + + +@pytest.mark.parametrize("output_content", ("small_fake_with_deps", "")) +def test_duplicate_reqs_combined(pip_conf, runner, output_content): + """ + Test pip-compile tracks dependencies properly when install requirements are + combined, especially when an output file already exists. + + Regression test for issue GH-1154. + """ + fake_package_dir = os.path.join(PACKAGES_PATH, "small_fake_with_deps") + fake_package_dir = path_to_url(fake_package_dir) + with open("requirements.in", "w") as req_in: + req_in.write(fake_package_dir + "\n") + req_in.write(fake_package_dir + "#egg=small_fake_with_deps\n") + + if output_content: + with open("requirements.txt", "w") as reqs_out: + reqs_out.write(output_content) + + out = runner.invoke(cli, []) + + assert out.exit_code == 0, out + assert fake_package_dir in out.stderr + assert "small-fake-a==0.1" in out.stderr diff --git a/tests/test_repository_local.py b/tests/test_repository_local.py index dc751177f..c0d4607c5 100644 --- a/tests/test_repository_local.py +++ b/tests/test_repository_local.py @@ -1,3 +1,7 @@ +import copy + +from tests.conftest import FakeRepository + from piptools.repositories.local import LocalRequirementsRepository from piptools.utils import name_from_req @@ -23,3 +27,25 @@ def test_get_hashes_local_repository_cache_hit(from_line, repository): with local_repository.allow_all_wheels(): hashes = local_repository.get_hashes(from_line("small-fake-a==0.1")) assert hashes == EXPECTED + + +class FakeRepositoryChecksForCopy(FakeRepository): + def __init__(self): + super(FakeRepositoryChecksForCopy, self).__init__() + self.copied = [] + + def copy_ireq_dependencies(self, source, dest): + self.copied.append(source) + + +def test_local_repository_copy_ireq_dependencies(from_line): + # Ensure that local repository forwards any messages to update its state + # of ireq dependencies. + checker = FakeRepositoryChecksForCopy() + local_repository = LocalRequirementsRepository({}, checker) + + src = from_line("small-fake-a==0.1") + dest = copy.deepcopy(src) + local_repository.copy_ireq_dependencies(src, dest) + + assert src in checker.copied