소스 검색

Get tests passing.

Dan Helfman 6 년 전
부모
커밋
0ca43ef67a
3개의 변경된 파일171개의 추가작업 그리고 93개의 파일을 삭제
  1. 13 21
      borgmatic/commands/borgmatic.py
  2. 140 66
      tests/integration/commands/test_borgmatic.py
  3. 18 6
      tests/unit/commands/test_borgmatic.py

+ 13 - 21
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,

+ 140 - 66
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 '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'])
 
-    assert parser.prune is True
-    assert parser.create is True
-    assert parser.check is True
+    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 '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 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_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 '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'])
 
-    assert parser.prune is False
-    assert parser.create is True
-    assert parser.check is True
+    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_before_create():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    module.parse_arguments('--progress', 'create', 'list')
 
 
-def test_parse_arguments_allows_progress_and_create():
+def test_parse_arguments_allows_progress_after_create():
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
 
-    module.parse_arguments('--progress', '--create', '--list')
+    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():

+ 18 - 6
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)