From 0ca43ef67a3dd818be58872e4aa949ad4d462909 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Sat, 22 Jun 2019 21:23:48 -0700 Subject: [PATCH] Get tests passing. --- borgmatic/commands/borgmatic.py | 34 ++- tests/integration/commands/test_borgmatic.py | 206 +++++++++++++------ tests/unit/commands/test_borgmatic.py | 24 ++- 3 files changed, 171 insertions(+), 93 deletions(-) diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 2671f14a9..42a2972ba 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -54,7 +54,7 @@ def parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers): for alias in aliases } - # Give each subparser a shot at parsing all arguments. + # Give each requested action's subparser a shot at parsing all arguments. for subparser_name, subparser in subparsers.choices.items(): if subparser_name not in unparsed_arguments: continue @@ -65,6 +65,13 @@ def parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers): parsed, remaining = subparser.parse_known_args(unparsed_arguments) arguments[canonical_name] = parsed + # If no actions are explicitly requested, assume defaults: prune, create, and check. + if not arguments and '--help' not in unparsed_arguments and '-h' not in unparsed_arguments: + for subparser_name in ('prune', 'create', 'check'): + subparser = subparsers.choices[subparser_name] + parsed, remaining = subparser.parse_known_args(unparsed_arguments) + arguments[subparser_name] = parsed + # Then ask each subparser, one by one, to greedily consume arguments. Any arguments that remain # are global arguments. for subparser_name in arguments.keys(): @@ -300,18 +307,11 @@ def parse_arguments(*unparsed_arguments): if ( 'list' in arguments and 'info' in arguments - and parged_arguments['list'].json - and parged_arguments['info'].json + and arguments['list'].json + and arguments['info'].json ): raise ValueError('With the --json option, list and info actions cannot be used together') - # If any of the action flags are explicitly requested, leave them as-is. Otherwise, assume - # defaults: Mutate the given arguments to enable the default actions. - if set(arguments) == {'global'}: - arguments['prune'], remaining = prune_parser.parse_known_args(unparsed_arguments) - arguments['create'], remaining = create_parser.parse_known_args(unparsed_arguments) - arguments['check'], remaining = check_parser.parse_known_args(unparsed_arguments) - return arguments @@ -431,18 +431,13 @@ def run_actions( ) if json_output: yield json.loads(json_output) - if 'check' in arguments and checks.repository_enabled_for_checks( - repository, consistency - ): + if 'check' in arguments and checks.repository_enabled_for_checks(repository, consistency): logger.info('{}: Running consistency checks'.format(repository)) borg_check.check_archives( repository, storage, consistency, local_path=local_path, remote_path=remote_path ) if 'extract' in arguments: - if ( - arguments['extract'].repository is None - or repository == arguments['extract'].repository - ): + if arguments['extract'].repository is None or repository == arguments['extract'].repository: logger.info( '{}: Extracting archive {}'.format(repository, arguments['extract'].archive) ) @@ -458,10 +453,7 @@ def run_actions( progress=arguments['extract'].progress, ) if 'list' in arguments: - if ( - arguments['list'].repository is None - or repository == arguments['list'].repository - ): + if arguments['list'].repository is None or repository == arguments['list'].repository: logger.info('{}: Listing archives'.format(repository)) json_output = borg_list.list_archives( repository, diff --git a/tests/integration/commands/test_borgmatic.py b/tests/integration/commands/test_borgmatic.py index 2e122bd2b..53ea2d363 100644 --- a/tests/integration/commands/test_borgmatic.py +++ b/tests/integration/commands/test_borgmatic.py @@ -10,82 +10,139 @@ def test_parse_arguments_with_no_arguments_uses_defaults(): config_paths = ['default'] flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths) - parser = module.parse_arguments() + arguments = module.parse_arguments() - assert parser.config_paths == config_paths - assert parser.excludes_filename is None - assert parser.verbosity == 0 - assert parser.syslog_verbosity == 0 - assert parser.json is False + global_arguments = arguments['global'] + assert global_arguments.config_paths == config_paths + assert global_arguments.excludes_filename is None + assert global_arguments.verbosity == 0 + assert global_arguments.syslog_verbosity == 0 def test_parse_arguments_with_multiple_config_paths_parses_as_list(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - parser = module.parse_arguments('--config', 'myconfig', 'otherconfig') + arguments = module.parse_arguments('--config', 'myconfig', 'otherconfig') - assert parser.config_paths == ['myconfig', 'otherconfig'] - assert parser.verbosity == 0 - assert parser.syslog_verbosity == 0 + global_arguments = arguments['global'] + assert global_arguments.config_paths == ['myconfig', 'otherconfig'] + assert global_arguments.verbosity == 0 + assert global_arguments.syslog_verbosity == 0 def test_parse_arguments_with_verbosity_overrides_default(): config_paths = ['default'] flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths) - parser = module.parse_arguments('--verbosity', '1') + arguments = module.parse_arguments('--verbosity', '1') - assert parser.config_paths == config_paths - assert parser.excludes_filename is None - assert parser.verbosity == 1 - assert parser.syslog_verbosity == 0 + global_arguments = arguments['global'] + assert global_arguments.config_paths == config_paths + assert global_arguments.excludes_filename is None + assert global_arguments.verbosity == 1 + assert global_arguments.syslog_verbosity == 0 def test_parse_arguments_with_syslog_verbosity_overrides_default(): config_paths = ['default'] flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths) - parser = module.parse_arguments('--syslog-verbosity', '2') + arguments = module.parse_arguments('--syslog-verbosity', '2') - assert parser.config_paths == config_paths - assert parser.excludes_filename is None - assert parser.verbosity == 0 - assert parser.syslog_verbosity == 2 + global_arguments = arguments['global'] + assert global_arguments.config_paths == config_paths + assert global_arguments.excludes_filename is None + assert global_arguments.verbosity == 0 + assert global_arguments.syslog_verbosity == 2 -def test_parse_arguments_with_json_overrides_default(): - parser = module.parse_arguments('--list', '--json') - assert parser.json is True +def test_parse_arguments_with_list_json_overrides_default(): + arguments = module.parse_arguments('list', '--json') + + assert 'list' in arguments + assert arguments['list'].json is True + + +def test_parse_arguments_with_dashed_list_json_overrides_default(): + arguments = module.parse_arguments('--list', '--json') + + assert 'list' in arguments + assert arguments['list'].json is True def test_parse_arguments_with_no_actions_defaults_to_all_actions_enabled(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - parser = module.parse_arguments() + arguments = module.parse_arguments() - assert parser.prune is True - assert parser.create is True - assert parser.check is True + assert 'prune' in arguments + assert 'create' in arguments + assert 'check' in arguments + + +def test_parse_arguments_with_help_and_no_actions_shows_global_help(capsys): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + with pytest.raises(SystemExit) as exit: + module.parse_arguments('--help') + + assert exit.value.code == 0 + captured = capsys.readouterr() + assert 'global arguments:' in captured.out + assert 'actions:' in captured.out + + +def test_parse_arguments_with_help_and_action_shows_action_help(capsys): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + with pytest.raises(SystemExit) as exit: + module.parse_arguments('create', '--help') + + assert exit.value.code == 0 + captured = capsys.readouterr() + assert 'global arguments:' not in captured.out + assert 'actions:' not in captured.out + assert 'create arguments:' in captured.out def test_parse_arguments_with_prune_action_leaves_other_actions_disabled(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - parser = module.parse_arguments('--prune') + arguments = module.parse_arguments('prune') - assert parser.prune is True - assert parser.create is False - assert parser.check is False + assert 'prune' in arguments + assert 'create' not in arguments + assert 'check' not in arguments + + +def test_parse_arguments_with_dashed_prune_action_leaves_other_actions_disabled(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + arguments = module.parse_arguments('--prune') + + assert 'prune' in arguments + assert 'create' not in arguments + assert 'check' not in arguments def test_parse_arguments_with_multiple_actions_leaves_other_action_disabled(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - parser = module.parse_arguments('--create', '--check') + arguments = module.parse_arguments('create', 'check') - assert parser.prune is False - assert parser.create is True - assert parser.check is True + assert 'prune' not in arguments + assert 'create' in arguments + assert 'check' in arguments + + +def test_parse_arguments_with_multiple_dashed_actions_leaves_other_action_disabled(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + arguments = module.parse_arguments('--create', '--check') + + assert 'prune' not in arguments + assert 'create' in arguments + assert 'check' in arguments def test_parse_arguments_with_invalid_arguments_exits(): @@ -105,47 +162,53 @@ def test_parse_arguments_disallows_deprecated_excludes_option(): def test_parse_arguments_disallows_encryption_mode_without_init(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): + with pytest.raises(SystemExit): module.parse_arguments('--config', 'myconfig', '--encryption', 'repokey') def test_parse_arguments_allows_encryption_mode_with_init(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + module.parse_arguments('--config', 'myconfig', 'init', '--encryption', 'repokey') + + +def test_parse_arguments_allows_encryption_mode_with_dashed_init(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + module.parse_arguments('--config', 'myconfig', '--init', '--encryption', 'repokey') def test_parse_arguments_requires_encryption_mode_with_init(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): - module.parse_arguments('--config', 'myconfig', '--init') + with pytest.raises(SystemExit): + module.parse_arguments('--config', 'myconfig', 'init') def test_parse_arguments_disallows_append_only_without_init(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): + with pytest.raises(SystemExit): module.parse_arguments('--config', 'myconfig', '--append-only') def test_parse_arguments_disallows_storage_quota_without_init(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): + with pytest.raises(SystemExit): module.parse_arguments('--config', 'myconfig', '--storage-quota', '5G') def test_parse_arguments_allows_init_and_prune(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--config', 'myconfig', '--init', '--encryption', 'repokey', '--prune') + module.parse_arguments('--config', 'myconfig', 'init', '--encryption', 'repokey', 'prune') def test_parse_arguments_allows_init_and_create(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--config', 'myconfig', '--init', '--encryption', 'repokey', '--create') + module.parse_arguments('--config', 'myconfig', 'init', '--encryption', 'repokey', 'create') def test_parse_arguments_disallows_init_and_dry_run(): @@ -153,14 +216,14 @@ def test_parse_arguments_disallows_init_and_dry_run(): with pytest.raises(ValueError): module.parse_arguments( - '--config', 'myconfig', '--init', '--encryption', 'repokey', '--dry-run' + '--config', 'myconfig', 'init', '--encryption', 'repokey', '--dry-run' ) def test_parse_arguments_disallows_repository_without_extract_or_list(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): + with pytest.raises(SystemExit): module.parse_arguments('--config', 'myconfig', '--repository', 'test.borg') @@ -168,85 +231,97 @@ def test_parse_arguments_allows_repository_with_extract(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) module.parse_arguments( - '--config', 'myconfig', '--extract', '--repository', 'test.borg', '--archive', 'test' + '--config', 'myconfig', 'extract', '--repository', 'test.borg', '--archive', 'test' ) def test_parse_arguments_allows_repository_with_list(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--config', 'myconfig', '--list', '--repository', 'test.borg') + module.parse_arguments('--config', 'myconfig', 'list', '--repository', 'test.borg') def test_parse_arguments_disallows_archive_without_extract_or_list(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): + with pytest.raises(SystemExit): module.parse_arguments('--config', 'myconfig', '--archive', 'test') def test_parse_arguments_disallows_restore_paths_without_extract(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): + with pytest.raises(SystemExit): module.parse_arguments('--config', 'myconfig', '--restore-path', 'test') def test_parse_arguments_allows_archive_with_extract(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + module.parse_arguments('--config', 'myconfig', 'extract', '--archive', 'test') + + +def test_parse_arguments_allows_archive_with_dashed_extract(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + module.parse_arguments('--config', 'myconfig', '--extract', '--archive', 'test') def test_parse_arguments_allows_archive_with_list(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--config', 'myconfig', '--list', '--archive', 'test') + module.parse_arguments('--config', 'myconfig', 'list', '--archive', 'test') def test_parse_arguments_requires_archive_with_extract(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): - module.parse_arguments('--config', 'myconfig', '--extract') + with pytest.raises(SystemExit): + module.parse_arguments('--config', 'myconfig', 'extract') -def test_parse_arguments_allows_progress_and_create(): +def test_parse_arguments_allows_progress_before_create(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--progress', '--create', '--list') + module.parse_arguments('--progress', 'create', 'list') + + +def test_parse_arguments_allows_progress_after_create(): + flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) + + module.parse_arguments('create', '--progress', 'list') def test_parse_arguments_allows_progress_and_extract(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--progress', '--extract', '--archive', 'test', '--list') + module.parse_arguments('--progress', 'extract', '--archive', 'test', 'list') def test_parse_arguments_disallows_progress_without_create(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): - module.parse_arguments('--progress', '--list') + with pytest.raises(SystemExit): + module.parse_arguments('--progress', 'list') def test_parse_arguments_with_stats_and_create_flags_does_not_raise(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--stats', '--create', '--list') + module.parse_arguments('--stats', 'create', 'list') def test_parse_arguments_with_stats_and_prune_flags_does_not_raise(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--stats', '--prune', '--list') + module.parse_arguments('--stats', 'prune', 'list') def test_parse_arguments_with_stats_flag_but_no_create_or_prune_flag_raises_value_error(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): - module.parse_arguments('--stats', '--list') + with pytest.raises(SystemExit): + module.parse_arguments('--stats', 'list') def test_parse_arguments_with_just_stats_flag_does_not_raise(): @@ -258,22 +333,21 @@ def test_parse_arguments_with_just_stats_flag_does_not_raise(): def test_parse_arguments_allows_json_with_list_or_info(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - module.parse_arguments('--list', '--json') - module.parse_arguments('--info', '--json') + module.parse_arguments('list', '--json') + module.parse_arguments('info', '--json') -def test_parse_arguments_disallows_json_without_list_or_info(): +def test_parse_arguments_allows_json_with_dashed_info(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) - with pytest.raises(ValueError): - module.parse_arguments('--json') + module.parse_arguments('--info', '--json') def test_parse_arguments_disallows_json_with_both_list_and_info(): flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) with pytest.raises(ValueError): - module.parse_arguments('--list', '--info', '--json') + module.parse_arguments('list', 'info', '--json') def test_borgmatic_version_matches_news_version(): diff --git a/tests/unit/commands/test_borgmatic.py b/tests/unit/commands/test_borgmatic.py index ed6da98cf..caca645d0 100644 --- a/tests/unit/commands/test_borgmatic.py +++ b/tests/unit/commands/test_borgmatic.py @@ -29,7 +29,9 @@ def test_collect_configuration_run_summary_logs_info_for_success(): flexmock(module).should_receive('run_configuration').and_return([]) arguments = {} - logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)) + logs = tuple( + module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments) + ) assert all(log for log in logs if log.levelno == module.logging.INFO) @@ -39,7 +41,9 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_extract(): flexmock(module).should_receive('run_configuration').and_return([]) arguments = {'extract': flexmock(repository='repo')} - logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)) + logs = tuple( + module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments) + ) assert all(log for log in logs if log.levelno == module.logging.INFO) @@ -50,7 +54,9 @@ def test_collect_configuration_run_summary_logs_critical_for_extract_with_reposi ) arguments = {'extract': flexmock(repository='repo')} - logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)) + logs = tuple( + module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments) + ) assert any(log for log in logs if log.levelno == module.logging.CRITICAL) @@ -61,7 +67,9 @@ def test_collect_configuration_run_summary_logs_critical_for_list_with_archive_a ) arguments = {'list': flexmock(repository='repo', archive='test')} - logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)) + logs = tuple( + module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments) + ) assert any(log for log in logs if log.levelno == module.logging.CRITICAL) @@ -70,7 +78,9 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_list(): flexmock(module).should_receive('run_configuration').and_return([]) arguments = {'list': flexmock(repository='repo', archive=None)} - logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)) + logs = tuple( + module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments) + ) assert all(log for log in logs if log.levelno == module.logging.INFO) @@ -80,7 +90,9 @@ def test_collect_configuration_run_summary_logs_critical_for_run_error(): flexmock(module).should_receive('run_configuration').and_raise(ValueError) arguments = {} - logs = tuple(module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)) + logs = tuple( + module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments) + ) assert any(log for log in logs if log.levelno == module.logging.CRITICAL)