Compare commits
No commits in common. "main" and "main" have entirely different histories.
26
NEWS
26
NEWS
|
@ -1,30 +1,6 @@
|
||||||
1.8.13.dev0
|
1.8.11.dev0
|
||||||
* #886: Fix a PagerDuty hook traceback with Python < 3.10.
|
|
||||||
* #889: Fix the Healthchecks ping body size limit, restoring it to the documented 100,000 bytes.
|
|
||||||
|
|
||||||
1.8.12
|
|
||||||
* #817: Add a "--max-duration" flag to the "check" action and a "max_duration" option to the
|
|
||||||
repository check configuration. This tells Borg to interrupt a repository check after a certain
|
|
||||||
duration.
|
|
||||||
* #860: Fix interaction between environment variable interpolation in constants and shell escaping.
|
|
||||||
* #863: When color output is disabled (explicitly or implicitly), don't prefix each log line with
|
|
||||||
the log level.
|
|
||||||
* #865: Add an "upload_buffer_size" option to set the size of the upload buffer used in "create"
|
|
||||||
action.
|
|
||||||
* #866: Fix "Argument list too long" error in the "spot" check when checking hundreds of thousands
|
|
||||||
of files at once.
|
|
||||||
* #874: Add the configured repository label as "repository_label" to the interpolated variables
|
|
||||||
passed to before/after command hooks.
|
|
||||||
* #881: Fix "Unrecognized argument" error when the same value is used with different command-line
|
|
||||||
flags.
|
|
||||||
* In the "spot" check, don't try to hash symlinked directories.
|
|
||||||
|
|
||||||
1.8.11
|
|
||||||
* #815: Add optional Healthchecks auto-provisioning via "create_slug" option.
|
|
||||||
* #851: Fix lack of file extraction when using "extract --strip-components all" on a path with a
|
* #851: Fix lack of file extraction when using "extract --strip-components all" on a path with a
|
||||||
leading slash.
|
leading slash.
|
||||||
* #854: Fix a traceback when the "data" consistency check is used.
|
|
||||||
* #857: Fix a traceback with "check --only spot" when the "spot" check is unconfigured.
|
|
||||||
|
|
||||||
1.8.10
|
1.8.10
|
||||||
* #656 (beta): Add a "spot" consistency check that compares file counts and contents between your
|
* #656 (beta): Add a "spot" consistency check that compares file counts and contents between your
|
||||||
|
|
|
@ -300,7 +300,8 @@ def collect_spot_check_source_paths(
|
||||||
'''
|
'''
|
||||||
Given a repository configuration dict, a configuration dict, the local Borg version, global
|
Given a repository configuration dict, a configuration dict, the local Borg version, global
|
||||||
arguments as an argparse.Namespace instance, the local Borg path, and the remote Borg path,
|
arguments as an argparse.Namespace instance, the local Borg path, and the remote Borg path,
|
||||||
collect the source paths that Borg would use in an actual create (but only include files).
|
collect the source paths that Borg would use in an actual create (but only include files and
|
||||||
|
symlinks).
|
||||||
'''
|
'''
|
||||||
stream_processes = any(
|
stream_processes = any(
|
||||||
borgmatic.hooks.dispatch.call_hooks(
|
borgmatic.hooks.dispatch.call_hooks(
|
||||||
|
@ -348,7 +349,7 @@ def collect_spot_check_source_paths(
|
||||||
if path_line and path_line.startswith('- ') or path_line.startswith('+ ')
|
if path_line and path_line.startswith('- ') or path_line.startswith('+ ')
|
||||||
)
|
)
|
||||||
|
|
||||||
return tuple(path for path in paths if os.path.isfile(path))
|
return tuple(path for path in paths if os.path.isfile(path) or os.path.islink(path))
|
||||||
|
|
||||||
|
|
||||||
BORG_DIRECTORY_FILE_TYPE = 'd'
|
BORG_DIRECTORY_FILE_TYPE = 'd'
|
||||||
|
@ -387,9 +388,6 @@ def collect_spot_check_archive_paths(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SAMPLE_PATHS_SUBSET_COUNT = 10000
|
|
||||||
|
|
||||||
|
|
||||||
def compare_spot_check_hashes(
|
def compare_spot_check_hashes(
|
||||||
repository,
|
repository,
|
||||||
archive,
|
archive,
|
||||||
|
@ -422,57 +420,32 @@ def compare_spot_check_hashes(
|
||||||
f'{log_label}: Sampling {sample_count} source paths (~{spot_check_config["data_sample_percentage"]}%) for spot check'
|
f'{log_label}: Sampling {sample_count} source paths (~{spot_check_config["data_sample_percentage"]}%) for spot check'
|
||||||
)
|
)
|
||||||
|
|
||||||
source_sample_paths_iterator = iter(source_sample_paths)
|
# Hash each file in the sample paths (if it exists).
|
||||||
source_hashes = {}
|
hash_output = borgmatic.execute.execute_command_and_capture_output(
|
||||||
archive_hashes = {}
|
(spot_check_config.get('xxh64sum_command', 'xxh64sum'),)
|
||||||
|
+ tuple(path for path in source_sample_paths if path in existing_source_sample_paths)
|
||||||
|
)
|
||||||
|
|
||||||
# Only hash a few thousand files at a time (a subset of the total paths) to avoid an "Argument
|
source_hashes = dict(
|
||||||
# list too long" shell error.
|
(reversed(line.split(' ', 1)) for line in hash_output.splitlines()),
|
||||||
while True:
|
**{path: '' for path in source_sample_paths if path not in existing_source_sample_paths},
|
||||||
# Hash each file in the sample paths (if it exists).
|
)
|
||||||
source_sample_paths_subset = tuple(
|
|
||||||
itertools.islice(source_sample_paths_iterator, SAMPLE_PATHS_SUBSET_COUNT)
|
|
||||||
)
|
|
||||||
if not source_sample_paths_subset:
|
|
||||||
break
|
|
||||||
|
|
||||||
hash_output = borgmatic.execute.execute_command_and_capture_output(
|
archive_hashes = dict(
|
||||||
(spot_check_config.get('xxh64sum_command', 'xxh64sum'),)
|
reversed(line.split(' ', 1))
|
||||||
+ tuple(
|
for line in borgmatic.borg.list.capture_archive_listing(
|
||||||
path for path in source_sample_paths_subset if path in existing_source_sample_paths
|
repository['path'],
|
||||||
)
|
archive,
|
||||||
)
|
config,
|
||||||
|
local_borg_version,
|
||||||
source_hashes.update(
|
global_arguments,
|
||||||
**dict(
|
list_paths=source_sample_paths,
|
||||||
(reversed(line.split(' ', 1)) for line in hash_output.splitlines()),
|
path_format='{xxh64} /{path}{NL}', # noqa: FS003
|
||||||
# Represent non-existent files as having empty hashes so the comparison below still works.
|
local_path=local_path,
|
||||||
**{
|
remote_path=remote_path,
|
||||||
path: ''
|
|
||||||
for path in source_sample_paths_subset
|
|
||||||
if path not in existing_source_sample_paths
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the hash for each file in the archive.
|
|
||||||
archive_hashes.update(
|
|
||||||
**dict(
|
|
||||||
reversed(line.split(' ', 1))
|
|
||||||
for line in borgmatic.borg.list.capture_archive_listing(
|
|
||||||
repository['path'],
|
|
||||||
archive,
|
|
||||||
config,
|
|
||||||
local_borg_version,
|
|
||||||
global_arguments,
|
|
||||||
list_paths=source_sample_paths_subset,
|
|
||||||
path_format='{xxh64} /{path}{NL}', # noqa: FS003
|
|
||||||
local_path=local_path,
|
|
||||||
remote_path=remote_path,
|
|
||||||
)
|
|
||||||
if line
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
if line
|
||||||
|
)
|
||||||
|
|
||||||
# Compare the source hashes with the archive hashes to see how many match.
|
# Compare the source hashes with the archive hashes to see how many match.
|
||||||
failing_paths = []
|
failing_paths = []
|
||||||
|
@ -507,13 +480,7 @@ def spot_check(
|
||||||
'''
|
'''
|
||||||
log_label = f'{repository.get("label", repository["path"])}'
|
log_label = f'{repository.get("label", repository["path"])}'
|
||||||
logger.debug(f'{log_label}: Running spot check')
|
logger.debug(f'{log_label}: Running spot check')
|
||||||
|
spot_check_config = next(check for check in config['checks'] if check['name'] == 'spot')
|
||||||
try:
|
|
||||||
spot_check_config = next(
|
|
||||||
check for check in config.get('checks', ()) if check.get('name') == 'spot'
|
|
||||||
)
|
|
||||||
except StopIteration:
|
|
||||||
raise ValueError('Cannot run spot check because it is unconfigured')
|
|
||||||
|
|
||||||
if spot_check_config['data_tolerance_percentage'] > spot_check_config['data_sample_percentage']:
|
if spot_check_config['data_tolerance_percentage'] > spot_check_config['data_sample_percentage']:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
|
@ -50,10 +50,10 @@ def make_archive_filter_flags(local_borg_version, config, checks, check_argument
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|
||||||
def make_check_name_flags(checks, archive_filter_flags):
|
def make_check_flags(checks, archive_filter_flags):
|
||||||
'''
|
'''
|
||||||
Given parsed checks set and a sequence of flags to filter archives, transform the checks into
|
Given a parsed sequence of checks and a sequence of flags to filter archives, transform the
|
||||||
tuple of command-line check flags.
|
checks into tuple of command-line check flags.
|
||||||
|
|
||||||
For example, given parsed checks of:
|
For example, given parsed checks of:
|
||||||
|
|
||||||
|
@ -68,13 +68,13 @@ def make_check_name_flags(checks, archive_filter_flags):
|
||||||
'''
|
'''
|
||||||
if 'data' in checks:
|
if 'data' in checks:
|
||||||
data_flags = ('--verify-data',)
|
data_flags = ('--verify-data',)
|
||||||
checks.update({'archives'})
|
checks += ('archives',)
|
||||||
else:
|
else:
|
||||||
data_flags = ()
|
data_flags = ()
|
||||||
|
|
||||||
common_flags = (archive_filter_flags if 'archives' in checks else ()) + data_flags
|
common_flags = (archive_filter_flags if 'archives' in checks else ()) + data_flags
|
||||||
|
|
||||||
if {'repository', 'archives'}.issubset(checks):
|
if {'repository', 'archives'}.issubset(set(checks)):
|
||||||
return common_flags
|
return common_flags
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -134,30 +134,10 @@ def check_archives(
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
verbosity_flags = ('--debug', '--show-rc')
|
verbosity_flags = ('--debug', '--show-rc')
|
||||||
|
|
||||||
try:
|
|
||||||
repository_check_config = next(
|
|
||||||
check for check in config.get('checks', ()) if check.get('name') == 'repository'
|
|
||||||
)
|
|
||||||
except StopIteration:
|
|
||||||
repository_check_config = {}
|
|
||||||
|
|
||||||
if check_arguments.max_duration and 'archives' in checks:
|
|
||||||
raise ValueError('The archives check cannot run when the --max-duration flag is used')
|
|
||||||
if repository_check_config.get('max_duration') and 'archives' in checks:
|
|
||||||
raise ValueError(
|
|
||||||
'The archives check cannot run when the repository check has the max_duration option set'
|
|
||||||
)
|
|
||||||
|
|
||||||
max_duration = check_arguments.max_duration or repository_check_config.get('max_duration')
|
|
||||||
|
|
||||||
borg_environment = environment.make_environment(config)
|
|
||||||
borg_exit_codes = config.get('borg_exit_codes')
|
|
||||||
|
|
||||||
full_command = (
|
full_command = (
|
||||||
(local_path, 'check')
|
(local_path, 'check')
|
||||||
+ (('--repair',) if check_arguments.repair else ())
|
+ (('--repair',) if check_arguments.repair else ())
|
||||||
+ (('--max-duration', str(max_duration)) if max_duration else ())
|
+ make_check_flags(checks, archive_filter_flags)
|
||||||
+ make_check_name_flags(checks, archive_filter_flags)
|
|
||||||
+ (('--remote-path', remote_path) if remote_path else ())
|
+ (('--remote-path', remote_path) if remote_path else ())
|
||||||
+ (('--log-json',) if global_arguments.log_json else ())
|
+ (('--log-json',) if global_arguments.log_json else ())
|
||||||
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||||
|
@ -167,6 +147,9 @@ def check_archives(
|
||||||
+ flags.make_repository_flags(repository_path, local_borg_version)
|
+ flags.make_repository_flags(repository_path, local_borg_version)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
borg_environment = environment.make_environment(config)
|
||||||
|
borg_exit_codes = config.get('borg_exit_codes')
|
||||||
|
|
||||||
# The Borg repair option triggers an interactive prompt, which won't work when output is
|
# The Borg repair option triggers an interactive prompt, which won't work when output is
|
||||||
# captured. And progress messes with the terminal directly.
|
# captured. And progress messes with the terminal directly.
|
||||||
if check_arguments.repair or check_arguments.progress:
|
if check_arguments.repair or check_arguments.progress:
|
||||||
|
|
|
@ -371,7 +371,6 @@ def make_base_create_command(
|
||||||
chunker_params = config.get('chunker_params', None)
|
chunker_params = config.get('chunker_params', None)
|
||||||
compression = config.get('compression', None)
|
compression = config.get('compression', None)
|
||||||
upload_rate_limit = config.get('upload_rate_limit', None)
|
upload_rate_limit = config.get('upload_rate_limit', None)
|
||||||
upload_buffer_size = config.get('upload_buffer_size', None)
|
|
||||||
umask = config.get('umask', None)
|
umask = config.get('umask', None)
|
||||||
lock_wait = config.get('lock_wait', None)
|
lock_wait = config.get('lock_wait', None)
|
||||||
list_filter_flags = make_list_filter_flags(local_borg_version, dry_run)
|
list_filter_flags = make_list_filter_flags(local_borg_version, dry_run)
|
||||||
|
@ -413,7 +412,6 @@ def make_base_create_command(
|
||||||
+ (('--chunker-params', chunker_params) if chunker_params else ())
|
+ (('--chunker-params', chunker_params) if chunker_params else ())
|
||||||
+ (('--compression', compression) if compression else ())
|
+ (('--compression', compression) if compression else ())
|
||||||
+ upload_ratelimit_flags
|
+ upload_ratelimit_flags
|
||||||
+ (('--upload-buffer', str(upload_buffer_size)) if upload_buffer_size else ())
|
|
||||||
+ (('--one-file-system',) if config.get('one_file_system') or stream_processes else ())
|
+ (('--one-file-system',) if config.get('one_file_system') or stream_processes else ())
|
||||||
+ numeric_ids_flags
|
+ numeric_ids_flags
|
||||||
+ atime_flags
|
+ atime_flags
|
||||||
|
|
|
@ -113,54 +113,6 @@ def parse_and_record_action_arguments(
|
||||||
return tuple(argument for argument in remaining if argument != action_name)
|
return tuple(argument for argument in remaining if argument != action_name)
|
||||||
|
|
||||||
|
|
||||||
def argument_is_flag(argument):
|
|
||||||
'''
|
|
||||||
Return True if the given argument looks like a flag, e.g. '--some-flag', as opposed to a
|
|
||||||
non-flag value.
|
|
||||||
'''
|
|
||||||
return isinstance(argument, str) and argument.startswith('--')
|
|
||||||
|
|
||||||
|
|
||||||
def group_arguments_with_values(arguments):
|
|
||||||
'''
|
|
||||||
Given a sequence of arguments, return a sequence of tuples where each one contains either a
|
|
||||||
single argument (such as for a stand-alone flag) or a flag argument and its corresponding value.
|
|
||||||
|
|
||||||
For instance, given the following arguments sequence as input:
|
|
||||||
|
|
||||||
('--foo', '--bar', '33', '--baz')
|
|
||||||
|
|
||||||
... return the following output:
|
|
||||||
|
|
||||||
(('--foo',), ('--bar', '33'), ('--baz',))
|
|
||||||
'''
|
|
||||||
grouped_arguments = []
|
|
||||||
index = 0
|
|
||||||
|
|
||||||
while index < len(arguments):
|
|
||||||
this_argument = arguments[index]
|
|
||||||
|
|
||||||
try:
|
|
||||||
next_argument = arguments[index + 1]
|
|
||||||
except IndexError:
|
|
||||||
grouped_arguments.append((this_argument,))
|
|
||||||
break
|
|
||||||
|
|
||||||
if (
|
|
||||||
argument_is_flag(this_argument)
|
|
||||||
and not argument_is_flag(next_argument)
|
|
||||||
and next_argument not in ACTION_ALIASES
|
|
||||||
):
|
|
||||||
grouped_arguments.append((this_argument, next_argument))
|
|
||||||
index += 2
|
|
||||||
continue
|
|
||||||
|
|
||||||
grouped_arguments.append((this_argument,))
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
return tuple(grouped_arguments)
|
|
||||||
|
|
||||||
|
|
||||||
def get_unparsable_arguments(remaining_action_arguments):
|
def get_unparsable_arguments(remaining_action_arguments):
|
||||||
'''
|
'''
|
||||||
Given a sequence of argument tuples (one per action parser that parsed arguments), determine the
|
Given a sequence of argument tuples (one per action parser that parsed arguments), determine the
|
||||||
|
@ -169,21 +121,12 @@ def get_unparsable_arguments(remaining_action_arguments):
|
||||||
if not remaining_action_arguments:
|
if not remaining_action_arguments:
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
grouped_action_arguments = tuple(
|
|
||||||
group_arguments_with_values(action_arguments)
|
|
||||||
for action_arguments in remaining_action_arguments
|
|
||||||
)
|
|
||||||
|
|
||||||
return tuple(
|
return tuple(
|
||||||
itertools.chain.from_iterable(
|
argument
|
||||||
argument_group
|
for argument in dict.fromkeys(
|
||||||
for argument_group in dict.fromkeys(
|
itertools.chain.from_iterable(remaining_action_arguments)
|
||||||
itertools.chain.from_iterable(grouped_action_arguments)
|
).keys()
|
||||||
).keys()
|
if all(argument in action_arguments for action_arguments in remaining_action_arguments)
|
||||||
if all(
|
|
||||||
argument_group in action_arguments for action_arguments in grouped_action_arguments
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -661,11 +604,6 @@ def make_parsers():
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Attempt to repair any inconsistencies found (for interactive use)',
|
help='Attempt to repair any inconsistencies found (for interactive use)',
|
||||||
)
|
)
|
||||||
check_group.add_argument(
|
|
||||||
'--max-duration',
|
|
||||||
metavar='SECONDS',
|
|
||||||
help='How long to check the repository before interrupting the check, defaults to no interruption',
|
|
||||||
)
|
|
||||||
check_group.add_argument(
|
check_group.add_argument(
|
||||||
'-a',
|
'-a',
|
||||||
'--match-archives',
|
'--match-archives',
|
||||||
|
|
|
@ -286,11 +286,10 @@ def run_actions(
|
||||||
global_arguments = arguments['global']
|
global_arguments = arguments['global']
|
||||||
dry_run_label = ' (dry run; not making any changes)' if global_arguments.dry_run else ''
|
dry_run_label = ' (dry run; not making any changes)' if global_arguments.dry_run else ''
|
||||||
hook_context = {
|
hook_context = {
|
||||||
'repository_label': repository.get('label', ''),
|
'repository': repository_path,
|
||||||
'log_file': global_arguments.log_file if global_arguments.log_file else '',
|
|
||||||
# Deprecated: For backwards compatibility with borgmatic < 1.6.0.
|
# Deprecated: For backwards compatibility with borgmatic < 1.6.0.
|
||||||
'repositories': ','.join([repo['path'] for repo in config['repositories']]),
|
'repositories': ','.join([repo['path'] for repo in config['repositories']]),
|
||||||
'repository': repository_path,
|
'log_file': global_arguments.log_file if global_arguments.log_file else '',
|
||||||
}
|
}
|
||||||
skip_actions = set(get_skip_actions(config, arguments))
|
skip_actions = set(get_skip_actions(config, arguments))
|
||||||
|
|
||||||
|
|
|
@ -50,15 +50,12 @@ def apply_constants(value, constants, shell_escape=False):
|
||||||
value[index] = apply_constants(list_value, constants, shell_escape)
|
value[index] = apply_constants(list_value, constants, shell_escape)
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
for option_name, option_value in value.items():
|
for option_name, option_value in value.items():
|
||||||
value[option_name] = apply_constants(
|
shell_escape = (
|
||||||
option_value,
|
shell_escape
|
||||||
constants,
|
or option_name.startswith('before_')
|
||||||
shell_escape=(
|
or option_name.startswith('after_')
|
||||||
shell_escape
|
or option_name == 'on_error'
|
||||||
or option_name.startswith('before_')
|
|
||||||
or option_name.startswith('after_')
|
|
||||||
or option_name == 'on_error'
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
value[option_name] = apply_constants(option_value, constants, shell_escape)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
|
@ -269,8 +269,7 @@ properties:
|
||||||
compression:
|
compression:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: |
|
||||||
Type of compression to use when creating archives. (Compression
|
Type of compression to use when creating archives. See
|
||||||
level can be added separated with a comma, like "zstd,7".) See
|
|
||||||
http://borgbackup.readthedocs.io/en/stable/usage/create.html for
|
http://borgbackup.readthedocs.io/en/stable/usage/create.html for
|
||||||
details. Defaults to "lz4".
|
details. Defaults to "lz4".
|
||||||
example: lz4
|
example: lz4
|
||||||
|
@ -280,11 +279,6 @@ properties:
|
||||||
Remote network upload rate limit in kiBytes/second. Defaults to
|
Remote network upload rate limit in kiBytes/second. Defaults to
|
||||||
unlimited.
|
unlimited.
|
||||||
example: 100
|
example: 100
|
||||||
upload_buffer_size:
|
|
||||||
type: integer
|
|
||||||
description: |
|
|
||||||
Size of network upload buffer in MiB. Defaults to no buffer.
|
|
||||||
example: 160
|
|
||||||
retries:
|
retries:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -516,6 +510,7 @@ properties:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
|
- repository
|
||||||
- archives
|
- archives
|
||||||
- data
|
- data
|
||||||
- extract
|
- extract
|
||||||
|
@ -546,50 +541,6 @@ properties:
|
||||||
"always": running this check every time checks
|
"always": running this check every time checks
|
||||||
are run.
|
are run.
|
||||||
example: 2 weeks
|
example: 2 weeks
|
||||||
- required: [name]
|
|
||||||
additionalProperties: false
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- repository
|
|
||||||
description: |
|
|
||||||
Name of consistency check to run: "repository",
|
|
||||||
"archives", "data", "spot", and/or "extract".
|
|
||||||
"repository" checks the consistency of the
|
|
||||||
repository, "archives" checks all of the
|
|
||||||
archives, "data" verifies the integrity of the
|
|
||||||
data within the archives, "spot" checks that
|
|
||||||
some percentage of source files are found in the
|
|
||||||
most recent archive (with identical contents),
|
|
||||||
and "extract" does an extraction dry-run of the
|
|
||||||
most recent archive. Note that "data" implies
|
|
||||||
"archives". See "skip_actions" for disabling
|
|
||||||
checks altogether.
|
|
||||||
example: spot
|
|
||||||
frequency:
|
|
||||||
type: string
|
|
||||||
description: |
|
|
||||||
How frequently to run this type of consistency
|
|
||||||
check (as a best effort). The value is a number
|
|
||||||
followed by a unit of time. E.g., "2 weeks" to
|
|
||||||
run this consistency check no more than every
|
|
||||||
two weeks for a given repository or "1 month" to
|
|
||||||
run it no more than monthly. Defaults to
|
|
||||||
"always": running this check every time checks
|
|
||||||
are run.
|
|
||||||
example: 2 weeks
|
|
||||||
max_duration:
|
|
||||||
type: integer
|
|
||||||
description: |
|
|
||||||
How many seconds to check the repository before
|
|
||||||
interrupting the check. Useful for splitting a
|
|
||||||
long-running repository check into multiple
|
|
||||||
partial checks. Defaults to no interruption. Only
|
|
||||||
applies to the "repository" check, does not check
|
|
||||||
the repository index, and is not compatible with a
|
|
||||||
simultaneous "archives" check or "--repair" flag.
|
|
||||||
example: 3600
|
|
||||||
- required:
|
- required:
|
||||||
- name
|
- name
|
||||||
- count_tolerance_percentage
|
- count_tolerance_percentage
|
||||||
|
@ -1711,14 +1662,6 @@ properties:
|
||||||
states.
|
states.
|
||||||
example:
|
example:
|
||||||
- finish
|
- finish
|
||||||
create_slug:
|
|
||||||
type: boolean
|
|
||||||
description: |
|
|
||||||
Create the check if it does not exist. Only works with
|
|
||||||
the slug URL scheme (https://hc-ping.com/<ping-key>/<slug>
|
|
||||||
as opposed to https://hc-ping.com/<uuid>).
|
|
||||||
Defaults to false.
|
|
||||||
example: true
|
|
||||||
description: |
|
description: |
|
||||||
Configuration for a monitoring integration with Healthchecks. Create
|
Configuration for a monitoring integration with Healthchecks. Create
|
||||||
an account at https://healthchecks.io (or self-host Healthchecks) if
|
an account at https://healthchecks.io (or self-host Healthchecks) if
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -15,7 +14,7 @@ MONITOR_STATE_TO_HEALTHCHECKS = {
|
||||||
monitor.State.LOG: 'log',
|
monitor.State.LOG: 'log',
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_PING_BODY_LIMIT_BYTES = 100000
|
DEFAULT_PING_BODY_LIMIT_BYTES = 1500
|
||||||
HANDLER_IDENTIFIER = 'healthchecks'
|
HANDLER_IDENTIFIER = 'healthchecks'
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,20 +59,10 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
ping_url_is_uuid = re.search(r'\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$', ping_url)
|
|
||||||
|
|
||||||
healthchecks_state = MONITOR_STATE_TO_HEALTHCHECKS.get(state)
|
healthchecks_state = MONITOR_STATE_TO_HEALTHCHECKS.get(state)
|
||||||
if healthchecks_state:
|
if healthchecks_state:
|
||||||
ping_url = f'{ping_url}/{healthchecks_state}'
|
ping_url = f'{ping_url}/{healthchecks_state}'
|
||||||
|
|
||||||
if hook_config.get('create_slug'):
|
|
||||||
if ping_url_is_uuid:
|
|
||||||
logger.warning(
|
|
||||||
f'{config_filename}: Healthchecks UUIDs do not support auto provisionning; ignoring'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ping_url = f'{ping_url}?create=1'
|
|
||||||
|
|
||||||
logger.info(f'{config_filename}: Pinging Healthchecks {state.name.lower()}{dry_run_label}')
|
logger.info(f'{config_filename}: Pinging Healthchecks {state.name.lower()}{dry_run_label}')
|
||||||
logger.debug(f'{config_filename}: Using Healthchecks ping URL {ping_url}')
|
logger.debug(f'{config_filename}: Using Healthchecks ping URL {ping_url}')
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||||
return
|
return
|
||||||
|
|
||||||
hostname = platform.node()
|
hostname = platform.node()
|
||||||
local_timestamp = datetime.datetime.now(datetime.timezone.utc).astimezone().isoformat()
|
local_timestamp = (
|
||||||
|
datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().isoformat()
|
||||||
|
)
|
||||||
payload = json.dumps(
|
payload = json.dumps(
|
||||||
{
|
{
|
||||||
'routing_key': hook_config['integration_key'],
|
'routing_key': hook_config['integration_key'],
|
||||||
|
|
|
@ -88,11 +88,6 @@ class Multi_stream_handler(logging.Handler):
|
||||||
handler.setLevel(level)
|
handler.setLevel(level)
|
||||||
|
|
||||||
|
|
||||||
class Console_no_color_formatter(logging.Formatter):
|
|
||||||
def format(self, record): # pragma: no cover
|
|
||||||
return record.msg
|
|
||||||
|
|
||||||
|
|
||||||
class Console_color_formatter(logging.Formatter):
|
class Console_color_formatter(logging.Formatter):
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
add_custom_log_levels()
|
add_custom_log_levels()
|
||||||
|
@ -203,8 +198,6 @@ def configure_logging(
|
||||||
|
|
||||||
if color_enabled:
|
if color_enabled:
|
||||||
console_handler.setFormatter(Console_color_formatter())
|
console_handler.setFormatter(Console_color_formatter())
|
||||||
else:
|
|
||||||
console_handler.setFormatter(Console_no_color_formatter())
|
|
||||||
|
|
||||||
console_handler.setLevel(console_log_level)
|
console_handler.setLevel(console_log_level)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
version: '3'
|
||||||
services:
|
services:
|
||||||
docs:
|
docs:
|
||||||
image: borgmatic-docs
|
image: borgmatic-docs
|
||||||
|
|
|
@ -84,9 +84,6 @@ variables you can use here:
|
||||||
path of the borgmatic log file, only set when the `--log-file` flag is used
|
path of the borgmatic log file, only set when the `--log-file` flag is used
|
||||||
* `repository`: path of the current repository as configured in the current
|
* `repository`: path of the current repository as configured in the current
|
||||||
borgmatic configuration file
|
borgmatic configuration file
|
||||||
* `repository_label` <span class="minilink minilink-addedin">New in version
|
|
||||||
1.8.12</span>: label of the current repository as configured in the current
|
|
||||||
borgmatic configuration file
|
|
||||||
|
|
||||||
Note that you can also interpolate in [arbitrary environment
|
Note that you can also interpolate in [arbitrary environment
|
||||||
variables](https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/).
|
variables](https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/).
|
||||||
|
|
|
@ -437,28 +437,19 @@ borgmatic's own configuration file. So include your configuration file in
|
||||||
backups to avoid getting caught without a way to restore a database.
|
backups to avoid getting caught without a way to restore a database.
|
||||||
3. borgmatic does not currently support backing up or restoring multiple
|
3. borgmatic does not currently support backing up or restoring multiple
|
||||||
databases that share the exact same name on different hosts.
|
databases that share the exact same name on different hosts.
|
||||||
4. When database hooks are enabled, borgmatic instructs Borg to consume
|
4. Because database hooks implicitly enable the `read_special` configuration,
|
||||||
special files (via `--read-special`) to support database dump
|
any special files are excluded from backups (named pipes, block devices,
|
||||||
streaming—regardless of the value of your `read_special` configuration option.
|
character devices, and sockets) to prevent hanging. Try a command like `find
|
||||||
And because this can cause Borg to hang, borgmatic also automatically excludes
|
/your/source/path -type b -or -type c -or -type p -or -type s` to find such
|
||||||
special files (and symlinks to them) that Borg may get stuck on. Even so,
|
files. Common directories to exclude are `/dev` and `/run`, but that may not
|
||||||
there are still potential edge cases in which applications on your system
|
be exhaustive. <span class="minilink minilink-addedin">New in version
|
||||||
create new special files *after* borgmatic constructs its exclude list,
|
1.7.3</span> When database hooks are enabled, borgmatic automatically excludes
|
||||||
resulting in Borg hangs. If that occurs, you can resort to manually excluding
|
special files (and symlinks to special files) that may cause Borg to hang, so
|
||||||
those files. And if you explicitly set the `read-special` option to `true`,
|
generally you no longer need to manually exclude them. There are potential
|
||||||
borgmatic will opt you out of the auto-exclude feature entirely, but will
|
edge cases though in which applications on your system create new special files
|
||||||
still instruct Borg to consume special files—you will just be on your own to
|
*after* borgmatic constructs its exclude list, resulting in Borg hangs. If that
|
||||||
exclude them. <span class="minilink minilink-addedin">Prior to version
|
occurs, you can resort to the manual excludes described above. And to opt out
|
||||||
1.7.3</span>Special files were not auto-excluded, and you were responsible for
|
of the auto-exclude feature entirely, explicitly set `read_special` to true.
|
||||||
excluding them yourself. Common directories to exclude are `/dev` and `/run`,
|
|
||||||
but that may not be exhaustive.
|
|
||||||
5. Database hooks also implicitly enable the `one_file_system` option, which
|
|
||||||
means Borg won't cross filesystem boundaries when looking for files to backup.
|
|
||||||
This is especially important when running borgmatic in a container, as
|
|
||||||
container volumes are mounted as separate filesystems. One work-around is to
|
|
||||||
explicitly add each mounted volume you'd like to backup to
|
|
||||||
`source_directories` instead of relying on Borg to include them implicitly via
|
|
||||||
a parent directory.
|
|
||||||
|
|
||||||
|
|
||||||
### Manual restoration
|
### Manual restoration
|
||||||
|
|
|
@ -20,7 +20,7 @@ default action ordering was `prune`, `compact`, `create`, and `check`.
|
||||||
### A la carte actions
|
### A la carte actions
|
||||||
|
|
||||||
If you find yourself wanting to customize the actions, you have some options.
|
If you find yourself wanting to customize the actions, you have some options.
|
||||||
First, you can run borgmatic's `create`, `prune`, `compact`, or `check`
|
First, you can run borgmatic's `prune`, `compact`, `create`, or `check`
|
||||||
actions separately. For instance, the following optional actions are
|
actions separately. For instance, the following optional actions are
|
||||||
available (among others):
|
available (among others):
|
||||||
|
|
||||||
|
@ -158,8 +158,7 @@ selected randomly each time, so in effect the spot check is probabilistic.
|
||||||
The `data_tolerance_percentage` is the percentage of total files in the source
|
The `data_tolerance_percentage` is the percentage of total files in the source
|
||||||
directories that can fail a spot check data comparison without failing the
|
directories that can fail a spot check data comparison without failing the
|
||||||
entire consistency check. The value must be lower than or equal to the
|
entire consistency check. The value must be lower than or equal to the
|
||||||
`data_sample_percentage`, because `data_tolerance_percentage` only looks at
|
`contents_sample_percentage`.
|
||||||
at the sampled files as determined by `data_sample_percentage`.
|
|
||||||
|
|
||||||
All three options are required when using the spot check. And because the
|
All three options are required when using the spot check. And because the
|
||||||
check relies on these configured tolerances, it may not be a
|
check relies on these configured tolerances, it may not be a
|
||||||
|
|
|
@ -102,9 +102,9 @@ and depend on containers for runtime dependencies. These tests do run on the
|
||||||
continuous integration (CI) server, and running them on your developer machine
|
continuous integration (CI) server, and running them on your developer machine
|
||||||
is the closest thing to dev-CI parity.
|
is the closest thing to dev-CI parity.
|
||||||
|
|
||||||
If you would like to run the end-to-end tests, first install Docker (or
|
If you would like to run the full test suite, first install Docker (or Podman;
|
||||||
Podman; see below) and [Docker
|
see below) and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||||
Compose](https://docs.docker.com/compose/install/). Then run:
|
Then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/run-end-to-end-tests
|
scripts/run-end-to-end-tests
|
||||||
|
@ -152,14 +152,12 @@ the following deviations from it:
|
||||||
* In general, spell out words in variable names instead of shortening them.
|
* In general, spell out words in variable names instead of shortening them.
|
||||||
So, think `index` instead of `idx`. There are some notable exceptions to
|
So, think `index` instead of `idx`. There are some notable exceptions to
|
||||||
this though (like `config`).
|
this though (like `config`).
|
||||||
* Favor blank lines around `if` statements, `return`s, logical code groupings,
|
|
||||||
etc. Readability is more important than packing the code tightly.
|
|
||||||
|
|
||||||
borgmatic code uses the [Black](https://black.readthedocs.io/en/stable/) code
|
borgmatic code uses the [Black](https://black.readthedocs.io/en/stable/) code
|
||||||
formatter, the [Flake8](http://flake8.pycqa.org/en/latest/) code checker, and
|
formatter, the [Flake8](http://flake8.pycqa.org/en/latest/) code checker, and
|
||||||
the [isort](https://github.com/timothycrosley/isort) import orderer, so
|
the [isort](https://github.com/timothycrosley/isort) import orderer, so
|
||||||
certain code style requirements are enforced when running automated tests. See
|
certain code style requirements will be enforced when running automated tests.
|
||||||
the Black, Flake8, and isort documentation for more information.
|
See the Black, Flake8, and isort documentation for more information.
|
||||||
|
|
||||||
|
|
||||||
## Continuous integration
|
## Continuous integration
|
||||||
|
|
|
@ -208,8 +208,8 @@ cronitor:
|
||||||
this option in the `hooks:` section of your configuration.
|
this option in the `hooks:` section of your configuration.
|
||||||
|
|
||||||
With this configuration, borgmatic pings your Cronitor monitor when a backup
|
With this configuration, borgmatic pings your Cronitor monitor when a backup
|
||||||
begins, ends, or errors, but only when any of the `create`, `prune`,
|
begins, ends, or errors, but only when any of the `prune`, `compact`,
|
||||||
`compact`, or `check` actions are run. Then, if the actions complete
|
`create`, or `check` actions are run. Then, if the actions complete
|
||||||
successfully or errors, borgmatic notifies Cronitor accordingly.
|
successfully or errors, borgmatic notifies Cronitor accordingly.
|
||||||
|
|
||||||
You can configure Cronitor to notify you by a [variety of
|
You can configure Cronitor to notify you by a [variety of
|
||||||
|
@ -235,8 +235,8 @@ cronhub:
|
||||||
this option in the `hooks:` section of your configuration.
|
this option in the `hooks:` section of your configuration.
|
||||||
|
|
||||||
With this configuration, borgmatic pings your Cronhub monitor when a backup
|
With this configuration, borgmatic pings your Cronhub monitor when a backup
|
||||||
begins, ends, or errors, but only when any of the `create`, `prune`,
|
begins, ends, or errors, but only when any of the `prune`, `compact`,
|
||||||
`compact`, or `check` actions are run. Then, if the actions complete
|
`create`, or `check` actions are run. Then, if the actions complete
|
||||||
successfully or errors, borgmatic notifies Cronhub accordingly.
|
successfully or errors, borgmatic notifies Cronhub accordingly.
|
||||||
|
|
||||||
Note that even though you configure borgmatic with the "start" variant of the
|
Note that even though you configure borgmatic with the "start" variant of the
|
||||||
|
@ -368,7 +368,7 @@ loki:
|
||||||
```
|
```
|
||||||
|
|
||||||
With this configuration, borgmatic sends its logs to your Loki instance as any
|
With this configuration, borgmatic sends its logs to your Loki instance as any
|
||||||
of the `create`, `prune`, `compact`, or `check` actions are run. Then, after
|
of the `prune`, `compact`, `create`, or `check` actions are run. Then, after
|
||||||
the actions complete, borgmatic notifies Loki of success or failure.
|
the actions complete, borgmatic notifies Loki of success or failure.
|
||||||
|
|
||||||
This hook supports sending arbitrary labels to Loki. For instance:
|
This hook supports sending arbitrary labels to Loki. For instance:
|
||||||
|
@ -420,8 +420,7 @@ pipx](https://torsion.org/borgmatic/docs/how-to/set-up-backups/#installation),
|
||||||
run the following to install Apprise so borgmatic can use it:
|
run the following to install Apprise so borgmatic can use it:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo pipx uninstall borgmatic
|
sudo pipx install --force borgmatic[Apprise]
|
||||||
sudo pipx install borgmatic[Apprise]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Omit `sudo` if borgmatic is installed as a non-root user.
|
Omit `sudo` if borgmatic is installed as a non-root user.
|
||||||
|
@ -436,16 +435,11 @@ apprise:
|
||||||
label: gotify
|
label: gotify
|
||||||
- url: mastodons://access_key@hostname/@user
|
- url: mastodons://access_key@hostname/@user
|
||||||
label: mastodon
|
label: mastodon
|
||||||
states:
|
|
||||||
- start
|
|
||||||
- finish
|
|
||||||
- fail
|
|
||||||
```
|
```
|
||||||
|
|
||||||
With this configuration, borgmatic pings each of the configured Apprise
|
With this configuration, borgmatic pings each of the configured Apprise
|
||||||
services when a backup begins, ends, or errors, but only when any of the
|
services when a backup begins, ends, or errors, but only when any of the
|
||||||
`create`, `prune`, `compact`, or `check` actions are run. (By default, if
|
`prune`, `compact`, `create`, or `check` actions are run.
|
||||||
`states` is not specified, Apprise services are only pinged on error.)
|
|
||||||
|
|
||||||
You can optionally customize the contents of the default messages sent to
|
You can optionally customize the contents of the default messages sent to
|
||||||
these services:
|
these services:
|
||||||
|
|
|
@ -133,8 +133,6 @@ borgmatic's `borg` action is not without limitations:
|
||||||
borgmatic action. In this case, only the Borg command is run.
|
borgmatic action. In this case, only the Borg command is run.
|
||||||
* Unlike normal borgmatic actions that support JSON, the `borg` action will
|
* Unlike normal borgmatic actions that support JSON, the `borg` action will
|
||||||
not disable certain borgmatic logs to avoid interfering with JSON output.
|
not disable certain borgmatic logs to avoid interfering with JSON output.
|
||||||
* The `borg` action bypasses most of borgmatic's machinery, so for instance
|
|
||||||
monitoring hooks will not get triggered when running `borgmatic borg create`.
|
|
||||||
* <span class="minilink minilink-addedin">Prior to version 1.8.0</span>
|
* <span class="minilink minilink-addedin">Prior to version 1.8.0</span>
|
||||||
borgmatic implicitly injected the repository/archive arguments on the Borg
|
borgmatic implicitly injected the repository/archive arguments on the Borg
|
||||||
command-line for you (based on your borgmatic configuration or the
|
command-line for you (based on your borgmatic configuration or the
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
VERSION = '1.8.13.dev0'
|
VERSION = '1.8.11.dev0'
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
appdirs==1.4.4
|
appdirs==1.4.4
|
||||||
apprise==1.8.0
|
apprise==1.3.0
|
||||||
attrs==23.2.0
|
attrs==22.2.0
|
||||||
black==24.4.2
|
black==24.3.0
|
||||||
certifi==2024.2.2
|
certifi==2023.7.22
|
||||||
chardet==5.2.0
|
chardet==5.1.0
|
||||||
click==8.1.7
|
click==8.1.3
|
||||||
codespell==2.2.6
|
codespell==2.2.4
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
coverage==7.5.1
|
coverage==7.2.3
|
||||||
flake8==7.0.0
|
flake8==6.0.0
|
||||||
flake8-quotes==3.4.0
|
flake8-quotes==3.3.2
|
||||||
flake8-use-fstring==1.4
|
flake8-use-fstring==1.4
|
||||||
flake8-variables-names==0.0.6
|
flake8-variables-names==0.0.5
|
||||||
flexmock==0.12.1
|
flexmock==0.11.3
|
||||||
idna==3.7
|
idna==3.7
|
||||||
isort==5.13.2
|
isort==5.12.0
|
||||||
jsonschema==4.22.0
|
jsonschema==4.17.3
|
||||||
Markdown==3.6
|
Markdown==3.4.1
|
||||||
mccabe==0.7.0
|
mccabe==0.7.0
|
||||||
packaging==24.0
|
packaging==23.1
|
||||||
pathspec==0.12.1
|
pathspec==0.11.1
|
||||||
pluggy==1.5.0
|
pluggy==1.0.0
|
||||||
py==1.11.0
|
py==1.11.0
|
||||||
pycodestyle==2.11.1
|
pycodestyle==2.10.0
|
||||||
pyflakes==3.2.0
|
pyflakes==3.0.1
|
||||||
pytest==8.2.1
|
pytest==7.3.0
|
||||||
pytest-cov==5.0.0
|
pytest-cov==4.0.0
|
||||||
PyYAML>5.0.0
|
PyYAML>5.0.0
|
||||||
regex
|
regex
|
||||||
requests==2.32.2
|
requests==2.31.0
|
||||||
ruamel.yaml>0.15.0
|
ruamel.yaml>0.15.0
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
typed-ast
|
typed-ast
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
version: '3'
|
||||||
services:
|
services:
|
||||||
postgresql:
|
postgresql:
|
||||||
image: docker.io/postgres:13.1-alpine
|
image: docker.io/postgres:13.1-alpine
|
||||||
|
|
|
@ -520,7 +520,7 @@ def test_collect_spot_check_source_paths_without_working_directory_parses_borg_o
|
||||||
) == ('/etc/path', '/etc/other')
|
) == ('/etc/path', '/etc/other')
|
||||||
|
|
||||||
|
|
||||||
def test_collect_spot_check_source_paths_skips_directories():
|
def test_collect_spot_check_source_paths_includes_symlinks_but_skips_directories():
|
||||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
|
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
|
||||||
{'hook1': False, 'hook2': True}
|
{'hook1': False, 'hook2': True}
|
||||||
)
|
)
|
||||||
|
@ -546,19 +546,18 @@ def test_collect_spot_check_source_paths_skips_directories():
|
||||||
'warning: stuff\n- /etc/path\n+ /etc/dir\n? /nope',
|
'warning: stuff\n- /etc/path\n+ /etc/dir\n? /nope',
|
||||||
)
|
)
|
||||||
flexmock(module.os.path).should_receive('isfile').with_args('/etc/path').and_return(False)
|
flexmock(module.os.path).should_receive('isfile').with_args('/etc/path').and_return(False)
|
||||||
|
flexmock(module.os.path).should_receive('islink').with_args('/etc/path').and_return(True)
|
||||||
flexmock(module.os.path).should_receive('isfile').with_args('/etc/dir').and_return(False)
|
flexmock(module.os.path).should_receive('isfile').with_args('/etc/dir').and_return(False)
|
||||||
|
flexmock(module.os.path).should_receive('islink').with_args('/etc/dir').and_return(False)
|
||||||
|
|
||||||
assert (
|
assert module.collect_spot_check_source_paths(
|
||||||
module.collect_spot_check_source_paths(
|
repository={'path': 'repo'},
|
||||||
repository={'path': 'repo'},
|
config={'working_directory': '/'},
|
||||||
config={'working_directory': '/'},
|
local_borg_version=flexmock(),
|
||||||
local_borg_version=flexmock(),
|
global_arguments=flexmock(),
|
||||||
global_arguments=flexmock(),
|
local_path=flexmock(),
|
||||||
local_path=flexmock(),
|
remote_path=flexmock(),
|
||||||
remote_path=flexmock(),
|
) == ('/etc/path',)
|
||||||
)
|
|
||||||
== ()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_spot_check_archive_paths_excludes_directories():
|
def test_collect_spot_check_archive_paths_excludes_directories():
|
||||||
|
@ -770,76 +769,6 @@ def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching()
|
||||||
) == ('/bar',)
|
) == ('/bar',)
|
||||||
|
|
||||||
|
|
||||||
def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in_chunks():
|
|
||||||
flexmock(module).SAMPLE_PATHS_SUBSET_COUNT = 2
|
|
||||||
flexmock(module.random).should_receive('sample').replace_with(
|
|
||||||
lambda population, count: population[:count]
|
|
||||||
)
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
|
||||||
flexmock(module.borgmatic.execute).should_receive(
|
|
||||||
'execute_command_and_capture_output'
|
|
||||||
).with_args(('xxh64sum', '/foo', '/bar')).and_return('hash1 /foo\nhash2 /bar')
|
|
||||||
flexmock(module.borgmatic.execute).should_receive(
|
|
||||||
'execute_command_and_capture_output'
|
|
||||||
).with_args(('xxh64sum', '/baz', '/quux')).and_return('hash3 /baz\nhash4 /quux')
|
|
||||||
flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
|
|
||||||
['hash1 /foo', 'hash2 /bar']
|
|
||||||
).and_return(['hash3 /baz', 'nothash4 /quux'])
|
|
||||||
|
|
||||||
assert module.compare_spot_check_hashes(
|
|
||||||
repository={'path': 'repo'},
|
|
||||||
archive='archive',
|
|
||||||
config={
|
|
||||||
'checks': [
|
|
||||||
{
|
|
||||||
'name': 'archives',
|
|
||||||
'frequency': '2 weeks',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'spot',
|
|
||||||
'data_sample_percentage': 100,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
log_label='repo',
|
|
||||||
source_paths=('/foo', '/bar', '/baz', '/quux'),
|
|
||||||
) == ('/quux',)
|
|
||||||
|
|
||||||
|
|
||||||
def test_spot_check_without_spot_configuration_errors():
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.spot_check(
|
|
||||||
repository={'path': 'repo'},
|
|
||||||
config={
|
|
||||||
'checks': [
|
|
||||||
{
|
|
||||||
'name': 'archives',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_spot_check_without_any_configuration_errors():
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.spot_check(
|
|
||||||
repository={'path': 'repo'},
|
|
||||||
config={},
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentage_errors():
|
def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentage_errors():
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
module.spot_check(
|
module.spot_check(
|
||||||
|
|
|
@ -222,35 +222,35 @@ def test_make_archive_filter_flags_with_default_checks_and_prefix_includes_match
|
||||||
assert flags == ('--match-archives', 'sh:foo-*')
|
assert flags == ('--match-archives', 'sh:foo-*')
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_name_flags_with_repository_check_returns_flag():
|
def test_make_check_flags_with_repository_check_returns_flag():
|
||||||
flags = module.make_check_name_flags({'repository'}, ())
|
flags = module.make_check_flags(('repository',), ())
|
||||||
|
|
||||||
assert flags == ('--repository-only',)
|
assert flags == ('--repository-only',)
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_name_flags_with_archives_check_returns_flag():
|
def test_make_check_flags_with_archives_check_returns_flag():
|
||||||
flags = module.make_check_name_flags({'archives'}, ())
|
flags = module.make_check_flags(('archives',), ())
|
||||||
|
|
||||||
assert flags == ('--archives-only',)
|
assert flags == ('--archives-only',)
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_name_flags_with_archives_check_and_archive_filter_flags_includes_those_flags():
|
def test_make_check_flags_with_archives_check_and_archive_filter_flags_includes_those_flags():
|
||||||
flags = module.make_check_name_flags({'archives'}, ('--match-archives', 'sh:foo-*'))
|
flags = module.make_check_flags(('archives',), ('--match-archives', 'sh:foo-*'))
|
||||||
|
|
||||||
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
|
assert flags == ('--archives-only', '--match-archives', 'sh:foo-*')
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_name_flags_without_archives_check_and_with_archive_filter_flags_includes_those_flags():
|
def test_make_check_flags_without_archives_check_and_with_archive_filter_flags_includes_those_flags():
|
||||||
flags = module.make_check_name_flags({'repository'}, ('--match-archives', 'sh:foo-*'))
|
flags = module.make_check_flags(('repository',), ('--match-archives', 'sh:foo-*'))
|
||||||
|
|
||||||
assert flags == ('--repository-only',)
|
assert flags == ('--repository-only',)
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_name_flags_with_data_check_returns_flag_and_implies_archives():
|
def test_make_check_flags_with_data_check_returns_flag_and_implies_archives():
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
|
|
||||||
flags = module.make_check_name_flags({'data'}, ())
|
flags = module.make_check_flags(('data',), ())
|
||||||
|
|
||||||
assert flags == (
|
assert flags == (
|
||||||
'--archives-only',
|
'--archives-only',
|
||||||
|
@ -258,24 +258,24 @@ def test_make_check_name_flags_with_data_check_returns_flag_and_implies_archives
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_name_flags_with_extract_omits_extract_flag():
|
def test_make_check_flags_with_extract_omits_extract_flag():
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
|
|
||||||
flags = module.make_check_name_flags({'extract'}, ())
|
flags = module.make_check_flags(('extract',), ())
|
||||||
|
|
||||||
assert flags == ()
|
assert flags == ()
|
||||||
|
|
||||||
|
|
||||||
def test_make_check_name_flags_with_repository_and_data_checks_does_not_return_repository_only():
|
def test_make_check_flags_with_repository_and_data_checks_does_not_return_repository_only():
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
|
|
||||||
flags = module.make_check_name_flags(
|
flags = module.make_check_flags(
|
||||||
{
|
(
|
||||||
'repository',
|
'repository',
|
||||||
'data',
|
'data',
|
||||||
},
|
),
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -332,7 +332,8 @@ def test_get_repository_id_with_missing_json_keys_raises():
|
||||||
|
|
||||||
def test_check_archives_with_progress_passes_through_to_borg():
|
def test_check_archives_with_progress_passes_through_to_borg():
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
flexmock(module).should_receive('make_check_flags').and_return(())
|
||||||
|
flexmock(module).should_receive('execute_command').never()
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
flexmock(module.environment).should_receive('make_environment')
|
||||||
flexmock(module).should_receive('execute_command').with_args(
|
flexmock(module).should_receive('execute_command').with_args(
|
||||||
|
@ -348,12 +349,7 @@ def test_check_archives_with_progress_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=True,
|
progress=True, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks={'repository'},
|
checks={'repository'},
|
||||||
|
@ -363,7 +359,8 @@ def test_check_archives_with_progress_passes_through_to_borg():
|
||||||
|
|
||||||
def test_check_archives_with_repair_passes_through_to_borg():
|
def test_check_archives_with_repair_passes_through_to_borg():
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
flexmock(module).should_receive('make_check_flags').and_return(())
|
||||||
|
flexmock(module).should_receive('execute_command').never()
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
flexmock(module.environment).should_receive('make_environment')
|
||||||
flexmock(module).should_receive('execute_command').with_args(
|
flexmock(module).should_receive('execute_command').with_args(
|
||||||
|
@ -379,148 +376,7 @@ def test_check_archives_with_repair_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=True, only_checks=None, force=None, match_archives=None
|
||||||
repair=True,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
|
||||||
global_arguments=flexmock(log_json=False),
|
|
||||||
checks={'repository'},
|
|
||||||
archive_filter_flags=(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_archives_with_max_duration_flag_passes_through_to_borg():
|
|
||||||
config = {}
|
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
|
||||||
flexmock(module).should_receive('execute_command').with_args(
|
|
||||||
('borg', 'check', '--max-duration', '33', 'repo'),
|
|
||||||
extra_environment=None,
|
|
||||||
borg_local_path='borg',
|
|
||||||
borg_exit_codes=None,
|
|
||||||
).once()
|
|
||||||
|
|
||||||
module.check_archives(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
check_arguments=flexmock(
|
|
||||||
progress=None,
|
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=33,
|
|
||||||
),
|
|
||||||
global_arguments=flexmock(log_json=False),
|
|
||||||
checks={'repository'},
|
|
||||||
archive_filter_flags=(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_archives_with_max_duration_flag_and_archives_check_errors():
|
|
||||||
config = {}
|
|
||||||
flexmock(module).should_receive('execute_command').never()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.check_archives(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
check_arguments=flexmock(
|
|
||||||
progress=None,
|
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=33,
|
|
||||||
),
|
|
||||||
global_arguments=flexmock(log_json=False),
|
|
||||||
checks={'repository', 'archives'},
|
|
||||||
archive_filter_flags=(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_archives_with_max_duration_option_passes_through_to_borg():
|
|
||||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}]}
|
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
|
||||||
flexmock(module).should_receive('execute_command').with_args(
|
|
||||||
('borg', 'check', '--max-duration', '33', 'repo'),
|
|
||||||
extra_environment=None,
|
|
||||||
borg_local_path='borg',
|
|
||||||
borg_exit_codes=None,
|
|
||||||
).once()
|
|
||||||
|
|
||||||
module.check_archives(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
check_arguments=flexmock(
|
|
||||||
progress=None,
|
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
|
||||||
global_arguments=flexmock(log_json=False),
|
|
||||||
checks={'repository'},
|
|
||||||
archive_filter_flags=(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_archives_with_max_duration_option_and_archives_check_errors():
|
|
||||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}]}
|
|
||||||
flexmock(module).should_receive('execute_command').never()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.check_archives(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
check_arguments=flexmock(
|
|
||||||
progress=None,
|
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
|
||||||
global_arguments=flexmock(log_json=False),
|
|
||||||
checks={'repository', 'archives'},
|
|
||||||
archive_filter_flags=(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_archives_with_max_duration_flag_overrides_max_duration_option():
|
|
||||||
config = {'checks': [{'name': 'repository', 'max_duration': 33}]}
|
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
|
||||||
flexmock(module).should_receive('execute_command').with_args(
|
|
||||||
('borg', 'check', '--max-duration', '44', 'repo'),
|
|
||||||
extra_environment=None,
|
|
||||||
borg_local_path='borg',
|
|
||||||
borg_exit_codes=None,
|
|
||||||
).once()
|
|
||||||
|
|
||||||
module.check_archives(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
check_arguments=flexmock(
|
|
||||||
progress=None,
|
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=44,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks={'repository'},
|
checks={'repository'},
|
||||||
|
@ -539,7 +395,7 @@ def test_check_archives_with_max_duration_flag_overrides_max_duration_option():
|
||||||
)
|
)
|
||||||
def test_check_archives_calls_borg_with_parameters(checks):
|
def test_check_archives_calls_borg_with_parameters(checks):
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(checks, ()).and_return(())
|
flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg', 'check', 'repo'))
|
insert_execute_command_mock(('borg', 'check', 'repo'))
|
||||||
|
|
||||||
|
@ -548,12 +404,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks=checks,
|
checks=checks,
|
||||||
|
@ -563,7 +414,7 @@ def test_check_archives_calls_borg_with_parameters(checks):
|
||||||
|
|
||||||
def test_check_archives_with_log_info_passes_through_to_borg():
|
def test_check_archives_with_log_info_passes_through_to_borg():
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
flexmock(module).should_receive('make_check_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_logging_mock(logging.INFO)
|
insert_logging_mock(logging.INFO)
|
||||||
insert_execute_command_mock(('borg', 'check', '--info', 'repo'))
|
insert_execute_command_mock(('borg', 'check', '--info', 'repo'))
|
||||||
|
@ -573,12 +424,7 @@ def test_check_archives_with_log_info_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks={'repository'},
|
checks={'repository'},
|
||||||
|
@ -588,7 +434,7 @@ def test_check_archives_with_log_info_passes_through_to_borg():
|
||||||
|
|
||||||
def test_check_archives_with_log_debug_passes_through_to_borg():
|
def test_check_archives_with_log_debug_passes_through_to_borg():
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
flexmock(module).should_receive('make_check_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_logging_mock(logging.DEBUG)
|
insert_logging_mock(logging.DEBUG)
|
||||||
insert_execute_command_mock(('borg', 'check', '--debug', '--show-rc', 'repo'))
|
insert_execute_command_mock(('borg', 'check', '--debug', '--show-rc', 'repo'))
|
||||||
|
@ -598,12 +444,7 @@ def test_check_archives_with_log_debug_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks={'repository'},
|
checks={'repository'},
|
||||||
|
@ -614,7 +455,7 @@ def test_check_archives_with_log_debug_passes_through_to_borg():
|
||||||
def test_check_archives_with_local_path_calls_borg_via_local_path():
|
def test_check_archives_with_local_path_calls_borg_via_local_path():
|
||||||
checks = {'repository'}
|
checks = {'repository'}
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(checks, ()).and_return(())
|
flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg1', 'check', 'repo'))
|
insert_execute_command_mock(('borg1', 'check', 'repo'))
|
||||||
|
|
||||||
|
@ -623,12 +464,7 @@ def test_check_archives_with_local_path_calls_borg_via_local_path():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks=checks,
|
checks=checks,
|
||||||
|
@ -641,7 +477,7 @@ def test_check_archives_with_exit_codes_calls_borg_using_them():
|
||||||
checks = {'repository'}
|
checks = {'repository'}
|
||||||
borg_exit_codes = flexmock()
|
borg_exit_codes = flexmock()
|
||||||
config = {'borg_exit_codes': borg_exit_codes}
|
config = {'borg_exit_codes': borg_exit_codes}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(checks, ()).and_return(())
|
flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg', 'check', 'repo'), borg_exit_codes=borg_exit_codes)
|
insert_execute_command_mock(('borg', 'check', 'repo'), borg_exit_codes=borg_exit_codes)
|
||||||
|
|
||||||
|
@ -650,12 +486,7 @@ def test_check_archives_with_exit_codes_calls_borg_using_them():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks=checks,
|
checks=checks,
|
||||||
|
@ -666,7 +497,7 @@ def test_check_archives_with_exit_codes_calls_borg_using_them():
|
||||||
def test_check_archives_with_remote_path_passes_through_to_borg():
|
def test_check_archives_with_remote_path_passes_through_to_borg():
|
||||||
checks = {'repository'}
|
checks = {'repository'}
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(checks, ()).and_return(())
|
flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
|
insert_execute_command_mock(('borg', 'check', '--remote-path', 'borg1', 'repo'))
|
||||||
|
|
||||||
|
@ -675,12 +506,7 @@ def test_check_archives_with_remote_path_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks=checks,
|
checks=checks,
|
||||||
|
@ -692,7 +518,7 @@ def test_check_archives_with_remote_path_passes_through_to_borg():
|
||||||
def test_check_archives_with_log_json_passes_through_to_borg():
|
def test_check_archives_with_log_json_passes_through_to_borg():
|
||||||
checks = {'repository'}
|
checks = {'repository'}
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(checks, ()).and_return(())
|
flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg', 'check', '--log-json', 'repo'))
|
insert_execute_command_mock(('borg', 'check', '--log-json', 'repo'))
|
||||||
|
|
||||||
|
@ -701,12 +527,7 @@ def test_check_archives_with_log_json_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=True),
|
global_arguments=flexmock(log_json=True),
|
||||||
checks=checks,
|
checks=checks,
|
||||||
|
@ -717,7 +538,7 @@ def test_check_archives_with_log_json_passes_through_to_borg():
|
||||||
def test_check_archives_with_lock_wait_passes_through_to_borg():
|
def test_check_archives_with_lock_wait_passes_through_to_borg():
|
||||||
checks = {'repository'}
|
checks = {'repository'}
|
||||||
config = {'lock_wait': 5}
|
config = {'lock_wait': 5}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(checks, ()).and_return(())
|
flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
|
insert_execute_command_mock(('borg', 'check', '--lock-wait', '5', 'repo'))
|
||||||
|
|
||||||
|
@ -726,12 +547,7 @@ def test_check_archives_with_lock_wait_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks=checks,
|
checks=checks,
|
||||||
|
@ -743,7 +559,7 @@ def test_check_archives_with_retention_prefix():
|
||||||
checks = {'repository'}
|
checks = {'repository'}
|
||||||
prefix = 'foo-'
|
prefix = 'foo-'
|
||||||
config = {'prefix': prefix}
|
config = {'prefix': prefix}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(checks, ()).and_return(())
|
flexmock(module).should_receive('make_check_flags').with_args(checks, ()).and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg', 'check', 'repo'))
|
insert_execute_command_mock(('borg', 'check', 'repo'))
|
||||||
|
|
||||||
|
@ -752,12 +568,7 @@ def test_check_archives_with_retention_prefix():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks=checks,
|
checks=checks,
|
||||||
|
@ -767,7 +578,7 @@ def test_check_archives_with_retention_prefix():
|
||||||
|
|
||||||
def test_check_archives_with_extra_borg_options_passes_through_to_borg():
|
def test_check_archives_with_extra_borg_options_passes_through_to_borg():
|
||||||
config = {'extra_borg_options': {'check': '--extra --options'}}
|
config = {'extra_borg_options': {'check': '--extra --options'}}
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(())
|
flexmock(module).should_receive('make_check_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
insert_execute_command_mock(('borg', 'check', '--extra', '--options', 'repo'))
|
insert_execute_command_mock(('borg', 'check', '--extra', '--options', 'repo'))
|
||||||
|
|
||||||
|
@ -776,12 +587,7 @@ def test_check_archives_with_extra_borg_options_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives=None
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives=None,
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks={'repository'},
|
checks={'repository'},
|
||||||
|
@ -791,9 +597,7 @@ def test_check_archives_with_extra_borg_options_passes_through_to_borg():
|
||||||
|
|
||||||
def test_check_archives_with_match_archives_passes_through_to_borg():
|
def test_check_archives_with_match_archives_passes_through_to_borg():
|
||||||
config = {}
|
config = {}
|
||||||
flexmock(module).should_receive('make_check_name_flags').and_return(
|
flexmock(module).should_receive('make_check_flags').and_return(('--match-archives', 'foo-*'))
|
||||||
('--match-archives', 'foo-*')
|
|
||||||
)
|
|
||||||
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
flexmock(module.environment).should_receive('make_environment')
|
||||||
flexmock(module).should_receive('execute_command').with_args(
|
flexmock(module).should_receive('execute_command').with_args(
|
||||||
|
@ -808,12 +612,7 @@ def test_check_archives_with_match_archives_passes_through_to_borg():
|
||||||
config=config,
|
config=config,
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
check_arguments=flexmock(
|
check_arguments=flexmock(
|
||||||
progress=None,
|
progress=None, repair=None, only_checks=None, force=None, match_archives='foo-*'
|
||||||
repair=None,
|
|
||||||
only_checks=None,
|
|
||||||
force=None,
|
|
||||||
match_archives='foo-*',
|
|
||||||
max_duration=None,
|
|
||||||
),
|
),
|
||||||
global_arguments=flexmock(log_json=False),
|
global_arguments=flexmock(log_json=False),
|
||||||
checks={'archives'},
|
checks={'archives'},
|
||||||
|
|
|
@ -693,7 +693,6 @@ def test_make_base_create_command_includes_exclude_patterns_in_borg_command():
|
||||||
('one_file_system', True, True, ('--one-file-system',)),
|
('one_file_system', True, True, ('--one-file-system',)),
|
||||||
('upload_rate_limit', 100, True, ('--upload-ratelimit', '100')),
|
('upload_rate_limit', 100, True, ('--upload-ratelimit', '100')),
|
||||||
('upload_rate_limit', 100, False, ('--remote-ratelimit', '100')),
|
('upload_rate_limit', 100, False, ('--remote-ratelimit', '100')),
|
||||||
('upload_buffer_size', 160, True, ('--upload-buffer', '160')),
|
|
||||||
('numeric_ids', True, True, ('--numeric-ids',)),
|
('numeric_ids', True, True, ('--numeric-ids',)),
|
||||||
('numeric_ids', True, False, ('--numeric-owner',)),
|
('numeric_ids', True, False, ('--numeric-owner',)),
|
||||||
('read_special', True, True, ('--read-special',)),
|
('read_special', True, True, ('--read-special',)),
|
||||||
|
|
|
@ -130,175 +130,36 @@ def test_parse_and_record_action_arguments_with_borg_action_consumes_arguments_a
|
||||||
assert borg_parsed_arguments.options == ('list',)
|
assert borg_parsed_arguments.options == ('list',)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'argument, expected',
|
|
||||||
[
|
|
||||||
('--foo', True),
|
|
||||||
('foo', False),
|
|
||||||
(33, False),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_argument_is_flag_only_for_string_starting_with_double_dash(argument, expected):
|
|
||||||
assert module.argument_is_flag(argument) == expected
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'arguments, expected',
|
'arguments, expected',
|
||||||
[
|
[
|
||||||
# Ending with a valueless flag.
|
# A global flag remaining from each parsed action.
|
||||||
(
|
(
|
||||||
('--foo', '--bar', 33, '--baz'),
|
|
||||||
(
|
(
|
||||||
('--foo',),
|
('--latest', 'archive', 'prune', 'extract', 'list', '--test-flag'),
|
||||||
('--bar', 33),
|
('--latest', 'archive', 'check', 'extract', 'list', '--test-flag'),
|
||||||
('--baz',),
|
('prune', 'check', 'list', '--test-flag'),
|
||||||
|
('prune', 'check', 'extract', '--test-flag'),
|
||||||
),
|
),
|
||||||
|
('--test-flag',),
|
||||||
),
|
),
|
||||||
# Ending with a flag and its corresponding value.
|
# No global flags remaining.
|
||||||
(
|
|
||||||
('--foo', '--bar', 33, '--baz', '--quux', 'thing'),
|
|
||||||
(('--foo',), ('--bar', 33), ('--baz',), ('--quux', 'thing')),
|
|
||||||
),
|
|
||||||
# Starting with an action name.
|
|
||||||
(
|
|
||||||
('check', '--foo', '--bar', 33, '--baz'),
|
|
||||||
(
|
|
||||||
('check',),
|
|
||||||
('--foo',),
|
|
||||||
('--bar', 33),
|
|
||||||
('--baz',),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
# Action name that one could mistake for a flag value.
|
|
||||||
(('--progress', 'list'), (('--progress',), ('list',))),
|
|
||||||
# No arguments.
|
|
||||||
((), ()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_group_arguments_with_values_returns_flags_with_corresponding_values(arguments, expected):
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('--foo').and_return(True)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('--bar').and_return(True)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('--baz').and_return(True)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('--quux').and_return(True)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('--progress').and_return(True)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args(33).and_return(False)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('thing').and_return(False)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('check').and_return(False)
|
|
||||||
flexmock(module).should_receive('argument_is_flag').with_args('list').and_return(False)
|
|
||||||
|
|
||||||
assert module.group_arguments_with_values(arguments) == expected
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'arguments, grouped_arguments, expected',
|
|
||||||
[
|
|
||||||
# An unparsable flag remaining from each parsed action.
|
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
('--latest', 'archive', 'prune', 'extract', 'list', '--flag'),
|
('--latest', 'archive', 'prune', 'extract', 'list'),
|
||||||
('--latest', 'archive', 'check', 'extract', 'list', '--flag'),
|
('--latest', 'archive', 'check', 'extract', 'list'),
|
||||||
('prune', 'check', 'list', '--flag'),
|
|
||||||
('prune', 'check', 'extract', '--flag'),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(
|
|
||||||
('--latest',),
|
|
||||||
('archive',),
|
|
||||||
('prune',),
|
|
||||||
('extract',),
|
|
||||||
('list',),
|
|
||||||
('--flag',),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
('--latest',),
|
|
||||||
('archive',),
|
|
||||||
('check',),
|
|
||||||
('extract',),
|
|
||||||
('list',),
|
|
||||||
('--flag',),
|
|
||||||
),
|
|
||||||
(('prune',), ('check',), ('list',), ('--flag',)),
|
|
||||||
(('prune',), ('check',), ('extract',), ('--flag',)),
|
|
||||||
),
|
|
||||||
('--flag',),
|
|
||||||
),
|
|
||||||
# No unparsable flags remaining.
|
|
||||||
(
|
|
||||||
(
|
|
||||||
('--archive', 'archive', 'prune', 'extract', 'list'),
|
|
||||||
('--archive', 'archive', 'check', 'extract', 'list'),
|
|
||||||
('prune', 'check', 'list'),
|
('prune', 'check', 'list'),
|
||||||
('prune', 'check', 'extract'),
|
('prune', 'check', 'extract'),
|
||||||
),
|
),
|
||||||
(
|
|
||||||
(
|
|
||||||
(
|
|
||||||
'--archive',
|
|
||||||
'archive',
|
|
||||||
),
|
|
||||||
('prune',),
|
|
||||||
('extract',),
|
|
||||||
('list',),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(
|
|
||||||
'--archive',
|
|
||||||
'archive',
|
|
||||||
),
|
|
||||||
('check',),
|
|
||||||
('extract',),
|
|
||||||
('list',),
|
|
||||||
),
|
|
||||||
(('prune',), ('check',), ('list',)),
|
|
||||||
(('prune',), ('check',), ('extract',)),
|
|
||||||
),
|
|
||||||
(),
|
|
||||||
),
|
|
||||||
# No unparsable flags remaining, but some values in common.
|
|
||||||
(
|
|
||||||
(
|
|
||||||
('--verbosity', '5', 'archive', 'prune', 'extract', 'list'),
|
|
||||||
('--last', '5', 'archive', 'check', 'extract', 'list'),
|
|
||||||
('prune', 'check', 'list', '--last', '5'),
|
|
||||||
('prune', 'check', '--verbosity', '5', 'extract'),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(('--verbosity', '5'), ('archive',), ('prune',), ('extract',), ('list',)),
|
|
||||||
(
|
|
||||||
(
|
|
||||||
'--last',
|
|
||||||
'5',
|
|
||||||
),
|
|
||||||
('archive',),
|
|
||||||
('check',),
|
|
||||||
('extract',),
|
|
||||||
('list',),
|
|
||||||
),
|
|
||||||
(('prune',), ('check',), ('list',), ('--last', '5')),
|
|
||||||
(
|
|
||||||
('prune',),
|
|
||||||
('check',),
|
|
||||||
(
|
|
||||||
'--verbosity',
|
|
||||||
'5',
|
|
||||||
),
|
|
||||||
('extract',),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(),
|
(),
|
||||||
),
|
),
|
||||||
# No flags.
|
# No flags.
|
||||||
((), (), ()),
|
((), ()),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_get_unparsable_arguments_returns_remaining_arguments_that_no_action_can_parse(
|
def test_get_unparsable_arguments_returns_remaining_arguments_that_no_action_can_parse(
|
||||||
arguments, grouped_arguments, expected
|
arguments, expected
|
||||||
):
|
):
|
||||||
for action_arguments, grouped_action_arguments in zip(arguments, grouped_arguments):
|
|
||||||
flexmock(module).should_receive('group_arguments_with_values').with_args(
|
|
||||||
action_arguments
|
|
||||||
).and_return(grouped_action_arguments)
|
|
||||||
|
|
||||||
assert module.get_unparsable_arguments(arguments) == expected
|
assert module.get_unparsable_arguments(arguments) == expected
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -487,45 +487,6 @@ def test_run_actions_runs_rcreate():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_adds_label_file_to_hook_context():
|
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
|
||||||
flexmock(module).should_receive('get_skip_actions').and_return([])
|
|
||||||
flexmock(module.command).should_receive('execute_hook')
|
|
||||||
expected = flexmock()
|
|
||||||
flexmock(borgmatic.actions.create).should_receive('run_create').with_args(
|
|
||||||
config_filename=object,
|
|
||||||
repository={'path': 'repo', 'label': 'my repo'},
|
|
||||||
config={'repositories': []},
|
|
||||||
config_paths=[],
|
|
||||||
hook_context={
|
|
||||||
'repository_label': 'my repo',
|
|
||||||
'log_file': '',
|
|
||||||
'repositories': '',
|
|
||||||
'repository': 'repo',
|
|
||||||
},
|
|
||||||
local_borg_version=object,
|
|
||||||
create_arguments=object,
|
|
||||||
global_arguments=object,
|
|
||||||
dry_run_label='',
|
|
||||||
local_path=object,
|
|
||||||
remote_path=object,
|
|
||||||
).once().and_return(expected)
|
|
||||||
|
|
||||||
result = tuple(
|
|
||||||
module.run_actions(
|
|
||||||
arguments={'global': flexmock(dry_run=False, log_file=None), 'create': flexmock()},
|
|
||||||
config_filename=flexmock(),
|
|
||||||
config={'repositories': []},
|
|
||||||
config_paths=[],
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
repository={'path': 'repo', 'label': 'my repo'},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
assert result == (expected,)
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_adds_log_file_to_hook_context():
|
def test_run_actions_adds_log_file_to_hook_context():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module).should_receive('get_skip_actions').and_return([])
|
flexmock(module).should_receive('get_skip_actions').and_return([])
|
||||||
|
@ -536,12 +497,7 @@ def test_run_actions_adds_log_file_to_hook_context():
|
||||||
repository={'path': 'repo'},
|
repository={'path': 'repo'},
|
||||||
config={'repositories': []},
|
config={'repositories': []},
|
||||||
config_paths=[],
|
config_paths=[],
|
||||||
hook_context={
|
hook_context={'repository': 'repo', 'repositories': '', 'log_file': 'foo'},
|
||||||
'repository_label': '',
|
|
||||||
'log_file': 'foo',
|
|
||||||
'repositories': '',
|
|
||||||
'repository': 'repo',
|
|
||||||
},
|
|
||||||
local_borg_version=object,
|
local_borg_version=object,
|
||||||
create_arguments=object,
|
create_arguments=object,
|
||||||
global_arguments=object,
|
global_arguments=object,
|
||||||
|
|
|
@ -50,16 +50,6 @@ def test_apply_constants_with_empty_constants_passes_through_value():
|
||||||
({'before_backup': '{inject}'}, {'before_backup': "'echo hi; naughty-command'"}),
|
({'before_backup': '{inject}'}, {'before_backup': "'echo hi; naughty-command'"}),
|
||||||
({'after_backup': '{inject}'}, {'after_backup': "'echo hi; naughty-command'"}),
|
({'after_backup': '{inject}'}, {'after_backup': "'echo hi; naughty-command'"}),
|
||||||
({'on_error': '{inject}'}, {'on_error': "'echo hi; naughty-command'"}),
|
({'on_error': '{inject}'}, {'on_error': "'echo hi; naughty-command'"}),
|
||||||
(
|
|
||||||
{
|
|
||||||
'before_backup': '{env_pass}',
|
|
||||||
'postgresql_databases': [{'name': 'users', 'password': '{env_pass}'}],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'before_backup': "'${PASS}'",
|
|
||||||
'postgresql_databases': [{'name': 'users', 'password': '${PASS}'}],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
(3, 3),
|
(3, 3),
|
||||||
(True, True),
|
(True, True),
|
||||||
(False, False),
|
(False, False),
|
||||||
|
@ -73,7 +63,6 @@ def test_apply_constants_makes_string_substitutions(value, expected_value):
|
||||||
'int': 3,
|
'int': 3,
|
||||||
'bool': True,
|
'bool': True,
|
||||||
'inject': 'echo hi; naughty-command',
|
'inject': 'echo hi; naughty-command',
|
||||||
'env_pass': '${PASS}',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert module.apply_constants(value, constants) == expected_value
|
assert module.apply_constants(value, constants) == expected_value
|
||||||
|
|
|
@ -264,75 +264,6 @@ def test_ping_monitor_hits_ping_url_when_states_matching():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_adds_create_query_parameter_when_create_slug_true():
|
|
||||||
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
|
||||||
hook_config = {'ping_url': 'https://example.com', 'create_slug': True}
|
|
||||||
flexmock(module.requests).should_receive('post').with_args(
|
|
||||||
'https://example.com/start?create=1', data=''.encode('utf-8'), verify=True
|
|
||||||
).and_return(flexmock(ok=True))
|
|
||||||
|
|
||||||
module.ping_monitor(
|
|
||||||
hook_config,
|
|
||||||
{},
|
|
||||||
'config.yaml',
|
|
||||||
state=module.monitor.State.START,
|
|
||||||
monitoring_log_level=1,
|
|
||||||
dry_run=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_does_not_add_create_query_parameter_when_create_slug_false():
|
|
||||||
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
|
||||||
hook_config = {'ping_url': 'https://example.com', 'create_slug': False}
|
|
||||||
flexmock(module.requests).should_receive('post').with_args(
|
|
||||||
'https://example.com/start', data=''.encode('utf-8'), verify=True
|
|
||||||
).and_return(flexmock(ok=True))
|
|
||||||
|
|
||||||
module.ping_monitor(
|
|
||||||
hook_config,
|
|
||||||
{},
|
|
||||||
'config.yaml',
|
|
||||||
state=module.monitor.State.START,
|
|
||||||
monitoring_log_level=1,
|
|
||||||
dry_run=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_does_not_add_create_query_parameter_when_ping_url_is_uuid():
|
|
||||||
hook_config = {'ping_url': 'b3611b24-df9c-4d36-9203-fa292820bf2a', 'create_slug': True}
|
|
||||||
flexmock(module.requests).should_receive('post').with_args(
|
|
||||||
f"https://hc-ping.com/{hook_config['ping_url']}",
|
|
||||||
data=''.encode('utf-8'),
|
|
||||||
verify=True,
|
|
||||||
).and_return(flexmock(ok=True))
|
|
||||||
|
|
||||||
module.ping_monitor(
|
|
||||||
hook_config,
|
|
||||||
{},
|
|
||||||
'config.yaml',
|
|
||||||
state=module.monitor.State.FINISH,
|
|
||||||
monitoring_log_level=1,
|
|
||||||
dry_run=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_issues_warning_when_ping_url_is_uuid_and_create_slug_true():
|
|
||||||
hook_config = {'ping_url': 'b3611b24-df9c-4d36-9203-fa292820bf2a', 'create_slug': True}
|
|
||||||
|
|
||||||
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True))
|
|
||||||
|
|
||||||
flexmock(module.logger).should_receive('warning').once()
|
|
||||||
|
|
||||||
module.ping_monitor(
|
|
||||||
hook_config,
|
|
||||||
{},
|
|
||||||
'config.yaml',
|
|
||||||
state=module.monitor.State.FINISH,
|
|
||||||
monitoring_log_level=1,
|
|
||||||
dry_run=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_with_connection_error_logs_warning():
|
def test_ping_monitor_with_connection_error_logs_warning():
|
||||||
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
flexmock(module.borgmatic.hooks.logs).should_receive('Forgetful_buffering_handler').never()
|
||||||
hook_config = {'ping_url': 'https://example.com'}
|
hook_config = {'ping_url': 'https://example.com'}
|
||||||
|
|
|
@ -217,11 +217,10 @@ def test_add_logging_level_skips_global_setting_if_already_set():
|
||||||
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux():
|
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
flexmock(module).should_receive('Console_color_formatter')
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.DEBUG, handlers=list
|
level=logging.DEBUG, handlers=list
|
||||||
|
@ -238,11 +237,10 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_linux(
|
||||||
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_macos():
|
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_macos():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
flexmock(module).should_receive('Console_color_formatter')
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.DEBUG, handlers=list
|
level=logging.DEBUG, handlers=list
|
||||||
|
@ -260,11 +258,10 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_macos(
|
||||||
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_freebsd():
|
def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_freebsd():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
flexmock(module).should_receive('Console_color_formatter')
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.DEBUG, handlers=list
|
level=logging.DEBUG, handlers=list
|
||||||
|
@ -283,11 +280,10 @@ def test_configure_logging_with_syslog_log_level_probes_for_log_socket_on_freebs
|
||||||
def test_configure_logging_without_syslog_log_level_skips_syslog():
|
def test_configure_logging_without_syslog_log_level_skips_syslog():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
flexmock(module).should_receive('Console_color_formatter')
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.INFO, handlers=list
|
level=logging.INFO, handlers=list
|
||||||
)
|
)
|
||||||
|
@ -300,11 +296,10 @@ def test_configure_logging_without_syslog_log_level_skips_syslog():
|
||||||
def test_configure_logging_skips_syslog_if_not_found():
|
def test_configure_logging_skips_syslog_if_not_found():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
flexmock(module).should_receive('Console_color_formatter')
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
level=logging.INFO, handlers=list
|
level=logging.INFO, handlers=list
|
||||||
)
|
)
|
||||||
|
@ -317,10 +312,8 @@ def test_configure_logging_skips_syslog_if_not_found():
|
||||||
def test_configure_logging_skips_log_file_if_log_file_logging_is_disabled():
|
def test_configure_logging_skips_log_file_if_log_file_logging_is_disabled():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).DISABLED = module.DISABLED
|
flexmock(module.logging).DISABLED = module.DISABLED
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -338,10 +331,8 @@ def test_configure_logging_skips_log_file_if_log_file_logging_is_disabled():
|
||||||
def test_configure_logging_to_log_file_instead_of_syslog():
|
def test_configure_logging_to_log_file_instead_of_syslog():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -365,10 +356,8 @@ def test_configure_logging_to_log_file_instead_of_syslog():
|
||||||
def test_configure_logging_to_both_log_file_and_syslog():
|
def test_configure_logging_to_both_log_file_and_syslog():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -398,10 +387,8 @@ def test_configure_logging_to_log_file_formats_with_custom_log_format():
|
||||||
flexmock(module.logging).should_receive('Formatter').with_args(
|
flexmock(module.logging).should_receive('Formatter').with_args(
|
||||||
'{message}', style='{' # noqa: FS003
|
'{message}', style='{' # noqa: FS003
|
||||||
).once()
|
).once()
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module).should_receive('interactive_console').and_return(False)
|
flexmock(module).should_receive('interactive_console').and_return(False)
|
||||||
|
@ -426,10 +413,8 @@ def test_configure_logging_to_log_file_formats_with_custom_log_format():
|
||||||
def test_configure_logging_skips_log_file_if_argument_is_none():
|
def test_configure_logging_skips_log_file_if_argument_is_none():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').once()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
@ -441,14 +426,11 @@ def test_configure_logging_skips_log_file_if_argument_is_none():
|
||||||
module.configure_logging(console_log_level=logging.INFO, log_file=None)
|
module.configure_logging(console_log_level=logging.INFO, log_file=None)
|
||||||
|
|
||||||
|
|
||||||
def test_configure_logging_uses_console_no_color_formatter_if_color_disabled():
|
def test_configure_logging_skips_console_color_formatter_if_color_disabled():
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
flexmock(module.logging).ANSWER = module.ANSWER
|
flexmock(module.logging).ANSWER = module.ANSWER
|
||||||
fake_formatter = flexmock()
|
|
||||||
flexmock(module).should_receive('Console_color_formatter').never()
|
|
||||||
flexmock(module).should_receive('Console_no_color_formatter').and_return(fake_formatter)
|
|
||||||
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
multi_stream_handler = flexmock(setLevel=lambda level: None, level=logging.INFO)
|
||||||
multi_stream_handler.should_receive('setFormatter').with_args(fake_formatter).once()
|
multi_stream_handler.should_receive('setFormatter').never()
|
||||||
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
flexmock(module).should_receive('Multi_stream_handler').and_return(multi_stream_handler)
|
||||||
|
|
||||||
flexmock(module.logging).should_receive('basicConfig').with_args(
|
flexmock(module.logging).should_receive('basicConfig').with_args(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user