def test_initmsg_debug(monkeypatch): """When in DEBUG, the init msg goes both to disk and terminal.""" monkeypatch.setenv('DEBUG', '1') cmd = create_command('somecommand') fake_stream = io.StringIO() with patch('charmcraft.main.COMMAND_GROUPS', [('test-group', 'whatever title', [cmd])]): with patch.object(logsetup.message_handler, 'ended_ok') as ended_ok_mock: with patch.object(logsetup.message_handler._stderr_handler, 'stream', fake_stream): main(['charmcraft', 'somecommand']) # get the logfile first line before removing it ended_ok_mock.assert_called_once_with() logged_to_file = pathlib.Path( logsetup.message_handler._log_filepath).read_text() file_first_line = logged_to_file.split('\n')[0] logsetup.message_handler.ended_ok() # get the terminal first line captured = fake_stream.getvalue() terminal_first_line = captured.split('\n')[0] expected = "Starting charmcraft version " + __version__ assert expected in file_first_line assert expected in terminal_first_line
def test_initmsg_verbose(): """In verbose mode, the init msg goes both to disk and terminal.""" cmd = create_command("somecommand") fake_stream = io.StringIO() with patch("charmcraft.main.COMMAND_GROUPS", [("test-group", "whatever title", [cmd])]): with patch.object(logsetup.message_handler, "ended_ok") as ended_ok_mock: with patch.object(logsetup.message_handler._stderr_handler, "stream", fake_stream): main(["charmcraft", "--verbose", "somecommand"]) # get the logfile first line before removing it ended_ok_mock.assert_called_once_with() logged_to_file = pathlib.Path( logsetup.message_handler._log_filepath).read_text() file_first_line = logged_to_file.split("\n")[0] logsetup.message_handler.ended_ok() # get the terminal first line captured = fake_stream.getvalue() terminal_first_line = captured.split("\n")[0] expected = "Starting charmcraft version " + __version__ assert expected in file_first_line assert expected in terminal_first_line
def test_main_logs_system_details(emitter, config): """Calling main ends up logging the system details.""" system_details = "test system details" with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as run_mock: with patch("charmcraft.main._get_system_details") as details_mock: details_mock.return_value = system_details run_mock.return_value = None main(["charmcraft", "version"]) emit_mock.trace.assert_called_once_with(system_details)
def test_main_managed_instance(monkeypatch): """Init emitter with a specific log filepath.""" monkeypatch.setenv("CHARMCRAFT_MANAGED_MODE", "1") with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.return_value = None main(["charmcraft", "version"]) # check how Emitter was initted emit_mock.init.assert_called_once_with( EmitterMode.NORMAL, "charmcraft", f"Starting charmcraft version {__version__}", log_filepath=env.get_managed_environment_log_path(), )
def test_main_no_args(): """The setup.py entry_point function needs to work with no arguments.""" with patch("sys.argv", ["charmcraft"]): with patch("charmcraft.main.message_handler") as mh_mock: retcode = main() assert retcode == 1 assert mh_mock.ended_cmderror.call_count == 1
def test_main_controlled_return_code(): """Work ended ok, and the command indicated the return code.""" with patch("charmcraft.main.message_handler") as mh_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.return_value = 9 retcode = main(["charmcraft", "version"]) assert retcode == 9 mh_mock.ended_ok.assert_called_once_with()
def test_main_ok(): """Work ended ok: message handler notified properly, return code in 0.""" with patch.object(logsetup, 'message_handler') as mh_mock: with patch('charmcraft.main.Dispatcher.run') as d_mock: d_mock.return_value = None retcode = main(['charmcraft', 'version']) assert retcode == 0 assert mh_mock.ended_ok.called_once()
def test_main_interrupted(): """Work interrupted: message handler notified properly, return code in 1.""" with patch.object(logsetup, 'message_handler') as mh_mock: with patch('charmcraft.main.Dispatcher.run') as d_mock: d_mock.side_effect = KeyboardInterrupt retcode = main(['charmcraft', 'version']) assert retcode == 1 assert mh_mock.ended_interrupt.called_once()
def test_main_controlled_return_code(base_config_present): """Work ended ok, and the command indicated the return code.""" with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.return_value = 9 retcode = main(["charmcraft", "version"]) assert retcode == 9 emit_mock.ended_ok.assert_called_once_with()
def test_main_ok(): """Work ended ok: message handler notified properly, return code in 0.""" with patch("charmcraft.main.message_handler") as mh_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.return_value = None retcode = main(["charmcraft", "version"]) assert retcode == 0 mh_mock.ended_ok.assert_called_once_with()
def test_main_interrupted(): """Work interrupted: message handler notified properly, return code in 1.""" with patch("charmcraft.main.message_handler") as mh_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = KeyboardInterrupt retcode = main(["charmcraft", "version"]) assert retcode == 1 assert mh_mock.ended_interrupt.call_count == 1
def test_main_no_args(): """The setup.py entry_point function needs to work with no arguments.""" with patch('sys.argv', ['charmcraft']): with patch.object(logsetup, 'message_handler') as mh_mock: with patch('charmcraft.main.Dispatcher.run') as d_mock: d_mock.side_effect = CommandError('boom', retcode=42) retcode = main() assert retcode == 42 assert mh_mock.ended_ok.called_once()
def test_main_crash(): """Work crashed: message handler notified properly, return code in 1.""" simulated_exception = ValueError('boom') with patch.object(logsetup, 'message_handler') as mh_mock: with patch('charmcraft.main.Dispatcher.run') as d_mock: d_mock.side_effect = simulated_exception retcode = main(['charmcraft', 'version']) assert retcode == 1 assert mh_mock.ended_crash.called_once_with(simulated_exception)
def test_main_controlled_error(): """Work raised CommandError: message handler notified properly, use indicated return code.""" simulated_exception = CommandError("boom", retcode=33) with patch("charmcraft.main.message_handler") as mh_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = simulated_exception retcode = main(["charmcraft", "version"]) assert retcode == 33 mh_mock.ended_cmderror.assert_called_once_with(simulated_exception)
def test_main_controlled_error(base_config_present): """Work raised CraftError: message handler notified properly, use indicated return code.""" simulated_exception = CraftError("boom", retcode=33) with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = simulated_exception retcode = main(["charmcraft", "version"]) assert retcode == 33 emit_mock.error.assert_called_once_with(simulated_exception)
def test_main_crash(): """Work crashed: message handler notified properly, return code in 1.""" simulated_exception = ValueError("boom") with patch("charmcraft.main.message_handler") as mh_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = simulated_exception retcode = main(["charmcraft", "version"]) assert retcode == 1 mh_mock.ended_crash.assert_called_once_with(simulated_exception)
def test_main_controlled_error(): """Work raised CommandError: message handler notified properly, use indicated return code.""" simulated_exception = CommandError('boom', retcode=33) with patch.object(logsetup, 'message_handler') as mh_mock: with patch('charmcraft.main.Dispatcher.run') as d_mock: d_mock.side_effect = simulated_exception retcode = main(['charmcraft', 'version']) assert retcode == 33 assert mh_mock.ended_cmderror.called_once_with(simulated_exception)
def test_main_environment_is_supported_error( mock_is_charmcraft_running_in_supported_environment, ): mock_is_charmcraft_running_in_supported_environment.return_value = False with patch("charmcraft.main.message_handler") as mh_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.return_value = None retcode = main(["charmcraft", "version"]) assert retcode == 1 assert mh_mock.ended_cmderror.call_count == 1
def test_main_controlled_arguments_error(capsys, base_config_present): """The execution failed because an argument parsing error.""" with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = ArgumentParsingError("test error") retcode = main(["charmcraft", "version"]) assert retcode == 1 emit_mock.ended_ok.assert_called_once_with() out, err = capsys.readouterr() assert not out assert err == "test error\n"
def test_main_providing_help(capsys): """The execution ended up providing a help message.""" with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = ProvideHelpException("nice and shiny help message") retcode = main(["charmcraft", "version"]) assert retcode == 0 emit_mock.ended_ok.assert_called_once_with() out, err = capsys.readouterr() assert not out assert err == "nice and shiny help message\n"
def test_main_crash(): """Work crashed: message handler notified properly, return code in 1.""" simulated_exception = ValueError("boom") with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = simulated_exception retcode = main(["charmcraft", "version"]) assert retcode == 1 (call,) = emit_mock.error.mock_calls (exc,) = call.args assert isinstance(exc, CraftError) assert str(exc) == "charmcraft internal error: ValueError('boom')" assert exc.__cause__ == simulated_exception
def test_main_interrupted(base_config_present): """Work interrupted: message handler notified properly, return code in 1.""" simulated_exception = KeyboardInterrupt() with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.side_effect = simulated_exception retcode = main(["charmcraft", "version"]) assert retcode == 1 (call, ) = emit_mock.error.mock_calls (exc, ) = call.args assert isinstance(exc, CraftError) assert str(exc) == "Interrupted." assert exc.__cause__ == simulated_exception
def test_main_load_config_not_present_ok(): """Config is not present but the command does not need it.""" class MyCommand(BaseCommand): help_msg = "some help" name = "cmdname" overview = "test overview" def run(self, parsed_args): assert not self.config.project.config_provided with patch("charmcraft.main.COMMAND_GROUPS", [CommandGroup("title", [MyCommand])]): retcode = main(["charmcraft", "cmdname", "--project-dir=/whatever"]) assert retcode == 0
def test_main_ok(): """Work ended ok: message handler notified properly, return code in 0.""" with patch("charmcraft.main.emit") as emit_mock: with patch("charmcraft.main.Dispatcher.run") as d_mock: d_mock.return_value = None retcode = main(["charmcraft", "version"]) assert retcode == 0 emit_mock.ended_ok.assert_called_once_with() # check how Emitter was initted emit_mock.init.assert_called_once_with( EmitterMode.NORMAL, "charmcraft", f"Starting charmcraft version {__version__}" )
def test_main_load_config_not_present_but_needed(capsys): """Command ends indicating the return code to be used.""" cmd = create_command("cmdname", needs_config_=True) with patch("charmcraft.main.COMMAND_GROUPS", [CommandGroup("title", [cmd])]): retcode = main(["charmcraft", "cmdname", "--project-dir=/whatever"]) assert retcode == 1 out, err = capsys.readouterr() assert not out assert err == ( "The specified command needs a valid 'charmcraft.yaml' configuration file (in " "the current directory or where specified with --project-dir option); see " "the reference: https://discourse.charmhub.io/t/charmcraft-configuration/4138\n" )
def test_main_load_config_not_present_ok(): """Command ends indicating the return code to be used.""" class MyCommand(BaseCommand): help_msg = "some help" name = "cmdname" def run(self, parsed_args): assert self.config.type is None assert not self.config.project.config_provided with patch("charmcraft.main.COMMAND_GROUPS", [CommandGroup("title", [MyCommand])]): retcode = main(["charmcraft", "cmdname", "--project-dir=/whatever"]) assert retcode == 0
def test_main_load_config_ok(create_config): """Command is properly executed, after loading and receiving the config.""" tmp_path = create_config(""" type: charm """) class MyCommand(BaseCommand): help_msg = "some help" name = "cmdname" overview = "test overview" def run(self, parsed_args): assert self.config.type == "charm" with patch("charmcraft.main.COMMAND_GROUPS", [CommandGroup("title", [MyCommand])]): retcode = main(["charmcraft", "cmdname", f"--project-dir={tmp_path}"]) assert retcode == 0
def test_main_load_config_not_present_but_needed(capsys): """Config is not present and the command needs it.""" class MyCommand(BaseCommand): help_msg = "some help" name = "cmdname" overview = "test overview" needs_config = True def run(self, parsed_args): pass with patch("charmcraft.main.COMMAND_GROUPS", [CommandGroup("title", [MyCommand])]): retcode = main(["charmcraft", "cmdname", "--project-dir=/whatever"]) assert retcode == 1 out, err = capsys.readouterr() assert not out assert err == ( "The specified command needs a valid 'charmcraft.yaml' configuration file (in " "the current directory or where specified with --project-dir option); see " "the reference: https://discourse.charmhub.io/t/charmcraft-configuration/4138\n" )
def test_main_no_args(): """The setup.py entry_point function needs to work with no arguments.""" with patch("sys.argv", ["charmcraft"]): retcode = main() assert retcode == 1