def test_publish_can_publish_to_given_repository( fixture_dir, mocker, config, fixture_name ): uploader_auth = mocker.patch("poetry.publishing.uploader.Uploader.auth") uploader_upload = mocker.patch("poetry.publishing.uploader.Uploader.upload") config.merge( { "repositories": {"foo": {"url": "http://foo.bar"}}, "http-basic": {"foo": {"username": "******", "password": "******"}}, } ) mocker.patch("poetry.factory.Factory.create_config", return_value=config) poetry = Factory().create_poetry(fixture_dir(fixture_name)) io = BufferedIO() publisher = Publisher(poetry, io) publisher.publish("foo", None, None) assert [("foo", "bar")] == uploader_auth.call_args assert [ ("http://foo.bar",), {"cert": None, "client_cert": None, "dry_run": False}, ] == uploader_upload.call_args assert "Publishing my-package (1.2.3) to foo" in io.fetch_output()
def test_render_better_error_message(): io = BufferedIO() try: raise Exception("Failed") except Exception as e: trace = ExceptionTrace(e) trace.render(io) expected = """\ Exception Failed at {}:19 in test_render_better_error_message 15│ def test_render_better_error_message(): 16│ io = BufferedIO() 17│ 18│ try: → 19│ raise Exception("Failed") 20│ except Exception as e: 21│ trace = ExceptionTrace(e) 22│ 23│ trace.render(io) """.format(trace._get_relative_file_path(__file__)) assert expected == io.fetch_output()
class ApplicationTester(object): """ Eases the testing of console applications. """ def __init__(self, application: Application) -> None: self._application = application self._application.auto_exits(False) self._io = BufferedIO() self._status_code = 0 @property def application(self) -> Application: return self._application @property def io(self): # type: () -> BufferedIO return self._io @property def status_code(self): # type: () -> int return self._status_code def execute( self, args: Optional[str] = "", inputs: Optional[str] = None, interactive: Optional[bool] = None, verbosity: Optional[Verbosity] = None, decorated: bool = False, supports_utf8: bool = True, ) -> int: """ Executes the command """ self._io.clear() input = StringInput(args) self._io.set_input(input) self._io.decorated(decorated) self._io.output.set_supports_utf8(supports_utf8) self._io.error_output.set_supports_utf8(supports_utf8) if inputs is not None: self._io.input.set_stream(StringIO(inputs)) if interactive is not None: self._io.interactive(interactive) if verbosity is not None: self._io.set_verbosity(verbosity) self._status_code = self._application.run( self._io.input, self._io.output, self._io.error_output, ) return self._status_code
def test_render_debug_better_error_message_recursion_error(): io = BufferedIO() io.set_verbosity(Verbosity.DEBUG) try: recursion_error() except RecursionError as e: trace = ExceptionTrace(e) trace.render(io) expected = r"""^ Stack trace: \d+ {}:99 in test_render_debug_better_error_message_recursion_error 97\│ 98\│ try: → 99\│ recursion_error\(\) 100\│ except RecursionError as e: 101\│ trace = ExceptionTrace\(e\) ... Previous frame repeated \d+ times \s*\d+ {}:91 in recursion_error 89\│ 90\│ def recursion_error\(\): → 91\│ recursion_error\(\) 92\│ 93\│ RecursionError maximum recursion depth exceeded at {}:91 in recursion_error 87\│ assert re.match\(expected, io.fetch_output\(\)\) is not None 88\│ 89\│ 90\│ def recursion_error\(\): → 91\│ recursion_error\(\) 92\│ 93\│ 94\│ def test_render_debug_better_error_message_recursion_error\(\): 95\│ io = BufferedIO\(\) """.format( re.escape(trace._get_relative_file_path(__file__)), re.escape(trace._get_relative_file_path(__file__)), re.escape(trace._get_relative_file_path(__file__)), ) assert re.match(expected, io.fetch_output()) is not None
def test_render_falls_back_on_ascii_symbols(): from crashtest.contracts.base_solution import BaseSolution from crashtest.contracts.provides_solution import ProvidesSolution from crashtest.solution_providers.solution_provider_repository import ( SolutionProviderRepository, ) class CustomError(ProvidesSolution, Exception): @property def solution(self): solution = BaseSolution("Solution Title.", "Solution Description") solution.documentation_links.append("https://example.com") solution.documentation_links.append("https://example2.com") return solution io = BufferedIO(supports_utf8=False) def call(): raise CustomError("Error with solution") with pytest.raises(CustomError) as e: call() trace = ExceptionTrace( e.value, solution_provider_repository=SolutionProviderRepository()) trace.render(io) expected = """ CustomError Error with solution at {}:411 in call 407| 408| io = BufferedIO(supports_utf8=False) 409| 410| def call(): > 411| raise CustomError("Error with solution") 412| 413| with pytest.raises(CustomError) as e: 414| call() 415| * Solution Title: Solution Description https://example.com, https://example2.com """.format(trace._get_relative_file_path(__file__), ) assert expected == io.fetch_output()
def test_render_supports_solutions(): from crashtest.contracts.base_solution import BaseSolution from crashtest.contracts.provides_solution import ProvidesSolution from crashtest.solution_providers.solution_provider_repository import ( SolutionProviderRepository, ) class CustomError(ProvidesSolution, Exception): @property def solution(self): solution = BaseSolution("Solution Title.", "Solution Description") solution.documentation_links.append("https://example.com") solution.documentation_links.append("https://example2.com") return solution io = BufferedIO() def call(): raise CustomError("Error with solution") with pytest.raises(CustomError) as e: call() trace = ExceptionTrace( e.value, solution_provider_repository=SolutionProviderRepository()) trace.render(io) expected = """ CustomError Error with solution at {}:355 in call 351│ 352│ io = BufferedIO() 353│ 354│ def call(): → 355│ raise CustomError("Error with solution") 356│ 357│ with pytest.raises(CustomError) as e: 358│ call() 359│ • Solution Title: Solution Description https://example.com, https://example2.com """.format(trace._get_relative_file_path(__file__), ) assert expected == io.fetch_output()
def test_render_can_ignore_given_files(): import os from .helpers import outer io = BufferedIO() io.set_verbosity(Verbosity.VERBOSE) def call(): def run(): outer() run() with pytest.raises(Exception) as e: call() trace = ExceptionTrace(e.value) helpers_file = os.path.join(os.path.dirname(__file__), "helpers.py") trace.ignore_files_in("^{}$".format(re.escape(helpers_file))) trace.render(io) expected = """ Stack trace: 2 {}:224 in test_render_can_ignore_given_files call() 1 {}:221 in call run() Exception Foo at {}:3 in inner 1│ def outer(): 2│ def inner(): → 3│ raise Exception("Foo") 4│ 5│ inner() 6│ """.format( trace._get_relative_file_path(__file__), trace._get_relative_file_path(__file__), trace._get_relative_file_path(helpers_file), ) assert expected == io.fetch_output()
def test_render_debug_better_error_message_recursion_error_with_multiple_duplicated_frames( ): io = BufferedIO() io.set_verbosity(Verbosity.VERBOSE) with pytest.raises(RecursionError) as e: first() trace = ExceptionTrace(e.value) trace.render(io) expected = r"... Previous 2 frames repeated \d+ times" assert re.search(expected, io.fetch_output()) is not None
def test_execute_works_with_no_ansi_output( mocker: MockerFixture, config: Config, pool: Pool, io_not_decorated: BufferedIO, tmp_dir: str, mock_file_downloads: None, env: MockEnv, ): 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 output == expected assert return_code == 0
def test_it_provides_the_correct_solution(): from poetry.mixology.solutions.solutions import PythonRequirementSolution incompatibility = Incompatibility( [Term(Dependency("foo", "^1.0"), True)], PythonCause("^3.5", ">=3.6") ) exception = SolverProblemError(SolveFailure(incompatibility)) solution = PythonRequirementSolution(exception) title = "Check your dependencies Python requirement." description = """\ The Python requirement can be specified via the `python` or `markers` properties For foo, a possible solution would be to set the `python` property to ">=3.6,<4.0"\ """ links = [ "https://python-poetry.org/docs/dependency-specification/#python-restricted-dependencies", "https://python-poetry.org/docs/dependency-specification/#using-environment-markers", ] assert title == solution.solution_title assert ( description == BufferedIO().remove_format(solution.solution_description).strip() ) assert links == solution.documentation_links
def test_execute_works_with_ansi_output( mocker: "MockerFixture", config: "Config", pool: Pool, io_decorated: BufferedIO, tmp_dir: str, mock_file_downloads: None, env: MockEnv, ): 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 return_code == 0
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 io(): io = BufferedIO() io.output.formatter.set_style("c1_dark", Style("cyan", options=["dark"])) io.output.formatter.set_style("c2_dark", Style("default", options=["bold", "dark"])) io.output.formatter.set_style("success_dark", Style("green", options=["dark"])) io.output.formatter.set_style("warning", Style("yellow")) return io
def test_load_plugins_with_plugins_disabled( no_plugin_manager: PluginManager, poetry: Poetry, io: BufferedIO, with_my_plugin: None, ): no_plugin_manager.load_plugins() assert poetry.package.version.text == "1.2.3" assert io.fetch_output() == ""
def test_render_debug_better_error_message(): io = BufferedIO() io.set_verbosity(Verbosity.DEBUG) try: fail() except Exception as e: # Exception trace = ExceptionTrace(e) trace.render(io) expected = r"""^ Stack trace: 1 {}:52 in test_render_debug_better_error_message 50\│ 51\│ try: → 52\│ fail\(\) 53\│ except Exception as e: # Exception 54\│ trace = ExceptionTrace\(e\) Exception Failed at {}:12 in fail 8\│ from cleo.ui.exception_trace import ExceptionTrace 9\│ 10\│ 11\│ def fail\(\): → 12\│ raise Exception\("Failed"\) 13\│ 14\│ 15\│ def test_render_better_error_message\(\): 16\│ io = BufferedIO\(\) """.format( re.escape(trace._get_relative_file_path(__file__)), re.escape(trace._get_relative_file_path(__file__)), ) assert re.match(expected, io.fetch_output()) is not None
def test_load_plugins_and_activate( manager_factory: ManagerFactory, poetry: Poetry, io: BufferedIO, with_my_plugin: None, ): manager = manager_factory() manager.load_plugins() manager.activate(poetry, io) assert poetry.package.readmes == ("README.md",) assert io.fetch_output() == "Setting readmes\n"
def test_load_plugins_with_plugins_disabled( no_plugin_manager: PluginManager, poetry: Poetry, io: BufferedIO, mocker: MockerFixture, ): mocker.patch( "entrypoints.get_group_all", return_value=[ EntryPoint("my-plugin", "tests.plugins.test_plugin_manager", "MyPlugin") ], ) no_plugin_manager.load_plugins() assert poetry.package.version.text == "1.2.3" assert io.fetch_output() == ""
def test_execute_should_show_operation_as_cancelled_on_subprocess_keyboard_interrupt( config: Config, pool: Pool, mocker: MockerFixture, io: BufferedIO, env: MockEnv): 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 executor.execute([Install(Package("clikit", "0.2.3"))]) == 1 expected = """ Package operations: 1 install, 0 updates, 0 removals • Installing clikit (0.2.3) • Installing clikit (0.2.3): Cancelled """ assert io.fetch_output() == expected
def test_load_plugins_and_activate( manager_factory: ManagerFactory, poetry: Poetry, io: BufferedIO, mocker: MockerFixture, ): manager = manager_factory() mocker.patch( "entrypoints.get_group_all", return_value=[ EntryPoint("my-plugin", "tests.plugins.test_plugin_manager", "MyPlugin") ], ) manager.load_plugins() manager.activate(poetry, io) assert poetry.package.readmes == ("README.md",) assert io.fetch_output() == "Setting readmes\n"
def test_execute_should_show_errors( config: "Config", pool: Pool, mocker: "MockerFixture", io: BufferedIO, env: MockEnv ): executor = Executor(env, pool, config, io) executor.verbose() mocker.patch.object(executor, "_install", side_effect=Exception("It failed!")) assert executor.execute([Install(Package("clikit", "0.2.3"))]) == 1 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_load_plugins_and_activate( manager_factory: ManagerFactory, poetry: Poetry, io: BufferedIO, mocker: "MockerFixture", ): manager = manager_factory() mocker.patch( "entrypoints.get_group_all", return_value=[ EntryPoint("my-plugin", "tests.plugins.test_plugin_manager", "MyPlugin") ], ) manager.load_plugins() manager.activate(poetry, io) assert poetry.package.version.text == "9.9.9" assert io.fetch_output() == "Updating version\n"
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)
def activate(self, poetry: Poetry, io: BufferedIO) -> None: io.write_line("Updating version") poetry.package.version = "9.9.9"
def activate(self, poetry: Poetry, io: BufferedIO) -> None: io.write_line("Setting readmes") poetry.package.readmes = ("README.md",)
def ansi_io(): input_ = StringInput("") input_.set_stream(StringIO()) return BufferedIO(input_, decorated=True)
def __init__(self, application: Application) -> None: self._application = application self._application.auto_exits(False) self._io = BufferedIO() self._status_code = 0
def io_decorated(): io = BufferedIO(decorated=True) io.output.formatter.set_style("c1", Style("cyan")) io.output.formatter.set_style("success", Style("green")) return io
def io_not_decorated(): io = BufferedIO(decorated=False) return io
def io() -> BufferedIO: return BufferedIO()