def test_execute_should_gracefully_handle_io_error(config: Config, pool: Pool, mocker: MockerFixture, io: BufferedIO, env: MockEnv): executor = Executor(env, pool, config, io) executor.verbose() original_write_line = executor._io.write_line def write_line(string: str, **kwargs: Any) -> None: # Simulate UnicodeEncodeError string.encode("ascii") original_write_line(string, **kwargs) mocker.patch.object(io, "write_line", side_effect=write_line) assert executor.execute([Install(Package("clikit", "0.2.3"))]) == 1 expected = r""" Package operations: 1 install, 0 updates, 0 removals \s*Unicode\w+Error """ assert re.match(expected, io.fetch_output())
def test_executor_should_delete_incomplete_downloads(config, io, tmp_dir, mocker, pool, mock_file_downloads): fixture = Path(__file__).parent.parent.joinpath( "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl") destination_fixture = Path(tmp_dir) / "tomlkit-0.5.3-py2.py3-none-any.whl" shutil.copyfile(str(fixture), str(destination_fixture)) mocker.patch( "poetry.installation.executor.Executor._download_archive", side_effect=Exception("Download error"), ) mocker.patch( "poetry.installation.chef.Chef.get_cached_archive_for_link", side_effect=lambda link: link, ) mocker.patch( "poetry.installation.chef.Chef.get_cache_directory_for_link", return_value=Path(tmp_dir), ) config = Config() config.merge({"cache-dir": tmp_dir}) env = MockEnv(path=Path(tmp_dir)) executor = Executor(env, pool, config, io) with pytest.raises(Exception, match="Download error"): executor._download(Install(Package("tomlkit", "0.5.3"))) assert not destination_fixture.exists()
def test_executor_should_write_pep610_url_references_for_editable_directories( tmp_venv, pool, config, io): url = Path(__file__).parent.parent.joinpath( "fixtures/simple_project").resolve() package = Package( "simple-project", "1.2.3", source_type="directory", source_url=url.as_posix(), develop=True, ) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) dist_info = tmp_venv.site_packages.path.joinpath( "simple_project-1.2.3.dist-info") assert dist_info.exists() direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) assert url_reference == { "dir_info": { "editable": True }, "url": url.as_uri() }
def test_execute_works_with_no_ansi_output(mocker, config, pool, io_not_decorated, tmp_dir, mock_file_downloads, env): config = Config() config.merge({"cache-dir": tmp_dir}) executor = Executor(env, pool, config, io_not_decorated) install_output = ( "some string that does not contain a keyb0ard !nterrupt or cance11ed by u$er" ) mocker.patch.object(env, "_run", return_value=install_output) return_code = executor.execute([ Install(Package("pytest", "3.5.2")), ]) env._run.assert_called_once() expected = """ Package operations: 1 install, 0 updates, 0 removals • Installing pytest (3.5.2) """ expected = set(expected.splitlines()) output = set(io_not_decorated.fetch_output().splitlines()) assert expected == output assert 0 == return_code
def test_executor_should_write_pep610_url_references_for_git( tmp_venv, pool, config, io, mock_file_downloads): package = Package( "demo", "0.1.2", source_type="git", source_reference="master", source_resolved_reference="123456", source_url="https://github.com/demo/demo.git", ) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, package, { "vcs_info": { "vcs": "git", "requested_revision": "master", "commit_id": "123456", }, "url": package.source_url, }, )
def test_executor_should_write_pep610_url_references_for_urls( tmp_venv, pool, config, io, mock_file_downloads): package = Package( "demo", "0.1.0", source_type="url", source_url= "https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", ) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info") assert dist_info.exists() direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) assert url_reference == { "archive_info": {}, "url": "https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", }
def test_executor_should_use_cached_link_and_hash( config, io, pool, mocker, fixture_dir, tmp_dir ): # Produce a file:/// URI that is a valid link link_cached = Link( fixture_dir("distributions") .joinpath("demo-0.1.0-py2.py3-none-any.whl") .as_uri() ) mocker.patch.object( Chef, "get_cached_archive_for_link", side_effect=lambda _: link_cached ) env = MockEnv(path=Path(tmp_dir)) executor = Executor(env, pool, config, io) package = Package("demo", "0.1.0") package.files = [ { "file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "md5:15507846fd4299596661d0197bfb4f90", } ] archive = executor._download_link( Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl") ) assert archive == link_cached
def test_executor_should_write_pep610_url_references_for_git( tmp_venv, pool, config, io, mock_file_downloads): package = Package( "demo", "0.1.2", source_type="git", source_reference="master", source_resolved_reference="123456", source_url="https://github.com/demo/demo.git", ) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.2.dist-info") assert dist_info.exists() direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) assert url_reference == { "vcs_info": { "vcs": "git", "requested_revision": "master", "commit_id": "123456", }, "url": "https://github.com/demo/demo.git", }
def test_executor_should_check_every_possible_hash_types( config, io, pool, mocker, fixture_dir, tmp_dir ): mocker.patch.object( Chef, "get_cached_archive_for_link", side_effect=lambda link: link, ) mocker.patch.object( Executor, "_download_archive", return_value=fixture_dir("distributions").joinpath( "demo-0.1.0-py2.py3-none-any.whl" ), ) env = MockEnv(path=Path(tmp_dir)) executor = Executor(env, pool, config, io) package = Package("demo", "0.1.0") package.files = [ { "file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "md5:15507846fd4299596661d0197bfb4f90", } ] archive = executor._download_link( Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl") ) assert archive == fixture_dir("distributions").joinpath( "demo-0.1.0-py2.py3-none-any.whl" )
def test_execute_works_with_ansi_output(mocker, config, pool, io_decorated, tmp_dir, mock_file_downloads, env): config = Config() config.merge({"cache-dir": tmp_dir}) executor = Executor(env, pool, config, io_decorated) install_output = ( "some string that does not contain a keyb0ard !nterrupt or cance11ed by u$er" ) mocker.patch.object(env, "_run", return_value=install_output) return_code = executor.execute([ Install(Package("pytest", "3.5.2")), ]) env._run.assert_called_once() expected = [ "\x1b[39;1mPackage operations\x1b[39;22m: \x1b[34m1\x1b[39m install, \x1b[34m0\x1b[39m updates, \x1b[34m0\x1b[39m removals", "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mPending...\x1b[39m", "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mDownloading...\x1b[39m", "\x1b[34;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[39;1m3.5.2\x1b[39;22m\x1b[39m)\x1b[39m: \x1b[34mInstalling...\x1b[39m", "\x1b[32;1m•\x1b[39;22m \x1b[39mInstalling \x1b[39m\x1b[36mpytest\x1b[39m\x1b[39m (\x1b[39m\x1b[32m3.5.2\x1b[39m\x1b[39m)\x1b[39m", # finished ] output = io_decorated.fetch_output() # hint: use print(repr(output)) if you need to debug this for line in expected: assert line in output assert 0 == return_code
def test_executor_should_use_cached_link_and_hash( tmp_venv: VirtualEnv, pool: Pool, config: Config, io: BufferedIO, mocker: MockerFixture, fixture_dir: FixtureDirGetter, ): # Produce a file:/// URI that is a valid link link_cached = Link( fixture_dir("distributions").joinpath( "demo-0.1.0-py2.py3-none-any.whl").as_uri()) mocker.patch( "poetry.installation.chef.Chef.get_cached_archive_for_link", return_value=link_cached, ) package = Package("demo", "0.1.0") # Set package.files so the executor will attempt to hash the package package.files = [{ "file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a", # noqa: E501 }] executor = Executor(tmp_venv, pool, config, io) archive = executor._download_link( Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"), ) assert archive == link_cached
def test_execute_executes_a_batch_of_operations( config, pool, io, tmp_dir, mock_file_downloads ): config = Config() config.merge({"cache-dir": tmp_dir}) env = MockEnv(path=Path(tmp_dir)) executor = Executor(env, pool, config, io) file_package = Package("demo", "0.1.0") file_package.source_type = "file" file_package.source_url = str( Path(__file__) .parent.parent.joinpath( "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl" ) .resolve() ) directory_package = Package("simple-project", "1.2.3") directory_package.source_type = "directory" directory_package.source_url = str( Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() ) git_package = Package("demo", "0.1.0") git_package.source_type = "git" git_package.source_reference = "master" git_package.source_url = "https://github.com/demo/demo.git" assert 0 == executor.execute( [ Install(Package("pytest", "3.5.2")), Uninstall(Package("attrs", "17.4.0")), Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")), Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"), Install(file_package), Install(directory_package), Install(git_package), ] ) expected = """ Package operations: 4 installs, 1 update, 1 removal • Installing pytest (3.5.2) • Removing attrs (17.4.0) • Updating requests (2.18.3 -> 2.18.4) • Installing demo (0.1.0 {}) • Installing simple-project (1.2.3 {}) • Installing demo (0.1.0 master) """.format( file_package.source_url, directory_package.source_url ) expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert expected == output assert 5 == len(env.executed)
def test_executor_should_write_pep610_url_references_for_directories( tmp_venv, pool, config, io ): url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() package = Package( "simple-project", "1.2.3", source_type="directory", source_url=url.as_posix() ) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, package, {"dir_info": {}, "url": url.as_uri()} )
def test_executor_should_write_pep610_url_references_for_files( tmp_venv, pool, config, io): url = (Path(__file__).parent.parent.joinpath( "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve()) package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) verify_installed_distribution(tmp_venv, package, { "archive_info": {}, "url": url.as_uri() })
def test_executor_should_write_pep610_url_references_for_urls( tmp_venv, pool, config, io, mock_file_downloads ): package = Package( "demo", "0.1.0", source_type="url", source_url="https://files.pythonhosted.org/demo-0.1.0-py2.py3-none-any.whl", ) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, package, {"archive_info": {}, "url": package.source_url} )
def test_executor_should_write_pep610_url_references_for_editable_directories( tmp_venv: "VirtualEnv", pool: Pool, config: "Config", io: BufferedIO ): url = Path(__file__).parent.parent.joinpath("fixtures/simple_project").resolve() package = Package( "simple-project", "1.2.3", source_type="directory", source_url=url.as_posix(), develop=True, ) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) verify_installed_distribution( tmp_venv, package, {"dir_info": {"editable": True}, "url": url.as_uri()} )
def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_interrupt( config, mocker, io, env): executor = Executor(env, pool, config, io) executor.verbose() # A return code of -2 means KeyboardInterrupt in the pip subprocess mocker.patch.object(executor, "_install", return_value=-2) assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))]) expected = """ Package operations: 1 install, 0 updates, 0 removals • Installing clikit (0.2.3) • Installing clikit (0.2.3): Cancelled """ assert expected == io.fetch_output()
def test_executor_should_not_write_pep610_url_references_for_cached_package( package: Package, mocker: MockerFixture, fixture_dir: FixtureDirGetter, tmp_venv: VirtualEnv, pool: Pool, config: Config, io: BufferedIO, ): link_cached = fixture_dir( "distributions") / "demo-0.1.0-py2.py3-none-any.whl" mocker.patch("poetry.installation.executor.Executor._download", return_value=link_cached) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) verify_installed_distribution(tmp_venv, package)
def test_execute_shows_skipped_operations_if_verbose(config, pool, io): config = Config() config.merge({"cache-dir": "/foo"}) env = MockEnv() executor = Executor(env, pool, config, io) executor.verbose() assert 0 == executor.execute([ Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed") ]) expected = """ Package operations: 0 installs, 0 updates, 0 removals, 1 skipped • Removing clikit (0.2.3): Skipped for the following reason: Not currently installed """ assert expected == io.fetch_output() assert 0 == len(env.executed)
def test_execute_should_show_errors(config, mocker, io, env): executor = Executor(env, pool, config, io) executor.verbose() mocker.patch.object(executor, "_install", side_effect=Exception("It failed!")) assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))]) expected = """ Package operations: 1 install, 0 updates, 0 removals • Installing clikit (0.2.3) Exception It failed! """ assert expected in io.fetch_output()
def test_execute_shows_skipped_operations_if_verbose(config: Config, pool: Pool, io: BufferedIO, config_cache_dir: Path, env: MockEnv): config.merge({"cache-dir": config_cache_dir.as_posix()}) executor = Executor(env, pool, config, io) executor.verbose() assert (executor.execute([ Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed") ]) == 0) expected = """ Package operations: 0 installs, 0 updates, 0 removals, 1 skipped • Removing clikit (0.2.3): Skipped for the following reason: Not currently installed """ assert io.fetch_output() == expected assert len(env.executed) == 0
def test_executor_should_write_pep610_url_references_for_files( tmp_venv, pool, config, io): url = (Path(__file__).parent.parent.joinpath( "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve()) package = Package("demo", "0.1.0", source_type="file", source_url=url.as_posix()) executor = Executor(tmp_venv, pool, config, io) executor.execute([Install(package)]) dist_info = tmp_venv.site_packages.path.joinpath("demo-0.1.0.dist-info") assert dist_info.exists() direct_url_file = dist_info.joinpath("direct_url.json") assert direct_url_file.exists() url_reference = json.loads(direct_url_file.read_text(encoding="utf-8")) assert url_reference == {"archive_info": {}, "url": url.as_uri()}
def test_executor_should_check_every_possible_hash_types_before_failing( config, io, pool, mocker, fixture_dir, tmp_dir ): mocker.patch.object( Chef, "get_cached_archive_for_link", side_effect=lambda link: link, ) mocker.patch.object( Executor, "_download_archive", return_value=fixture_dir("distributions").joinpath( "demo-0.1.0-py2.py3-none-any.whl" ), ) env = MockEnv(path=Path(tmp_dir)) executor = Executor(env, pool, config, io) package = Package("demo", "0.1.0") package.files = [ {"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "md5:123456"}, {"file": "demo-0.1.0-py2.py3-none-any.whl", "hash": "sha256:123456"}, ] expected_message = ( "Invalid hashes " "(" "md5:15507846fd4299596661d0197bfb4f90, " "sha256:70e704135718fffbcbf61ed1fc45933cfd86951a744b681000eaaa75da31f17a" ") " "for demo (0.1.0) using archive demo-0.1.0-py2.py3-none-any.whl. " "Expected one of md5:123456, sha256:123456." ) with pytest.raises(RuntimeError, match=re.escape(expected_message)): executor._download_link( Install(package), Link("https://example.com/demo-0.1.0-py2.py3-none-any.whl"), )
def test_execute_should_gracefully_handle_io_error(config, mocker, io, env): executor = Executor(env, pool, config, io) executor.verbose() original_write_line = executor._io.write_line def write_line(string, flags=None): # Simulate UnicodeEncodeError string.encode("ascii") original_write_line(string, flags) mocker.patch.object(io, "write_line", side_effect=write_line) assert 1 == executor.execute([Install(Package("clikit", "0.2.3"))]) expected = r""" Package operations: 1 install, 0 updates, 0 removals \s*Unicode\w+Error """ assert re.match(expected, io.fetch_output())
def test_executor_should_be_initialized_with_correct_workers( tmp_venv: "VirtualEnv", pool: Pool, config: "Config", io: BufferedIO, mocker: "MockerFixture", max_workers: Optional[int], cpu_count: Optional[int], side_effect: Optional[Exception], expected_workers: int, ): config.merge({"installer": {"max-workers": max_workers}}) mocker.patch("os.cpu_count", return_value=cpu_count, side_effect=side_effect) executor = Executor(tmp_venv, pool, config, io) assert executor._max_workers == expected_workers
def __init__( self, io: "IO", env: "Env", package: "ProjectPackage", locker: "Locker", pool: Pool, config: "Config", installed: Union[Repository, None] = None, executor: Optional[Executor] = None, ): self._io = io self._env = env self._package = package self._locker = locker self._pool = pool self._dry_run = False self._requires_synchronization = False self._update = False self._verbose = False self._write_lock = True self._without_groups = None self._with_groups = None self._only_groups = None self._execute_operations = True self._lock = False self._whitelist = [] self._extras = [] if executor is None: executor = Executor(self._env, self._pool, config, self._io) self._executor = executor self._use_executor = False self._installer = self._get_installer() if installed is None: installed = self._get_installed() self._installed_repository = installed
def __init__( self, io: IO, env: Env, package: ProjectPackage, locker: Locker, pool: Pool, config: Config, installed: Repository | None = None, executor: Executor | None = None, ): self._io = io self._env = env self._package = package self._locker = locker self._pool = pool self._dry_run = False self._requires_synchronization = False self._update = False self._verbose = False self._write_lock = True self._without_groups = None self._with_groups = None self._only_groups = None self._execute_operations = True self._lock = False self._whitelist = [] self._extras = [] if executor is None: executor = Executor(self._env, self._pool, config, self._io) self._executor = executor self._use_executor = False self._installer = self._get_installer() if installed is None: installed = self._get_installed() self._installed_repository = installed
def test_execute_executes_a_batch_of_operations(mocker, config, pool, io, tmp_dir, mock_file_downloads, env): pip_editable_install = mocker.patch( "poetry.installation.executor.pip_editable_install", unsafe=not PY36) config = Config() config.merge({"cache-dir": tmp_dir}) executor = Executor(env, pool, config, io) file_package = Package( "demo", "0.1.0", source_type="file", source_url=Path(__file__).parent.parent.joinpath( "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve( ).as_posix(), ) directory_package = Package( "simple-project", "1.2.3", source_type="directory", source_url=Path(__file__).parent.parent.joinpath( "fixtures/simple_project").resolve().as_posix(), ) git_package = Package( "demo", "0.1.0", source_type="git", source_reference="master", source_url="https://github.com/demo/demo.git", develop=True, ) return_code = executor.execute([ Install(Package("pytest", "3.5.2")), Uninstall(Package("attrs", "17.4.0")), Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")), Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"), Install(file_package), Install(directory_package), Install(git_package), ]) expected = """ Package operations: 4 installs, 1 update, 1 removal • Installing pytest (3.5.2) • Removing attrs (17.4.0) • Updating requests (2.18.3 -> 2.18.4) • Installing demo (0.1.0 {}) • Installing simple-project (1.2.3 {}) • Installing demo (0.1.0 master) """.format(file_package.source_url, directory_package.source_url) expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert expected == output assert 5 == len(env.executed) assert 0 == return_code pip_editable_install.assert_called_once()
def test_execute_executes_a_batch_of_operations( mocker: MockerFixture, config: Config, pool: Pool, io: BufferedIO, tmp_dir: str, mock_file_downloads: None, env: MockEnv, ): pip_install = mocker.patch("poetry.installation.executor.pip_install") config.merge({"cache-dir": tmp_dir}) executor = Executor(env, pool, config, io) file_package = Package( "demo", "0.1.0", source_type="file", source_url=Path(__file__).parent.parent.joinpath( "fixtures/distributions/demo-0.1.0-py2.py3-none-any.whl").resolve( ).as_posix(), ) directory_package = Package( "simple-project", "1.2.3", source_type="directory", source_url=Path(__file__).parent.parent.joinpath( "fixtures/simple_project").resolve().as_posix(), ) git_package = Package( "demo", "0.1.0", source_type="git", source_reference="master", source_url="https://github.com/demo/demo.git", develop=True, ) return_code = executor.execute([ Install(Package("pytest", "3.5.2")), Uninstall(Package("attrs", "17.4.0")), Update(Package("requests", "2.18.3"), Package("requests", "2.18.4")), Uninstall(Package("clikit", "0.2.3")).skip("Not currently installed"), Install(file_package), Install(directory_package), Install(git_package), ]) expected = f""" Package operations: 4 installs, 1 update, 1 removal • Installing pytest (3.5.2) • Removing attrs (17.4.0) • Updating requests (2.18.3 -> 2.18.4) • Installing demo (0.1.0 {file_package.source_url}) • Installing simple-project (1.2.3 {directory_package.source_url}) • Installing demo (0.1.0 master) """ expected = set(expected.splitlines()) output = set(io.fetch_output().splitlines()) assert output == expected assert len(env.executed) == 1 assert return_code == 0 assert pip_install.call_count == 5 assert pip_install.call_args.kwargs.get("upgrade", False) assert pip_install.call_args.kwargs.get("editable", False)