Compare commits

..
20 Commits
Author SHA1 Message Date
Dan Helfman 1a1bb71af1 Fix error with "borgmatic check --only" command-line flag with "extract" consistency check (#217). 2019-09-20 11:43:27 -07:00
Dan Helfman ae45dfe63a Clarify command-like help for check --only. 2019-09-19 15:20:05 -07:00
Dan Helfman d6ac7a9192 Upgrade various dependencies. 2019-09-19 13:04:59 -07:00
Dan Helfman d959fdbf8d Document new "check --only" command-line flag. 2019-09-19 11:50:29 -07:00
Dan Helfman 81739791e0 Override configured consistency checks via "borgmatic check --only" command-line flag (#210). 2019-09-19 11:43:53 -07:00
Dan Helfman 4cdff74e9b Support for Borg check --verify-data flag via borgmatic "data" consistency check (#210). 2019-09-18 16:52:27 -07:00
Dan Helfman 11e830bb1d Fix flake8 warning. 2019-09-18 14:11:56 -07:00
Dan Helfman cba00a9c4e Add NEWS entry for generate-borgmatic-config comment change. 2019-09-18 14:06:03 -07:00
Dan HelfmanandGitea f2198de151 Merge branch 'comments-white-space' of polyzen/borgmatic into master 2019-09-18 21:03:56 +00:00
Daniel M. Capella 0c439c0c02 Add space to separate comments from tokens
https://yaml.org/spec/1.2/spec.html#id2780069
2019-09-17 20:00:58 -04:00
Dan Helfman f11a9bb4aa Revert "Fix for spurious Borg traceback when initializing a repository in an empty directory (#201)."
This reverts commit 9585c8f908.
2019-09-14 16:14:20 -07:00
Dan HelfmanandGitea ee6f390910 Merge branch 'point-to-stable-docs' of polyzen/borgmatic into master 2019-09-14 21:53:34 +00:00
Daniel M. Capella 9a5117db14 Consistently point to stable Borg docs 2019-09-14 17:30:28 -04:00
Dan Helfman 9585c8f908 Fix for spurious Borg traceback when initializing a repository in an empty directory (#201). 2019-09-13 13:08:23 -07:00
Dan Helfman 3495484ddd Bump version for release. 2019-09-12 21:35:00 -07:00
Dan Helfman 67ab2acb82 Fix for hook erroring with exit code 1 not being interpreted as an error (#214). 2019-09-12 16:37:43 -07:00
Dan Helfman c085bacccf Reorder arguments passed to Borg to fix duplicate directories when using Borg patterns (#213). 2019-09-12 15:27:04 -07:00
Dan Helfman 896401088e Fix for traceback when the "checks" option has an empty value (#208). 2019-08-26 09:52:32 -07:00
Dan Helfman ef3dda9213 Bypass Borg error about a moved repository (#209). 2019-08-26 09:39:41 -07:00
Dan Helfman c9f5d9b048 In issue template, use python3 instead of python. 2019-08-24 13:08:18 -07:00
32 changed files with 452 additions and 288 deletions
+1 -1
View File
@@ -26,6 +26,6 @@ Use `sudo borg --version`
**Python version:** [version here]
Use `python --version`
Use `python3 --version`
**operating system and version:** [OS here]
+16
View File
@@ -1,3 +1,19 @@
1.3.17
* #217: Fix error with "borgmatic check --only" command-line flag with "extract" consistency check.
1.3.16
* #210: Support for Borg check --verify-data flag via borgmatic "data" consistency check.
* #210: Override configured consistency checks via "borgmatic check --only" command-line flag.
* When generating sample configuration with generate-borgmatic-config, add a space after each "#"
comment indicator.
1.3.15
* #208: Fix for traceback when the "checks" option has an empty value.
* #209: Bypass Borg error about a moved repository via "relocated_repo_access_is_ok" option in
borgmatic storage configuration section.
* #213: Reorder arguments passed to Borg to fix duplicate directories when using Borg patterns.
* #214: Fix for hook erroring with exit code 1 not being interpreted as an error.
1.3.14
* #204: Do not treat Borg warnings (exit code 1) as failures.
* When validating configuration files, require strings instead of allowing any scalar type.
+30 -16
View File
@@ -10,9 +10,10 @@ DEFAULT_PREFIX = '{hostname}-'
logger = logging.getLogger(__name__)
def _parse_checks(consistency_config):
def _parse_checks(consistency_config, only_checks=None):
'''
Given a consistency config with a "checks" list, transform it to a tuple of named checks to run.
Given a consistency config with a "checks" list, and an optional list of override checks,
transform them a tuple of named checks to run.
For example, given a retention config of:
@@ -22,16 +23,21 @@ def _parse_checks(consistency_config):
('repository', 'archives')
If no "checks" option is present, return the DEFAULT_CHECKS. If the checks value is the string
"disabled", return an empty tuple, meaning that no checks should be run.
If no "checks" option is present in the config, return the DEFAULT_CHECKS. If the checks value
is the string "disabled", return an empty tuple, meaning that no checks should be run.
If the "data" option is present, then make sure the "archives" option is included as well.
'''
checks = consistency_config.get('checks', [])
checks = [
check.lower() for check in (only_checks or consistency_config.get('checks', []) or [])
]
if checks == ['disabled']:
return ()
return (
tuple(check for check in checks if check.lower() not in ('disabled', '')) or DEFAULT_CHECKS
)
if 'data' in checks and 'archives' not in checks:
checks.append('archives')
return tuple(check for check in checks if check not in ('disabled', '')) or DEFAULT_CHECKS
def _make_check_flags(checks, check_last=None, prefix=None):
@@ -68,30 +74,37 @@ def _make_check_flags(checks, check_last=None, prefix=None):
'Ignoring consistency prefix option, as "archives" is not in consistency checks.'
)
common_flags = last_flags + prefix_flags + (('--verify-data',) if 'data' in checks else ())
if set(DEFAULT_CHECKS).issubset(set(checks)):
return last_flags + prefix_flags
return common_flags
return (
tuple('--{}-only'.format(check) for check in checks if check in DEFAULT_CHECKS)
+ last_flags
+ prefix_flags
+ common_flags
)
def check_archives(
repository, storage_config, consistency_config, local_path='borg', remote_path=None
repository,
storage_config,
consistency_config,
local_path='borg',
remote_path=None,
only_checks=None,
):
'''
Given a local or remote repository path, a storage config dict, a consistency config dict,
and a local/remote commands to run, check the contained Borg archives for consistency.
local/remote commands to run, and an optional list of checks to use instead of configured
checks, check the contained Borg archives for consistency.
If there are no consistency checks to run, skip running them.
'''
checks = _parse_checks(consistency_config)
checks = _parse_checks(consistency_config, only_checks)
check_last = consistency_config.get('check_last', None)
lock_wait = None
if set(checks).intersection(set(DEFAULT_CHECKS)):
if set(checks).intersection(set(DEFAULT_CHECKS + ('data',))):
remote_path_flags = ('--remote-path', remote_path) if remote_path else ()
lock_wait = storage_config.get('lock_wait', None)
lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else ()
@@ -105,11 +118,12 @@ def check_archives(
prefix = consistency_config.get('prefix', DEFAULT_PREFIX)
full_command = (
(local_path, 'check', repository)
(local_path, 'check')
+ _make_check_flags(checks, check_last, prefix)
+ remote_path_flags
+ lock_wait_flags
+ verbosity_flags
+ (repository,)
)
execute_command(full_command)
+7 -8
View File
@@ -126,14 +126,7 @@ def create_archive(
archive_name_format = storage_config.get('archive_name_format', default_archive_name_format)
full_command = (
(
local_path,
'create',
'{repository}::{archive_name_format}'.format(
repository=repository, archive_name_format=archive_name_format
),
)
+ sources
(local_path, 'create')
+ _make_pattern_flags(location_config, pattern_file.name if pattern_file else None)
+ _make_exclude_flags(location_config, exclude_file.name if exclude_file else None)
+ (('--checkpoint-interval', str(checkpoint_interval)) if checkpoint_interval else ())
@@ -162,6 +155,12 @@ def create_archive(
+ (('--dry-run',) if dry_run else ())
+ (('--progress',) if progress else ())
+ (('--json',) if json else ())
+ (
'{repository}::{archive_name_format}'.format(
repository=repository, archive_name_format=archive_name_format
),
)
+ sources
)
if json:
+12
View File
@@ -11,9 +11,21 @@ OPTION_TO_ENVIRONMENT_VARIABLE = {
'ssh_command': 'BORG_RSH',
}
DEFAULT_BOOL_OPTION_TO_ENVIRONMENT_VARIABLE = {
'relocated_repo_access_is_ok': 'BORG_RELOCATED_REPO_ACCESS_IS_OK',
'unknown_unencrypted_repo_access_is_ok': 'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK',
}
def initialize(storage_config):
for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
value = storage_config.get(option_name)
if value:
os.environ[environment_variable_name] = value
for (
option_name,
environment_variable_name,
) in DEFAULT_BOOL_OPTION_TO_ENVIRONMENT_VARIABLE.items():
value = storage_config.get(option_name, False)
os.environ[environment_variable_name] = 'yes' if value else 'no'
+11 -11
View File
@@ -19,10 +19,11 @@ def extract_last_archive_dry_run(repository, lock_wait=None, local_path='borg',
verbosity_flags = ('--info',)
full_list_command = (
(local_path, 'list', '--short', repository)
(local_path, 'list', '--short')
+ remote_path_flags
+ lock_wait_flags
+ verbosity_flags
+ (repository,)
)
list_output = execute_command(full_list_command, output_log_level=None)
@@ -34,18 +35,16 @@ def extract_last_archive_dry_run(repository, lock_wait=None, local_path='borg',
list_flag = ('--list',) if logger.isEnabledFor(logging.DEBUG) else ()
full_extract_command = (
(
local_path,
'extract',
'--dry-run',
'{repository}::{last_archive_name}'.format(
repository=repository, last_archive_name=last_archive_name
),
)
(local_path, 'extract', '--dry-run')
+ remote_path_flags
+ lock_wait_flags
+ verbosity_flags
+ list_flag
+ (
'{repository}::{last_archive_name}'.format(
repository=repository, last_archive_name=last_archive_name
),
)
)
execute_command(full_extract_command)
@@ -71,8 +70,7 @@ def extract_archive(
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(local_path, 'extract', '::'.join((repository, archive)))
+ (tuple(restore_paths) if restore_paths else ())
(local_path, 'extract')
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--numeric-owner',) if location_config.get('numeric_owner') else ())
+ (('--umask', str(umask)) if umask else ())
@@ -81,6 +79,8 @@ def extract_archive(
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--dry-run',) if dry_run else ())
+ (('--progress',) if progress else ())
+ ('::'.join((repository, archive)),)
+ (tuple(restore_paths) if restore_paths else ())
)
execute_command(full_command)
+6 -7
View File
@@ -17,13 +17,7 @@ def display_archives_info(
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(
local_path,
'info',
'::'.join((repository, info_arguments.archive))
if info_arguments.archive
else repository,
)
(local_path, 'info')
+ (
('--info',)
if logger.getEffectiveLevel() == logging.INFO and not info_arguments.json
@@ -37,6 +31,11 @@ def display_archives_info(
+ make_flags('remote-path', remote_path)
+ make_flags('lock-wait', lock_wait)
+ make_flags_from_arguments(info_arguments, excludes=('repository', 'archive'))
+ (
'::'.join((repository, info_arguments.archive))
if info_arguments.archive
else repository,
)
)
return execute_command(
+2 -1
View File
@@ -34,13 +34,14 @@ def initialize_repository(
raise
init_command = (
(local_path, 'init', repository)
(local_path, 'init')
+ (('--encryption', encryption_mode) if encryption_mode else ())
+ (('--append-only',) if append_only else ())
+ (('--storage-quota', storage_quota) if storage_quota else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--remote-path', remote_path) if remote_path else ())
+ (repository,)
)
# Don't use execute_command() here because it doesn't support interactive prompts.
+6 -7
View File
@@ -15,13 +15,7 @@ def list_archives(repository, storage_config, list_arguments, local_path='borg',
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(
local_path,
'list',
'::'.join((repository, list_arguments.archive))
if list_arguments.archive
else repository,
)
(local_path, 'list')
+ (
('--info',)
if logger.getEffectiveLevel() == logging.INFO and not list_arguments.json
@@ -35,6 +29,11 @@ def list_archives(repository, storage_config, list_arguments, local_path='borg',
+ make_flags('remote-path', remote_path)
+ make_flags('lock-wait', lock_wait)
+ make_flags_from_arguments(list_arguments, excludes=('repository', 'archive'))
+ (
'::'.join((repository, list_arguments.archive))
if list_arguments.archive
else repository,
)
)
return execute_command(
+2 -1
View File
@@ -51,7 +51,7 @@ def prune_archives(
lock_wait = storage_config.get('lock_wait', None)
full_command = (
(local_path, 'prune', repository)
(local_path, 'prune')
+ tuple(element for pair in _make_prune_flags(retention_config) for element in pair)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
@@ -61,6 +61,7 @@ def prune_archives(
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--dry-run',) if dry_run else ())
+ (('--stats',) if stats else ())
+ (repository,)
)
execute_command(full_command)
+57 -20
View File
@@ -14,14 +14,14 @@ SUBPARSER_ALIASES = {
}
def parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers):
def parse_subparser_arguments(unparsed_arguments, 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.
Given a sequence of arguments, and a subparsers object as returned by
argparse.ArgumentParser().add_subparsers(), give each requested action's subparser a shot at
parsing all arguments. This allows common arguments like "--repository" to be shared across
multiple subparsers.
Return the result as a dict mapping from subparser name (or "global") to a parsed namespace of
arguments.
Return the result as a dict mapping from subparser name to a parsed namespace of arguments.
'''
arguments = collections.OrderedDict()
remaining_arguments = list(unparsed_arguments)
@@ -31,35 +31,63 @@ def parse_subparser_arguments(unparsed_arguments, top_level_parser, subparsers):
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:
if subparser_name not in remaining_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)
# If a parsed value happens to be the same as the name of a subparser, remove it from the
# remaining arguments. This prevents, for instance, "check --only extract" from triggering
# the "extract" subparser.
parsed, unused_remaining = subparser.parse_known_args(unparsed_arguments)
for value in vars(parsed).values():
if isinstance(value, str):
if value in subparsers.choices:
remaining_arguments.remove(value)
elif isinstance(value, list):
for item in value:
if item in subparsers.choices:
remaining_arguments.remove(item)
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)
parsed, unused_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_global_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(), parse and return any global
arguments as a parsed argparse.Namespace instance.
'''
# Ask each subparser, one by one, to greedily consume arguments. Any arguments that remain
# are global arguments.
remaining_arguments = list(unparsed_arguments)
present_subparser_names = set()
for subparser_name, subparser in subparsers.choices.items():
if subparser_name not in remaining_arguments:
continue
present_subparser_names.add(subparser_name)
unused_parsed, remaining_arguments = subparser.parse_known_args(remaining_arguments)
# Remove the subparser names themselves.
for subparser_name in present_subparser_names:
if subparser_name in remaining_arguments:
remaining_arguments.remove(subparser_name)
return top_level_parser.parse_args(remaining_arguments)
def parse_arguments(*unparsed_arguments):
'''
Given command-line arguments with which this script was invoked, parse the arguments and return
@@ -212,6 +240,14 @@ def parse_arguments(*unparsed_arguments):
add_help=False,
)
check_group = check_parser.add_argument_group('check arguments')
check_group.add_argument(
'--only',
metavar='CHECK',
choices=('repository', 'archives', 'data', 'extract'),
dest='only',
action='append',
help='Run a particular consistency check (repository, archives, data, or extract) instead of configured checks; can specify flag multiple times',
)
check_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
extract_parser = subparsers.add_parser(
@@ -331,7 +367,8 @@ def parse_arguments(*unparsed_arguments):
)
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)
arguments = parse_subparser_arguments(unparsed_arguments, subparsers)
arguments['global'] = parse_global_arguments(unparsed_arguments, top_level_parser, subparsers)
if arguments['global'].excludes_filename:
raise ValueError(
+6 -1
View File
@@ -147,7 +147,12 @@ def run_actions(
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
repository,
storage,
consistency,
local_path=local_path,
remote_path=remote_path,
only_checks=arguments['check'].only,
)
if 'extract' in arguments:
if arguments['extract'].repository is None or repository == arguments['extract'].repository:
+2 -2
View File
@@ -45,10 +45,10 @@ def _comment_out_line(line):
# Comment out the names of optional sections.
one_indent = ' ' * INDENT
if not line.startswith(one_indent):
return '#' + line
return '# ' + line
# Otherwise, comment out the line, but insert the "#" after the first indent for aesthetics.
return '#'.join((one_indent, line[INDENT:]))
return '# '.join((one_indent, line[INDENT:]))
REQUIRED_KEYS = {'source_directories', 'repositories', 'keep_daily'}
+17 -6
View File
@@ -224,6 +224,16 @@ map:
archives with a different archive name format. And you should also specify a
prefix in the consistency section as well.
example: "{hostname}-documents-{now}"
relocated_repo_access_is_ok:
type: bool
desc: Bypass Borg error about a repository that has been moved. Defaults to false.
example: true
unknown_unencrypted_repo_access_is_ok:
type: bool
desc: |
Bypass Borg error about a previously unknown unencrypted repository. Defaults to
false.
example: true
retention:
desc: |
Retention policy for how many backups to keep in each category. See
@@ -280,14 +290,15 @@ map:
checks:
seq:
- type: str
enum: ['repository', 'archives', 'extract', 'disabled']
enum: ['repository', 'archives', 'data', 'extract', 'disabled']
unique: true
desc: |
List of one or more consistency checks to run: "repository", "archives", and/or
"extract". Defaults to "repository" and "archives". Set to "disabled" to disable
all consistency checks. "repository" checks the consistency of the repository,
"archive" checks all of the archives, and "extract" does an extraction dry-run
of the most recent archive.
List of one or more consistency checks to run: "repository", "archives", "data",
and/or "extract". Defaults to "repository" and "archives". Set to "disabled" to
disable all consistency checks. "repository" checks the consistency of the
repository, "archives" checks all of the archives, "data" verifies the integrity
of the data within the archives, and "extract" does an extraction dry-run of the
most recent archive. Note that "data" implies "archives".
example:
- repository
- archives
+6 -1
View File
@@ -32,7 +32,12 @@ def execute_and_log_output(full_command, output_log_level, shell):
logger.log(output_log_level, remaining_output)
exit_code = process.poll()
if exit_code >= BORG_ERROR_EXIT_CODE:
# If shell is True, assume we're running something other than Borg and should treat all non-zero
# exit codes as errors.
error = bool(exit_code != 0) if shell else bool(exit_code >= BORG_ERROR_EXIT_CODE)
if error:
# If an error occurs, include its output in the raised exception so that we don't
# inadvertently hide error output.
if len(last_lines) == ERROR_OUTPUT_MAX_LINE_COUNT:
+3 -3
View File
@@ -1,4 +1,4 @@
FROM python:3.7.3-alpine3.10 as borgmatic
FROM python:3.7.4-alpine3.10 as borgmatic
COPY . /app
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
@@ -7,7 +7,7 @@ RUN borgmatic --help > /command-line.txt \
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
&& borgmatic "$action" --help >> /command-line.txt; done
FROM node:12.4.0-alpine as html
FROM node:12.10.0-alpine as html
WORKDIR /source
@@ -23,7 +23,7 @@ COPY . /source
RUN npx eleventy --input=/source/docs --output=/output/docs \
&& mv /output/docs/index.html /output/index.html
FROM nginx:1.16.0-alpine
FROM nginx:1.16.1-alpine
COPY --from=html /output /usr/share/nginx/html
COPY --from=borgmatic /etc/borgmatic/config.yaml /usr/share/nginx/html/docs/reference/config.yaml
@@ -68,6 +68,16 @@ consistency:
- path/of/repository_to_check.borg
```
Finally, you can override your configuration file's consistency checks, and
run particular checks via the command-line. For instance:
```bash
borgmatic check --only data --only extract
```
This is useful for running slow consistency checks on an infrequent basis,
separate from your regular checks.
## Troubleshooting
+4 -4
View File
@@ -4,7 +4,7 @@ title: How to set up backups with borgmatic
## Installation
To get up and running, first [install
Borg](https://borgbackup.readthedocs.io/en/latest/installation.html), at
Borg](https://borgbackup.readthedocs.io/en/stable/installation.html), at
least version 1.1.
Borgmatic consumes configurations in `/etc/borgmatic/` and `/etc/borgmatic.d/`
@@ -88,7 +88,7 @@ encrypt your Borg repository with a passphrase instead of a key file, you'll
either need to set the borgmatic `encryption_passphrase` configuration
variable or set the `BORG_PASSPHRASE` environment variable. See the
[repository encryption
section](https://borgbackup.readthedocs.io/en/latest/quickstart.html#repository-encryption)
section](https://borgbackup.readthedocs.io/en/stable/quickstart.html#repository-encryption)
of the Borg Quick Start for more info.
Alternatively, you can specify the passphrase programatically by setting
@@ -133,11 +133,11 @@ This uses the borgmatic configuration file you created above to determine
which local or remote repository to create, and encrypts it with the
encryption passphrase specified there if one is provided. Read about [Borg
encryption
modes](https://borgbackup.readthedocs.io/en/latest/usage/init.html#encryption-modes)
modes](https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-modes)
for the menu of available encryption modes.
Also, optionally check out the [Borg Quick
Start](https://borgbackup.readthedocs.org/en/latest/quickstart.html) for more
Start](https://borgbackup.readthedocs.org/en/stable/quickstart.html) for more
background about repository initialization.
Note that borgmatic skips repository initialization if the repository already
+1 -1
View File
@@ -1,6 +1,6 @@
from setuptools import find_packages, setup
VERSION = '1.3.14'
VERSION = '1.3.17'
setup(
+7 -7
View File
@@ -4,21 +4,21 @@ attrs==19.1.0
black==19.3b0; python_version >= '3.6'
click==7.0
colorama==0.4.1
coverage==4.5.3
coverage==4.5.4
docopt==0.6.2
flake8==3.7.7
flake8==3.7.8
flexmock==0.10.4
isort==4.3.20
isort==4.3.21
mccabe==0.6.1
more-itertools==7.0.0
pluggy==0.12.0
more-itertools==7.2.0
pluggy==0.13.0
py==1.8.0
pycodestyle==2.5.0
pyflakes==2.1.1
pykwalify==1.7.0
pytest==4.6.3
pytest==5.1.2
pytest-cov==2.7.1
python-dateutil==2.8.0
PyYAML==5.1.1
PyYAML==5.1.2
ruamel.yaml>0.15.0,<0.17.0
toml==0.10.0
+18 -6
View File
@@ -322,12 +322,6 @@ def test_parse_arguments_with_stats_flag_but_no_create_or_prune_flag_raises_valu
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'])
@@ -346,3 +340,21 @@ def test_parse_arguments_disallows_json_with_both_list_and_info():
with pytest.raises(ValueError):
module.parse_arguments('list', 'info', '--json')
def test_parse_arguments_check_only_extract_does_not_raise_extract_subparser_error():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
module.parse_arguments('check', '--only', 'extract')
def test_parse_arguments_extract_archive_check_does_not_raise_check_subparser_error():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
module.parse_arguments('extract', '--archive', 'check')
def test_parse_arguments_extract_with_check_only_extract_does_not_raise():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
module.parse_arguments('extract', '--archive', 'name', 'check', '--only', 'extract')
+11 -10
View File
@@ -31,17 +31,17 @@ def test_comment_out_line_skips_already_commented_out_line():
def test_comment_out_line_comments_section_name():
line = 'figgy-pudding:'
assert module._comment_out_line(line) == '#' + line
assert module._comment_out_line(line) == '# ' + line
def test_comment_out_line_comments_indented_option():
line = ' enabled: true'
assert module._comment_out_line(line) == ' #enabled: true'
assert module._comment_out_line(line) == ' # enabled: true'
def test_comment_out_optional_configuration_comments_optional_config_only():
flexmock(module)._comment_out_line = lambda line: '#' + line
flexmock(module)._comment_out_line = lambda line: '# ' + line
config = '''
foo:
bar:
@@ -56,18 +56,19 @@ location:
other: thing
'''
# flake8: noqa
expected_config = '''
#foo:
# bar:
# - baz
# - quux
#
# foo:
# bar:
# - baz
# - quux
#
location:
repositories:
- one
- two
#
# other: thing
#
# other: thing
'''
assert module._comment_out_optional_configuration(config.strip()) == expected_config.strip()
+9
View File
@@ -32,6 +32,15 @@ def test_execute_and_log_output_includes_borg_error_output_in_exception():
assert error.value.output
def test_execute_and_log_output_with_shell_error_raises():
flexmock(module.logger).should_receive('log')
with pytest.raises(subprocess.CalledProcessError) as error:
module.execute_and_log_output(['false'], output_log_level=logging.INFO, shell=True)
assert error.value.returncode == 1
def test_execute_and_log_output_truncates_long_borg_error_output():
flexmock(module).ERROR_OUTPUT_MAX_LINE_COUNT = 0
flexmock(module.logger).should_receive('log')
+46 -4
View File
@@ -34,18 +34,60 @@ def test_parse_checks_with_blank_value_returns_defaults():
assert checks == module.DEFAULT_CHECKS
def test_parse_checks_with_none_value_returns_defaults():
checks = module._parse_checks({'checks': None})
assert checks == module.DEFAULT_CHECKS
def test_parse_checks_with_disabled_returns_no_checks():
checks = module._parse_checks({'checks': ['disabled']})
assert checks == ()
def test_parse_checks_with_data_check_also_injects_archives():
checks = module._parse_checks({'checks': ['data']})
assert checks == ('data', 'archives')
def test_parse_checks_with_data_check_passes_through_archives():
checks = module._parse_checks({'checks': ['data', 'archives']})
assert checks == ('data', 'archives')
def test_parse_checks_prefers_override_checks_to_configured_checks():
checks = module._parse_checks({'checks': ['archives']}, only_checks=['repository', 'extract'])
assert checks == ('repository', 'extract')
def test_parse_checks_with_override_data_check_also_injects_archives():
checks = module._parse_checks({'checks': ['extract']}, only_checks=['data'])
assert checks == ('data', 'archives')
def test_make_check_flags_with_repository_check_returns_flag():
flags = module._make_check_flags(('repository',))
assert flags == ('--repository-only',)
def test_make_check_flags_with_archives_check_returns_flag():
flags = module._make_check_flags(('archives',))
assert flags == ('--archives-only',)
def test_make_check_flags_with_data_check_returns_flag():
flags = module._make_check_flags(('data',))
assert flags == ('--verify-data',)
def test_make_check_flags_with_extract_omits_extract_flag():
flags = module._make_check_flags(('extract',))
@@ -157,7 +199,7 @@ def test_check_archives_with_log_info_calls_borg_with_info_parameter():
flexmock(module).should_receive('_parse_checks').and_return(checks)
flexmock(module).should_receive('_make_check_flags').and_return(())
insert_logging_mock(logging.INFO)
insert_execute_command_mock(('borg', 'check', 'repo', '--info'))
insert_execute_command_mock(('borg', 'check', '--info', 'repo'))
module.check_archives(
repository='repo', storage_config={}, consistency_config=consistency_config
@@ -170,7 +212,7 @@ def test_check_archives_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module).should_receive('_parse_checks').and_return(checks)
flexmock(module).should_receive('_make_check_flags').and_return(())
insert_logging_mock(logging.DEBUG)
insert_execute_command_mock(('borg', 'check', 'repo', '--debug', '--show-rc'))
insert_execute_command_mock(('borg', 'check', '--debug', '--show-rc', 'repo'))
module.check_archives(
repository='repo', storage_config={}, consistency_config=consistency_config
@@ -213,7 +255,7 @@ def test_check_archives_with_remote_path_calls_borg_with_remote_path_parameters(
flexmock(module).should_receive('_make_check_flags').with_args(
checks, check_last, module.DEFAULT_PREFIX
).and_return(())
insert_execute_command_mock(('borg', 'check', 'repo', '--remote-path', 'borg1'))
insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
module.check_archives(
repository='repo',
@@ -231,7 +273,7 @@ def test_check_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
flexmock(module).should_receive('_make_check_flags').with_args(
checks, check_last, module.DEFAULT_PREFIX
).and_return(())
insert_execute_command_mock(('borg', 'check', 'repo', '--lock-wait', '5'))
insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
module.check_archives(
repository='repo', storage_config={'lock_wait': 5}, consistency_config=consistency_config
+38 -28
View File
@@ -157,7 +157,7 @@ def test_make_exclude_flags_is_empty_when_config_has_no_excludes():
DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
CREATE_COMMAND = ('borg', 'create', 'repo::{}'.format(DEFAULT_ARCHIVE_NAME), 'foo', 'bar')
ARCHIVE_WITH_PATHS = ('repo::{}'.format(DEFAULT_ARCHIVE_NAME), 'foo', 'bar')
def test_create_archive_calls_borg_with_parameters():
@@ -167,7 +167,7 @@ def test_create_archive_calls_borg_with_parameters():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND, output_log_level=logging.INFO
('borg', 'create') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -192,7 +192,7 @@ def test_create_archive_with_patterns_calls_borg_with_patterns():
flexmock(module).should_receive('_make_pattern_flags').and_return(pattern_flags)
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + pattern_flags, output_log_level=logging.INFO
('borg', 'create') + pattern_flags + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -217,7 +217,7 @@ def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(exclude_flags)
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + exclude_flags, output_log_level=logging.INFO
('borg', 'create') + exclude_flags + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -240,7 +240,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--list', '--filter', 'AME-', '--info', '--stats'),
('borg', 'create', '--list', '--filter', 'AME-', '--info', '--stats') + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
insert_logging_mock(logging.INFO)
@@ -265,7 +265,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--json',), output_log_level=None
('borg', 'create', '--json') + ARCHIVE_WITH_PATHS, output_log_level=None
)
insert_logging_mock(logging.INFO)
@@ -289,7 +289,8 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--list', '--filter', 'AME-', '--stats', '--debug', '--show-rc'),
('borg', 'create', '--list', '--filter', 'AME-', '--stats', '--debug', '--show-rc')
+ ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
insert_logging_mock(logging.DEBUG)
@@ -313,7 +314,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--json',), output_log_level=None
('borg', 'create', '--json') + ARCHIVE_WITH_PATHS, output_log_level=None
)
insert_logging_mock(logging.DEBUG)
@@ -338,7 +339,7 @@ def test_create_archive_with_dry_run_calls_borg_with_dry_run_parameter():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--dry-run',), output_log_level=logging.INFO
('borg', 'create', '--dry-run') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -363,7 +364,8 @@ def test_create_archive_with_dry_run_and_log_info_calls_borg_without_stats_param
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--list', '--filter', 'AME-', '--info', '--dry-run'),
('borg', 'create', '--list', '--filter', 'AME-', '--info', '--dry-run')
+ ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
insert_logging_mock(logging.INFO)
@@ -390,7 +392,8 @@ def test_create_archive_with_dry_run_and_log_debug_calls_borg_without_stats_para
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--list', '--filter', 'AME-', '--debug', '--show-rc', '--dry-run'),
('borg', 'create', '--list', '--filter', 'AME-', '--debug', '--show-rc', '--dry-run')
+ ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
insert_logging_mock(logging.DEBUG)
@@ -414,7 +417,8 @@ def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_inte
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--checkpoint-interval', '600'), output_log_level=logging.INFO
('borg', 'create', '--checkpoint-interval', '600') + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
module.create_archive(
@@ -436,7 +440,8 @@ def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_param
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--chunker-params', '1,2,3,4'), output_log_level=logging.INFO
('borg', 'create', '--chunker-params', '1,2,3,4') + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
module.create_archive(
@@ -458,7 +463,8 @@ def test_create_archive_with_compression_calls_borg_with_compression_parameters(
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--compression', 'rle'), output_log_level=logging.INFO
('borg', 'create', '--compression', 'rle') + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
module.create_archive(
@@ -480,7 +486,8 @@ def test_create_archive_with_remote_rate_limit_calls_borg_with_remote_ratelimit_
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--remote-ratelimit', '100'), output_log_level=logging.INFO
('borg', 'create', '--remote-ratelimit', '100') + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
module.create_archive(
@@ -502,7 +509,7 @@ def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_par
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--one-file-system',), output_log_level=logging.INFO
('borg', 'create', '--one-file-system') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -525,7 +532,7 @@ def test_create_archive_with_numeric_owner_calls_borg_with_numeric_owner_paramet
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--numeric-owner',), output_log_level=logging.INFO
('borg', 'create', '--numeric-owner') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -548,7 +555,7 @@ def test_create_archive_with_read_special_calls_borg_with_read_special_parameter
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--read-special',), output_log_level=logging.INFO
('borg', 'create', '--read-special') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -572,7 +579,7 @@ def test_create_archive_with_option_true_calls_borg_without_corresponding_parame
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND, output_log_level=logging.INFO
('borg', 'create') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -596,7 +603,8 @@ def test_create_archive_with_option_false_calls_borg_with_corresponding_paramete
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--no' + option_name.replace('_', ''),), output_log_level=logging.INFO
('borg', 'create', '--no' + option_name.replace('_', '')) + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
module.create_archive(
@@ -619,7 +627,8 @@ def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters(
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--files-cache', 'ctime,size'), output_log_level=logging.INFO
('borg', 'create', '--files-cache', 'ctime,size') + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
module.create_archive(
@@ -642,7 +651,7 @@ def test_create_archive_with_local_path_calls_borg_via_local_path():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
('borg1',) + CREATE_COMMAND[1:], output_log_level=logging.INFO
('borg1', 'create') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -665,7 +674,8 @@ def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters(
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--remote-path', 'borg1'), output_log_level=logging.INFO
('borg', 'create', '--remote-path', 'borg1') + ARCHIVE_WITH_PATHS,
output_log_level=logging.INFO,
)
module.create_archive(
@@ -688,7 +698,7 @@ def test_create_archive_with_umask_calls_borg_with_umask_parameters():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--umask', '740'), output_log_level=logging.INFO
('borg', 'create', '--umask', '740') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -710,7 +720,7 @@ def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--lock-wait', '5'), output_log_level=logging.INFO
('borg', 'create', '--lock-wait', '5') + ARCHIVE_WITH_PATHS, output_log_level=logging.INFO
)
module.create_archive(
@@ -732,7 +742,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--stats',), output_log_level=logging.WARNING
('borg', 'create', '--stats') + ARCHIVE_WITH_PATHS, output_log_level=logging.WARNING
)
module.create_archive(
@@ -755,7 +765,7 @@ def test_create_archive_with_json_calls_borg_with_json_parameter():
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--json',), output_log_level=None
('borg', 'create', '--json') + ARCHIVE_WITH_PATHS, output_log_level=None
).and_return('[]')
json_output = module.create_archive(
@@ -780,7 +790,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter()
flexmock(module).should_receive('_make_pattern_flags').and_return(())
flexmock(module).should_receive('_make_exclude_flags').and_return(())
flexmock(module).should_receive('execute_command').with_args(
CREATE_COMMAND + ('--json',), output_log_level=None
('borg', 'create', '--json') + ARCHIVE_WITH_PATHS, output_log_level=None
).and_return('[]')
json_output = module.create_archive(
+16 -2
View File
@@ -36,13 +36,27 @@ def test_initialize_with_ssh_command_should_set_environment():
os.environ = orig_environ
def test_initialize_without_configuration_should_not_set_environment():
def test_initialize_without_configuration_should_only_set_default_environment():
orig_environ = os.environ
try:
os.environ = {}
module.initialize({})
assert sum(1 for key in os.environ.keys() if key.startswith('BORG_')) == 0
assert {key: value for key, value in os.environ.items() if key.startswith('BORG_')} == {
'BORG_RELOCATED_REPO_ACCESS_IS_OK': 'no',
'BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK': 'no',
}
finally:
os.environ = orig_environ
def test_initialize_with_relocated_repo_access_should_override_default():
orig_environ = os.environ
try:
os.environ = {}
module.initialize({'relocated_repo_access_is_ok': True})
assert os.environ.get('BORG_RELOCATED_REPO_ACCESS_IS_OK') == 'yes'
finally:
os.environ = orig_environ
+16 -16
View File
@@ -34,9 +34,9 @@ def test_extract_last_archive_dry_run_without_any_archives_should_not_raise():
def test_extract_last_archive_dry_run_with_log_info_calls_borg_with_info_parameter():
insert_execute_command_output_mock(
('borg', 'list', '--short', 'repo', '--info'), result='archive1\narchive2\n'
('borg', 'list', '--short', '--info', 'repo'), result='archive1\narchive2\n'
)
insert_execute_command_mock(('borg', 'extract', '--dry-run', 'repo::archive2', '--info'))
insert_execute_command_mock(('borg', 'extract', '--dry-run', '--info', 'repo::archive2'))
insert_logging_mock(logging.INFO)
module.extract_last_archive_dry_run(repository='repo', lock_wait=None)
@@ -44,10 +44,10 @@ def test_extract_last_archive_dry_run_with_log_info_calls_borg_with_info_paramet
def test_extract_last_archive_dry_run_with_log_debug_calls_borg_with_debug_parameter():
insert_execute_command_output_mock(
('borg', 'list', '--short', 'repo', '--debug', '--show-rc'), result='archive1\narchive2\n'
('borg', 'list', '--short', '--debug', '--show-rc', 'repo'), result='archive1\narchive2\n'
)
insert_execute_command_mock(
('borg', 'extract', '--dry-run', 'repo::archive2', '--debug', '--show-rc', '--list')
('borg', 'extract', '--dry-run', '--debug', '--show-rc', '--list', 'repo::archive2')
)
insert_logging_mock(logging.DEBUG)
@@ -65,10 +65,10 @@ def test_extract_last_archive_dry_run_calls_borg_via_local_path():
def test_extract_last_archive_dry_run_calls_borg_with_remote_path_parameters():
insert_execute_command_output_mock(
('borg', 'list', '--short', 'repo', '--remote-path', 'borg1'), result='archive1\narchive2\n'
('borg', 'list', '--short', '--remote-path', 'borg1', 'repo'), result='archive1\narchive2\n'
)
insert_execute_command_mock(
('borg', 'extract', '--dry-run', 'repo::archive2', '--remote-path', 'borg1')
('borg', 'extract', '--dry-run', '--remote-path', 'borg1', 'repo::archive2')
)
module.extract_last_archive_dry_run(repository='repo', lock_wait=None, remote_path='borg1')
@@ -76,10 +76,10 @@ def test_extract_last_archive_dry_run_calls_borg_with_remote_path_parameters():
def test_extract_last_archive_dry_run_calls_borg_with_lock_wait_parameters():
insert_execute_command_output_mock(
('borg', 'list', '--short', 'repo', '--lock-wait', '5'), result='archive1\narchive2\n'
('borg', 'list', '--short', '--lock-wait', '5', 'repo'), result='archive1\narchive2\n'
)
insert_execute_command_mock(
('borg', 'extract', '--dry-run', 'repo::archive2', '--lock-wait', '5')
('borg', 'extract', '--dry-run', '--lock-wait', '5', 'repo::archive2')
)
module.extract_last_archive_dry_run(repository='repo', lock_wait=5)
@@ -99,7 +99,7 @@ def test_extract_archive_calls_borg_with_restore_path_parameters():
def test_extract_archive_calls_borg_with_remote_path_parameters():
insert_execute_command_mock(('borg', 'extract', 'repo::archive', '--remote-path', 'borg1'))
insert_execute_command_mock(('borg', 'extract', '--remote-path', 'borg1', 'repo::archive'))
module.extract_archive(
dry_run=False,
@@ -113,7 +113,7 @@ def test_extract_archive_calls_borg_with_remote_path_parameters():
def test_extract_archive_calls_borg_with_numeric_owner_parameter():
insert_execute_command_mock(('borg', 'extract', 'repo::archive', '--numeric-owner'))
insert_execute_command_mock(('borg', 'extract', '--numeric-owner', 'repo::archive'))
module.extract_archive(
dry_run=False,
@@ -126,7 +126,7 @@ def test_extract_archive_calls_borg_with_numeric_owner_parameter():
def test_extract_archive_calls_borg_with_umask_parameters():
insert_execute_command_mock(('borg', 'extract', 'repo::archive', '--umask', '0770'))
insert_execute_command_mock(('borg', 'extract', '--umask', '0770', 'repo::archive'))
module.extract_archive(
dry_run=False,
@@ -139,7 +139,7 @@ def test_extract_archive_calls_borg_with_umask_parameters():
def test_extract_archive_calls_borg_with_lock_wait_parameters():
insert_execute_command_mock(('borg', 'extract', 'repo::archive', '--lock-wait', '5'))
insert_execute_command_mock(('borg', 'extract', '--lock-wait', '5', 'repo::archive'))
module.extract_archive(
dry_run=False,
@@ -152,7 +152,7 @@ def test_extract_archive_calls_borg_with_lock_wait_parameters():
def test_extract_archive_with_log_info_calls_borg_with_info_parameter():
insert_execute_command_mock(('borg', 'extract', 'repo::archive', '--info'))
insert_execute_command_mock(('borg', 'extract', '--info', 'repo::archive'))
insert_logging_mock(logging.INFO)
module.extract_archive(
@@ -167,7 +167,7 @@ def test_extract_archive_with_log_info_calls_borg_with_info_parameter():
def test_extract_archive_with_log_debug_calls_borg_with_debug_parameters():
insert_execute_command_mock(
('borg', 'extract', 'repo::archive', '--debug', '--list', '--show-rc')
('borg', 'extract', '--debug', '--list', '--show-rc', 'repo::archive')
)
insert_logging_mock(logging.DEBUG)
@@ -182,7 +182,7 @@ def test_extract_archive_with_log_debug_calls_borg_with_debug_parameters():
def test_extract_archive_calls_borg_with_dry_run_parameter():
insert_execute_command_mock(('borg', 'extract', 'repo::archive', '--dry-run'))
insert_execute_command_mock(('borg', 'extract', '--dry-run', 'repo::archive'))
module.extract_archive(
dry_run=True,
@@ -195,7 +195,7 @@ def test_extract_archive_calls_borg_with_dry_run_parameter():
def test_extract_archive_calls_borg_with_progress_parameter():
insert_execute_command_mock(('borg', 'extract', 'repo::archive', '--progress'))
insert_execute_command_mock(('borg', 'extract', '--progress', 'repo::archive'))
module.extract_archive(
dry_run=False,
+10 -12
View File
@@ -7,12 +7,10 @@ from borgmatic.borg import info as module
from ..test_verbosity import insert_logging_mock
INFO_COMMAND = ('borg', 'info', 'repo')
def test_display_archives_info_calls_borg_with_parameters():
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND, output_log_level=logging.WARNING
('borg', 'info', 'repo'), output_log_level=logging.WARNING
)
module.display_archives_info(
@@ -22,7 +20,7 @@ def test_display_archives_info_calls_borg_with_parameters():
def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--info',), output_log_level=logging.WARNING
('borg', 'info', '--info', 'repo'), output_log_level=logging.WARNING
)
insert_logging_mock(logging.INFO)
module.display_archives_info(
@@ -32,7 +30,7 @@ def test_display_archives_info_with_log_info_calls_borg_with_info_parameter():
def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--json',), output_log_level=None
('borg', 'info', '--json', 'repo'), output_log_level=None
).and_return('[]')
insert_logging_mock(logging.INFO)
@@ -45,7 +43,7 @@ def test_display_archives_info_with_log_info_and_json_suppresses_most_borg_outpu
def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--debug', '--show-rc'), output_log_level=logging.WARNING
('borg', 'info', '--debug', '--show-rc', 'repo'), output_log_level=logging.WARNING
)
insert_logging_mock(logging.DEBUG)
@@ -56,7 +54,7 @@ def test_display_archives_info_with_log_debug_calls_borg_with_debug_parameter():
def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--json',), output_log_level=None
('borg', 'info', '--json', 'repo'), output_log_level=None
).and_return('[]')
insert_logging_mock(logging.DEBUG)
@@ -69,7 +67,7 @@ def test_display_archives_info_with_log_debug_and_json_suppresses_most_borg_outp
def test_display_archives_info_with_json_calls_borg_with_json_parameter():
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--json',), output_log_level=None
('borg', 'info', '--json', 'repo'), output_log_level=None
).and_return('[]')
json_output = module.display_archives_info(
@@ -91,7 +89,7 @@ def test_display_archives_info_with_archive_calls_borg_with_archive_parameter():
def test_display_archives_info_with_local_path_calls_borg_via_local_path():
flexmock(module).should_receive('execute_command').with_args(
('borg1',) + INFO_COMMAND[1:], output_log_level=logging.WARNING
('borg1', 'info', 'repo'), output_log_level=logging.WARNING
)
module.display_archives_info(
@@ -104,7 +102,7 @@ def test_display_archives_info_with_local_path_calls_borg_via_local_path():
def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_parameters():
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--remote-path', 'borg1'), output_log_level=logging.WARNING
('borg', 'info', '--remote-path', 'borg1', 'repo'), output_log_level=logging.WARNING
)
module.display_archives_info(
@@ -118,7 +116,7 @@ def test_display_archives_info_with_remote_path_calls_borg_with_remote_path_para
def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_parameters():
storage_config = {'lock_wait': 5}
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--lock-wait', '5'), output_log_level=logging.WARNING
('borg', 'info', '--lock-wait', '5', 'repo'), output_log_level=logging.WARNING
)
module.display_archives_info(
@@ -131,7 +129,7 @@ def test_display_archives_info_with_lock_wait_calls_borg_with_lock_wait_paramete
@pytest.mark.parametrize('argument_name', ('prefix', 'glob_archives', 'sort_by', 'first', 'last'))
def test_display_archives_info_passes_through_arguments_to_borg(argument_name):
flexmock(module).should_receive('execute_command').with_args(
INFO_COMMAND + ('--' + argument_name.replace('_', '-'), 'value'),
('borg', 'info', '--' + argument_name.replace('_', '-'), 'value', 'repo'),
output_log_level=logging.WARNING,
)
+8 -8
View File
@@ -9,7 +9,7 @@ from borgmatic.borg import init as module
from ..test_verbosity import insert_logging_mock
INFO_SOME_UNKNOWN_EXIT_CODE = -999
INIT_COMMAND = ('borg', 'init', 'repo', '--encryption', 'repokey')
INIT_COMMAND = ('borg', 'init', '--encryption', 'repokey')
def insert_info_command_found_mock():
@@ -30,7 +30,7 @@ def insert_init_command_mock(init_command, **kwargs):
def test_initialize_repository_calls_borg_with_parameters():
insert_info_command_not_found_mock()
insert_init_command_mock(INIT_COMMAND)
insert_init_command_mock(INIT_COMMAND + ('repo',))
module.initialize_repository(repository='repo', encryption_mode='repokey')
@@ -72,21 +72,21 @@ def test_initialize_repository_raises_for_unknown_info_command_error():
def test_initialize_repository_with_append_only_calls_borg_with_append_only_parameter():
insert_info_command_not_found_mock()
insert_init_command_mock(INIT_COMMAND + ('--append-only',))
insert_init_command_mock(INIT_COMMAND + ('--append-only', 'repo'))
module.initialize_repository(repository='repo', encryption_mode='repokey', append_only=True)
def test_initialize_repository_with_storage_quota_calls_borg_with_storage_quota_parameter():
insert_info_command_not_found_mock()
insert_init_command_mock(INIT_COMMAND + ('--storage-quota', '5G'))
insert_init_command_mock(INIT_COMMAND + ('--storage-quota', '5G', 'repo'))
module.initialize_repository(repository='repo', encryption_mode='repokey', storage_quota='5G')
def test_initialize_repository_with_log_info_calls_borg_with_info_parameter():
insert_info_command_not_found_mock()
insert_init_command_mock(INIT_COMMAND + ('--info',))
insert_init_command_mock(INIT_COMMAND + ('--info', 'repo'))
insert_logging_mock(logging.INFO)
module.initialize_repository(repository='repo', encryption_mode='repokey')
@@ -94,7 +94,7 @@ def test_initialize_repository_with_log_info_calls_borg_with_info_parameter():
def test_initialize_repository_with_log_debug_calls_borg_with_debug_parameter():
insert_info_command_not_found_mock()
insert_init_command_mock(INIT_COMMAND + ('--debug',))
insert_init_command_mock(INIT_COMMAND + ('--debug', 'repo'))
insert_logging_mock(logging.DEBUG)
module.initialize_repository(repository='repo', encryption_mode='repokey')
@@ -102,13 +102,13 @@ def test_initialize_repository_with_log_debug_calls_borg_with_debug_parameter():
def test_initialize_repository_with_local_path_calls_borg_via_local_path():
insert_info_command_not_found_mock()
insert_init_command_mock(('borg1',) + INIT_COMMAND[1:])
insert_init_command_mock(('borg1',) + INIT_COMMAND[1:] + ('repo',))
module.initialize_repository(repository='repo', encryption_mode='repokey', local_path='borg1')
def test_initialize_repository_with_remote_path_calls_borg_with_remote_path_parameter():
insert_info_command_not_found_mock()
insert_init_command_mock(INIT_COMMAND + ('--remote-path', 'borg1'))
insert_init_command_mock(INIT_COMMAND + ('--remote-path', 'borg1', 'repo'))
module.initialize_repository(repository='repo', encryption_mode='repokey', remote_path='borg1')
+11 -13
View File
@@ -7,12 +7,10 @@ from borgmatic.borg import list as module
from ..test_verbosity import insert_logging_mock
LIST_COMMAND = ('borg', 'list', 'repo')
def test_list_archives_calls_borg_with_parameters():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND, output_log_level=logging.WARNING
('borg', 'list', 'repo'), output_log_level=logging.WARNING
)
module.list_archives(
@@ -22,7 +20,7 @@ def test_list_archives_calls_borg_with_parameters():
def test_list_archives_with_log_info_calls_borg_with_info_parameter():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--info',), output_log_level=logging.WARNING
('borg', 'list', '--info', 'repo'), output_log_level=logging.WARNING
)
insert_logging_mock(logging.INFO)
@@ -33,7 +31,7 @@ def test_list_archives_with_log_info_calls_borg_with_info_parameter():
def test_list_archives_with_log_info_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--json',), output_log_level=None
('borg', 'list', '--json', 'repo'), output_log_level=None
)
insert_logging_mock(logging.INFO)
@@ -44,7 +42,7 @@ def test_list_archives_with_log_info_and_json_suppresses_most_borg_output():
def test_list_archives_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--debug', '--show-rc'), output_log_level=logging.WARNING
('borg', 'list', '--debug', '--show-rc', 'repo'), output_log_level=logging.WARNING
)
insert_logging_mock(logging.DEBUG)
@@ -55,7 +53,7 @@ def test_list_archives_with_log_debug_calls_borg_with_debug_parameter():
def test_list_archives_with_log_debug_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--json',), output_log_level=None
('borg', 'list', '--json', 'repo'), output_log_level=None
)
insert_logging_mock(logging.DEBUG)
@@ -67,7 +65,7 @@ def test_list_archives_with_log_debug_and_json_suppresses_most_borg_output():
def test_list_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
storage_config = {'lock_wait': 5}
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--lock-wait', '5'), output_log_level=logging.WARNING
('borg', 'list', '--lock-wait', '5', 'repo'), output_log_level=logging.WARNING
)
module.list_archives(
@@ -92,7 +90,7 @@ def test_list_archives_with_archive_calls_borg_with_archive_parameter():
def test_list_archives_with_local_path_calls_borg_via_local_path():
flexmock(module).should_receive('execute_command').with_args(
('borg1',) + LIST_COMMAND[1:], output_log_level=logging.WARNING
('borg1', 'list', 'repo'), output_log_level=logging.WARNING
)
module.list_archives(
@@ -105,7 +103,7 @@ def test_list_archives_with_local_path_calls_borg_via_local_path():
def test_list_archives_with_remote_path_calls_borg_with_remote_path_parameters():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--remote-path', 'borg1'), output_log_level=logging.WARNING
('borg', 'list', '--remote-path', 'borg1', 'repo'), output_log_level=logging.WARNING
)
module.list_archives(
@@ -118,7 +116,7 @@ def test_list_archives_with_remote_path_calls_borg_with_remote_path_parameters()
def test_list_archives_with_short_calls_borg_with_short_parameter():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--short',), output_log_level=logging.WARNING
('borg', 'list', '--short', 'repo'), output_log_level=logging.WARNING
).and_return('[]')
module.list_archives(
@@ -144,7 +142,7 @@ def test_list_archives_with_short_calls_borg_with_short_parameter():
)
def test_list_archives_passes_through_arguments_to_borg(argument_name):
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--' + argument_name.replace('_', '-'), 'value'),
('borg', 'list', '--' + argument_name.replace('_', '-'), 'value', 'repo'),
output_log_level=logging.WARNING,
).and_return('[]')
@@ -157,7 +155,7 @@ def test_list_archives_passes_through_arguments_to_borg(argument_name):
def test_list_archives_with_json_calls_borg_with_json_parameter():
flexmock(module).should_receive('execute_command').with_args(
LIST_COMMAND + ('--json',), output_log_level=None
('borg', 'list', '--json', 'repo'), output_log_level=None
).and_return('[]')
json_output = module.list_archives(
+11 -19
View File
@@ -53,17 +53,7 @@ def test_make_prune_flags_treats_none_prefix_as_no_prefix():
assert tuple(result) == expected
PRUNE_COMMAND = (
'borg',
'prune',
'repo',
'--keep-daily',
'1',
'--keep-weekly',
'2',
'--keep-monthly',
'3',
)
PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
def test_prune_archives_calls_borg_with_parameters():
@@ -71,7 +61,7 @@ def test_prune_archives_calls_borg_with_parameters():
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(PRUNE_COMMAND)
insert_execute_command_mock(PRUNE_COMMAND + ('repo',))
module.prune_archives(
dry_run=False, repository='repo', storage_config={}, retention_config=retention_config
@@ -83,7 +73,7 @@ def test_prune_archives_with_log_info_calls_borg_with_info_parameter():
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(PRUNE_COMMAND + ('--stats', '--info'))
insert_execute_command_mock(PRUNE_COMMAND + ('--stats', '--info', 'repo'))
insert_logging_mock(logging.INFO)
module.prune_archives(
@@ -96,7 +86,9 @@ def test_prune_archives_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(PRUNE_COMMAND + ('--stats', '--debug', '--list', '--show-rc'))
insert_execute_command_mock(
PRUNE_COMMAND + ('--stats', '--debug', '--list', '--show-rc', 'repo')
)
insert_logging_mock(logging.DEBUG)
module.prune_archives(
@@ -109,7 +101,7 @@ def test_prune_archives_with_dry_run_calls_borg_with_dry_run_parameter():
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run',))
insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run', 'repo'))
module.prune_archives(
repository='repo', storage_config={}, dry_run=True, retention_config=retention_config
@@ -121,7 +113,7 @@ def test_prune_archives_with_local_path_calls_borg_via_local_path():
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:])
insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:] + ('repo',))
module.prune_archives(
dry_run=False,
@@ -137,7 +129,7 @@ def test_prune_archives_with_remote_path_calls_borg_with_remote_path_parameters(
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1'))
insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1', 'repo'))
module.prune_archives(
dry_run=False,
@@ -154,7 +146,7 @@ def test_prune_archives_with_umask_calls_borg_with_umask_parameters():
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077'))
insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077', 'repo'))
module.prune_archives(
dry_run=False,
@@ -170,7 +162,7 @@ def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_parameters():
flexmock(module).should_receive('_make_prune_flags').with_args(retention_config).and_return(
BASE_PRUNE_FLAGS
)
insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5'))
insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5', 'repo'))
module.prune_archives(
dry_run=False,
+52 -73
View File
@@ -4,9 +4,7 @@ from borgmatic.commands import arguments as module
def test_parse_subparser_arguments_consumes_subparser_arguments_before_subparser_name():
global_namespace = flexmock()
action_namespace = flexmock(foo=True)
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'action': flexmock(parse_known_args=lambda arguments: (action_namespace, [])),
@@ -14,17 +12,13 @@ def test_parse_subparser_arguments_consumes_subparser_arguments_before_subparser
}
)
arguments = module.parse_subparser_arguments(
('--foo', 'true', 'action'), top_level_parser, subparsers
)
arguments = module.parse_subparser_arguments(('--foo', 'true', 'action'), subparsers)
assert arguments == {'action': action_namespace, 'global': global_namespace}
assert arguments == {'action': action_namespace}
def test_parse_subparser_arguments_consumes_subparser_arguments_after_subparser_name():
global_namespace = flexmock()
action_namespace = flexmock(foo=True)
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'action': flexmock(parse_known_args=lambda arguments: (action_namespace, [])),
@@ -32,57 +26,13 @@ def test_parse_subparser_arguments_consumes_subparser_arguments_after_subparser_
}
)
arguments = module.parse_subparser_arguments(
('action', '--foo', 'true'), top_level_parser, subparsers
)
arguments = module.parse_subparser_arguments(('action', '--foo', 'true'), subparsers)
assert arguments == {'action': action_namespace, 'global': global_namespace}
def test_parse_subparser_arguments_consumes_global_arguments_before_subparser_name():
global_namespace = flexmock(verbosity='lots')
action_namespace = flexmock()
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'action': flexmock(
parse_known_args=lambda arguments: (action_namespace, ['--verbosity', 'lots'])
),
'other': flexmock(),
}
)
arguments = module.parse_subparser_arguments(
('--verbosity', 'lots', 'action'), top_level_parser, subparsers
)
assert arguments == {'action': action_namespace, 'global': global_namespace}
def test_parse_subparser_arguments_consumes_global_arguments_after_subparser_name():
global_namespace = flexmock(verbosity='lots')
action_namespace = flexmock()
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'action': flexmock(
parse_known_args=lambda arguments: (action_namespace, ['--verbosity', 'lots'])
),
'other': flexmock(),
}
)
arguments = module.parse_subparser_arguments(
('action', '--verbosity', 'lots'), top_level_parser, subparsers
)
assert arguments == {'action': action_namespace, 'global': global_namespace}
assert arguments == {'action': action_namespace}
def test_parse_subparser_arguments_consumes_subparser_arguments_with_alias():
global_namespace = flexmock()
action_namespace = flexmock(foo=True)
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
action_subparser = flexmock(parse_known_args=lambda arguments: (action_namespace, []))
subparsers = flexmock(
choices={
@@ -94,18 +44,14 @@ def test_parse_subparser_arguments_consumes_subparser_arguments_with_alias():
)
flexmock(module).SUBPARSER_ALIASES = {'action': ['-a'], 'other': ['-o']}
arguments = module.parse_subparser_arguments(
('-a', '--foo', 'true'), top_level_parser, subparsers
)
arguments = module.parse_subparser_arguments(('-a', '--foo', 'true'), subparsers)
assert arguments == {'action': action_namespace, 'global': global_namespace}
assert arguments == {'action': action_namespace}
def test_parse_subparser_arguments_consumes_multiple_subparser_arguments():
global_namespace = flexmock()
action_namespace = flexmock(foo=True)
other_namespace = flexmock(bar=3)
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'action': flexmock(
@@ -116,22 +62,16 @@ def test_parse_subparser_arguments_consumes_multiple_subparser_arguments():
)
arguments = module.parse_subparser_arguments(
('action', '--foo', 'true', 'other', '--bar', '3'), top_level_parser, subparsers
('action', '--foo', 'true', 'other', '--bar', '3'), subparsers
)
assert arguments == {
'action': action_namespace,
'other': other_namespace,
'global': global_namespace,
}
assert arguments == {'action': action_namespace, 'other': other_namespace}
def test_parse_subparser_arguments_applies_default_subparsers():
global_namespace = flexmock()
prune_namespace = flexmock()
create_namespace = flexmock(progress=True)
check_namespace = flexmock()
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'prune': flexmock(parse_known_args=lambda arguments: (prune_namespace, ['--progress'])),
@@ -141,17 +81,16 @@ def test_parse_subparser_arguments_applies_default_subparsers():
}
)
arguments = module.parse_subparser_arguments(('--progress'), top_level_parser, subparsers)
arguments = module.parse_subparser_arguments(('--progress'), subparsers)
assert arguments == {
'prune': prune_namespace,
'create': create_namespace,
'check': check_namespace,
'global': global_namespace,
}
def test_parse_subparser_arguments_with_help_does_not_apply_default_subparsers():
def test_parse_global_arguments_with_help_does_not_apply_default_subparsers():
global_namespace = flexmock(verbosity='lots')
action_namespace = flexmock()
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
@@ -164,8 +103,48 @@ def test_parse_subparser_arguments_with_help_does_not_apply_default_subparsers()
}
)
arguments = module.parse_subparser_arguments(
arguments = module.parse_global_arguments(
('--verbosity', 'lots', '--help'), top_level_parser, subparsers
)
assert arguments == {'global': global_namespace}
assert arguments == global_namespace
def test_parse_global_arguments_consumes_global_arguments_before_subparser_name():
global_namespace = flexmock(verbosity='lots')
action_namespace = flexmock()
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'action': flexmock(
parse_known_args=lambda arguments: (action_namespace, ['--verbosity', 'lots'])
),
'other': flexmock(),
}
)
arguments = module.parse_global_arguments(
('--verbosity', 'lots', 'action'), top_level_parser, subparsers
)
assert arguments == global_namespace
def test_parse_global_arguments_consumes_global_arguments_after_subparser_name():
global_namespace = flexmock(verbosity='lots')
action_namespace = flexmock()
top_level_parser = flexmock(parse_args=lambda arguments: global_namespace)
subparsers = flexmock(
choices={
'action': flexmock(
parse_known_args=lambda arguments: (action_namespace, ['--verbosity', 'lots'])
),
'other': flexmock(),
}
)
arguments = module.parse_global_arguments(
('action', '--verbosity', 'lots'), top_level_parser, subparsers
)
assert arguments == global_namespace