Selaa lähdekoodia

Break out main borgmatic arguments-parsing code into a separate file.

Dan Helfman 6 vuotta sitten
vanhempi
sitoutus
4bf3e906a1

+ 292 - 0
borgmatic/commands/arguments.py

@@ -0,0 +1,292 @@
+import collections
+from argparse import ArgumentParser
+
+from borgmatic.config import collect
+
+SUBPARSER_ALIASES = {
+    'init': ['--init', '-I'],
+    'prune': ['--prune', '-p'],
+    'create': ['--create', '-C'],
+    'check': ['--check', '-k'],
+    'extract': ['--extract', '-x'],
+    'list': ['--list', '-l'],
+    'info': ['--info', '-i'],
+}
+
+
+def parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers):
+    '''
+    Given a sequence of arguments, a top-level parser (containing subparsers), and a subparsers
+    object as returned by argparse.ArgumentParser().add_subparsers(), ask each subparser to parse
+    its own arguments and the top-level parser to parse any remaining arguments.
+
+    Return the result as a dict mapping from subparser name (or "global") to a parsed namespace of
+    arguments.
+    '''
+    arguments = collections.OrderedDict()
+    remaining_arguments = list(unparsed_arguments)
+    alias_to_subparser_name = {
+        alias: subparser_name
+        for subparser_name, aliases in SUBPARSER_ALIASES.items()
+        for alias in aliases
+    }
+
+    # 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
+
+        remaining_arguments.remove(subparser_name)
+        canonical_name = alias_to_subparser_name.get(subparser_name, subparser_name)
+
+        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():
+        subparser = subparsers.choices[subparser_name]
+        parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)
+
+    arguments['global'] = top_level_parser.parse_args(remaining_arguments)
+
+    return arguments
+
+
+def parse_arguments(*unparsed_arguments):
+    '''
+    Given command-line arguments with which this script was invoked, parse the arguments and return
+    them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
+    '''
+    config_paths = collect.get_default_config_paths()
+
+    global_parser = ArgumentParser(add_help=False)
+    global_group = global_parser.add_argument_group('global arguments')
+
+    global_group.add_argument(
+        '-c',
+        '--config',
+        nargs='*',
+        dest='config_paths',
+        default=config_paths,
+        help='Configuration filenames or directories, defaults to: {}'.format(
+            ' '.join(config_paths)
+        ),
+    )
+    global_group.add_argument(
+        '--excludes',
+        dest='excludes_filename',
+        help='Deprecated in favor of exclude_patterns within configuration',
+    )
+    global_group.add_argument(
+        '-n',
+        '--dry-run',
+        dest='dry_run',
+        action='store_true',
+        help='Go through the motions, but do not actually write to any repositories',
+    )
+    global_group.add_argument(
+        '-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
+    )
+    global_group.add_argument(
+        '-v',
+        '--verbosity',
+        type=int,
+        choices=range(0, 3),
+        default=0,
+        help='Display verbose progress to the console (from none to lots: 0, 1, or 2)',
+    )
+    global_group.add_argument(
+        '--syslog-verbosity',
+        type=int,
+        choices=range(0, 3),
+        default=0,
+        help='Display verbose progress to syslog (from none to lots: 0, 1, or 2)',
+    )
+    global_group.add_argument(
+        '--version',
+        dest='version',
+        default=False,
+        action='store_true',
+        help='Display installed version number of borgmatic and exit',
+    )
+
+    top_level_parser = ArgumentParser(
+        description='''
+            A simple wrapper script for the Borg backup software that creates and prunes backups.
+            If none of the action options are given, then borgmatic defaults to: prune, create, and
+            check archives.
+            ''',
+        parents=[global_parser],
+    )
+
+    subparsers = top_level_parser.add_subparsers(title='actions', metavar='')
+    init_parser = subparsers.add_parser(
+        'init',
+        aliases=SUBPARSER_ALIASES['init'],
+        help='Initialize an empty Borg repository',
+        description='Initialize an empty Borg repository',
+        add_help=False,
+    )
+    init_group = init_parser.add_argument_group('init arguments')
+    init_group.add_argument(
+        '-e',
+        '--encryption',
+        dest='encryption_mode',
+        help='Borg repository encryption mode',
+        required=True,
+    )
+    init_group.add_argument(
+        '--append-only',
+        dest='append_only',
+        action='store_true',
+        help='Create an append-only repository',
+    )
+    init_group.add_argument(
+        '--storage-quota',
+        dest='storage_quota',
+        help='Create a repository with a fixed storage quota',
+    )
+    init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+
+    prune_parser = subparsers.add_parser(
+        'prune',
+        aliases=SUBPARSER_ALIASES['prune'],
+        help='Prune archives according to the retention policy',
+        description='Prune archives according to the retention policy',
+        add_help=False,
+    )
+    prune_group = prune_parser.add_argument_group('prune arguments')
+    prune_group.add_argument(
+        '--stats',
+        dest='stats',
+        default=False,
+        action='store_true',
+        help='Display statistics of archive',
+    )
+    prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+
+    create_parser = subparsers.add_parser(
+        'create',
+        aliases=SUBPARSER_ALIASES['create'],
+        help='Create archives (actually perform backups)',
+        description='Create archives (actually perform backups)',
+        add_help=False,
+    )
+    create_group = create_parser.add_argument_group('create arguments')
+    create_group.add_argument(
+        '--progress',
+        dest='progress',
+        default=False,
+        action='store_true',
+        help='Display progress for each file as it is processed',
+    )
+    create_group.add_argument(
+        '--stats',
+        dest='stats',
+        default=False,
+        action='store_true',
+        help='Display statistics of archive',
+    )
+    create_group.add_argument(
+        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
+    )
+    create_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+
+    check_parser = subparsers.add_parser(
+        'check',
+        aliases=SUBPARSER_ALIASES['check'],
+        help='Check archives for consistency',
+        description='Check archives for consistency',
+        add_help=False,
+    )
+    check_group = check_parser.add_argument_group('check arguments')
+    check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+
+    extract_parser = subparsers.add_parser(
+        'extract',
+        aliases=SUBPARSER_ALIASES['extract'],
+        help='Extract a named archive to the current directory',
+        description='Extract a named archive to the current directory',
+        add_help=False,
+    )
+    extract_group = extract_parser.add_argument_group('extract arguments')
+    extract_group.add_argument(
+        '--repository',
+        help='Path of repository to use, defaults to the configured repository if there is only one',
+    )
+    extract_group.add_argument('--archive', help='Name of archive to operate on', required=True)
+    extract_group.add_argument(
+        '--restore-path',
+        nargs='+',
+        dest='restore_paths',
+        help='Paths to restore from archive, defaults to the entire archive',
+    )
+    extract_group.add_argument(
+        '--progress',
+        dest='progress',
+        default=False,
+        action='store_true',
+        help='Display progress for each file as it is processed',
+    )
+    extract_group.add_argument(
+        '-h', '--help', action='help', help='Show this help message and exit'
+    )
+
+    list_parser = subparsers.add_parser(
+        'list',
+        aliases=SUBPARSER_ALIASES['list'],
+        help='List archives',
+        description='List archives',
+        add_help=False,
+    )
+    list_group = list_parser.add_argument_group('list arguments')
+    list_group.add_argument(
+        '--repository',
+        help='Path of repository to use, defaults to the configured repository if there is only one',
+    )
+    list_group.add_argument('--archive', help='Name of archive to operate on')
+    list_group.add_argument(
+        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
+    )
+    list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+
+    info_parser = subparsers.add_parser(
+        'info',
+        aliases=SUBPARSER_ALIASES['info'],
+        help='Display summary information on archives',
+        description='Display summary information on archives',
+        add_help=False,
+    )
+    info_group = info_parser.add_argument_group('info arguments')
+    info_group.add_argument(
+        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
+    )
+    info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
+
+    arguments = parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers)
+
+    if arguments['global'].excludes_filename:
+        raise ValueError(
+            'The --excludes option has been replaced with exclude_patterns in configuration'
+        )
+
+    if 'init' in arguments and arguments['global'].dry_run:
+        raise ValueError('The init action cannot be used with the --dry-run option')
+
+    if (
+        'list' in arguments
+        and 'info' in arguments
+        and arguments['list'].json
+        and arguments['info'].json
+    ):
+        raise ValueError('With the --json option, list and info actions cannot be used together')
+
+    return arguments

+ 1 - 288
borgmatic/commands/borgmatic.py

@@ -3,7 +3,6 @@ import json
 import logging
 import logging
 import os
 import os
 import sys
 import sys
-from argparse import ArgumentParser
 from subprocess import CalledProcessError
 from subprocess import CalledProcessError
 
 
 import colorama
 import colorama
@@ -18,6 +17,7 @@ from borgmatic.borg import info as borg_info
 from borgmatic.borg import init as borg_init
 from borgmatic.borg import init as borg_init
 from borgmatic.borg import list as borg_list
 from borgmatic.borg import list as borg_list
 from borgmatic.borg import prune as borg_prune
 from borgmatic.borg import prune as borg_prune
+from borgmatic.commands.arguments import parse_arguments
 from borgmatic.config import checks, collect, convert, validate
 from borgmatic.config import checks, collect, convert, validate
 from borgmatic.logger import configure_logging, should_do_markup
 from borgmatic.logger import configure_logging, should_do_markup
 from borgmatic.signals import configure_signals
 from borgmatic.signals import configure_signals
@@ -26,293 +26,6 @@ from borgmatic.verbosity import verbosity_to_log_level
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
 LEGACY_CONFIG_PATH = '/etc/borgmatic/config'
-SUBPARSER_ALIASES = {
-    'init': ['--init', '-I'],
-    'prune': ['--prune', '-p'],
-    'create': ['--create', '-C'],
-    'check': ['--check', '-k'],
-    'extract': ['--extract', '-x'],
-    'list': ['--list', '-l'],
-    'info': ['--info', '-i'],
-}
-
-
-def parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers):
-    '''
-    Given a sequence of arguments, a top-level parser (containing subparsers), and a subparsers
-    object as returned by argparse.ArgumentParser().add_subparsers(), ask each subparser to parse
-    its own arguments and the top-level parser to parse any remaining arguments.
-
-    Return the result as a dict mapping from subparser name (or "global") to a parsed namespace of
-    arguments.
-    '''
-    arguments = collections.OrderedDict()
-    remaining_arguments = list(unparsed_arguments)
-    alias_to_subparser_name = {
-        alias: subparser_name
-        for subparser_name, aliases in SUBPARSER_ALIASES.items()
-        for alias in aliases
-    }
-
-    # 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
-
-        remaining_arguments.remove(subparser_name)
-        canonical_name = alias_to_subparser_name.get(subparser_name, subparser_name)
-
-        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():
-        subparser = subparsers.choices[subparser_name]
-        parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)
-
-    arguments['global'] = top_level_parser.parse_args(remaining_arguments)
-
-    return arguments
-
-
-def parse_arguments(*unparsed_arguments):
-    '''
-    Given command-line arguments with which this script was invoked, parse the arguments and return
-    them as a dict mapping from subparser name (or "global") to an argparse.Namespace instance.
-    '''
-    config_paths = collect.get_default_config_paths()
-
-    global_parser = ArgumentParser(add_help=False)
-    global_group = global_parser.add_argument_group('global arguments')
-
-    global_group.add_argument(
-        '-c',
-        '--config',
-        nargs='*',
-        dest='config_paths',
-        default=config_paths,
-        help='Configuration filenames or directories, defaults to: {}'.format(
-            ' '.join(config_paths)
-        ),
-    )
-    global_group.add_argument(
-        '--excludes',
-        dest='excludes_filename',
-        help='Deprecated in favor of exclude_patterns within configuration',
-    )
-    global_group.add_argument(
-        '-n',
-        '--dry-run',
-        dest='dry_run',
-        action='store_true',
-        help='Go through the motions, but do not actually write to any repositories',
-    )
-    global_group.add_argument(
-        '-nc', '--no-color', dest='no_color', action='store_true', help='Disable colored output'
-    )
-    global_group.add_argument(
-        '-v',
-        '--verbosity',
-        type=int,
-        choices=range(0, 3),
-        default=0,
-        help='Display verbose progress to the console (from none to lots: 0, 1, or 2)',
-    )
-    global_group.add_argument(
-        '--syslog-verbosity',
-        type=int,
-        choices=range(0, 3),
-        default=0,
-        help='Display verbose progress to syslog (from none to lots: 0, 1, or 2)',
-    )
-    global_group.add_argument(
-        '--version',
-        dest='version',
-        default=False,
-        action='store_true',
-        help='Display installed version number of borgmatic and exit',
-    )
-
-    top_level_parser = ArgumentParser(
-        description='''
-            A simple wrapper script for the Borg backup software that creates and prunes backups.
-            If none of the action options are given, then borgmatic defaults to: prune, create, and
-            check archives.
-            ''',
-        parents=[global_parser],
-    )
-
-    subparsers = top_level_parser.add_subparsers(title='actions', metavar='')
-    init_parser = subparsers.add_parser(
-        'init',
-        aliases=SUBPARSER_ALIASES['init'],
-        help='Initialize an empty Borg repository',
-        description='Initialize an empty Borg repository',
-        add_help=False,
-    )
-    init_group = init_parser.add_argument_group('init arguments')
-    init_group.add_argument(
-        '-e',
-        '--encryption',
-        dest='encryption_mode',
-        help='Borg repository encryption mode',
-        required=True,
-    )
-    init_group.add_argument(
-        '--append-only',
-        dest='append_only',
-        action='store_true',
-        help='Create an append-only repository',
-    )
-    init_group.add_argument(
-        '--storage-quota',
-        dest='storage_quota',
-        help='Create a repository with a fixed storage quota',
-    )
-    init_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
-
-    prune_parser = subparsers.add_parser(
-        'prune',
-        aliases=SUBPARSER_ALIASES['prune'],
-        help='Prune archives according to the retention policy',
-        description='Prune archives according to the retention policy',
-        add_help=False,
-    )
-    prune_group = prune_parser.add_argument_group('prune arguments')
-    prune_group.add_argument(
-        '--stats',
-        dest='stats',
-        default=False,
-        action='store_true',
-        help='Display statistics of archive',
-    )
-    prune_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
-
-    create_parser = subparsers.add_parser(
-        'create',
-        aliases=SUBPARSER_ALIASES['create'],
-        help='Create archives (actually perform backups)',
-        description='Create archives (actually perform backups)',
-        add_help=False,
-    )
-    create_group = create_parser.add_argument_group('create arguments')
-    create_group.add_argument(
-        '--progress',
-        dest='progress',
-        default=False,
-        action='store_true',
-        help='Display progress for each file as it is processed',
-    )
-    create_group.add_argument(
-        '--stats',
-        dest='stats',
-        default=False,
-        action='store_true',
-        help='Display statistics of archive',
-    )
-    create_group.add_argument(
-        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
-    )
-    create_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
-
-    check_parser = subparsers.add_parser(
-        'check',
-        aliases=SUBPARSER_ALIASES['check'],
-        help='Check archives for consistency',
-        description='Check archives for consistency',
-        add_help=False,
-    )
-    check_group = check_parser.add_argument_group('check arguments')
-    check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
-
-    extract_parser = subparsers.add_parser(
-        'extract',
-        aliases=SUBPARSER_ALIASES['extract'],
-        help='Extract a named archive to the current directory',
-        description='Extract a named archive to the current directory',
-        add_help=False,
-    )
-    extract_group = extract_parser.add_argument_group('extract arguments')
-    extract_group.add_argument(
-        '--repository',
-        help='Path of repository to use, defaults to the configured repository if there is only one',
-    )
-    extract_group.add_argument('--archive', help='Name of archive to operate on', required=True)
-    extract_group.add_argument(
-        '--restore-path',
-        nargs='+',
-        dest='restore_paths',
-        help='Paths to restore from archive, defaults to the entire archive',
-    )
-    extract_group.add_argument(
-        '--progress',
-        dest='progress',
-        default=False,
-        action='store_true',
-        help='Display progress for each file as it is processed',
-    )
-    extract_group.add_argument(
-        '-h', '--help', action='help', help='Show this help message and exit'
-    )
-
-    list_parser = subparsers.add_parser(
-        'list',
-        aliases=SUBPARSER_ALIASES['list'],
-        help='List archives',
-        description='List archives',
-        add_help=False,
-    )
-    list_group = list_parser.add_argument_group('list arguments')
-    list_group.add_argument(
-        '--repository',
-        help='Path of repository to use, defaults to the configured repository if there is only one',
-    )
-    list_group.add_argument('--archive', help='Name of archive to operate on')
-    list_group.add_argument(
-        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
-    )
-    list_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
-
-    info_parser = subparsers.add_parser(
-        'info',
-        aliases=SUBPARSER_ALIASES['info'],
-        help='Display summary information on archives',
-        description='Display summary information on archives',
-        add_help=False,
-    )
-    info_group = info_parser.add_argument_group('info arguments')
-    info_group.add_argument(
-        '--json', dest='json', default=False, action='store_true', help='Output results as JSON'
-    )
-    info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
-
-    arguments = parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers)
-
-    if arguments['global'].excludes_filename:
-        raise ValueError(
-            'The --excludes option has been replaced with exclude_patterns in configuration'
-        )
-
-    if 'init' in arguments and arguments['global'].dry_run:
-        raise ValueError('The init action cannot be used with the --dry-run option')
-
-    if (
-        'list' in arguments
-        and 'info' in arguments
-        and arguments['list'].json
-        and arguments['info'].json
-    ):
-        raise ValueError('With the --json option, list and info actions cannot be used together')
-
-    return arguments
 
 
 
 
 def run_configuration(config_filename, config, arguments):  # pragma: no cover
 def run_configuration(config_filename, config, arguments):  # pragma: no cover

+ 348 - 0
tests/integration/commands/test_arguments.py

@@ -0,0 +1,348 @@
+import pytest
+from flexmock import flexmock
+
+from borgmatic.commands import arguments as module
+
+
+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)
+
+    arguments = module.parse_arguments()
+
+    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'])
+
+    arguments = module.parse_arguments('--config', 'myconfig', 'otherconfig')
+
+    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)
+
+    arguments = module.parse_arguments('--verbosity', '1')
+
+    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)
+
+    arguments = module.parse_arguments('--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_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'])
+
+    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'])
+
+    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'])
+
+    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 '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'])
+
+    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'])
+
+    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():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    with pytest.raises(SystemExit):
+        module.parse_arguments('--posix-me-harder')
+
+
+def test_parse_arguments_disallows_deprecated_excludes_option():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    with pytest.raises(ValueError):
+        module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes')
+
+
+def test_parse_arguments_disallows_encryption_mode_without_init():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    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(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(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(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')
+
+
+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')
+
+
+def test_parse_arguments_disallows_init_and_dry_run():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    with pytest.raises(ValueError):
+        module.parse_arguments(
+            '--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(SystemExit):
+        module.parse_arguments('--config', 'myconfig', '--repository', 'test.borg')
+
+
+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'
+    )
+
+
+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')
+
+
+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(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(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')
+
+
+def test_parse_arguments_requires_archive_with_extract():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    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_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')
+
+
+def test_parse_arguments_disallows_progress_without_create():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    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')
+
+
+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')
+
+
+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(SystemExit):
+        module.parse_arguments('--stats', 'list')
+
+
+def test_parse_arguments_with_just_stats_flag_does_not_raise():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    module.parse_arguments('--stats')
+
+
+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')
+
+
+def test_parse_arguments_allows_json_with_dashed_info():
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
+
+    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')

+ 0 - 345
tests/integration/commands/test_borgmatic.py

@@ -1,355 +1,10 @@
 import subprocess
 import subprocess
 
 
-import pytest
 from flexmock import flexmock
 from flexmock import flexmock
 
 
 from borgmatic.commands import borgmatic as module
 from borgmatic.commands import borgmatic as module
 
 
 
 
-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)
-
-    arguments = module.parse_arguments()
-
-    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'])
-
-    arguments = module.parse_arguments('--config', 'myconfig', 'otherconfig')
-
-    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)
-
-    arguments = module.parse_arguments('--verbosity', '1')
-
-    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)
-
-    arguments = module.parse_arguments('--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_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'])
-
-    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'])
-
-    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'])
-
-    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 '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'])
-
-    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'])
-
-    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():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    with pytest.raises(SystemExit):
-        module.parse_arguments('--posix-me-harder')
-
-
-def test_parse_arguments_disallows_deprecated_excludes_option():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    with pytest.raises(ValueError):
-        module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes')
-
-
-def test_parse_arguments_disallows_encryption_mode_without_init():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    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(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(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(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')
-
-
-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')
-
-
-def test_parse_arguments_disallows_init_and_dry_run():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    with pytest.raises(ValueError):
-        module.parse_arguments(
-            '--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(SystemExit):
-        module.parse_arguments('--config', 'myconfig', '--repository', 'test.borg')
-
-
-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'
-    )
-
-
-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')
-
-
-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(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(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')
-
-
-def test_parse_arguments_requires_archive_with_extract():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    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_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')
-
-
-def test_parse_arguments_disallows_progress_without_create():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    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')
-
-
-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')
-
-
-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(SystemExit):
-        module.parse_arguments('--stats', 'list')
-
-
-def test_parse_arguments_with_just_stats_flag_does_not_raise():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    module.parse_arguments('--stats')
-
-
-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')
-
-
-def test_parse_arguments_allows_json_with_dashed_info():
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
-
-    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')
-
-
 def test_borgmatic_version_matches_news_version():
 def test_borgmatic_version_matches_news_version():
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])