mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-08-02 22:53:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13d49fda9b | ||
|
|
b01b8498aa | ||
|
|
a573e606a5 | ||
|
|
81db67c759 | ||
|
|
0b4aff9277 | ||
|
|
7de1c2121c | ||
|
|
2c8dc5858f | ||
|
|
f27a96e22d | ||
|
|
a892a308bd | ||
|
|
2db023f785 | ||
|
|
edaca2b3cd | ||
|
|
bc79eafb0b | ||
|
|
68fafffe99 | ||
|
|
6c068a297a | ||
|
|
06a6444c86 | ||
|
|
f6de79060e | ||
|
|
0d94a6587a | ||
|
|
2f535056ee | ||
|
|
360156e3b1 |
@@ -1,3 +1,22 @@
|
|||||||
|
2.0.3
|
||||||
|
* #1065: Fix a regression in monitoring hooks in which an error pinged the finish state instead of
|
||||||
|
the fail state.
|
||||||
|
* #1066: Add a "states" option to command hooks, so you can optionally skip an "after" hook if
|
||||||
|
borgmatic encounters an error.
|
||||||
|
* #1071: Fix an error in the LVM hook when removing a snapshot directory.
|
||||||
|
|
||||||
|
2.0.2
|
||||||
|
* #1035: Document potential performance issues and workarounds with the ZFS, Btrfs, and LVM hooks:
|
||||||
|
https://torsion.org/borgmatic/docs/how-to/snapshot-your-filesystems/
|
||||||
|
* #1053: Display a nicer error message when the "recreate" action encounters an archive that
|
||||||
|
already exists.
|
||||||
|
* #1059: Fix a regression in which soft failure exit codes in command hooks were not respected.
|
||||||
|
* #1060: Fix action command hooks getting run too many times when multiple borgmatic actions are
|
||||||
|
executed (implicitly or explicitly).
|
||||||
|
* #1060: Don't run action command hooks for actions listed in the "skip_actions" option.
|
||||||
|
* #1062: Fix a regression that broke environment variable interpolation.
|
||||||
|
* #1063: List the configured "when" action names in the log entries for command hooks.
|
||||||
|
|
||||||
2.0.1
|
2.0.1
|
||||||
* #1057: Fix argument parsing to avoid using Python 3.12+ string features. Now borgmatic will
|
* #1057: Fix argument parsing to avoid using Python 3.12+ string features. Now borgmatic will
|
||||||
work with Python 3.9, 3.10, and 3.11 again.
|
work with Python 3.9, 3.10, and 3.11 again.
|
||||||
|
|||||||
@@ -40,8 +40,10 @@ checks:
|
|||||||
frequency: 2 weeks
|
frequency: 2 weeks
|
||||||
|
|
||||||
# Custom preparation scripts to run.
|
# Custom preparation scripts to run.
|
||||||
before_backup:
|
commands:
|
||||||
- prepare-for-backup.sh
|
- before: action
|
||||||
|
when: [create]
|
||||||
|
run: [prepare-for-backup.sh]
|
||||||
|
|
||||||
# Databases to dump and include in backups.
|
# Databases to dump and include in backups.
|
||||||
postgresql_databases:
|
postgresql_databases:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import pathlib
|
|||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import borgmatic.actions.create
|
import borgmatic.actions.pattern
|
||||||
import borgmatic.borg.check
|
import borgmatic.borg.check
|
||||||
import borgmatic.borg.create
|
import borgmatic.borg.create
|
||||||
import borgmatic.borg.environment
|
import borgmatic.borg.environment
|
||||||
@@ -373,8 +373,8 @@ def collect_spot_check_source_paths(
|
|||||||
dry_run=True,
|
dry_run=True,
|
||||||
repository_path=repository['path'],
|
repository_path=repository['path'],
|
||||||
config=dict(config, list_details=True),
|
config=dict(config, list_details=True),
|
||||||
patterns=borgmatic.actions.create.process_patterns(
|
patterns=borgmatic.actions.pattern.process_patterns(
|
||||||
borgmatic.actions.create.collect_patterns(config),
|
borgmatic.actions.pattern.collect_patterns(config),
|
||||||
working_directory,
|
working_directory,
|
||||||
),
|
),
|
||||||
local_borg_version=local_borg_version,
|
local_borg_version=local_borg_version,
|
||||||
|
|||||||
+5
-260
@@ -1,272 +1,15 @@
|
|||||||
import glob
|
|
||||||
import itertools
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
|
|
||||||
import borgmatic.actions.json
|
import borgmatic.actions.json
|
||||||
import borgmatic.borg.create
|
import borgmatic.borg.create
|
||||||
import borgmatic.borg.pattern
|
|
||||||
import borgmatic.config.paths
|
import borgmatic.config.paths
|
||||||
import borgmatic.config.validate
|
import borgmatic.config.validate
|
||||||
import borgmatic.hooks.command
|
|
||||||
import borgmatic.hooks.dispatch
|
import borgmatic.hooks.dispatch
|
||||||
|
from borgmatic.actions import pattern
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def parse_pattern(pattern_line, default_style=borgmatic.borg.pattern.Pattern_style.NONE):
|
|
||||||
'''
|
|
||||||
Given a Borg pattern as a string, parse it into a borgmatic.borg.pattern.Pattern instance and
|
|
||||||
return it.
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
(pattern_type, remainder) = pattern_line.split(' ', maxsplit=1)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(f'Invalid pattern: {pattern_line}')
|
|
||||||
|
|
||||||
try:
|
|
||||||
(parsed_pattern_style, path) = remainder.split(':', maxsplit=1)
|
|
||||||
pattern_style = borgmatic.borg.pattern.Pattern_style(parsed_pattern_style)
|
|
||||||
except ValueError:
|
|
||||||
pattern_style = default_style
|
|
||||||
path = remainder
|
|
||||||
|
|
||||||
return borgmatic.borg.pattern.Pattern(
|
|
||||||
path,
|
|
||||||
borgmatic.borg.pattern.Pattern_type(pattern_type),
|
|
||||||
borgmatic.borg.pattern.Pattern_style(pattern_style),
|
|
||||||
source=borgmatic.borg.pattern.Pattern_source.CONFIG,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def collect_patterns(config):
|
|
||||||
'''
|
|
||||||
Given a configuration dict, produce a single sequence of patterns comprised of the configured
|
|
||||||
source directories, patterns, excludes, pattern files, and exclude files.
|
|
||||||
|
|
||||||
The idea is that Borg has all these different ways of specifying includes, excludes, source
|
|
||||||
directories, etc., but we'd like to collapse them all down to one common format (patterns) for
|
|
||||||
ease of manipulation within borgmatic.
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
return (
|
|
||||||
tuple(
|
|
||||||
borgmatic.borg.pattern.Pattern(
|
|
||||||
source_directory, source=borgmatic.borg.pattern.Pattern_source.CONFIG
|
|
||||||
)
|
|
||||||
for source_directory in config.get('source_directories', ())
|
|
||||||
)
|
|
||||||
+ tuple(
|
|
||||||
parse_pattern(pattern_line.strip())
|
|
||||||
for pattern_line in config.get('patterns', ())
|
|
||||||
if not pattern_line.lstrip().startswith('#')
|
|
||||||
if pattern_line.strip()
|
|
||||||
)
|
|
||||||
+ tuple(
|
|
||||||
parse_pattern(
|
|
||||||
f'{borgmatic.borg.pattern.Pattern_type.NO_RECURSE.value} {exclude_line.strip()}',
|
|
||||||
borgmatic.borg.pattern.Pattern_style.FNMATCH,
|
|
||||||
)
|
|
||||||
for exclude_line in config.get('exclude_patterns', ())
|
|
||||||
)
|
|
||||||
+ tuple(
|
|
||||||
parse_pattern(pattern_line.strip())
|
|
||||||
for filename in config.get('patterns_from', ())
|
|
||||||
for pattern_line in open(filename).readlines()
|
|
||||||
if not pattern_line.lstrip().startswith('#')
|
|
||||||
if pattern_line.strip()
|
|
||||||
)
|
|
||||||
+ tuple(
|
|
||||||
parse_pattern(
|
|
||||||
f'{borgmatic.borg.pattern.Pattern_type.NO_RECURSE.value} {exclude_line.strip()}',
|
|
||||||
borgmatic.borg.pattern.Pattern_style.FNMATCH,
|
|
||||||
)
|
|
||||||
for filename in config.get('exclude_from', ())
|
|
||||||
for exclude_line in open(filename).readlines()
|
|
||||||
if not exclude_line.lstrip().startswith('#')
|
|
||||||
if exclude_line.strip()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except (FileNotFoundError, OSError) as error:
|
|
||||||
logger.debug(error)
|
|
||||||
|
|
||||||
raise ValueError(f'Cannot read patterns_from/exclude_from file: {error.filename}')
|
|
||||||
|
|
||||||
|
|
||||||
def expand_directory(directory, working_directory):
|
|
||||||
'''
|
|
||||||
Given a directory path, expand any tilde (representing a user's home directory) and any globs
|
|
||||||
therein. Return a list of one or more resulting paths.
|
|
||||||
|
|
||||||
Take into account the given working directory so that relative paths are supported.
|
|
||||||
'''
|
|
||||||
expanded_directory = os.path.expanduser(directory)
|
|
||||||
|
|
||||||
# This would be a lot easier to do with glob(..., root_dir=working_directory), but root_dir is
|
|
||||||
# only available in Python 3.10+.
|
|
||||||
normalized_directory = os.path.join(working_directory or '', expanded_directory)
|
|
||||||
glob_paths = glob.glob(normalized_directory)
|
|
||||||
|
|
||||||
if not glob_paths:
|
|
||||||
return [expanded_directory]
|
|
||||||
|
|
||||||
working_directory_prefix = os.path.join(working_directory or '', '')
|
|
||||||
|
|
||||||
return [
|
|
||||||
(
|
|
||||||
glob_path
|
|
||||||
# If these are equal, that means we didn't add any working directory prefix above.
|
|
||||||
if normalized_directory == expanded_directory
|
|
||||||
# Remove the working directory prefix that we added above in order to make glob() work.
|
|
||||||
# We can't use os.path.relpath() here because it collapses any use of Borg's slashdot
|
|
||||||
# hack.
|
|
||||||
else glob_path.removeprefix(working_directory_prefix)
|
|
||||||
)
|
|
||||||
for glob_path in glob_paths
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def expand_patterns(patterns, working_directory=None, skip_paths=None):
|
|
||||||
'''
|
|
||||||
Given a sequence of borgmatic.borg.pattern.Pattern instances and an optional working directory,
|
|
||||||
expand tildes and globs in each root pattern and expand just tildes in each non-root pattern.
|
|
||||||
The idea is that non-root patterns may be regular expressions or other pattern styles containing
|
|
||||||
"*" that borgmatic should not expand as a shell glob.
|
|
||||||
|
|
||||||
Return all the resulting patterns as a tuple.
|
|
||||||
|
|
||||||
If a set of paths are given to skip, then don't expand any patterns matching them.
|
|
||||||
'''
|
|
||||||
if patterns is None:
|
|
||||||
return ()
|
|
||||||
|
|
||||||
return tuple(
|
|
||||||
itertools.chain.from_iterable(
|
|
||||||
(
|
|
||||||
(
|
|
||||||
borgmatic.borg.pattern.Pattern(
|
|
||||||
expanded_path,
|
|
||||||
pattern.type,
|
|
||||||
pattern.style,
|
|
||||||
pattern.device,
|
|
||||||
pattern.source,
|
|
||||||
)
|
|
||||||
for expanded_path in expand_directory(pattern.path, working_directory)
|
|
||||||
)
|
|
||||||
if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
|
||||||
and pattern.path not in (skip_paths or ())
|
|
||||||
else (
|
|
||||||
borgmatic.borg.pattern.Pattern(
|
|
||||||
os.path.expanduser(pattern.path),
|
|
||||||
pattern.type,
|
|
||||||
pattern.style,
|
|
||||||
pattern.device,
|
|
||||||
pattern.source,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for pattern in patterns
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def device_map_patterns(patterns, working_directory=None):
|
|
||||||
'''
|
|
||||||
Given a sequence of borgmatic.borg.pattern.Pattern instances and an optional working directory,
|
|
||||||
determine the identifier for the device on which the pattern's path resides—or None if the path
|
|
||||||
doesn't exist or is from a non-root pattern. Return an updated sequence of patterns with the
|
|
||||||
device field populated. But if the device field is already set, don't bother setting it again.
|
|
||||||
|
|
||||||
This is handy for determining whether two different pattern paths are on the same filesystem
|
|
||||||
(have the same device identifier).
|
|
||||||
'''
|
|
||||||
return tuple(
|
|
||||||
borgmatic.borg.pattern.Pattern(
|
|
||||||
pattern.path,
|
|
||||||
pattern.type,
|
|
||||||
pattern.style,
|
|
||||||
device=pattern.device
|
|
||||||
or (
|
|
||||||
os.stat(full_path).st_dev
|
|
||||||
if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
|
||||||
and os.path.exists(full_path)
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
source=pattern.source,
|
|
||||||
)
|
|
||||||
for pattern in patterns
|
|
||||||
for full_path in (os.path.join(working_directory or '', pattern.path),)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def deduplicate_patterns(patterns):
|
|
||||||
'''
|
|
||||||
Given a sequence of borgmatic.borg.pattern.Pattern instances, return them with all duplicate
|
|
||||||
root child patterns removed. For instance, if two root patterns are given with paths "/foo" and
|
|
||||||
"/foo/bar", return just the one with "/foo". Non-root patterns are passed through without
|
|
||||||
modification.
|
|
||||||
|
|
||||||
The one exception to deduplication is two paths are on different filesystems (devices). In that
|
|
||||||
case, they won't get deduplicated, in case they both need to be passed to Borg (e.g. the
|
|
||||||
one_file_system option is true).
|
|
||||||
|
|
||||||
The idea is that if Borg is given a root parent pattern, then it doesn't also need to be given
|
|
||||||
child patterns, because it will naturally spider the contents of the parent pattern's path. And
|
|
||||||
there are cases where Borg coming across the same file twice will result in duplicate reads and
|
|
||||||
even hangs, e.g. when a database hook is using a named pipe for streaming database dumps to
|
|
||||||
Borg.
|
|
||||||
'''
|
|
||||||
deduplicated = {} # Use just the keys as an ordered set.
|
|
||||||
|
|
||||||
for pattern in patterns:
|
|
||||||
if pattern.type != borgmatic.borg.pattern.Pattern_type.ROOT:
|
|
||||||
deduplicated[pattern] = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
parents = pathlib.PurePath(pattern.path).parents
|
|
||||||
|
|
||||||
# If another directory in the given list is a parent of current directory (even n levels up)
|
|
||||||
# and both are on the same filesystem, then the current directory is a duplicate.
|
|
||||||
for other_pattern in patterns:
|
|
||||||
if other_pattern.type != borgmatic.borg.pattern.Pattern_type.ROOT:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if any(
|
|
||||||
pathlib.PurePath(other_pattern.path) == parent
|
|
||||||
and pattern.device is not None
|
|
||||||
and other_pattern.device == pattern.device
|
|
||||||
for parent in parents
|
|
||||||
):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
deduplicated[pattern] = True
|
|
||||||
|
|
||||||
return tuple(deduplicated.keys())
|
|
||||||
|
|
||||||
|
|
||||||
def process_patterns(patterns, working_directory, skip_expand_paths=None):
|
|
||||||
'''
|
|
||||||
Given a sequence of Borg patterns and a configured working directory, expand and deduplicate any
|
|
||||||
"root" patterns, returning the resulting root and non-root patterns as a list.
|
|
||||||
|
|
||||||
If any paths are given to skip, don't expand them.
|
|
||||||
'''
|
|
||||||
skip_paths = set(skip_expand_paths or ())
|
|
||||||
|
|
||||||
return list(
|
|
||||||
deduplicate_patterns(
|
|
||||||
device_map_patterns(
|
|
||||||
expand_patterns(
|
|
||||||
patterns,
|
|
||||||
working_directory=working_directory,
|
|
||||||
skip_paths=skip_paths,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_create(
|
def run_create(
|
||||||
config_filename,
|
config_filename,
|
||||||
repository,
|
repository,
|
||||||
@@ -310,7 +53,7 @@ def run_create(
|
|||||||
borgmatic_runtime_directory,
|
borgmatic_runtime_directory,
|
||||||
global_arguments.dry_run,
|
global_arguments.dry_run,
|
||||||
)
|
)
|
||||||
patterns = process_patterns(collect_patterns(config), working_directory)
|
patterns = pattern.process_patterns(pattern.collect_patterns(config), working_directory)
|
||||||
active_dumps = borgmatic.hooks.dispatch.call_hooks(
|
active_dumps = borgmatic.hooks.dispatch.call_hooks(
|
||||||
'dump_data_sources',
|
'dump_data_sources',
|
||||||
config,
|
config,
|
||||||
@@ -324,7 +67,9 @@ def run_create(
|
|||||||
# Process the patterns again in case any data source hooks updated them. Without this step,
|
# Process the patterns again in case any data source hooks updated them. Without this step,
|
||||||
# we could end up with duplicate paths that cause Borg to hang when it tries to read from
|
# we could end up with duplicate paths that cause Borg to hang when it tries to read from
|
||||||
# the same named pipe twice.
|
# the same named pipe twice.
|
||||||
patterns = process_patterns(patterns, working_directory, skip_expand_paths=config_paths)
|
patterns = pattern.process_patterns(
|
||||||
|
patterns, working_directory, skip_expand_paths=config_paths
|
||||||
|
)
|
||||||
stream_processes = [process for processes in active_dumps.values() for process in processes]
|
stream_processes = [process for processes in active_dumps.values() for process in processes]
|
||||||
|
|
||||||
json_output = borgmatic.borg.create.create_archive(
|
json_output = borgmatic.borg.create.create_archive(
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import glob
|
||||||
|
import itertools
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
import borgmatic.borg.pattern
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pattern(pattern_line, default_style=borgmatic.borg.pattern.Pattern_style.NONE):
|
||||||
|
'''
|
||||||
|
Given a Borg pattern as a string, parse it into a borgmatic.borg.pattern.Pattern instance and
|
||||||
|
return it.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
(pattern_type, remainder) = pattern_line.split(' ', maxsplit=1)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f'Invalid pattern: {pattern_line}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
(parsed_pattern_style, path) = remainder.split(':', maxsplit=1)
|
||||||
|
pattern_style = borgmatic.borg.pattern.Pattern_style(parsed_pattern_style)
|
||||||
|
except ValueError:
|
||||||
|
pattern_style = default_style
|
||||||
|
path = remainder
|
||||||
|
|
||||||
|
return borgmatic.borg.pattern.Pattern(
|
||||||
|
path,
|
||||||
|
borgmatic.borg.pattern.Pattern_type(pattern_type),
|
||||||
|
borgmatic.borg.pattern.Pattern_style(pattern_style),
|
||||||
|
source=borgmatic.borg.pattern.Pattern_source.CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def collect_patterns(config):
|
||||||
|
'''
|
||||||
|
Given a configuration dict, produce a single sequence of patterns comprised of the configured
|
||||||
|
source directories, patterns, excludes, pattern files, and exclude files.
|
||||||
|
|
||||||
|
The idea is that Borg has all these different ways of specifying includes, excludes, source
|
||||||
|
directories, etc., but we'd like to collapse them all down to one common format (patterns) for
|
||||||
|
ease of manipulation within borgmatic.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
return (
|
||||||
|
tuple(
|
||||||
|
borgmatic.borg.pattern.Pattern(
|
||||||
|
source_directory, source=borgmatic.borg.pattern.Pattern_source.CONFIG
|
||||||
|
)
|
||||||
|
for source_directory in config.get('source_directories', ())
|
||||||
|
)
|
||||||
|
+ tuple(
|
||||||
|
parse_pattern(pattern_line.strip())
|
||||||
|
for pattern_line in config.get('patterns', ())
|
||||||
|
if not pattern_line.lstrip().startswith('#')
|
||||||
|
if pattern_line.strip()
|
||||||
|
)
|
||||||
|
+ tuple(
|
||||||
|
parse_pattern(
|
||||||
|
f'{borgmatic.borg.pattern.Pattern_type.NO_RECURSE.value} {exclude_line.strip()}',
|
||||||
|
borgmatic.borg.pattern.Pattern_style.FNMATCH,
|
||||||
|
)
|
||||||
|
for exclude_line in config.get('exclude_patterns', ())
|
||||||
|
)
|
||||||
|
+ tuple(
|
||||||
|
parse_pattern(pattern_line.strip())
|
||||||
|
for filename in config.get('patterns_from', ())
|
||||||
|
for pattern_line in open(filename).readlines()
|
||||||
|
if not pattern_line.lstrip().startswith('#')
|
||||||
|
if pattern_line.strip()
|
||||||
|
)
|
||||||
|
+ tuple(
|
||||||
|
parse_pattern(
|
||||||
|
f'{borgmatic.borg.pattern.Pattern_type.NO_RECURSE.value} {exclude_line.strip()}',
|
||||||
|
borgmatic.borg.pattern.Pattern_style.FNMATCH,
|
||||||
|
)
|
||||||
|
for filename in config.get('exclude_from', ())
|
||||||
|
for exclude_line in open(filename).readlines()
|
||||||
|
if not exclude_line.lstrip().startswith('#')
|
||||||
|
if exclude_line.strip()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except (FileNotFoundError, OSError) as error:
|
||||||
|
logger.debug(error)
|
||||||
|
|
||||||
|
raise ValueError(f'Cannot read patterns_from/exclude_from file: {error.filename}')
|
||||||
|
|
||||||
|
|
||||||
|
def expand_directory(directory, working_directory):
|
||||||
|
'''
|
||||||
|
Given a directory path, expand any tilde (representing a user's home directory) and any globs
|
||||||
|
therein. Return a list of one or more resulting paths.
|
||||||
|
|
||||||
|
Take into account the given working directory so that relative paths are supported.
|
||||||
|
'''
|
||||||
|
expanded_directory = os.path.expanduser(directory)
|
||||||
|
|
||||||
|
# This would be a lot easier to do with glob(..., root_dir=working_directory), but root_dir is
|
||||||
|
# only available in Python 3.10+.
|
||||||
|
normalized_directory = os.path.join(working_directory or '', expanded_directory)
|
||||||
|
glob_paths = glob.glob(normalized_directory)
|
||||||
|
|
||||||
|
if not glob_paths:
|
||||||
|
return [expanded_directory]
|
||||||
|
|
||||||
|
working_directory_prefix = os.path.join(working_directory or '', '')
|
||||||
|
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
glob_path
|
||||||
|
# If these are equal, that means we didn't add any working directory prefix above.
|
||||||
|
if normalized_directory == expanded_directory
|
||||||
|
# Remove the working directory prefix that we added above in order to make glob() work.
|
||||||
|
# We can't use os.path.relpath() here because it collapses any use of Borg's slashdot
|
||||||
|
# hack.
|
||||||
|
else glob_path.removeprefix(working_directory_prefix)
|
||||||
|
)
|
||||||
|
for glob_path in glob_paths
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def expand_patterns(patterns, working_directory=None, skip_paths=None):
|
||||||
|
'''
|
||||||
|
Given a sequence of borgmatic.borg.pattern.Pattern instances and an optional working directory,
|
||||||
|
expand tildes and globs in each root pattern and expand just tildes in each non-root pattern.
|
||||||
|
The idea is that non-root patterns may be regular expressions or other pattern styles containing
|
||||||
|
"*" that borgmatic should not expand as a shell glob.
|
||||||
|
|
||||||
|
Return all the resulting patterns as a tuple.
|
||||||
|
|
||||||
|
If a set of paths are given to skip, then don't expand any patterns matching them.
|
||||||
|
'''
|
||||||
|
if patterns is None:
|
||||||
|
return ()
|
||||||
|
|
||||||
|
return tuple(
|
||||||
|
itertools.chain.from_iterable(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
borgmatic.borg.pattern.Pattern(
|
||||||
|
expanded_path,
|
||||||
|
pattern.type,
|
||||||
|
pattern.style,
|
||||||
|
pattern.device,
|
||||||
|
pattern.source,
|
||||||
|
)
|
||||||
|
for expanded_path in expand_directory(pattern.path, working_directory)
|
||||||
|
)
|
||||||
|
if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
||||||
|
and pattern.path not in (skip_paths or ())
|
||||||
|
else (
|
||||||
|
borgmatic.borg.pattern.Pattern(
|
||||||
|
os.path.expanduser(pattern.path),
|
||||||
|
pattern.type,
|
||||||
|
pattern.style,
|
||||||
|
pattern.device,
|
||||||
|
pattern.source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for pattern in patterns
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def device_map_patterns(patterns, working_directory=None):
|
||||||
|
'''
|
||||||
|
Given a sequence of borgmatic.borg.pattern.Pattern instances and an optional working directory,
|
||||||
|
determine the identifier for the device on which the pattern's path resides—or None if the path
|
||||||
|
doesn't exist or is from a non-root pattern. Return an updated sequence of patterns with the
|
||||||
|
device field populated. But if the device field is already set, don't bother setting it again.
|
||||||
|
|
||||||
|
This is handy for determining whether two different pattern paths are on the same filesystem
|
||||||
|
(have the same device identifier).
|
||||||
|
'''
|
||||||
|
return tuple(
|
||||||
|
borgmatic.borg.pattern.Pattern(
|
||||||
|
pattern.path,
|
||||||
|
pattern.type,
|
||||||
|
pattern.style,
|
||||||
|
device=pattern.device
|
||||||
|
or (
|
||||||
|
os.stat(full_path).st_dev
|
||||||
|
if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
||||||
|
and os.path.exists(full_path)
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
source=pattern.source,
|
||||||
|
)
|
||||||
|
for pattern in patterns
|
||||||
|
for full_path in (os.path.join(working_directory or '', pattern.path),)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def deduplicate_patterns(patterns):
|
||||||
|
'''
|
||||||
|
Given a sequence of borgmatic.borg.pattern.Pattern instances, return them with all duplicate
|
||||||
|
root child patterns removed. For instance, if two root patterns are given with paths "/foo" and
|
||||||
|
"/foo/bar", return just the one with "/foo". Non-root patterns are passed through without
|
||||||
|
modification.
|
||||||
|
|
||||||
|
The one exception to deduplication is two paths are on different filesystems (devices). In that
|
||||||
|
case, they won't get deduplicated, in case they both need to be passed to Borg (e.g. the
|
||||||
|
one_file_system option is true).
|
||||||
|
|
||||||
|
The idea is that if Borg is given a root parent pattern, then it doesn't also need to be given
|
||||||
|
child patterns, because it will naturally spider the contents of the parent pattern's path. And
|
||||||
|
there are cases where Borg coming across the same file twice will result in duplicate reads and
|
||||||
|
even hangs, e.g. when a database hook is using a named pipe for streaming database dumps to
|
||||||
|
Borg.
|
||||||
|
'''
|
||||||
|
deduplicated = {} # Use just the keys as an ordered set.
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
if pattern.type != borgmatic.borg.pattern.Pattern_type.ROOT:
|
||||||
|
deduplicated[pattern] = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
parents = pathlib.PurePath(pattern.path).parents
|
||||||
|
|
||||||
|
# If another directory in the given list is a parent of current directory (even n levels up)
|
||||||
|
# and both are on the same filesystem, then the current directory is a duplicate.
|
||||||
|
for other_pattern in patterns:
|
||||||
|
if other_pattern.type != borgmatic.borg.pattern.Pattern_type.ROOT:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if any(
|
||||||
|
pathlib.PurePath(other_pattern.path) == parent
|
||||||
|
and pattern.device is not None
|
||||||
|
and other_pattern.device == pattern.device
|
||||||
|
for parent in parents
|
||||||
|
):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
deduplicated[pattern] = True
|
||||||
|
|
||||||
|
return tuple(deduplicated.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def process_patterns(patterns, working_directory, skip_expand_paths=None):
|
||||||
|
'''
|
||||||
|
Given a sequence of Borg patterns and a configured working directory, expand and deduplicate any
|
||||||
|
"root" patterns, returning the resulting root and non-root patterns as a list.
|
||||||
|
|
||||||
|
If any paths are given to skip, don't expand them.
|
||||||
|
'''
|
||||||
|
skip_paths = set(skip_expand_paths or ())
|
||||||
|
|
||||||
|
return list(
|
||||||
|
deduplicate_patterns(
|
||||||
|
device_map_patterns(
|
||||||
|
expand_patterns(
|
||||||
|
patterns,
|
||||||
|
working_directory=working_directory,
|
||||||
|
skip_paths=skip_paths,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import borgmatic.borg.info
|
||||||
import borgmatic.borg.recreate
|
import borgmatic.borg.recreate
|
||||||
|
import borgmatic.borg.repo_list
|
||||||
import borgmatic.config.validate
|
import borgmatic.config.validate
|
||||||
from borgmatic.actions.create import collect_patterns, process_patterns
|
from borgmatic.actions.pattern import collect_patterns, process_patterns
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS = 30
|
||||||
|
|
||||||
|
|
||||||
def run_recreate(
|
def run_recreate(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -32,22 +38,47 @@ def run_recreate(
|
|||||||
collect_patterns(config), borgmatic.config.paths.get_working_directory(config)
|
collect_patterns(config), borgmatic.config.paths.get_working_directory(config)
|
||||||
)
|
)
|
||||||
|
|
||||||
borgmatic.borg.recreate.recreate_archive(
|
archive = borgmatic.borg.repo_list.resolve_archive_name(
|
||||||
repository['path'],
|
repository['path'],
|
||||||
borgmatic.borg.repo_list.resolve_archive_name(
|
recreate_arguments.archive,
|
||||||
repository['path'],
|
|
||||||
recreate_arguments.archive,
|
|
||||||
config,
|
|
||||||
local_borg_version,
|
|
||||||
global_arguments,
|
|
||||||
local_path,
|
|
||||||
remote_path,
|
|
||||||
),
|
|
||||||
config,
|
config,
|
||||||
local_borg_version,
|
local_borg_version,
|
||||||
recreate_arguments,
|
|
||||||
global_arguments,
|
global_arguments,
|
||||||
local_path=local_path,
|
local_path,
|
||||||
remote_path=remote_path,
|
remote_path,
|
||||||
patterns=processed_patterns,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if archive and archive.endswith('.recreate'):
|
||||||
|
if recreate_arguments.archive == 'latest':
|
||||||
|
raise ValueError(
|
||||||
|
f'The latest archive "{archive}" is leftover from a prior recreate. Delete it first or select a different archive.'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f'The archive "{recreate_arguments.archive}" is leftover from a prior recreate. Select a different archive.'
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
borgmatic.borg.recreate.recreate_archive(
|
||||||
|
repository['path'],
|
||||||
|
archive,
|
||||||
|
config,
|
||||||
|
local_borg_version,
|
||||||
|
recreate_arguments,
|
||||||
|
global_arguments,
|
||||||
|
local_path=local_path,
|
||||||
|
remote_path=remote_path,
|
||||||
|
patterns=processed_patterns,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
if error.returncode == BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS:
|
||||||
|
if recreate_arguments.target:
|
||||||
|
raise ValueError(
|
||||||
|
f'The archive "{recreate_arguments.target}" already exists. Delete it first or set a different target archive name.'
|
||||||
|
)
|
||||||
|
elif archive:
|
||||||
|
raise ValueError(
|
||||||
|
f'The archive "{archive}.recreate" is leftover from a prior recreate. Delete it first or select a different archive.'
|
||||||
|
)
|
||||||
|
|
||||||
|
raise
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import itertools
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import stat
|
import stat
|
||||||
import tempfile
|
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import borgmatic.borg.pattern
|
import borgmatic.borg.pattern
|
||||||
@@ -20,76 +18,6 @@ from borgmatic.execute import (
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def write_patterns_file(patterns, borgmatic_runtime_directory, patterns_file=None):
|
|
||||||
'''
|
|
||||||
Given a sequence of patterns as borgmatic.borg.pattern.Pattern instances, write them to a named
|
|
||||||
temporary file in the given borgmatic runtime directory and return the file object so it can
|
|
||||||
continue to exist on disk as long as the caller needs it.
|
|
||||||
|
|
||||||
If an optional open pattern file is given, append to it instead of making a new temporary file.
|
|
||||||
Return None if no patterns are provided.
|
|
||||||
'''
|
|
||||||
if not patterns:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if patterns_file is None:
|
|
||||||
patterns_file = tempfile.NamedTemporaryFile('w', dir=borgmatic_runtime_directory)
|
|
||||||
operation_name = 'Writing'
|
|
||||||
else:
|
|
||||||
patterns_file.write('\n')
|
|
||||||
operation_name = 'Appending'
|
|
||||||
|
|
||||||
patterns_output = '\n'.join(
|
|
||||||
f'{pattern.type.value} {pattern.style.value}{":" if pattern.style.value else ""}{pattern.path}'
|
|
||||||
for pattern in patterns
|
|
||||||
)
|
|
||||||
logger.debug(f'{operation_name} patterns to {patterns_file.name}:\n{patterns_output}')
|
|
||||||
|
|
||||||
patterns_file.write(patterns_output)
|
|
||||||
patterns_file.flush()
|
|
||||||
|
|
||||||
return patterns_file
|
|
||||||
|
|
||||||
|
|
||||||
def make_exclude_flags(config):
|
|
||||||
'''
|
|
||||||
Given a configuration dict with various exclude options, return the corresponding Borg flags as
|
|
||||||
a tuple.
|
|
||||||
'''
|
|
||||||
caches_flag = ('--exclude-caches',) if config.get('exclude_caches') else ()
|
|
||||||
if_present_flags = tuple(
|
|
||||||
itertools.chain.from_iterable(
|
|
||||||
('--exclude-if-present', if_present)
|
|
||||||
for if_present in config.get('exclude_if_present', ())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
keep_exclude_tags_flags = ('--keep-exclude-tags',) if config.get('keep_exclude_tags') else ()
|
|
||||||
exclude_nodump_flags = ('--exclude-nodump',) if config.get('exclude_nodump') else ()
|
|
||||||
|
|
||||||
return caches_flag + if_present_flags + keep_exclude_tags_flags + exclude_nodump_flags
|
|
||||||
|
|
||||||
|
|
||||||
def make_list_filter_flags(local_borg_version, dry_run):
|
|
||||||
'''
|
|
||||||
Given the local Borg version and whether this is a dry run, return the corresponding flags for
|
|
||||||
passing to "--list --filter". The general idea is that excludes are shown for a dry run or when
|
|
||||||
the verbosity is debug.
|
|
||||||
'''
|
|
||||||
base_flags = 'AME'
|
|
||||||
show_excludes = logger.isEnabledFor(logging.DEBUG)
|
|
||||||
|
|
||||||
if feature.available(feature.Feature.EXCLUDED_FILES_MINUS, local_borg_version):
|
|
||||||
if show_excludes or dry_run:
|
|
||||||
return f'{base_flags}+-'
|
|
||||||
else:
|
|
||||||
return base_flags
|
|
||||||
|
|
||||||
if show_excludes:
|
|
||||||
return f'{base_flags}x-'
|
|
||||||
else:
|
|
||||||
return f'{base_flags}-'
|
|
||||||
|
|
||||||
|
|
||||||
def special_file(path, working_directory=None):
|
def special_file(path, working_directory=None):
|
||||||
'''
|
'''
|
||||||
Return whether the given path is a special file (character device, block device, or named pipe
|
Return whether the given path is a special file (character device, block device, or named pipe
|
||||||
@@ -182,24 +110,6 @@ def collect_special_file_paths(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_all_root_patterns_exist(patterns):
|
|
||||||
'''
|
|
||||||
Given a sequence of borgmatic.borg.pattern.Pattern instances, check that all root pattern
|
|
||||||
paths exist. If any don't, raise an exception.
|
|
||||||
'''
|
|
||||||
missing_paths = [
|
|
||||||
pattern.path
|
|
||||||
for pattern in patterns
|
|
||||||
if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
|
||||||
if not os.path.exists(pattern.path)
|
|
||||||
]
|
|
||||||
|
|
||||||
if missing_paths:
|
|
||||||
raise ValueError(
|
|
||||||
f"Source directories or root pattern paths do not exist: {', '.join(missing_paths)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
MAX_SPECIAL_FILE_PATHS_LENGTH = 1000
|
MAX_SPECIAL_FILE_PATHS_LENGTH = 1000
|
||||||
|
|
||||||
|
|
||||||
@@ -224,9 +134,11 @@ def make_base_create_command(
|
|||||||
arguments, open pattern file handle).
|
arguments, open pattern file handle).
|
||||||
'''
|
'''
|
||||||
if config.get('source_directories_must_exist', False):
|
if config.get('source_directories_must_exist', False):
|
||||||
check_all_root_patterns_exist(patterns)
|
borgmatic.borg.pattern.check_all_root_patterns_exist(patterns)
|
||||||
|
|
||||||
patterns_file = write_patterns_file(patterns, borgmatic_runtime_directory)
|
patterns_file = borgmatic.borg.pattern.write_patterns_file(
|
||||||
|
patterns, borgmatic_runtime_directory
|
||||||
|
)
|
||||||
checkpoint_interval = config.get('checkpoint_interval', None)
|
checkpoint_interval = config.get('checkpoint_interval', None)
|
||||||
checkpoint_volume = config.get('checkpoint_volume', None)
|
checkpoint_volume = config.get('checkpoint_volume', None)
|
||||||
chunker_params = config.get('chunker_params', None)
|
chunker_params = config.get('chunker_params', None)
|
||||||
@@ -235,7 +147,7 @@ def make_base_create_command(
|
|||||||
upload_buffer_size = config.get('upload_buffer_size', 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 = flags.make_list_filter_flags(local_borg_version, dry_run)
|
||||||
files_cache = config.get('files_cache')
|
files_cache = config.get('files_cache')
|
||||||
archive_name_format = config.get(
|
archive_name_format = config.get(
|
||||||
'archive_name_format', flags.get_default_archive_name_format(local_borg_version)
|
'archive_name_format', flags.get_default_archive_name_format(local_borg_version)
|
||||||
@@ -270,7 +182,7 @@ def make_base_create_command(
|
|||||||
tuple(local_path.split(' '))
|
tuple(local_path.split(' '))
|
||||||
+ ('create',)
|
+ ('create',)
|
||||||
+ (('--patterns-from', patterns_file.name) if patterns_file else ())
|
+ (('--patterns-from', patterns_file.name) if patterns_file else ())
|
||||||
+ make_exclude_flags(config)
|
+ flags.make_exclude_flags(config)
|
||||||
+ (('--checkpoint-interval', str(checkpoint_interval)) if checkpoint_interval else ())
|
+ (('--checkpoint-interval', str(checkpoint_interval)) if checkpoint_interval else ())
|
||||||
+ (('--checkpoint-volume', str(checkpoint_volume)) if checkpoint_volume else ())
|
+ (('--checkpoint-volume', str(checkpoint_volume)) if checkpoint_volume else ())
|
||||||
+ (('--chunker-params', chunker_params) if chunker_params else ())
|
+ (('--chunker-params', chunker_params) if chunker_params else ())
|
||||||
@@ -329,7 +241,7 @@ def make_base_create_command(
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
f'Excluding special files to prevent Borg from hanging: {truncated_special_file_paths}'
|
f'Excluding special files to prevent Borg from hanging: {truncated_special_file_paths}'
|
||||||
)
|
)
|
||||||
patterns_file = write_patterns_file(
|
patterns_file = borgmatic.borg.pattern.write_patterns_file(
|
||||||
tuple(
|
tuple(
|
||||||
borgmatic.borg.pattern.Pattern(
|
borgmatic.borg.pattern.Pattern(
|
||||||
special_file_path,
|
special_file_path,
|
||||||
|
|||||||
@@ -197,3 +197,42 @@ def omit_flag_and_value(arguments, flag):
|
|||||||
if flag not in (previous_argument, argument)
|
if flag not in (previous_argument, argument)
|
||||||
if not argument.startswith(f'{flag}=')
|
if not argument.startswith(f'{flag}=')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_exclude_flags(config):
|
||||||
|
'''
|
||||||
|
Given a configuration dict with various exclude options, return the corresponding Borg flags as
|
||||||
|
a tuple.
|
||||||
|
'''
|
||||||
|
caches_flag = ('--exclude-caches',) if config.get('exclude_caches') else ()
|
||||||
|
if_present_flags = tuple(
|
||||||
|
itertools.chain.from_iterable(
|
||||||
|
('--exclude-if-present', if_present)
|
||||||
|
for if_present in config.get('exclude_if_present', ())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
keep_exclude_tags_flags = ('--keep-exclude-tags',) if config.get('keep_exclude_tags') else ()
|
||||||
|
exclude_nodump_flags = ('--exclude-nodump',) if config.get('exclude_nodump') else ()
|
||||||
|
|
||||||
|
return caches_flag + if_present_flags + keep_exclude_tags_flags + exclude_nodump_flags
|
||||||
|
|
||||||
|
|
||||||
|
def make_list_filter_flags(local_borg_version, dry_run):
|
||||||
|
'''
|
||||||
|
Given the local Borg version and whether this is a dry run, return the corresponding flags for
|
||||||
|
passing to "--list --filter". The general idea is that excludes are shown for a dry run or when
|
||||||
|
the verbosity is debug.
|
||||||
|
'''
|
||||||
|
base_flags = 'AME'
|
||||||
|
show_excludes = logger.isEnabledFor(logging.DEBUG)
|
||||||
|
|
||||||
|
if feature.available(feature.Feature.EXCLUDED_FILES_MINUS, local_borg_version):
|
||||||
|
if show_excludes or dry_run:
|
||||||
|
return f'{base_flags}+-'
|
||||||
|
else:
|
||||||
|
return base_flags
|
||||||
|
|
||||||
|
if show_excludes:
|
||||||
|
return f'{base_flags}x-'
|
||||||
|
else:
|
||||||
|
return f'{base_flags}-'
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import collections
|
import collections
|
||||||
import enum
|
import enum
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import borgmatic.borg.pattern
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# See https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns
|
# See https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns
|
||||||
@@ -48,3 +55,52 @@ Pattern = collections.namedtuple(
|
|||||||
Pattern_source.HOOK,
|
Pattern_source.HOOK,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def write_patterns_file(patterns, borgmatic_runtime_directory, patterns_file=None):
|
||||||
|
'''
|
||||||
|
Given a sequence of patterns as borgmatic.borg.pattern.Pattern instances, write them to a named
|
||||||
|
temporary file in the given borgmatic runtime directory and return the file object so it can
|
||||||
|
continue to exist on disk as long as the caller needs it.
|
||||||
|
|
||||||
|
If an optional open pattern file is given, append to it instead of making a new temporary file.
|
||||||
|
Return None if no patterns are provided.
|
||||||
|
'''
|
||||||
|
if not patterns:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if patterns_file is None:
|
||||||
|
patterns_file = tempfile.NamedTemporaryFile('w', dir=borgmatic_runtime_directory)
|
||||||
|
operation_name = 'Writing'
|
||||||
|
else:
|
||||||
|
patterns_file.write('\n')
|
||||||
|
operation_name = 'Appending'
|
||||||
|
|
||||||
|
patterns_output = '\n'.join(
|
||||||
|
f'{pattern.type.value} {pattern.style.value}{":" if pattern.style.value else ""}{pattern.path}'
|
||||||
|
for pattern in patterns
|
||||||
|
)
|
||||||
|
logger.debug(f'{operation_name} patterns to {patterns_file.name}:\n{patterns_output}')
|
||||||
|
|
||||||
|
patterns_file.write(patterns_output)
|
||||||
|
patterns_file.flush()
|
||||||
|
|
||||||
|
return patterns_file
|
||||||
|
|
||||||
|
|
||||||
|
def check_all_root_patterns_exist(patterns):
|
||||||
|
'''
|
||||||
|
Given a sequence of borgmatic.borg.pattern.Pattern instances, check that all root pattern
|
||||||
|
paths exist. If any don't, raise an exception.
|
||||||
|
'''
|
||||||
|
missing_paths = [
|
||||||
|
pattern.path
|
||||||
|
for pattern in patterns
|
||||||
|
if pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT
|
||||||
|
if not os.path.exists(pattern.path)
|
||||||
|
]
|
||||||
|
|
||||||
|
if missing_paths:
|
||||||
|
raise ValueError(
|
||||||
|
f"Source directories or root pattern paths do not exist: {', '.join(missing_paths)}"
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import borgmatic.borg.feature
|
|||||||
import borgmatic.config.paths
|
import borgmatic.config.paths
|
||||||
import borgmatic.execute
|
import borgmatic.execute
|
||||||
from borgmatic.borg import flags
|
from borgmatic.borg import flags
|
||||||
from borgmatic.borg.create import make_exclude_flags, make_list_filter_flags, write_patterns_file
|
from borgmatic.borg.pattern import write_patterns_file
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -29,9 +29,10 @@ def recreate_archive(
|
|||||||
arguments.
|
arguments.
|
||||||
'''
|
'''
|
||||||
lock_wait = config.get('lock_wait', None)
|
lock_wait = config.get('lock_wait', None)
|
||||||
exclude_flags = make_exclude_flags(config)
|
exclude_flags = flags.make_exclude_flags(config)
|
||||||
compression = config.get('compression', None)
|
compression = config.get('compression', None)
|
||||||
chunker_params = config.get('chunker_params', None)
|
chunker_params = config.get('chunker_params', None)
|
||||||
|
|
||||||
# Available recompress MODES: "if-different", "always", "never" (default)
|
# Available recompress MODES: "if-different", "always", "never" (default)
|
||||||
recompress = config.get('recompress', None)
|
recompress = config.get('recompress', None)
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ def recreate_archive(
|
|||||||
(
|
(
|
||||||
'--list',
|
'--list',
|
||||||
'--filter',
|
'--filter',
|
||||||
make_list_filter_flags(local_borg_version, global_arguments.dry_run),
|
flags.make_list_filter_flags(local_borg_version, global_arguments.dry_run),
|
||||||
)
|
)
|
||||||
if config.get('list_details')
|
if config.get('list_details')
|
||||||
else ()
|
else ()
|
||||||
|
|||||||
@@ -587,7 +587,7 @@ def make_parsers(schema, unparsed_arguments):
|
|||||||
'--no-environment-interpolation',
|
'--no-environment-interpolation',
|
||||||
dest='resolve_env',
|
dest='resolve_env',
|
||||||
action='store_false',
|
action='store_false',
|
||||||
help='Do not resolve environment variables in configuration file',
|
help='Do not resolve environment variables in configuration files',
|
||||||
)
|
)
|
||||||
global_group.add_argument(
|
global_group.add_argument(
|
||||||
'--bash-completion',
|
'--bash-completion',
|
||||||
|
|||||||
@@ -279,8 +279,19 @@ def run_configuration(config_filename, config, config_paths, arguments):
|
|||||||
encountered_error = error
|
encountered_error = error
|
||||||
error_repository = repository
|
error_repository = repository
|
||||||
|
|
||||||
|
# Re-raise any error, so that the Monitoring_hooks context manager wrapping this
|
||||||
|
# code can see the error and act accordingly. Do this here rather than as soon as
|
||||||
|
# the error is encountered so that an error with one repository doesn't prevent
|
||||||
|
# other repositories from running.
|
||||||
|
if encountered_error:
|
||||||
|
raise encountered_error
|
||||||
|
|
||||||
except (OSError, CalledProcessError, ValueError) as error:
|
except (OSError, CalledProcessError, ValueError) as error:
|
||||||
yield from log_error_records('Error running configuration', error)
|
# No need to repeat logging of the error if it was already logged above.
|
||||||
|
if error_repository:
|
||||||
|
yield from log_error_records('Error running configuration')
|
||||||
|
else:
|
||||||
|
yield from log_error_records('Error running configuration', error)
|
||||||
|
|
||||||
encountered_error = error
|
encountered_error = error
|
||||||
|
|
||||||
@@ -290,7 +301,10 @@ def run_configuration(config_filename, config, config_paths, arguments):
|
|||||||
try:
|
try:
|
||||||
command.execute_hooks(
|
command.execute_hooks(
|
||||||
command.filter_hooks(
|
command.filter_hooks(
|
||||||
config.get('commands'), after='error', action_names=arguments.keys()
|
config.get('commands'),
|
||||||
|
after='error',
|
||||||
|
action_names=arguments.keys(),
|
||||||
|
state_names=['fail'],
|
||||||
),
|
),
|
||||||
config.get('umask'),
|
config.get('umask'),
|
||||||
borgmatic.config.paths.get_working_directory(config),
|
borgmatic.config.paths.get_working_directory(config),
|
||||||
@@ -356,7 +370,7 @@ def run_actions(
|
|||||||
**hook_context,
|
**hook_context,
|
||||||
):
|
):
|
||||||
for action_name, action_arguments in arguments.items():
|
for action_name, action_arguments in arguments.items():
|
||||||
if action_name == 'global':
|
if action_name == 'global' or action_name in skip_actions:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
with borgmatic.hooks.command.Before_after_hooks(
|
with borgmatic.hooks.command.Before_after_hooks(
|
||||||
@@ -365,10 +379,10 @@ def run_actions(
|
|||||||
umask=config.get('umask'),
|
umask=config.get('umask'),
|
||||||
working_directory=borgmatic.config.paths.get_working_directory(config),
|
working_directory=borgmatic.config.paths.get_working_directory(config),
|
||||||
dry_run=global_arguments.dry_run,
|
dry_run=global_arguments.dry_run,
|
||||||
action_names=arguments.keys(),
|
action_names=(action_name,),
|
||||||
**hook_context,
|
**hook_context,
|
||||||
):
|
):
|
||||||
if action_name == 'repo-create' and action_name not in skip_actions:
|
if action_name == 'repo-create':
|
||||||
borgmatic.actions.repo_create.run_repo_create(
|
borgmatic.actions.repo_create.run_repo_create(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -378,7 +392,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'transfer' and action_name not in skip_actions:
|
elif action_name == 'transfer':
|
||||||
borgmatic.actions.transfer.run_transfer(
|
borgmatic.actions.transfer.run_transfer(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -388,7 +402,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'create' and action_name not in skip_actions:
|
elif action_name == 'create':
|
||||||
yield from borgmatic.actions.create.run_create(
|
yield from borgmatic.actions.create.run_create(
|
||||||
config_filename,
|
config_filename,
|
||||||
repository,
|
repository,
|
||||||
@@ -401,7 +415,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'recreate' and action_name not in skip_actions:
|
elif action_name == 'recreate':
|
||||||
borgmatic.actions.recreate.run_recreate(
|
borgmatic.actions.recreate.run_recreate(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -411,7 +425,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'prune' and action_name not in skip_actions:
|
elif action_name == 'prune':
|
||||||
borgmatic.actions.prune.run_prune(
|
borgmatic.actions.prune.run_prune(
|
||||||
config_filename,
|
config_filename,
|
||||||
repository,
|
repository,
|
||||||
@@ -423,7 +437,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'compact' and action_name not in skip_actions:
|
elif action_name == 'compact':
|
||||||
borgmatic.actions.compact.run_compact(
|
borgmatic.actions.compact.run_compact(
|
||||||
config_filename,
|
config_filename,
|
||||||
repository,
|
repository,
|
||||||
@@ -435,7 +449,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'check' and action_name not in skip_actions:
|
elif action_name == 'check':
|
||||||
if checks.repository_enabled_for_checks(repository, config):
|
if checks.repository_enabled_for_checks(repository, config):
|
||||||
borgmatic.actions.check.run_check(
|
borgmatic.actions.check.run_check(
|
||||||
config_filename,
|
config_filename,
|
||||||
@@ -447,7 +461,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'extract' and action_name not in skip_actions:
|
elif action_name == 'extract':
|
||||||
borgmatic.actions.extract.run_extract(
|
borgmatic.actions.extract.run_extract(
|
||||||
config_filename,
|
config_filename,
|
||||||
repository,
|
repository,
|
||||||
@@ -458,7 +472,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'export-tar' and action_name not in skip_actions:
|
elif action_name == 'export-tar':
|
||||||
borgmatic.actions.export_tar.run_export_tar(
|
borgmatic.actions.export_tar.run_export_tar(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -468,7 +482,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'mount' and action_name not in skip_actions:
|
elif action_name == 'mount':
|
||||||
borgmatic.actions.mount.run_mount(
|
borgmatic.actions.mount.run_mount(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -478,7 +492,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'restore' and action_name not in skip_actions:
|
elif action_name == 'restore':
|
||||||
borgmatic.actions.restore.run_restore(
|
borgmatic.actions.restore.run_restore(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -488,7 +502,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'repo-list' and action_name not in skip_actions:
|
elif action_name == 'repo-list':
|
||||||
yield from borgmatic.actions.repo_list.run_repo_list(
|
yield from borgmatic.actions.repo_list.run_repo_list(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -498,7 +512,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'list' and action_name not in skip_actions:
|
elif action_name == 'list':
|
||||||
yield from borgmatic.actions.list.run_list(
|
yield from borgmatic.actions.list.run_list(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -508,7 +522,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'repo-info' and action_name not in skip_actions:
|
elif action_name == 'repo-info':
|
||||||
yield from borgmatic.actions.repo_info.run_repo_info(
|
yield from borgmatic.actions.repo_info.run_repo_info(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -518,7 +532,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'info' and action_name not in skip_actions:
|
elif action_name == 'info':
|
||||||
yield from borgmatic.actions.info.run_info(
|
yield from borgmatic.actions.info.run_info(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -528,7 +542,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'break-lock' and action_name not in skip_actions:
|
elif action_name == 'break-lock':
|
||||||
borgmatic.actions.break_lock.run_break_lock(
|
borgmatic.actions.break_lock.run_break_lock(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -538,7 +552,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'export' and action_name not in skip_actions:
|
elif action_name == 'export':
|
||||||
borgmatic.actions.export_key.run_export_key(
|
borgmatic.actions.export_key.run_export_key(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -548,7 +562,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'import' and action_name not in skip_actions:
|
elif action_name == 'import':
|
||||||
borgmatic.actions.import_key.run_import_key(
|
borgmatic.actions.import_key.run_import_key(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -558,7 +572,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'change-passphrase' and action_name not in skip_actions:
|
elif action_name == 'change-passphrase':
|
||||||
borgmatic.actions.change_passphrase.run_change_passphrase(
|
borgmatic.actions.change_passphrase.run_change_passphrase(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -568,7 +582,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'delete' and action_name not in skip_actions:
|
elif action_name == 'delete':
|
||||||
borgmatic.actions.delete.run_delete(
|
borgmatic.actions.delete.run_delete(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -578,7 +592,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'repo-delete' and action_name not in skip_actions:
|
elif action_name == 'repo-delete':
|
||||||
borgmatic.actions.repo_delete.run_repo_delete(
|
borgmatic.actions.repo_delete.run_repo_delete(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -588,7 +602,7 @@ def run_actions(
|
|||||||
local_path,
|
local_path,
|
||||||
remote_path,
|
remote_path,
|
||||||
)
|
)
|
||||||
elif action_name == 'borg' and action_name not in skip_actions:
|
elif action_name == 'borg':
|
||||||
borgmatic.actions.borg.run_borg(
|
borgmatic.actions.borg.run_borg(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
@@ -896,6 +910,7 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
|||||||
|
|
||||||
# Execute the actions corresponding to each configuration file.
|
# Execute the actions corresponding to each configuration file.
|
||||||
json_results = []
|
json_results = []
|
||||||
|
encountered_error = False
|
||||||
|
|
||||||
for config_filename, config in configs.items():
|
for config_filename, config in configs.items():
|
||||||
with Log_prefix(config_filename):
|
with Log_prefix(config_filename):
|
||||||
@@ -906,6 +921,7 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
|||||||
)
|
)
|
||||||
|
|
||||||
if error_logs:
|
if error_logs:
|
||||||
|
encountered_error = True
|
||||||
yield from log_error_records('An error occurred')
|
yield from log_error_records('An error occurred')
|
||||||
yield from error_logs
|
yield from error_logs
|
||||||
else:
|
else:
|
||||||
@@ -928,6 +944,7 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
|||||||
local_path=get_local_path(configs),
|
local_path=get_local_path(configs),
|
||||||
)
|
)
|
||||||
except (CalledProcessError, OSError) as error:
|
except (CalledProcessError, OSError) as error:
|
||||||
|
encountered_error = True
|
||||||
yield from log_error_records('Error unmounting mount point', error)
|
yield from log_error_records('Error unmounting mount point', error)
|
||||||
|
|
||||||
if json_results:
|
if json_results:
|
||||||
@@ -937,7 +954,10 @@ def collect_configuration_run_summary_logs(configs, config_paths, arguments, log
|
|||||||
for config_filename, config in configs.items():
|
for config_filename, config in configs.items():
|
||||||
command.execute_hooks(
|
command.execute_hooks(
|
||||||
command.filter_hooks(
|
command.filter_hooks(
|
||||||
config.get('commands'), after='everything', action_names=arguments.keys()
|
config.get('commands'),
|
||||||
|
after='everything',
|
||||||
|
action_names=arguments.keys(),
|
||||||
|
state_names=['fail' if encountered_error else 'finish'],
|
||||||
),
|
),
|
||||||
config.get('umask'),
|
config.get('umask'),
|
||||||
borgmatic.config.paths.get_working_directory(config),
|
borgmatic.config.paths.get_working_directory(config),
|
||||||
@@ -1040,7 +1060,7 @@ def main(extra_summary_logs=[]): # pragma: no cover
|
|||||||
config_filenames,
|
config_filenames,
|
||||||
arguments,
|
arguments,
|
||||||
global_arguments.overrides,
|
global_arguments.overrides,
|
||||||
resolve_env=global_arguments.resolve_env and not validate,
|
resolve_env=global_arguments.resolve_env and not arguments.get('validate'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use the helper function to check and show help on no arguments, passing the preloaded configs
|
# Use the helper function to check and show help on no arguments, passing the preloaded configs
|
||||||
|
|||||||
@@ -1225,6 +1225,26 @@ properties:
|
|||||||
particular actions listed here. Defaults to
|
particular actions listed here. Defaults to
|
||||||
running for all actions.
|
running for all actions.
|
||||||
example: [create, prune, compact, check]
|
example: [create, prune, compact, check]
|
||||||
|
states:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- finish
|
||||||
|
- fail
|
||||||
|
description: |
|
||||||
|
Only trigger the hook if borgmatic encounters one
|
||||||
|
of the states (execution results) listed here,
|
||||||
|
where:
|
||||||
|
* "finish": No errors occurred.
|
||||||
|
* "fail": An error occurred.
|
||||||
|
This state is evaluated only for the scope of the
|
||||||
|
configured "action", "repository", etc., rather
|
||||||
|
than for the entire borgmatic run. Only available
|
||||||
|
for "after" hooks. Defaults to running the hook
|
||||||
|
for all states.
|
||||||
|
example:
|
||||||
|
- finish
|
||||||
run:
|
run:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|||||||
+25
-16
@@ -1,3 +1,4 @@
|
|||||||
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -46,21 +47,25 @@ def make_environment(current_environment, sys_module=sys):
|
|||||||
return environment
|
return environment
|
||||||
|
|
||||||
|
|
||||||
def filter_hooks(command_hooks, before=None, after=None, hook_name=None, action_names=None):
|
def filter_hooks(command_hooks, before=None, after=None, action_names=None, state_names=None):
|
||||||
'''
|
'''
|
||||||
Given a sequence of command hook dicts from configuration and one or more filters (before name,
|
Given a sequence of command hook dicts from configuration and one or more filters (before name,
|
||||||
after name, calling hook name, or a sequence of action names), filter down the command hooks to
|
after name, a sequence of action names, and/or a sequence of execution result state names),
|
||||||
just the ones that match the given filters.
|
filter down the command hooks to just the ones that match the given filters.
|
||||||
'''
|
'''
|
||||||
return tuple(
|
return tuple(
|
||||||
hook_config
|
hook_config
|
||||||
for hook_config in command_hooks or ()
|
for hook_config in command_hooks or ()
|
||||||
for config_action_names in (hook_config.get('when'),)
|
for config_action_names in (hook_config.get('when'),)
|
||||||
|
for config_state_names in (hook_config.get('states'),)
|
||||||
if before is None or hook_config.get('before') == before
|
if before is None or hook_config.get('before') == before
|
||||||
if after is None or hook_config.get('after') == after
|
if after is None or hook_config.get('after') == after
|
||||||
if action_names is None
|
if action_names is None
|
||||||
or config_action_names is None
|
or config_action_names is None
|
||||||
or set(config_action_names or ()).intersection(set(action_names))
|
or set(config_action_names or ()).intersection(set(action_names))
|
||||||
|
if state_names is None
|
||||||
|
or config_state_names is None
|
||||||
|
or set(config_state_names or ()).intersection(set(state_names))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -81,11 +86,14 @@ def execute_hooks(command_hooks, umask, working_directory, dry_run, **context):
|
|||||||
|
|
||||||
for hook_config in command_hooks:
|
for hook_config in command_hooks:
|
||||||
commands = hook_config.get('run')
|
commands = hook_config.get('run')
|
||||||
|
when_description = (
|
||||||
|
f"{'/'.join(hook_config.get('when'))} " if hook_config.get('when') else ''
|
||||||
|
)
|
||||||
|
|
||||||
if 'before' in hook_config:
|
if 'before' in hook_config:
|
||||||
description = f'before {hook_config.get("before")}'
|
description = f'before {when_description}{hook_config.get("before")}'
|
||||||
elif 'after' in hook_config:
|
elif 'after' in hook_config:
|
||||||
description = f'after {hook_config.get("after")}'
|
description = f'after {when_description}{hook_config.get("after")}'
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Invalid hook configuration: {hook_config}')
|
raise ValueError(f'Invalid hook configuration: {hook_config}')
|
||||||
|
|
||||||
@@ -139,7 +147,7 @@ class Before_after_hooks:
|
|||||||
before_after='do_stuff',
|
before_after='do_stuff',
|
||||||
umask=config.get('umask'),
|
umask=config.get('umask'),
|
||||||
dry_run=dry_run,
|
dry_run=dry_run,
|
||||||
hook_name='myhook',
|
action_names=['create'],
|
||||||
):
|
):
|
||||||
do()
|
do()
|
||||||
some()
|
some()
|
||||||
@@ -156,22 +164,20 @@ class Before_after_hooks:
|
|||||||
umask,
|
umask,
|
||||||
working_directory,
|
working_directory,
|
||||||
dry_run,
|
dry_run,
|
||||||
hook_name=None,
|
|
||||||
action_names=None,
|
action_names=None,
|
||||||
**context,
|
**context,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Given a sequence of command hook configuration dicts, the before/after name, a umask to run
|
Given a sequence of command hook configuration dicts, the before/after name, a umask to run
|
||||||
commands with, a working directory to run commands with, a dry run flag, the name of the
|
commands with, a working directory to run commands with, a dry run flag, a sequence of
|
||||||
calling hook, a sequence of action names, and any context for the executed commands, save
|
action names, and any context for the executed commands, save those data points for use
|
||||||
those data points for use below.
|
below.
|
||||||
'''
|
'''
|
||||||
self.command_hooks = command_hooks
|
self.command_hooks = command_hooks
|
||||||
self.before_after = before_after
|
self.before_after = before_after
|
||||||
self.umask = umask
|
self.umask = umask
|
||||||
self.working_directory = working_directory
|
self.working_directory = working_directory
|
||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
self.hook_name = hook_name
|
|
||||||
self.action_names = action_names
|
self.action_names = action_names
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
@@ -184,7 +190,6 @@ class Before_after_hooks:
|
|||||||
borgmatic.hooks.command.filter_hooks(
|
borgmatic.hooks.command.filter_hooks(
|
||||||
self.command_hooks,
|
self.command_hooks,
|
||||||
before=self.before_after,
|
before=self.before_after,
|
||||||
hook_name=self.hook_name,
|
|
||||||
action_names=self.action_names,
|
action_names=self.action_names,
|
||||||
),
|
),
|
||||||
self.umask,
|
self.umask,
|
||||||
@@ -194,11 +199,11 @@ class Before_after_hooks:
|
|||||||
)
|
)
|
||||||
except (OSError, subprocess.CalledProcessError) as error:
|
except (OSError, subprocess.CalledProcessError) as error:
|
||||||
if considered_soft_failure(error):
|
if considered_soft_failure(error):
|
||||||
return
|
raise
|
||||||
|
|
||||||
# Trigger the after hook manually, since raising here will prevent it from being run
|
# Trigger the after hook manually, since raising here will prevent it from being run
|
||||||
# otherwise.
|
# otherwise.
|
||||||
self.__exit__(None, None, None)
|
self.__exit__(exception_type=type(error), exception=error, traceback=None)
|
||||||
|
|
||||||
raise ValueError(f'Error running before {self.before_after} hook: {error}')
|
raise ValueError(f'Error running before {self.before_after} hook: {error}')
|
||||||
|
|
||||||
@@ -211,8 +216,8 @@ class Before_after_hooks:
|
|||||||
borgmatic.hooks.command.filter_hooks(
|
borgmatic.hooks.command.filter_hooks(
|
||||||
self.command_hooks,
|
self.command_hooks,
|
||||||
after=self.before_after,
|
after=self.before_after,
|
||||||
hook_name=self.hook_name,
|
|
||||||
action_names=self.action_names,
|
action_names=self.action_names,
|
||||||
|
state_names=['fail' if exception_type else 'finish'],
|
||||||
),
|
),
|
||||||
self.umask,
|
self.umask,
|
||||||
self.working_directory,
|
self.working_directory,
|
||||||
@@ -221,16 +226,20 @@ class Before_after_hooks:
|
|||||||
)
|
)
|
||||||
except (OSError, subprocess.CalledProcessError) as error:
|
except (OSError, subprocess.CalledProcessError) as error:
|
||||||
if considered_soft_failure(error):
|
if considered_soft_failure(error):
|
||||||
return
|
raise
|
||||||
|
|
||||||
raise ValueError(f'Error running after {self.before_after} hook: {error}')
|
raise ValueError(f'Error running after {self.before_after} hook: {error}')
|
||||||
|
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
def considered_soft_failure(error):
|
def considered_soft_failure(error):
|
||||||
'''
|
'''
|
||||||
Given a configuration filename and an exception object, return whether the exception object
|
Given a configuration filename and an exception object, return whether the exception object
|
||||||
represents a subprocess.CalledProcessError with a return code of SOFT_FAIL_EXIT_CODE. If so,
|
represents a subprocess.CalledProcessError with a return code of SOFT_FAIL_EXIT_CODE. If so,
|
||||||
that indicates that the error is a "soft failure", and should not result in an error.
|
that indicates that the error is a "soft failure", and should not result in an error.
|
||||||
|
|
||||||
|
The results of this function are cached so that it can be called multiple times without logging
|
||||||
|
multiple times.
|
||||||
'''
|
'''
|
||||||
exit_code = getattr(error, 'returncode', None)
|
exit_code = getattr(error, 'returncode', None)
|
||||||
if exit_code is None:
|
if exit_code is None:
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
shutil.rmtree(snapshots_directory)
|
shutil.rmtree(snapshots_directory, ignore_errors=True)
|
||||||
|
|
||||||
# Delete snapshots.
|
# Delete snapshots.
|
||||||
lvremove_command = hook_config.get('lvremove_command', 'lvremove')
|
lvremove_command = hook_config.get('lvremove_command', 'lvremove')
|
||||||
|
|||||||
@@ -55,6 +55,29 @@ commands:
|
|||||||
- "echo Backup: start"
|
- "echo Backup: start"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
By default, an `after` command hook runs even if an error occurs in the
|
||||||
|
corresponding `before` hook or between those two hooks. This allows you to
|
||||||
|
perform cleanup steps that correspond to `before` preparation commands—even when
|
||||||
|
something goes wrong. You may notice that this is a departure from the way that
|
||||||
|
the deprecated `after_*` hooks worked in borgmatic prior to version 2.0.0.
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">New in version 2.0.3</span> You can
|
||||||
|
customize this behavior with the `states` option. For instance, here's an
|
||||||
|
example of an `after` hook that only triggers on success and not on error:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
commands:
|
||||||
|
- after: action
|
||||||
|
when: [create]
|
||||||
|
states: [finish]
|
||||||
|
run:
|
||||||
|
- echo "After successful create!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, when command hooks run, they respect the `working_directory`
|
||||||
|
option if it is configured, meaning that the hook commands are run in that
|
||||||
|
directory.
|
||||||
|
|
||||||
Each command in the `commands:` list has the following options:
|
Each command in the `commands:` list has the following options:
|
||||||
|
|
||||||
* `before` or `after`: Name for the point in borgmatic's execution that the commands should be run before or after, one of:
|
* `before` or `after`: Name for the point in borgmatic's execution that the commands should be run before or after, one of:
|
||||||
@@ -64,18 +87,11 @@ Each command in the `commands:` list has the following options:
|
|||||||
* `everything` runs before or after all configuration files. Errors here do not trigger `error` hooks or the `fail` state in monitoring hooks. This replaces the deprecated `before_everything` and `after_everything`.
|
* `everything` runs before or after all configuration files. Errors here do not trigger `error` hooks or the `fail` state in monitoring hooks. This replaces the deprecated `before_everything` and `after_everything`.
|
||||||
* `error` runs after an error occurs—and it's only available for `after`. This replaces the deprecated `on_error` hook.
|
* `error` runs after an error occurs—and it's only available for `after`. This replaces the deprecated `on_error` hook.
|
||||||
* `when`: Only trigger the hook when borgmatic is run with particular actions (`create`, `prune`, etc.) listed here. Defaults to running for all actions.
|
* `when`: Only trigger the hook when borgmatic is run with particular actions (`create`, `prune`, etc.) listed here. Defaults to running for all actions.
|
||||||
|
* `states`: <span class="minilink minilink-addedin">New in version 2.0.3</span> Only trigger the hook if borgmatic encounters one of the states (execution results) listed here. This state is evaluated only for the scope of the configured `action`, `repository`, etc., rather than for the entire borgmatic run. Only available for `after` hooks. Defaults to running the hook for all states. One or more of:
|
||||||
|
* `finish`: No errors occurred.
|
||||||
|
* `fail`: An error occurred.
|
||||||
* `run`: List of one or more shell commands or scripts to run when this command hook is triggered.
|
* `run`: List of one or more shell commands or scripts to run when this command hook is triggered.
|
||||||
|
|
||||||
An `after` command hook runs even if an error occurs in the corresponding
|
|
||||||
`before` hook or between those two hooks. This allows you to perform cleanup
|
|
||||||
steps that correspond to `before` preparation commands—even when something goes
|
|
||||||
wrong. This is a departure from the way that the deprecated `after_*` hooks
|
|
||||||
worked in borgmatic prior to version 2.0.0.
|
|
||||||
|
|
||||||
Additionally, when command hooks run, they respect the `working_directory`
|
|
||||||
option if it is configured, meaning that the hook commands are run in that
|
|
||||||
directory.
|
|
||||||
|
|
||||||
|
|
||||||
### Order of execution
|
### Order of execution
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,27 @@ you can use the standard
|
|||||||
extract them.
|
extract them.
|
||||||
|
|
||||||
|
|
||||||
|
#### ZFS performance
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">With Borg version 1.x</span> Because of
|
||||||
|
the way that ZFS snapshot paths can change from one borgmatic invocation to the
|
||||||
|
next, the [Borg file
|
||||||
|
cache](https://borgbackup.readthedocs.io/en/stable/internals/data-structures.html#cache)
|
||||||
|
may not get cache hits on snapshotted files. This makes backing up ZFS snapshots
|
||||||
|
a little slower than non-snapshotted files that have consistent paths. You can
|
||||||
|
mitigate this by setting a fixed [runtime
|
||||||
|
directory](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/#runtime-directory)
|
||||||
|
(that's not located in `/tmp`). This allows borgmatic to use a consistent
|
||||||
|
snapshot path from one run to the next, thereby resulting in Borg files cache
|
||||||
|
hits.
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">With Borg version 2.x</span> Snapshotted
|
||||||
|
files should get cache hits regardless of whether their paths change, because
|
||||||
|
Borg 2.x is smarter about how it looks up file paths in its cache—it constructs
|
||||||
|
the cache key with the path *as it's seen in the archive* (which is consistent
|
||||||
|
across runs) rather than the full absolute source path (which can change).
|
||||||
|
|
||||||
|
|
||||||
### Btrfs
|
### Btrfs
|
||||||
|
|
||||||
<span class="minilink minilink-addedin">New in version 1.9.4</span> <span
|
<span class="minilink minilink-addedin">New in version 1.9.4</span> <span
|
||||||
@@ -197,6 +218,22 @@ action](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/) to extract
|
|||||||
them.
|
them.
|
||||||
|
|
||||||
|
|
||||||
|
#### Btrfs performance
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">With Borg version 1.x</span> Because of
|
||||||
|
the way that Btrfs snapshot paths change from one borgmatic invocation to the
|
||||||
|
next, the [Borg file
|
||||||
|
cache](https://borgbackup.readthedocs.io/en/stable/internals/data-structures.html#cache)
|
||||||
|
will never get cache hits on snapshotted files. This makes backing up Btrfs
|
||||||
|
snapshots a little slower than non-snapshotted files that have consistent paths.
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">With Borg version 2.x</span> Even
|
||||||
|
snapshotted files should get cache hits, because Borg 2.x is smarter about how
|
||||||
|
it looks up file paths in its cache—it constructs the cache key with the path
|
||||||
|
*as it's seen in the archive* (which is consistent across runs) rather than the
|
||||||
|
full absolute source path (which changes).
|
||||||
|
|
||||||
|
|
||||||
### LVM
|
### LVM
|
||||||
|
|
||||||
<span class="minilink minilink-addedin">New in version 1.9.4</span> <span
|
<span class="minilink minilink-addedin">New in version 1.9.4</span> <span
|
||||||
@@ -311,3 +348,24 @@ Logical volume snapshots are stored in a Borg archive as normal files, so
|
|||||||
you can use the standard
|
you can use the standard
|
||||||
[extract action](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/) to
|
[extract action](https://torsion.org/borgmatic/docs/how-to/extract-a-backup/) to
|
||||||
extract them.
|
extract them.
|
||||||
|
|
||||||
|
|
||||||
|
#### LVM performance
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">With Borg version 1.x</span> Because of
|
||||||
|
the way that LVM snapshot paths can change from one borgmatic invocation to the
|
||||||
|
next, the [Borg file
|
||||||
|
cache](https://borgbackup.readthedocs.io/en/stable/internals/data-structures.html#cache)
|
||||||
|
may not get cache hits on snapshotted files. This makes backing up LVM snapshots
|
||||||
|
a little slower than non-snapshotted files that have consistent paths. You can
|
||||||
|
mitigate this by setting a fixed [runtime
|
||||||
|
directory](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/#runtime-directory)
|
||||||
|
(that's not located in `/tmp`). This allows borgmatic to use a consistent
|
||||||
|
snapshot path from one run to the next, thereby resulting in Borg files cache
|
||||||
|
hits.
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">With Borg version 2.x</span> Snapshotted
|
||||||
|
files should get cache hits regardless of whether their paths change, because
|
||||||
|
Borg 2.x is smarter about how it looks up file paths in its cache—it constructs
|
||||||
|
the cache key with the path *as it's seen in the archive* (which is consistent
|
||||||
|
across runs) rather than the full absolute source path (which can change).
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "borgmatic"
|
name = "borgmatic"
|
||||||
version = "2.0.1"
|
version = "2.0.3"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Dan Helfman", email="witten@torsion.org" },
|
{ name="Dan Helfman", email="witten@torsion.org" },
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ twine upload -r pypi --username __token__ dist/borgmatic-*-py3-none-any.whl
|
|||||||
|
|
||||||
# Set release changelogs on projects.torsion.org and GitHub.
|
# Set release changelogs on projects.torsion.org and GitHub.
|
||||||
release_changelog="$(cat NEWS | sed '/^$/q' | grep -v '^\S')"
|
release_changelog="$(cat NEWS | sed '/^$/q' | grep -v '^\S')"
|
||||||
escaped_release_changelog="$(echo "$release_changelog" | sed -z 's/\n/\\n/g' | sed -z 's/\"/\\"/g' | sed -z 's/\*/\\*/g')"
|
escaped_release_changelog="$(echo "$release_changelog" | sed -z 's/\n/\\n/g' | sed -z 's/\"/\\"/g')"
|
||||||
curl --silent --request POST \
|
curl --silent --request POST \
|
||||||
"https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/releases" \
|
"https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/releases" \
|
||||||
--header "Authorization: token $projects_token" \
|
--header "Authorization: token $projects_token" \
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
def generate_configuration(config_path, repository_path):
|
||||||
|
'''
|
||||||
|
Generate borgmatic configuration into a file at the config path, and update the defaults so as
|
||||||
|
to work for testing, including updating the source directories, injecting the given repository
|
||||||
|
path, and tacking on an encryption passphrase environment variable (which will get passed in
|
||||||
|
when the Borg command is called below).
|
||||||
|
'''
|
||||||
|
subprocess.check_call(f'borgmatic config generate --destination {config_path}'.split(' '))
|
||||||
|
config = (
|
||||||
|
open(config_path)
|
||||||
|
.read()
|
||||||
|
.replace('ssh://user@backupserver/./sourcehostname.borg', repository_path)
|
||||||
|
.replace('- path: /mnt/backup', '')
|
||||||
|
.replace('label: local', '')
|
||||||
|
.replace('- /home/user/path with spaces', '')
|
||||||
|
.replace('- /home', f'- {config_path}')
|
||||||
|
.replace('- /etc', '')
|
||||||
|
.replace('- /var/log/syslog*', '')
|
||||||
|
+ '\nencryption_passphrase: "${PASSPHRASE}"'
|
||||||
|
# Disable automatic storage of config files so we can test storage and extraction manually.
|
||||||
|
+ '\nbootstrap:\n store_config_files: false'
|
||||||
|
)
|
||||||
|
config_file = open(config_path, 'w')
|
||||||
|
config_file.write(config)
|
||||||
|
config_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_borgmatic_command():
|
||||||
|
# Create a Borg repository.
|
||||||
|
temporary_directory = tempfile.mkdtemp()
|
||||||
|
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||||
|
extract_path = os.path.join(temporary_directory, 'extract')
|
||||||
|
|
||||||
|
original_working_directory = os.getcwd()
|
||||||
|
os.mkdir(extract_path)
|
||||||
|
os.chdir(extract_path)
|
||||||
|
environment = dict(os.environ, **{'PASSPHRASE': 'test'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||||
|
generate_configuration(config_path, repository_path)
|
||||||
|
|
||||||
|
subprocess.check_call(
|
||||||
|
f'borgmatic -v 2 --config {config_path} repo-create --encryption repokey'.split(' '),
|
||||||
|
env=environment,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run borgmatic to generate a backup archive, and then list it to make sure it exists.
|
||||||
|
subprocess.check_call(
|
||||||
|
f'borgmatic --config {config_path}'.split(' '),
|
||||||
|
env=environment,
|
||||||
|
)
|
||||||
|
output = subprocess.check_output(
|
||||||
|
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||||
|
env=environment,
|
||||||
|
).decode(sys.stdout.encoding)
|
||||||
|
parsed_output = json.loads(output)
|
||||||
|
|
||||||
|
assert len(parsed_output) == 1
|
||||||
|
assert len(parsed_output[0]['archives']) == 1
|
||||||
|
finally:
|
||||||
|
os.chdir(original_working_directory)
|
||||||
|
shutil.rmtree(temporary_directory)
|
||||||
@@ -12,3 +12,59 @@ def test_borgmatic_version_matches_news_version():
|
|||||||
news_version = open('NEWS').readline()
|
news_version = open('NEWS').readline()
|
||||||
|
|
||||||
assert borgmatic_version == news_version
|
assert borgmatic_version == news_version
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_configuration_without_error_pings_monitoring_hooks_start_and_finish():
|
||||||
|
config = {'repositories': [{'path': 'foo'}]}
|
||||||
|
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
|
||||||
|
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||||
|
flexmock(module).should_receive('run_actions').and_return([])
|
||||||
|
flexmock(module.dispatch).should_receive('call_hooks')
|
||||||
|
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||||
|
'ping_monitor',
|
||||||
|
config,
|
||||||
|
module.dispatch.Hook_type.MONITORING,
|
||||||
|
'test.yaml',
|
||||||
|
module.monitor.State.START,
|
||||||
|
object,
|
||||||
|
object,
|
||||||
|
).once()
|
||||||
|
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||||
|
'ping_monitor',
|
||||||
|
config,
|
||||||
|
module.dispatch.Hook_type.MONITORING,
|
||||||
|
'test.yaml',
|
||||||
|
module.monitor.State.FINISH,
|
||||||
|
object,
|
||||||
|
object,
|
||||||
|
).once()
|
||||||
|
|
||||||
|
list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_configuration_with_action_error_pings_monioring_hooks_start_and_fail():
|
||||||
|
config = {'repositories': [{'path': 'foo'}]}
|
||||||
|
arguments = {'global': flexmock(monitoring_verbosity=1, dry_run=False), 'create': flexmock()}
|
||||||
|
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||||
|
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||||
|
flexmock(module.dispatch).should_receive('call_hooks')
|
||||||
|
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||||
|
'ping_monitor',
|
||||||
|
config,
|
||||||
|
module.dispatch.Hook_type.MONITORING,
|
||||||
|
'test.yaml',
|
||||||
|
module.monitor.State.START,
|
||||||
|
object,
|
||||||
|
object,
|
||||||
|
).once()
|
||||||
|
flexmock(module.dispatch).should_receive('call_hooks').with_args(
|
||||||
|
'ping_monitor',
|
||||||
|
config,
|
||||||
|
module.dispatch.Hook_type.MONITORING,
|
||||||
|
'test.yaml',
|
||||||
|
module.monitor.State.FAIL,
|
||||||
|
object,
|
||||||
|
object,
|
||||||
|
).once()
|
||||||
|
|
||||||
|
list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
|
||||||
|
|||||||
@@ -20,21 +20,28 @@ ACTIONS_MODULE_NAMES_TO_OMIT = {
|
|||||||
'export_key',
|
'export_key',
|
||||||
'import_key',
|
'import_key',
|
||||||
'json',
|
'json',
|
||||||
|
'pattern',
|
||||||
}
|
}
|
||||||
ACTIONS_MODULE_NAMES_TO_ADD = {'key', 'umount'}
|
ACTIONS_MODULE_NAMES_TO_ADD = {'key', 'umount'}
|
||||||
|
|
||||||
|
|
||||||
def test_schema_skip_actions_correspond_to_supported_actions():
|
def test_schema_actions_correspond_to_supported_actions():
|
||||||
'''
|
'''
|
||||||
Ensure that the allowed actions in the schema's "skip_actions" option don't drift from
|
Ensure that the allowed actions in the schema's various options don't drift from borgmatic's
|
||||||
borgmatic's actual supported actions.
|
actual supported actions.
|
||||||
'''
|
'''
|
||||||
schema = borgmatic.config.load.load_configuration(borgmatic.config.validate.schema_filename())
|
schema = borgmatic.config.load.load_configuration(borgmatic.config.validate.schema_filename())
|
||||||
schema_skip_actions = set(schema['properties']['skip_actions']['items']['enum'])
|
|
||||||
supported_actions = {
|
supported_actions = {
|
||||||
module.name.replace('_', '-')
|
module.name.replace('_', '-')
|
||||||
for module in pkgutil.iter_modules(borgmatic.actions.__path__)
|
for module in pkgutil.iter_modules(borgmatic.actions.__path__)
|
||||||
if module.name not in ACTIONS_MODULE_NAMES_TO_OMIT
|
if module.name not in ACTIONS_MODULE_NAMES_TO_OMIT
|
||||||
}.union(ACTIONS_MODULE_NAMES_TO_ADD)
|
}.union(ACTIONS_MODULE_NAMES_TO_ADD)
|
||||||
|
properties = schema['properties']
|
||||||
|
commands_one_of = properties['commands']['items']['oneOf']
|
||||||
|
|
||||||
assert schema_skip_actions == supported_actions
|
for schema_actions in (
|
||||||
|
set(properties['skip_actions']['items']['enum']),
|
||||||
|
set(commands_one_of[0]['properties']['when']['items']['enum']),
|
||||||
|
set(commands_one_of[1]['properties']['when']['items']['enum']),
|
||||||
|
):
|
||||||
|
assert schema_actions == supported_actions
|
||||||
|
|||||||
@@ -561,10 +561,10 @@ def test_collect_spot_check_source_paths_parses_borg_output():
|
|||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('collect_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('process_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return(
|
||||||
[Pattern('foo'), Pattern('bar')]
|
[Pattern('foo'), Pattern('bar')]
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
||||||
@@ -608,10 +608,10 @@ def test_collect_spot_check_source_paths_passes_through_stream_processes_false()
|
|||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('collect_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('process_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return(
|
||||||
[Pattern('foo'), Pattern('bar')]
|
[Pattern('foo'), Pattern('bar')]
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
||||||
@@ -655,10 +655,10 @@ def test_collect_spot_check_source_paths_without_working_directory_parses_borg_o
|
|||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('collect_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('process_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return(
|
||||||
[Pattern('foo'), Pattern('bar')]
|
[Pattern('foo'), Pattern('bar')]
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
||||||
@@ -702,10 +702,10 @@ def test_collect_spot_check_source_paths_skips_directories():
|
|||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('collect_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('process_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return(
|
||||||
[Pattern('foo'), Pattern('bar')]
|
[Pattern('foo'), Pattern('bar')]
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
||||||
@@ -847,10 +847,10 @@ def test_collect_spot_check_source_paths_uses_working_directory():
|
|||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('collect_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(
|
||||||
flexmock()
|
flexmock()
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.actions.create).should_receive('process_patterns').and_return(
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return(
|
||||||
[Pattern('foo'), Pattern('bar')]
|
[Pattern('foo'), Pattern('bar')]
|
||||||
)
|
)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
flexmock(module.borgmatic.borg.create).should_receive('make_base_create_command').with_args(
|
||||||
|
|||||||
@@ -1,429 +1,9 @@
|
|||||||
import io
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flexmock import flexmock
|
from flexmock import flexmock
|
||||||
|
|
||||||
from borgmatic.actions import create as module
|
from borgmatic.actions import create as module
|
||||||
from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'pattern_line,expected_pattern',
|
|
||||||
(
|
|
||||||
('R /foo', Pattern('/foo', source=Pattern_source.CONFIG)),
|
|
||||||
('P sh', Pattern('sh', Pattern_type.PATTERN_STYLE, source=Pattern_source.CONFIG)),
|
|
||||||
('+ /foo*', Pattern('/foo*', Pattern_type.INCLUDE, source=Pattern_source.CONFIG)),
|
|
||||||
(
|
|
||||||
'+ sh:/foo*',
|
|
||||||
Pattern(
|
|
||||||
'/foo*', Pattern_type.INCLUDE, Pattern_style.SHELL, source=Pattern_source.CONFIG
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_parse_pattern_transforms_pattern_line_to_instance(pattern_line, expected_pattern):
|
|
||||||
module.parse_pattern(pattern_line) == expected_pattern
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_pattern_with_invalid_pattern_line_errors():
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.parse_pattern('/foo')
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_patterns_converts_source_directories():
|
|
||||||
assert module.collect_patterns({'source_directories': ['/foo', '/bar']}) == (
|
|
||||||
Pattern('/foo', source=Pattern_source.CONFIG),
|
|
||||||
Pattern('/bar', source=Pattern_source.CONFIG),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_patterns_parses_config_patterns():
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('R /foo').and_return(Pattern('/foo'))
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args(' ').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('R /bar').and_return(Pattern('/bar'))
|
|
||||||
|
|
||||||
assert module.collect_patterns({'patterns': ['R /foo', '# comment', '', ' ', 'R /bar']}) == (
|
|
||||||
Pattern('/foo'),
|
|
||||||
Pattern('/bar'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_patterns_converts_exclude_patterns():
|
|
||||||
assert module.collect_patterns({'exclude_patterns': ['/foo', '/bar', 'sh:**/baz']}) == (
|
|
||||||
Pattern(
|
|
||||||
'/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, source=Pattern_source.CONFIG
|
|
||||||
),
|
|
||||||
Pattern(
|
|
||||||
'/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, source=Pattern_source.CONFIG
|
|
||||||
),
|
|
||||||
Pattern(
|
|
||||||
'**/baz', Pattern_type.NO_RECURSE, Pattern_style.SHELL, source=Pattern_source.CONFIG
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_patterns_reads_config_patterns_from_file():
|
|
||||||
builtins = flexmock(sys.modules['builtins'])
|
|
||||||
builtins.should_receive('open').with_args('file1.txt').and_return(io.StringIO('R /foo'))
|
|
||||||
builtins.should_receive('open').with_args('file2.txt').and_return(
|
|
||||||
io.StringIO('R /bar\n# comment\n\n \nR /baz')
|
|
||||||
)
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('R /foo').and_return(Pattern('/foo'))
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args(' ').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('R /bar').and_return(Pattern('/bar'))
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('R /baz').and_return(Pattern('/baz'))
|
|
||||||
|
|
||||||
assert module.collect_patterns({'patterns_from': ['file1.txt', 'file2.txt']}) == (
|
|
||||||
Pattern('/foo'),
|
|
||||||
Pattern('/bar'),
|
|
||||||
Pattern('/baz'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_patterns_errors_on_missing_config_patterns_from_file():
|
|
||||||
builtins = flexmock(sys.modules['builtins'])
|
|
||||||
builtins.should_receive('open').with_args('file1.txt').and_raise(FileNotFoundError)
|
|
||||||
flexmock(module).should_receive('parse_pattern').never()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.collect_patterns({'patterns_from': ['file1.txt', 'file2.txt']})
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_patterns_reads_config_exclude_from_file():
|
|
||||||
builtins = flexmock(sys.modules['builtins'])
|
|
||||||
builtins.should_receive('open').with_args('file1.txt').and_return(io.StringIO('/foo'))
|
|
||||||
builtins.should_receive('open').with_args('file2.txt').and_return(
|
|
||||||
io.StringIO('/bar\n# comment\n\n \n/baz')
|
|
||||||
)
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args(
|
|
||||||
'! /foo', default_style=Pattern_style.FNMATCH
|
|
||||||
).and_return(Pattern('/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args(
|
|
||||||
'! /bar', default_style=Pattern_style.FNMATCH
|
|
||||||
).and_return(Pattern('/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args('').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args(' ').never()
|
|
||||||
flexmock(module).should_receive('parse_pattern').with_args(
|
|
||||||
'! /baz', default_style=Pattern_style.FNMATCH
|
|
||||||
).and_return(Pattern('/baz', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
|
|
||||||
|
|
||||||
assert module.collect_patterns({'exclude_from': ['file1.txt', 'file2.txt']}) == (
|
|
||||||
Pattern('/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
|
||||||
Pattern('/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
|
||||||
Pattern('/baz', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_patterns_errors_on_missing_config_exclude_from_file():
|
|
||||||
builtins = flexmock(sys.modules['builtins'])
|
|
||||||
builtins.should_receive('open').with_args('file1.txt').and_raise(OSError)
|
|
||||||
flexmock(module).should_receive('parse_pattern').never()
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.collect_patterns({'exclude_from': ['file1.txt', 'file2.txt']})
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_directory_with_basic_path_passes_it_through():
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').and_return('foo')
|
|
||||||
flexmock(module.glob).should_receive('glob').and_return([])
|
|
||||||
|
|
||||||
paths = module.expand_directory('foo', None)
|
|
||||||
|
|
||||||
assert paths == ['foo']
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_directory_with_glob_expands():
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').and_return('foo*')
|
|
||||||
flexmock(module.glob).should_receive('glob').and_return(['foo', 'food'])
|
|
||||||
|
|
||||||
paths = module.expand_directory('foo*', None)
|
|
||||||
|
|
||||||
assert paths == ['foo', 'food']
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_directory_strips_off_working_directory():
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').and_return('foo')
|
|
||||||
flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo').and_return([]).once()
|
|
||||||
|
|
||||||
paths = module.expand_directory('foo', working_directory='/working/dir')
|
|
||||||
|
|
||||||
assert paths == ['foo']
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_directory_globs_working_directory_and_strips_it_off():
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').and_return('foo*')
|
|
||||||
flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo*').and_return(
|
|
||||||
['/working/dir/foo', '/working/dir/food']
|
|
||||||
).once()
|
|
||||||
|
|
||||||
paths = module.expand_directory('foo*', working_directory='/working/dir')
|
|
||||||
|
|
||||||
assert paths == ['foo', 'food']
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_directory_with_slashdot_hack_globs_working_directory_and_strips_it_off():
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').and_return('./foo*')
|
|
||||||
flexmock(module.glob).should_receive('glob').with_args('/working/dir/./foo*').and_return(
|
|
||||||
['/working/dir/./foo', '/working/dir/./food']
|
|
||||||
).once()
|
|
||||||
|
|
||||||
paths = module.expand_directory('./foo*', working_directory='/working/dir')
|
|
||||||
|
|
||||||
assert paths == ['./foo', './food']
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_directory_with_working_directory_matching_start_of_directory_does_not_strip_it_off():
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').and_return('/working/dir/foo')
|
|
||||||
flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo').and_return(
|
|
||||||
['/working/dir/foo']
|
|
||||||
).once()
|
|
||||||
|
|
||||||
paths = module.expand_directory('/working/dir/foo', working_directory='/working/dir')
|
|
||||||
|
|
||||||
assert paths == ['/working/dir/foo']
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_patterns_flattens_expanded_directories():
|
|
||||||
flexmock(module).should_receive('expand_directory').with_args('~/foo', None).and_return(
|
|
||||||
['/root/foo']
|
|
||||||
)
|
|
||||||
flexmock(module).should_receive('expand_directory').with_args('bar*', None).and_return(
|
|
||||||
['bar', 'barf']
|
|
||||||
)
|
|
||||||
|
|
||||||
paths = module.expand_patterns((Pattern('~/foo'), Pattern('bar*')))
|
|
||||||
|
|
||||||
assert paths == (Pattern('/root/foo'), Pattern('bar'), Pattern('barf'))
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_patterns_with_working_directory_passes_it_through():
|
|
||||||
flexmock(module).should_receive('expand_directory').with_args('foo', '/working/dir').and_return(
|
|
||||||
['/working/dir/foo']
|
|
||||||
)
|
|
||||||
|
|
||||||
patterns = module.expand_patterns((Pattern('foo'),), working_directory='/working/dir')
|
|
||||||
|
|
||||||
assert patterns == (Pattern('/working/dir/foo'),)
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_patterns_does_not_expand_skip_paths():
|
|
||||||
flexmock(module).should_receive('expand_directory').with_args('/foo', None).and_return(['/foo'])
|
|
||||||
flexmock(module).should_receive('expand_directory').with_args('/bar*', None).never()
|
|
||||||
|
|
||||||
patterns = module.expand_patterns((Pattern('/foo'), Pattern('/bar*')), skip_paths=('/bar*',))
|
|
||||||
|
|
||||||
assert patterns == (Pattern('/foo'), Pattern('/bar*'))
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_patterns_considers_none_as_no_patterns():
|
|
||||||
assert module.expand_patterns(None) == ()
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_patterns_expands_tildes_and_globs_in_root_patterns():
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').never()
|
|
||||||
flexmock(module).should_receive('expand_directory').and_return(
|
|
||||||
['/root/foo/one', '/root/foo/two']
|
|
||||||
)
|
|
||||||
|
|
||||||
paths = module.expand_patterns((Pattern('~/foo/*'),))
|
|
||||||
|
|
||||||
assert paths == (Pattern('/root/foo/one'), Pattern('/root/foo/two'))
|
|
||||||
|
|
||||||
|
|
||||||
def test_expand_patterns_expands_only_tildes_in_non_root_patterns():
|
|
||||||
flexmock(module).should_receive('expand_directory').never()
|
|
||||||
flexmock(module.os.path).should_receive('expanduser').and_return('/root/bar/*')
|
|
||||||
|
|
||||||
paths = module.expand_patterns((Pattern('~/bar/*', Pattern_type.INCLUDE),))
|
|
||||||
|
|
||||||
assert paths == (Pattern('/root/bar/*', Pattern_type.INCLUDE),)
|
|
||||||
|
|
||||||
|
|
||||||
def test_device_map_patterns_gives_device_id_per_path():
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/bar').and_return(flexmock(st_dev=66))
|
|
||||||
|
|
||||||
device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar')))
|
|
||||||
|
|
||||||
assert device_map == (
|
|
||||||
Pattern('/foo', device=55),
|
|
||||||
Pattern('/bar', device=66),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_device_map_patterns_only_considers_root_patterns():
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/bar*').never()
|
|
||||||
|
|
||||||
device_map = module.device_map_patterns(
|
|
||||||
(Pattern('/foo'), Pattern('/bar*', Pattern_type.INCLUDE))
|
|
||||||
)
|
|
||||||
|
|
||||||
assert device_map == (
|
|
||||||
Pattern('/foo', device=55),
|
|
||||||
Pattern('/bar*', Pattern_type.INCLUDE),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_device_map_patterns_with_missing_path_does_not_error():
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(True).and_return(False)
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/bar').never()
|
|
||||||
|
|
||||||
device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar')))
|
|
||||||
|
|
||||||
assert device_map == (
|
|
||||||
Pattern('/foo', device=55),
|
|
||||||
Pattern('/bar'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_device_map_patterns_uses_working_directory_to_construct_path():
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/working/dir/bar').and_return(
|
|
||||||
flexmock(st_dev=66)
|
|
||||||
)
|
|
||||||
|
|
||||||
device_map = module.device_map_patterns(
|
|
||||||
(Pattern('/foo'), Pattern('bar')), working_directory='/working/dir'
|
|
||||||
)
|
|
||||||
|
|
||||||
assert device_map == (
|
|
||||||
Pattern('/foo', device=55),
|
|
||||||
Pattern('bar', device=66),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_device_map_patterns_with_existing_device_id_does_not_overwrite_it():
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
|
||||||
flexmock(module.os).should_receive('stat').with_args('/bar').and_return(flexmock(st_dev=100))
|
|
||||||
|
|
||||||
device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar', device=66)))
|
|
||||||
|
|
||||||
assert device_map == (
|
|
||||||
Pattern('/foo', device=55),
|
|
||||||
Pattern('/bar', device=66),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'patterns,expected_patterns',
|
|
||||||
(
|
|
||||||
((Pattern('/', device=1), Pattern('/root', device=1)), (Pattern('/', device=1),)),
|
|
||||||
((Pattern('/', device=1), Pattern('/root/', device=1)), (Pattern('/', device=1),)),
|
|
||||||
(
|
|
||||||
(Pattern('/', device=1), Pattern('/root', device=2)),
|
|
||||||
(Pattern('/', device=1), Pattern('/root', device=2)),
|
|
||||||
),
|
|
||||||
((Pattern('/root', device=1), Pattern('/', device=1)), (Pattern('/', device=1),)),
|
|
||||||
(
|
|
||||||
(Pattern('/root', device=1), Pattern('/root/foo', device=1)),
|
|
||||||
(Pattern('/root', device=1),),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(Pattern('/root/', device=1), Pattern('/root/foo', device=1)),
|
|
||||||
(Pattern('/root/', device=1),),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(Pattern('/root', device=1), Pattern('/root/foo/', device=1)),
|
|
||||||
(Pattern('/root', device=1),),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(Pattern('/root', device=1), Pattern('/root/foo', device=2)),
|
|
||||||
(Pattern('/root', device=1), Pattern('/root/foo', device=2)),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(Pattern('/root/foo', device=1), Pattern('/root', device=1)),
|
|
||||||
(Pattern('/root', device=1),),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(Pattern('/root', device=None), Pattern('/root/foo', device=None)),
|
|
||||||
(Pattern('/root'), Pattern('/root/foo')),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(
|
|
||||||
Pattern('/root', device=1),
|
|
||||||
Pattern('/etc', device=1),
|
|
||||||
Pattern('/root/foo/bar', device=1),
|
|
||||||
),
|
|
||||||
(Pattern('/root', device=1), Pattern('/etc', device=1)),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(
|
|
||||||
Pattern('/root', device=1),
|
|
||||||
Pattern('/root/foo', device=1),
|
|
||||||
Pattern('/root/foo/bar', device=1),
|
|
||||||
),
|
|
||||||
(Pattern('/root', device=1),),
|
|
||||||
),
|
|
||||||
((Pattern('/dup', device=1), Pattern('/dup', device=1)), (Pattern('/dup', device=1),)),
|
|
||||||
(
|
|
||||||
(Pattern('/foo', device=1), Pattern('/bar', device=1)),
|
|
||||||
(Pattern('/foo', device=1), Pattern('/bar', device=1)),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(Pattern('/foo', device=1), Pattern('/bar', device=2)),
|
|
||||||
(Pattern('/foo', device=1), Pattern('/bar', device=2)),
|
|
||||||
),
|
|
||||||
((Pattern('/root/foo', device=1),), (Pattern('/root/foo', device=1),)),
|
|
||||||
(
|
|
||||||
(Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
|
|
||||||
(Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
|
|
||||||
(Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def test_deduplicate_patterns_omits_child_paths_on_the_same_filesystem(patterns, expected_patterns):
|
|
||||||
assert module.deduplicate_patterns(patterns) == expected_patterns
|
|
||||||
|
|
||||||
|
|
||||||
def test_process_patterns_includes_patterns():
|
|
||||||
flexmock(module).should_receive('deduplicate_patterns').and_return(
|
|
||||||
(Pattern('foo'), Pattern('bar'))
|
|
||||||
)
|
|
||||||
flexmock(module).should_receive('device_map_patterns').and_return({})
|
|
||||||
flexmock(module).should_receive('expand_patterns').with_args(
|
|
||||||
(Pattern('foo'), Pattern('bar')),
|
|
||||||
working_directory='/working',
|
|
||||||
skip_paths=set(),
|
|
||||||
).and_return(()).once()
|
|
||||||
|
|
||||||
assert module.process_patterns(
|
|
||||||
(Pattern('foo'), Pattern('bar')),
|
|
||||||
working_directory='/working',
|
|
||||||
) == [Pattern('foo'), Pattern('bar')]
|
|
||||||
|
|
||||||
|
|
||||||
def test_process_patterns_skips_expand_for_requested_paths():
|
|
||||||
skip_paths = {flexmock()}
|
|
||||||
flexmock(module).should_receive('deduplicate_patterns').and_return(
|
|
||||||
(Pattern('foo'), Pattern('bar'))
|
|
||||||
)
|
|
||||||
flexmock(module).should_receive('device_map_patterns').and_return({})
|
|
||||||
flexmock(module).should_receive('expand_patterns').with_args(
|
|
||||||
(Pattern('foo'), Pattern('bar')),
|
|
||||||
working_directory='/working',
|
|
||||||
skip_paths=skip_paths,
|
|
||||||
).and_return(()).once()
|
|
||||||
|
|
||||||
assert module.process_patterns(
|
|
||||||
(Pattern('foo'), Pattern('bar')),
|
|
||||||
working_directory='/working',
|
|
||||||
skip_expand_paths=skip_paths,
|
|
||||||
) == [Pattern('foo'), Pattern('bar')]
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_create_executes_and_calls_hooks_for_configured_repository():
|
def test_run_create_executes_and_calls_hooks_for_configured_repository():
|
||||||
@@ -437,9 +17,9 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository():
|
|||||||
flexmock(module.borgmatic.hooks.dispatch).should_receive(
|
flexmock(module.borgmatic.hooks.dispatch).should_receive(
|
||||||
'call_hooks_even_if_unconfigured'
|
'call_hooks_even_if_unconfigured'
|
||||||
).and_return({})
|
).and_return({})
|
||||||
flexmock(module).should_receive('collect_patterns').and_return(())
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(())
|
||||||
flexmock(module).should_receive('process_patterns').and_return([])
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([])
|
||||||
flexmock(module.os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
|
flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
|
||||||
create_arguments = flexmock(
|
create_arguments = flexmock(
|
||||||
repository=None,
|
repository=None,
|
||||||
progress=flexmock(),
|
progress=flexmock(),
|
||||||
@@ -478,9 +58,9 @@ def test_run_create_runs_with_selected_repository():
|
|||||||
flexmock(module.borgmatic.hooks.dispatch).should_receive(
|
flexmock(module.borgmatic.hooks.dispatch).should_receive(
|
||||||
'call_hooks_even_if_unconfigured'
|
'call_hooks_even_if_unconfigured'
|
||||||
).and_return({})
|
).and_return({})
|
||||||
flexmock(module).should_receive('collect_patterns').and_return(())
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(())
|
||||||
flexmock(module).should_receive('process_patterns').and_return([])
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([])
|
||||||
flexmock(module.os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
|
flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
|
||||||
create_arguments = flexmock(
|
create_arguments = flexmock(
|
||||||
repository=flexmock(),
|
repository=flexmock(),
|
||||||
progress=flexmock(),
|
progress=flexmock(),
|
||||||
@@ -621,9 +201,9 @@ def test_run_create_produces_json():
|
|||||||
flexmock(module.borgmatic.hooks.dispatch).should_receive(
|
flexmock(module.borgmatic.hooks.dispatch).should_receive(
|
||||||
'call_hooks_even_if_unconfigured'
|
'call_hooks_even_if_unconfigured'
|
||||||
).and_return({})
|
).and_return({})
|
||||||
flexmock(module).should_receive('collect_patterns').and_return(())
|
flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(())
|
||||||
flexmock(module).should_receive('process_patterns').and_return([])
|
flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([])
|
||||||
flexmock(module.os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
|
flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
|
||||||
create_arguments = flexmock(
|
create_arguments = flexmock(
|
||||||
repository=flexmock(),
|
repository=flexmock(),
|
||||||
progress=flexmock(),
|
progress=flexmock(),
|
||||||
|
|||||||
@@ -0,0 +1,426 @@
|
|||||||
|
import io
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from flexmock import flexmock
|
||||||
|
|
||||||
|
from borgmatic.actions import pattern as module
|
||||||
|
from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'pattern_line,expected_pattern',
|
||||||
|
(
|
||||||
|
('R /foo', Pattern('/foo', source=Pattern_source.CONFIG)),
|
||||||
|
('P sh', Pattern('sh', Pattern_type.PATTERN_STYLE, source=Pattern_source.CONFIG)),
|
||||||
|
('+ /foo*', Pattern('/foo*', Pattern_type.INCLUDE, source=Pattern_source.CONFIG)),
|
||||||
|
(
|
||||||
|
'+ sh:/foo*',
|
||||||
|
Pattern(
|
||||||
|
'/foo*', Pattern_type.INCLUDE, Pattern_style.SHELL, source=Pattern_source.CONFIG
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_parse_pattern_transforms_pattern_line_to_instance(pattern_line, expected_pattern):
|
||||||
|
module.parse_pattern(pattern_line) == expected_pattern
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_pattern_with_invalid_pattern_line_errors():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.parse_pattern('/foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_patterns_converts_source_directories():
|
||||||
|
assert module.collect_patterns({'source_directories': ['/foo', '/bar']}) == (
|
||||||
|
Pattern('/foo', source=Pattern_source.CONFIG),
|
||||||
|
Pattern('/bar', source=Pattern_source.CONFIG),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_patterns_parses_config_patterns():
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('R /foo').and_return(Pattern('/foo'))
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args(' ').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('R /bar').and_return(Pattern('/bar'))
|
||||||
|
|
||||||
|
assert module.collect_patterns({'patterns': ['R /foo', '# comment', '', ' ', 'R /bar']}) == (
|
||||||
|
Pattern('/foo'),
|
||||||
|
Pattern('/bar'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_patterns_converts_exclude_patterns():
|
||||||
|
assert module.collect_patterns({'exclude_patterns': ['/foo', '/bar', 'sh:**/baz']}) == (
|
||||||
|
Pattern(
|
||||||
|
'/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, source=Pattern_source.CONFIG
|
||||||
|
),
|
||||||
|
Pattern(
|
||||||
|
'/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, source=Pattern_source.CONFIG
|
||||||
|
),
|
||||||
|
Pattern(
|
||||||
|
'**/baz', Pattern_type.NO_RECURSE, Pattern_style.SHELL, source=Pattern_source.CONFIG
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_patterns_reads_config_patterns_from_file():
|
||||||
|
builtins = flexmock(sys.modules['builtins'])
|
||||||
|
builtins.should_receive('open').with_args('file1.txt').and_return(io.StringIO('R /foo'))
|
||||||
|
builtins.should_receive('open').with_args('file2.txt').and_return(
|
||||||
|
io.StringIO('R /bar\n# comment\n\n \nR /baz')
|
||||||
|
)
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('R /foo').and_return(Pattern('/foo'))
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args(' ').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('R /bar').and_return(Pattern('/bar'))
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('R /baz').and_return(Pattern('/baz'))
|
||||||
|
|
||||||
|
assert module.collect_patterns({'patterns_from': ['file1.txt', 'file2.txt']}) == (
|
||||||
|
Pattern('/foo'),
|
||||||
|
Pattern('/bar'),
|
||||||
|
Pattern('/baz'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_patterns_errors_on_missing_config_patterns_from_file():
|
||||||
|
builtins = flexmock(sys.modules['builtins'])
|
||||||
|
builtins.should_receive('open').with_args('file1.txt').and_raise(FileNotFoundError)
|
||||||
|
flexmock(module).should_receive('parse_pattern').never()
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.collect_patterns({'patterns_from': ['file1.txt', 'file2.txt']})
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_patterns_reads_config_exclude_from_file():
|
||||||
|
builtins = flexmock(sys.modules['builtins'])
|
||||||
|
builtins.should_receive('open').with_args('file1.txt').and_return(io.StringIO('/foo'))
|
||||||
|
builtins.should_receive('open').with_args('file2.txt').and_return(
|
||||||
|
io.StringIO('/bar\n# comment\n\n \n/baz')
|
||||||
|
)
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args(
|
||||||
|
'! /foo', default_style=Pattern_style.FNMATCH
|
||||||
|
).and_return(Pattern('/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args(
|
||||||
|
'! /bar', default_style=Pattern_style.FNMATCH
|
||||||
|
).and_return(Pattern('/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args('').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args(' ').never()
|
||||||
|
flexmock(module).should_receive('parse_pattern').with_args(
|
||||||
|
'! /baz', default_style=Pattern_style.FNMATCH
|
||||||
|
).and_return(Pattern('/baz', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
|
||||||
|
|
||||||
|
assert module.collect_patterns({'exclude_from': ['file1.txt', 'file2.txt']}) == (
|
||||||
|
Pattern('/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
||||||
|
Pattern('/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
||||||
|
Pattern('/baz', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_patterns_errors_on_missing_config_exclude_from_file():
|
||||||
|
builtins = flexmock(sys.modules['builtins'])
|
||||||
|
builtins.should_receive('open').with_args('file1.txt').and_raise(OSError)
|
||||||
|
flexmock(module).should_receive('parse_pattern').never()
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.collect_patterns({'exclude_from': ['file1.txt', 'file2.txt']})
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_directory_with_basic_path_passes_it_through():
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').and_return('foo')
|
||||||
|
flexmock(module.glob).should_receive('glob').and_return([])
|
||||||
|
|
||||||
|
paths = module.expand_directory('foo', None)
|
||||||
|
|
||||||
|
assert paths == ['foo']
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_directory_with_glob_expands():
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').and_return('foo*')
|
||||||
|
flexmock(module.glob).should_receive('glob').and_return(['foo', 'food'])
|
||||||
|
|
||||||
|
paths = module.expand_directory('foo*', None)
|
||||||
|
|
||||||
|
assert paths == ['foo', 'food']
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_directory_strips_off_working_directory():
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').and_return('foo')
|
||||||
|
flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo').and_return([]).once()
|
||||||
|
|
||||||
|
paths = module.expand_directory('foo', working_directory='/working/dir')
|
||||||
|
|
||||||
|
assert paths == ['foo']
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_directory_globs_working_directory_and_strips_it_off():
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').and_return('foo*')
|
||||||
|
flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo*').and_return(
|
||||||
|
['/working/dir/foo', '/working/dir/food']
|
||||||
|
).once()
|
||||||
|
|
||||||
|
paths = module.expand_directory('foo*', working_directory='/working/dir')
|
||||||
|
|
||||||
|
assert paths == ['foo', 'food']
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_directory_with_slashdot_hack_globs_working_directory_and_strips_it_off():
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').and_return('./foo*')
|
||||||
|
flexmock(module.glob).should_receive('glob').with_args('/working/dir/./foo*').and_return(
|
||||||
|
['/working/dir/./foo', '/working/dir/./food']
|
||||||
|
).once()
|
||||||
|
|
||||||
|
paths = module.expand_directory('./foo*', working_directory='/working/dir')
|
||||||
|
|
||||||
|
assert paths == ['./foo', './food']
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_directory_with_working_directory_matching_start_of_directory_does_not_strip_it_off():
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').and_return('/working/dir/foo')
|
||||||
|
flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo').and_return(
|
||||||
|
['/working/dir/foo']
|
||||||
|
).once()
|
||||||
|
|
||||||
|
paths = module.expand_directory('/working/dir/foo', working_directory='/working/dir')
|
||||||
|
|
||||||
|
assert paths == ['/working/dir/foo']
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_patterns_flattens_expanded_directories():
|
||||||
|
flexmock(module).should_receive('expand_directory').with_args('~/foo', None).and_return(
|
||||||
|
['/root/foo']
|
||||||
|
)
|
||||||
|
flexmock(module).should_receive('expand_directory').with_args('bar*', None).and_return(
|
||||||
|
['bar', 'barf']
|
||||||
|
)
|
||||||
|
|
||||||
|
paths = module.expand_patterns((Pattern('~/foo'), Pattern('bar*')))
|
||||||
|
|
||||||
|
assert paths == (Pattern('/root/foo'), Pattern('bar'), Pattern('barf'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_patterns_with_working_directory_passes_it_through():
|
||||||
|
flexmock(module).should_receive('expand_directory').with_args('foo', '/working/dir').and_return(
|
||||||
|
['/working/dir/foo']
|
||||||
|
)
|
||||||
|
|
||||||
|
patterns = module.expand_patterns((Pattern('foo'),), working_directory='/working/dir')
|
||||||
|
|
||||||
|
assert patterns == (Pattern('/working/dir/foo'),)
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_patterns_does_not_expand_skip_paths():
|
||||||
|
flexmock(module).should_receive('expand_directory').with_args('/foo', None).and_return(['/foo'])
|
||||||
|
flexmock(module).should_receive('expand_directory').with_args('/bar*', None).never()
|
||||||
|
|
||||||
|
patterns = module.expand_patterns((Pattern('/foo'), Pattern('/bar*')), skip_paths=('/bar*',))
|
||||||
|
|
||||||
|
assert patterns == (Pattern('/foo'), Pattern('/bar*'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_patterns_considers_none_as_no_patterns():
|
||||||
|
assert module.expand_patterns(None) == ()
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_patterns_expands_tildes_and_globs_in_root_patterns():
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').never()
|
||||||
|
flexmock(module).should_receive('expand_directory').and_return(
|
||||||
|
['/root/foo/one', '/root/foo/two']
|
||||||
|
)
|
||||||
|
|
||||||
|
paths = module.expand_patterns((Pattern('~/foo/*'),))
|
||||||
|
|
||||||
|
assert paths == (Pattern('/root/foo/one'), Pattern('/root/foo/two'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_expand_patterns_expands_only_tildes_in_non_root_patterns():
|
||||||
|
flexmock(module).should_receive('expand_directory').never()
|
||||||
|
flexmock(module.os.path).should_receive('expanduser').and_return('/root/bar/*')
|
||||||
|
|
||||||
|
paths = module.expand_patterns((Pattern('~/bar/*', Pattern_type.INCLUDE),))
|
||||||
|
|
||||||
|
assert paths == (Pattern('/root/bar/*', Pattern_type.INCLUDE),)
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_map_patterns_gives_device_id_per_path():
|
||||||
|
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/bar').and_return(flexmock(st_dev=66))
|
||||||
|
|
||||||
|
device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar')))
|
||||||
|
|
||||||
|
assert device_map == (
|
||||||
|
Pattern('/foo', device=55),
|
||||||
|
Pattern('/bar', device=66),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_map_patterns_only_considers_root_patterns():
|
||||||
|
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/bar*').never()
|
||||||
|
|
||||||
|
device_map = module.device_map_patterns(
|
||||||
|
(Pattern('/foo'), Pattern('/bar*', Pattern_type.INCLUDE))
|
||||||
|
)
|
||||||
|
|
||||||
|
assert device_map == (
|
||||||
|
Pattern('/foo', device=55),
|
||||||
|
Pattern('/bar*', Pattern_type.INCLUDE),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_map_patterns_with_missing_path_does_not_error():
|
||||||
|
flexmock(module.os.path).should_receive('exists').and_return(True).and_return(False)
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/bar').never()
|
||||||
|
|
||||||
|
device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar')))
|
||||||
|
|
||||||
|
assert device_map == (
|
||||||
|
Pattern('/foo', device=55),
|
||||||
|
Pattern('/bar'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_map_patterns_uses_working_directory_to_construct_path():
|
||||||
|
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/working/dir/bar').and_return(
|
||||||
|
flexmock(st_dev=66)
|
||||||
|
)
|
||||||
|
|
||||||
|
device_map = module.device_map_patterns(
|
||||||
|
(Pattern('/foo'), Pattern('bar')), working_directory='/working/dir'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert device_map == (
|
||||||
|
Pattern('/foo', device=55),
|
||||||
|
Pattern('bar', device=66),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_device_map_patterns_with_existing_device_id_does_not_overwrite_it():
|
||||||
|
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
|
||||||
|
flexmock(module.os).should_receive('stat').with_args('/bar').and_return(flexmock(st_dev=100))
|
||||||
|
|
||||||
|
device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar', device=66)))
|
||||||
|
|
||||||
|
assert device_map == (
|
||||||
|
Pattern('/foo', device=55),
|
||||||
|
Pattern('/bar', device=66),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'patterns,expected_patterns',
|
||||||
|
(
|
||||||
|
((Pattern('/', device=1), Pattern('/root', device=1)), (Pattern('/', device=1),)),
|
||||||
|
((Pattern('/', device=1), Pattern('/root/', device=1)), (Pattern('/', device=1),)),
|
||||||
|
(
|
||||||
|
(Pattern('/', device=1), Pattern('/root', device=2)),
|
||||||
|
(Pattern('/', device=1), Pattern('/root', device=2)),
|
||||||
|
),
|
||||||
|
((Pattern('/root', device=1), Pattern('/', device=1)), (Pattern('/', device=1),)),
|
||||||
|
(
|
||||||
|
(Pattern('/root', device=1), Pattern('/root/foo', device=1)),
|
||||||
|
(Pattern('/root', device=1),),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Pattern('/root/', device=1), Pattern('/root/foo', device=1)),
|
||||||
|
(Pattern('/root/', device=1),),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Pattern('/root', device=1), Pattern('/root/foo/', device=1)),
|
||||||
|
(Pattern('/root', device=1),),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Pattern('/root', device=1), Pattern('/root/foo', device=2)),
|
||||||
|
(Pattern('/root', device=1), Pattern('/root/foo', device=2)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Pattern('/root/foo', device=1), Pattern('/root', device=1)),
|
||||||
|
(Pattern('/root', device=1),),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Pattern('/root', device=None), Pattern('/root/foo', device=None)),
|
||||||
|
(Pattern('/root'), Pattern('/root/foo')),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
Pattern('/root', device=1),
|
||||||
|
Pattern('/etc', device=1),
|
||||||
|
Pattern('/root/foo/bar', device=1),
|
||||||
|
),
|
||||||
|
(Pattern('/root', device=1), Pattern('/etc', device=1)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
Pattern('/root', device=1),
|
||||||
|
Pattern('/root/foo', device=1),
|
||||||
|
Pattern('/root/foo/bar', device=1),
|
||||||
|
),
|
||||||
|
(Pattern('/root', device=1),),
|
||||||
|
),
|
||||||
|
((Pattern('/dup', device=1), Pattern('/dup', device=1)), (Pattern('/dup', device=1),)),
|
||||||
|
(
|
||||||
|
(Pattern('/foo', device=1), Pattern('/bar', device=1)),
|
||||||
|
(Pattern('/foo', device=1), Pattern('/bar', device=1)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Pattern('/foo', device=1), Pattern('/bar', device=2)),
|
||||||
|
(Pattern('/foo', device=1), Pattern('/bar', device=2)),
|
||||||
|
),
|
||||||
|
((Pattern('/root/foo', device=1),), (Pattern('/root/foo', device=1),)),
|
||||||
|
(
|
||||||
|
(Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
|
||||||
|
(Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
|
||||||
|
(Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_deduplicate_patterns_omits_child_paths_on_the_same_filesystem(patterns, expected_patterns):
|
||||||
|
assert module.deduplicate_patterns(patterns) == expected_patterns
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_patterns_includes_patterns():
|
||||||
|
flexmock(module).should_receive('deduplicate_patterns').and_return(
|
||||||
|
(Pattern('foo'), Pattern('bar'))
|
||||||
|
)
|
||||||
|
flexmock(module).should_receive('device_map_patterns').and_return({})
|
||||||
|
flexmock(module).should_receive('expand_patterns').with_args(
|
||||||
|
(Pattern('foo'), Pattern('bar')),
|
||||||
|
working_directory='/working',
|
||||||
|
skip_paths=set(),
|
||||||
|
).and_return(()).once()
|
||||||
|
|
||||||
|
assert module.process_patterns(
|
||||||
|
(Pattern('foo'), Pattern('bar')),
|
||||||
|
working_directory='/working',
|
||||||
|
) == [Pattern('foo'), Pattern('bar')]
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_patterns_skips_expand_for_requested_paths():
|
||||||
|
skip_paths = {flexmock()}
|
||||||
|
flexmock(module).should_receive('deduplicate_patterns').and_return(
|
||||||
|
(Pattern('foo'), Pattern('bar'))
|
||||||
|
)
|
||||||
|
flexmock(module).should_receive('device_map_patterns').and_return({})
|
||||||
|
flexmock(module).should_receive('expand_patterns').with_args(
|
||||||
|
(Pattern('foo'), Pattern('bar')),
|
||||||
|
working_directory='/working',
|
||||||
|
skip_paths=skip_paths,
|
||||||
|
).and_return(()).once()
|
||||||
|
|
||||||
|
assert module.process_patterns(
|
||||||
|
(Pattern('foo'), Pattern('bar')),
|
||||||
|
working_directory='/working',
|
||||||
|
skip_expand_paths=skip_paths,
|
||||||
|
) == [Pattern('foo'), Pattern('bar')]
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import pytest
|
||||||
from flexmock import flexmock
|
from flexmock import flexmock
|
||||||
|
|
||||||
from borgmatic.actions import recreate as module
|
from borgmatic.actions import recreate as module
|
||||||
@@ -6,15 +7,19 @@ from borgmatic.actions import recreate as module
|
|||||||
def test_run_recreate_does_not_raise():
|
def test_run_recreate_does_not_raise():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
|
None
|
||||||
|
)
|
||||||
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive')
|
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive')
|
||||||
|
|
||||||
recreate_arguments = flexmock(repository=flexmock(), archive=None)
|
|
||||||
|
|
||||||
module.run_recreate(
|
module.run_recreate(
|
||||||
repository={'path': 'repo'},
|
repository={'path': 'repo'},
|
||||||
config={},
|
config={},
|
||||||
local_borg_version=None,
|
local_borg_version=None,
|
||||||
recreate_arguments=recreate_arguments,
|
recreate_arguments=flexmock(repository=flexmock(), archive=None),
|
||||||
global_arguments=flexmock(),
|
global_arguments=flexmock(),
|
||||||
local_path=None,
|
local_path=None,
|
||||||
remote_path=None,
|
remote_path=None,
|
||||||
@@ -24,16 +29,154 @@ def test_run_recreate_does_not_raise():
|
|||||||
def test_run_recreate_with_archive_does_not_raise():
|
def test_run_recreate_with_archive_does_not_raise():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
|
'test-archive'
|
||||||
|
)
|
||||||
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive')
|
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive')
|
||||||
|
|
||||||
recreate_arguments = flexmock(repository=flexmock(), archive='test-archive')
|
|
||||||
|
|
||||||
module.run_recreate(
|
module.run_recreate(
|
||||||
repository={'path': 'repo'},
|
repository={'path': 'repo'},
|
||||||
config={},
|
config={},
|
||||||
local_borg_version=None,
|
local_borg_version=None,
|
||||||
recreate_arguments=recreate_arguments,
|
recreate_arguments=flexmock(repository=flexmock(), archive='test-archive'),
|
||||||
global_arguments=flexmock(),
|
global_arguments=flexmock(),
|
||||||
local_path=None,
|
local_path=None,
|
||||||
remote_path=None,
|
remote_path=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_recreate_with_leftover_recreate_archive_raises():
|
||||||
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
|
'test-archive.recreate'
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive')
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.run_recreate(
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
config={},
|
||||||
|
local_borg_version=None,
|
||||||
|
recreate_arguments=flexmock(repository=flexmock(), archive='test-archive.recreate'),
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path=None,
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_recreate_with_latest_archive_resolving_to_leftover_recreate_archive_raises():
|
||||||
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
|
'test-archive.recreate'
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive')
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.run_recreate(
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
config={},
|
||||||
|
local_borg_version=None,
|
||||||
|
recreate_arguments=flexmock(repository=flexmock(), archive='latest'),
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path=None,
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_recreate_with_archive_already_exists_error_raises():
|
||||||
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
|
'test-archive'
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive').and_raise(
|
||||||
|
module.subprocess.CalledProcessError(
|
||||||
|
returncode=module.BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS,
|
||||||
|
cmd='borg recreate or whatever',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.run_recreate(
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
config={},
|
||||||
|
local_borg_version=None,
|
||||||
|
recreate_arguments=flexmock(repository=flexmock(), archive='test-archive', target=None),
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path=None,
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_recreate_with_target_and_archive_already_exists_error_raises():
|
||||||
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
|
'test-archive'
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive').and_raise(
|
||||||
|
module.subprocess.CalledProcessError(
|
||||||
|
returncode=module.BORG_EXIT_CODE_ARCHIVE_ALREADY_EXISTS,
|
||||||
|
cmd='borg recreate or whatever',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.run_recreate(
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
config={},
|
||||||
|
local_borg_version=None,
|
||||||
|
recreate_arguments=flexmock(
|
||||||
|
repository=flexmock(), archive='test-archive', target='target-archive'
|
||||||
|
),
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path=None,
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_recreate_with_other_called_process_error_passes_it_through():
|
||||||
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
|
'test-archive'
|
||||||
|
)
|
||||||
|
flexmock(module.borgmatic.borg.recreate).should_receive('recreate_archive').and_raise(
|
||||||
|
module.subprocess.CalledProcessError(
|
||||||
|
returncode=1,
|
||||||
|
cmd='borg recreate or whatever',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(module.subprocess.CalledProcessError):
|
||||||
|
module.run_recreate(
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
config={},
|
||||||
|
local_borg_version=None,
|
||||||
|
recreate_arguments=flexmock(
|
||||||
|
repository=flexmock(), archive='test-archive', target='target-archive'
|
||||||
|
),
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path=None,
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|||||||
+60
-202
@@ -9,131 +9,6 @@ from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Patte
|
|||||||
from ..test_verbosity import insert_logging_mock
|
from ..test_verbosity import insert_logging_mock
|
||||||
|
|
||||||
|
|
||||||
def test_write_patterns_file_writes_pattern_lines():
|
|
||||||
temporary_file = flexmock(name='filename', flush=lambda: None)
|
|
||||||
temporary_file.should_receive('write').with_args('R /foo\n+ sh:/foo/bar')
|
|
||||||
flexmock(module.tempfile).should_receive('NamedTemporaryFile').and_return(temporary_file)
|
|
||||||
|
|
||||||
module.write_patterns_file(
|
|
||||||
[Pattern('/foo'), Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.SHELL)],
|
|
||||||
borgmatic_runtime_directory='/run/user/0',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_write_patterns_file_with_empty_exclude_patterns_does_not_raise():
|
|
||||||
module.write_patterns_file([], borgmatic_runtime_directory='/run/user/0')
|
|
||||||
|
|
||||||
|
|
||||||
def test_write_patterns_file_appends_to_existing():
|
|
||||||
patterns_file = flexmock(name='filename', flush=lambda: None)
|
|
||||||
patterns_file.should_receive('write').with_args('\n')
|
|
||||||
patterns_file.should_receive('write').with_args('R /foo\n+ /foo/bar')
|
|
||||||
flexmock(module.tempfile).should_receive('NamedTemporaryFile').never()
|
|
||||||
|
|
||||||
module.write_patterns_file(
|
|
||||||
[Pattern('/foo'), Pattern('/foo/bar', Pattern_type.INCLUDE)],
|
|
||||||
borgmatic_runtime_directory='/run/user/0',
|
|
||||||
patterns_file=patterns_file,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_includes_exclude_caches_when_true_in_config():
|
|
||||||
exclude_flags = module.make_exclude_flags(config={'exclude_caches': True})
|
|
||||||
|
|
||||||
assert exclude_flags == ('--exclude-caches',)
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_does_not_include_exclude_caches_when_false_in_config():
|
|
||||||
exclude_flags = module.make_exclude_flags(config={'exclude_caches': False})
|
|
||||||
|
|
||||||
assert exclude_flags == ()
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_includes_exclude_if_present_when_in_config():
|
|
||||||
exclude_flags = module.make_exclude_flags(
|
|
||||||
config={'exclude_if_present': ['exclude_me', 'also_me']}
|
|
||||||
)
|
|
||||||
|
|
||||||
assert exclude_flags == (
|
|
||||||
'--exclude-if-present',
|
|
||||||
'exclude_me',
|
|
||||||
'--exclude-if-present',
|
|
||||||
'also_me',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_includes_keep_exclude_tags_when_true_in_config():
|
|
||||||
exclude_flags = module.make_exclude_flags(config={'keep_exclude_tags': True})
|
|
||||||
|
|
||||||
assert exclude_flags == ('--keep-exclude-tags',)
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_does_not_include_keep_exclude_tags_when_false_in_config():
|
|
||||||
exclude_flags = module.make_exclude_flags(config={'keep_exclude_tags': False})
|
|
||||||
|
|
||||||
assert exclude_flags == ()
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_includes_exclude_nodump_when_true_in_config():
|
|
||||||
exclude_flags = module.make_exclude_flags(config={'exclude_nodump': True})
|
|
||||||
|
|
||||||
assert exclude_flags == ('--exclude-nodump',)
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_does_not_include_exclude_nodump_when_false_in_config():
|
|
||||||
exclude_flags = module.make_exclude_flags(config={'exclude_nodump': False})
|
|
||||||
|
|
||||||
assert exclude_flags == ()
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_exclude_flags_is_empty_when_config_has_no_excludes():
|
|
||||||
exclude_flags = module.make_exclude_flags(config={})
|
|
||||||
|
|
||||||
assert exclude_flags == ()
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_list_filter_flags_with_debug_and_feature_available_includes_plus_and_minus():
|
|
||||||
flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
|
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
|
||||||
|
|
||||||
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME+-'
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_list_filter_flags_with_info_and_feature_available_omits_plus_and_minus():
|
|
||||||
flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
|
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
|
||||||
|
|
||||||
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME'
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_list_filter_flags_with_debug_and_feature_available_and_dry_run_includes_plus_and_minus():
|
|
||||||
flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
|
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
|
||||||
|
|
||||||
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=True) == 'AME+-'
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_list_filter_flags_with_info_and_feature_available_and_dry_run_includes_plus_and_minus():
|
|
||||||
flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
|
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
|
||||||
|
|
||||||
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=True) == 'AME+-'
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_list_filter_flags_with_debug_and_feature_not_available_includes_x():
|
|
||||||
flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
|
|
||||||
flexmock(module.feature).should_receive('available').and_return(False)
|
|
||||||
|
|
||||||
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AMEx-'
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_list_filter_flags_with_info_and_feature_not_available_omits_x():
|
|
||||||
flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
|
|
||||||
flexmock(module.feature).should_receive('available').and_return(False)
|
|
||||||
|
|
||||||
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME-'
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'character_device,block_device,fifo,expected_result',
|
'character_device,block_device,fifo,expected_result',
|
||||||
(
|
(
|
||||||
@@ -326,10 +201,10 @@ REPO_ARCHIVE = (f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
|||||||
|
|
||||||
def test_make_base_create_produces_borg_command():
|
def test_make_base_create_produces_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -355,16 +230,16 @@ def test_make_base_create_produces_borg_command():
|
|||||||
def test_make_base_create_command_includes_patterns_file_in_borg_command():
|
def test_make_base_create_command_includes_patterns_file_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
mock_pattern_file = flexmock(name='/tmp/patterns')
|
mock_pattern_file = flexmock(name='/tmp/patterns')
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(mock_pattern_file).and_return(
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(
|
||||||
None
|
mock_pattern_file
|
||||||
)
|
).and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
pattern_flags = ('--patterns-from', mock_pattern_file.name)
|
pattern_flags = ('--patterns-from', mock_pattern_file.name)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -390,13 +265,13 @@ def test_make_base_create_command_includes_patterns_file_in_borg_command():
|
|||||||
|
|
||||||
def test_make_base_create_command_with_store_config_false_omits_config_files():
|
def test_make_base_create_command_with_store_config_false_omits_config_files():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -455,13 +330,13 @@ def test_make_base_create_command_includes_configuration_option_as_command_flag(
|
|||||||
option_name, option_value, feature_available, option_flags
|
option_name, option_value, feature_available, option_flags
|
||||||
):
|
):
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(feature_available)
|
flexmock(module.feature).should_receive('available').and_return(feature_available)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -487,13 +362,13 @@ def test_make_base_create_command_includes_configuration_option_as_command_flag(
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_dry_run_in_borg_command():
|
def test_make_base_create_command_includes_dry_run_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -519,13 +394,13 @@ def test_make_base_create_command_includes_dry_run_in_borg_command():
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_local_path_in_borg_command():
|
def test_make_base_create_command_includes_local_path_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -551,13 +426,13 @@ def test_make_base_create_command_includes_local_path_in_borg_command():
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_remote_path_in_borg_command():
|
def test_make_base_create_command_includes_remote_path_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -583,13 +458,13 @@ def test_make_base_create_command_includes_remote_path_in_borg_command():
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_log_json_in_borg_command():
|
def test_make_base_create_command_includes_log_json_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -615,13 +490,13 @@ def test_make_base_create_command_includes_log_json_in_borg_command():
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_list_flags_in_borg_command():
|
def test_make_base_create_command_includes_list_flags_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -649,22 +524,22 @@ def test_make_base_create_command_with_stream_processes_ignores_read_special_fal
|
|||||||
patterns = [Pattern('foo'), Pattern('bar')]
|
patterns = [Pattern('foo'), Pattern('bar')]
|
||||||
patterns_file = flexmock(name='patterns')
|
patterns_file = flexmock(name='patterns')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').with_args(
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
|
||||||
patterns, '/run/borgmatic'
|
patterns, '/run/borgmatic'
|
||||||
).and_return(patterns_file)
|
).and_return(patterns_file)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
flexmock(module.logger).should_receive('warning').twice()
|
flexmock(module.logger).should_receive('warning').twice()
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
flexmock(module.environment).should_receive('make_environment')
|
||||||
flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
|
flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
|
||||||
flexmock(module).should_receive('write_patterns_file').with_args(
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
|
||||||
(
|
(
|
||||||
Pattern(
|
Pattern(
|
||||||
'/dev/null',
|
'/dev/null',
|
||||||
@@ -676,7 +551,7 @@ def test_make_base_create_command_with_stream_processes_ignores_read_special_fal
|
|||||||
'/run/borgmatic',
|
'/run/borgmatic',
|
||||||
patterns_file=patterns_file,
|
patterns_file=patterns_file,
|
||||||
).and_return(patterns_file).once()
|
).and_return(patterns_file).once()
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
|
|
||||||
(create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
|
(create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
@@ -700,22 +575,22 @@ def test_make_base_create_command_with_stream_processes_ignores_read_special_fal
|
|||||||
|
|
||||||
def test_make_base_create_command_without_patterns_and_with_stream_processes_ignores_read_special_false_and_excludes_special_files():
|
def test_make_base_create_command_without_patterns_and_with_stream_processes_ignores_read_special_false_and_excludes_special_files():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').with_args(
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
|
||||||
[], '/run/borgmatic'
|
[], '/run/borgmatic'
|
||||||
).and_return(None)
|
).and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
flexmock(module.logger).should_receive('warning').twice()
|
flexmock(module.logger).should_receive('warning').twice()
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
flexmock(module.environment).should_receive('make_environment')
|
||||||
flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
|
flexmock(module).should_receive('collect_special_file_paths').and_return(('/dev/null',)).once()
|
||||||
flexmock(module).should_receive('write_patterns_file').with_args(
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').with_args(
|
||||||
(
|
(
|
||||||
Pattern(
|
Pattern(
|
||||||
'/dev/null',
|
'/dev/null',
|
||||||
@@ -727,7 +602,7 @@ def test_make_base_create_command_without_patterns_and_with_stream_processes_ign
|
|||||||
'/run/borgmatic',
|
'/run/borgmatic',
|
||||||
patterns_file=None,
|
patterns_file=None,
|
||||||
).and_return(flexmock(name='patterns')).once()
|
).and_return(flexmock(name='patterns')).once()
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
|
|
||||||
(create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
|
(create_flags, create_positional_arguments, pattern_file) = module.make_base_create_command(
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
@@ -751,13 +626,13 @@ def test_make_base_create_command_without_patterns_and_with_stream_processes_ign
|
|||||||
|
|
||||||
def test_make_base_create_command_with_stream_processes_and_read_special_true_skips_special_files_excludes():
|
def test_make_base_create_command_with_stream_processes_and_read_special_true_skips_special_files_excludes():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -786,13 +661,13 @@ def test_make_base_create_command_with_stream_processes_and_read_special_true_sk
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_archive_name_format_in_borg_command():
|
def test_make_base_create_command_includes_archive_name_format_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
('repo::ARCHIVE_NAME',)
|
('repo::ARCHIVE_NAME',)
|
||||||
)
|
)
|
||||||
@@ -818,13 +693,13 @@ def test_make_base_create_command_includes_archive_name_format_in_borg_command()
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_default_archive_name_format_in_borg_command():
|
def test_make_base_create_command_includes_default_archive_name_format_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
('repo::{hostname}',)
|
('repo::{hostname}',)
|
||||||
)
|
)
|
||||||
@@ -849,13 +724,13 @@ def test_make_base_create_command_includes_default_archive_name_format_in_borg_c
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_archive_name_format_with_placeholders_in_borg_command():
|
def test_make_base_create_command_includes_archive_name_format_with_placeholders_in_borg_command():
|
||||||
repository_archive_pattern = 'repo::Documents_{hostname}-{now}' # noqa: FS003
|
repository_archive_pattern = 'repo::Documents_{hostname}-{now}' # noqa: FS003
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(repository_archive_pattern,)
|
(repository_archive_pattern,)
|
||||||
)
|
)
|
||||||
@@ -881,13 +756,13 @@ def test_make_base_create_command_includes_archive_name_format_with_placeholders
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_repository_and_archive_name_format_with_placeholders_in_borg_command():
|
def test_make_base_create_command_includes_repository_and_archive_name_format_with_placeholders_in_borg_command():
|
||||||
repository_archive_pattern = '{fqdn}::Documents_{hostname}-{now}' # noqa: FS003
|
repository_archive_pattern = '{fqdn}::Documents_{hostname}-{now}' # noqa: FS003
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(repository_archive_pattern,)
|
(repository_archive_pattern,)
|
||||||
)
|
)
|
||||||
@@ -913,13 +788,13 @@ def test_make_base_create_command_includes_repository_and_archive_name_format_wi
|
|||||||
|
|
||||||
def test_make_base_create_command_includes_extra_borg_options_in_borg_command():
|
def test_make_base_create_command_includes_extra_borg_options_in_borg_command():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('FOO')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('FOO')
|
||||||
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
flexmock(module.flags).should_receive('get_default_archive_name_format').and_return(
|
||||||
'{hostname}'
|
'{hostname}'
|
||||||
)
|
)
|
||||||
flexmock(module.feature).should_receive('available').and_return(True)
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
|
||||||
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
|
||||||
)
|
)
|
||||||
@@ -945,7 +820,9 @@ def test_make_base_create_command_includes_extra_borg_options_in_borg_command():
|
|||||||
|
|
||||||
def test_make_base_create_command_with_non_existent_directory_and_source_directories_must_exist_raises():
|
def test_make_base_create_command_with_non_existent_directory_and_source_directories_must_exist_raises():
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
||||||
flexmock(module).should_receive('check_all_root_patterns_exist').and_raise(ValueError)
|
flexmock(module.borgmatic.borg.pattern).should_receive(
|
||||||
|
'check_all_root_patterns_exist'
|
||||||
|
).and_raise(ValueError)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
module.make_base_create_command(
|
module.make_base_create_command(
|
||||||
@@ -1571,22 +1448,3 @@ def test_create_archive_calls_borg_with_working_directory():
|
|||||||
global_arguments=flexmock(),
|
global_arguments=flexmock(),
|
||||||
borgmatic_runtime_directory='/borgmatic/run',
|
borgmatic_runtime_directory='/borgmatic/run',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_check_all_root_patterns_exist_with_existent_pattern_path_does_not_raise():
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
|
||||||
|
|
||||||
module.check_all_root_patterns_exist([Pattern('foo')])
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_all_root_patterns_exist_with_non_root_pattern_skips_existence_check():
|
|
||||||
flexmock(module.os.path).should_receive('exists').never()
|
|
||||||
|
|
||||||
module.check_all_root_patterns_exist([Pattern('foo', Pattern_type.INCLUDE)])
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_all_root_patterns_exist_with_non_existent_pattern_path_raises():
|
|
||||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.check_all_root_patterns_exist([Pattern('foo')])
|
|
||||||
|
|||||||
@@ -327,3 +327,100 @@ def test_omit_flag_and_value_without_flag_present_passes_through_arguments():
|
|||||||
'create',
|
'create',
|
||||||
'--other',
|
'--other',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_includes_exclude_caches_when_true_in_config():
|
||||||
|
exclude_flags = module.make_exclude_flags(config={'exclude_caches': True})
|
||||||
|
|
||||||
|
assert exclude_flags == ('--exclude-caches',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_does_not_include_exclude_caches_when_false_in_config():
|
||||||
|
exclude_flags = module.make_exclude_flags(config={'exclude_caches': False})
|
||||||
|
|
||||||
|
assert exclude_flags == ()
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_includes_exclude_if_present_when_in_config():
|
||||||
|
exclude_flags = module.make_exclude_flags(
|
||||||
|
config={'exclude_if_present': ['exclude_me', 'also_me']}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exclude_flags == (
|
||||||
|
'--exclude-if-present',
|
||||||
|
'exclude_me',
|
||||||
|
'--exclude-if-present',
|
||||||
|
'also_me',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_includes_keep_exclude_tags_when_true_in_config():
|
||||||
|
exclude_flags = module.make_exclude_flags(config={'keep_exclude_tags': True})
|
||||||
|
|
||||||
|
assert exclude_flags == ('--keep-exclude-tags',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_does_not_include_keep_exclude_tags_when_false_in_config():
|
||||||
|
exclude_flags = module.make_exclude_flags(config={'keep_exclude_tags': False})
|
||||||
|
|
||||||
|
assert exclude_flags == ()
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_includes_exclude_nodump_when_true_in_config():
|
||||||
|
exclude_flags = module.make_exclude_flags(config={'exclude_nodump': True})
|
||||||
|
|
||||||
|
assert exclude_flags == ('--exclude-nodump',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_does_not_include_exclude_nodump_when_false_in_config():
|
||||||
|
exclude_flags = module.make_exclude_flags(config={'exclude_nodump': False})
|
||||||
|
|
||||||
|
assert exclude_flags == ()
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_exclude_flags_is_empty_when_config_has_no_excludes():
|
||||||
|
exclude_flags = module.make_exclude_flags(config={})
|
||||||
|
|
||||||
|
assert exclude_flags == ()
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_list_filter_flags_with_debug_and_feature_available_includes_plus_and_minus():
|
||||||
|
flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
|
||||||
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
|
|
||||||
|
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME+-'
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_list_filter_flags_with_info_and_feature_available_omits_plus_and_minus():
|
||||||
|
flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
|
||||||
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
|
|
||||||
|
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME'
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_list_filter_flags_with_debug_and_feature_available_and_dry_run_includes_plus_and_minus():
|
||||||
|
flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
|
||||||
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
|
|
||||||
|
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=True) == 'AME+-'
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_list_filter_flags_with_info_and_feature_available_and_dry_run_includes_plus_and_minus():
|
||||||
|
flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
|
||||||
|
flexmock(module.feature).should_receive('available').and_return(True)
|
||||||
|
|
||||||
|
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=True) == 'AME+-'
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_list_filter_flags_with_debug_and_feature_not_available_includes_x():
|
||||||
|
flexmock(module.logger).should_receive('isEnabledFor').and_return(True)
|
||||||
|
flexmock(module.feature).should_receive('available').and_return(False)
|
||||||
|
|
||||||
|
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AMEx-'
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_list_filter_flags_with_info_and_feature_not_available_omits_x():
|
||||||
|
flexmock(module.logger).should_receive('isEnabledFor').and_return(False)
|
||||||
|
flexmock(module.feature).should_receive('available').and_return(False)
|
||||||
|
|
||||||
|
assert module.make_list_filter_flags(local_borg_version=flexmock(), dry_run=False) == 'AME-'
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import pytest
|
||||||
|
from flexmock import flexmock
|
||||||
|
|
||||||
|
from borgmatic.borg import pattern as module
|
||||||
|
from borgmatic.borg.pattern import Pattern, Pattern_style, Pattern_type
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_patterns_file_writes_pattern_lines():
|
||||||
|
temporary_file = flexmock(name='filename', flush=lambda: None)
|
||||||
|
temporary_file.should_receive('write').with_args('R /foo\n+ sh:/foo/bar')
|
||||||
|
flexmock(module.tempfile).should_receive('NamedTemporaryFile').and_return(temporary_file)
|
||||||
|
|
||||||
|
module.write_patterns_file(
|
||||||
|
[Pattern('/foo'), Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.SHELL)],
|
||||||
|
borgmatic_runtime_directory='/run/user/0',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_patterns_file_with_empty_exclude_patterns_does_not_raise():
|
||||||
|
module.write_patterns_file([], borgmatic_runtime_directory='/run/user/0')
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_patterns_file_appends_to_existing():
|
||||||
|
patterns_file = flexmock(name='filename', flush=lambda: None)
|
||||||
|
patterns_file.should_receive('write').with_args('\n')
|
||||||
|
patterns_file.should_receive('write').with_args('R /foo\n+ /foo/bar')
|
||||||
|
flexmock(module.tempfile).should_receive('NamedTemporaryFile').never()
|
||||||
|
|
||||||
|
module.write_patterns_file(
|
||||||
|
[Pattern('/foo'), Pattern('/foo/bar', Pattern_type.INCLUDE)],
|
||||||
|
borgmatic_runtime_directory='/run/user/0',
|
||||||
|
patterns_file=patterns_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_all_root_patterns_exist_with_existent_pattern_path_does_not_raise():
|
||||||
|
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||||
|
|
||||||
|
module.check_all_root_patterns_exist([Pattern('foo')])
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_all_root_patterns_exist_with_non_root_pattern_skips_existence_check():
|
||||||
|
flexmock(module.os.path).should_receive('exists').never()
|
||||||
|
|
||||||
|
module.check_all_root_patterns_exist([Pattern('foo', Pattern_type.INCLUDE)])
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_all_root_patterns_exist_with_non_existent_pattern_path_raises():
|
||||||
|
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.check_all_root_patterns_exist([Pattern('foo')])
|
||||||
@@ -21,9 +21,9 @@ def insert_execute_command_mock(command, working_directory=None, borg_exit_codes
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_archive_dry_run_skips_execution():
|
def test_recreate_archive_dry_run_skips_execution():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -59,9 +59,9 @@ def test_recreate_archive_dry_run_skips_execution():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_calls_borg_with_required_flags():
|
def test_recreate_calls_borg_with_required_flags():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -94,9 +94,9 @@ def test_recreate_calls_borg_with_required_flags():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_remote_path():
|
def test_recreate_with_remote_path():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -129,9 +129,9 @@ def test_recreate_with_remote_path():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_lock_wait():
|
def test_recreate_with_lock_wait():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -163,9 +163,9 @@ def test_recreate_with_lock_wait():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_log_info():
|
def test_recreate_with_log_info():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -199,9 +199,9 @@ def test_recreate_with_log_info():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_log_debug():
|
def test_recreate_with_log_debug():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -234,9 +234,9 @@ def test_recreate_with_log_debug():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_log_json():
|
def test_recreate_with_log_json():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -268,8 +268,8 @@ def test_recreate_with_log_json():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_list_config_calls_borg_with_list_flag():
|
def test_recreate_with_list_config_calls_borg_with_list_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -280,7 +280,9 @@ def test_recreate_with_list_config_calls_borg_with_list_flag():
|
|||||||
'repo',
|
'repo',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
flexmock(module).should_receive('make_list_filter_flags').and_return('AME+-')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return(
|
||||||
|
'AME+-'
|
||||||
|
)
|
||||||
insert_execute_command_mock(
|
insert_execute_command_mock(
|
||||||
('borg', 'recreate', '--list', '--filter', 'AME+-', '--repo', 'repo')
|
('borg', 'recreate', '--list', '--filter', 'AME+-', '--repo', 'repo')
|
||||||
)
|
)
|
||||||
@@ -304,8 +306,8 @@ def test_recreate_with_list_config_calls_borg_with_list_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_patterns_from_flag():
|
def test_recreate_with_patterns_from_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -341,8 +343,8 @@ def test_recreate_with_patterns_from_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_exclude_flags():
|
def test_recreate_with_exclude_flags():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -353,7 +355,9 @@ def test_recreate_with_exclude_flags():
|
|||||||
'repo',
|
'repo',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
flexmock(module).should_receive('make_exclude_flags').and_return(('--exclude', 'pattern'))
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(
|
||||||
|
('--exclude', 'pattern')
|
||||||
|
)
|
||||||
insert_execute_command_mock(('borg', 'recreate', '--exclude', 'pattern', '--repo', 'repo'))
|
insert_execute_command_mock(('borg', 'recreate', '--exclude', 'pattern', '--repo', 'repo'))
|
||||||
|
|
||||||
module.recreate_archive(
|
module.recreate_archive(
|
||||||
@@ -375,9 +379,9 @@ def test_recreate_with_exclude_flags():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_target_flag():
|
def test_recreate_with_target_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -409,9 +413,9 @@ def test_recreate_with_target_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_comment_flag():
|
def test_recreate_with_comment_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -445,9 +449,9 @@ def test_recreate_with_comment_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_timestamp_flag():
|
def test_recreate_with_timestamp_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -481,9 +485,9 @@ def test_recreate_with_timestamp_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_compression_flag():
|
def test_recreate_with_compression_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -515,9 +519,9 @@ def test_recreate_with_compression_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_chunker_params_flag():
|
def test_recreate_with_chunker_params_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -551,9 +555,9 @@ def test_recreate_with_chunker_params_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_recompress_flag():
|
def test_recreate_with_recompress_flag():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -585,9 +589,9 @@ def test_recreate_with_recompress_flag():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_match_archives_star():
|
def test_recreate_with_match_archives_star():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -619,9 +623,9 @@ def test_recreate_with_match_archives_star():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_match_archives_regex():
|
def test_recreate_with_match_archives_regex():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -653,9 +657,9 @@ def test_recreate_with_match_archives_regex():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_match_archives_shell():
|
def test_recreate_with_match_archives_shell():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
@@ -687,9 +691,9 @@ def test_recreate_with_match_archives_shell():
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_match_archives_and_feature_available_calls_borg_with_match_archives():
|
def test_recreate_with_match_archives_and_feature_available_calls_borg_with_match_archives():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').with_args(
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').with_args(
|
||||||
'foo-*', None, '1.2.3'
|
'foo-*', None, '1.2.3'
|
||||||
).and_return(('--match-archives', 'foo-*'))
|
).and_return(('--match-archives', 'foo-*'))
|
||||||
@@ -719,9 +723,9 @@ def test_recreate_with_match_archives_and_feature_available_calls_borg_with_matc
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_archives_flag_and_feature_available_calls_borg_with_match_archives():
|
def test_recreate_with_archives_flag_and_feature_available_calls_borg_with_match_archives():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').with_args(
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').with_args(
|
||||||
'archive', None, '1.2.3'
|
'archive', None, '1.2.3'
|
||||||
).and_return(('--match-archives', 'archive'))
|
).and_return(('--match-archives', 'archive'))
|
||||||
@@ -753,9 +757,9 @@ def test_recreate_with_archives_flag_and_feature_available_calls_borg_with_match
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_match_archives_and_feature_not_available_calls_borg_without_match_archives():
|
def test_recreate_with_match_archives_and_feature_not_available_calls_borg_without_match_archives():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').never()
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').never()
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(False)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(False)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return(
|
flexmock(module.borgmatic.borg.flags).should_receive('make_repository_flags').and_return(
|
||||||
@@ -783,9 +787,9 @@ def test_recreate_with_match_archives_and_feature_not_available_calls_borg_witho
|
|||||||
|
|
||||||
|
|
||||||
def test_recreate_with_archives_flags_and_feature_not_available_calls_borg_with_combined_repo_and_archive():
|
def test_recreate_with_archives_flags_and_feature_not_available_calls_borg_with_combined_repo_and_archive():
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_exclude_flags').and_return(())
|
flexmock(module.borgmatic.borg.flags).should_receive('make_exclude_flags').and_return(())
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('write_patterns_file').and_return(None)
|
flexmock(module.borgmatic.borg.pattern).should_receive('write_patterns_file').and_return(None)
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('make_list_filter_flags').and_return('')
|
flexmock(module.borgmatic.borg.flags).should_receive('make_list_filter_flags').and_return('')
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').never()
|
flexmock(module.borgmatic.borg.flags).should_receive('make_match_archives_flags').never()
|
||||||
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(False)
|
flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(False)
|
||||||
flexmock(module.borgmatic.borg.flags).should_receive(
|
flexmock(module.borgmatic.borg.flags).should_receive(
|
||||||
|
|||||||
@@ -465,8 +465,10 @@ def test_run_configuration_logs_actions_error():
|
|||||||
flexmock(module).should_receive('Monitoring_hooks').and_return(flexmock())
|
flexmock(module).should_receive('Monitoring_hooks').and_return(flexmock())
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||||
expected_results = [flexmock()]
|
expected_results = [flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').and_return(expected_results)
|
flexmock(module).should_receive('log_error_records').and_return(
|
||||||
|
expected_results[:1]
|
||||||
|
).and_return(expected_results[1:])
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
@@ -532,7 +534,7 @@ def test_run_configuration_logs_on_error_hook_error():
|
|||||||
expected_results = [flexmock(), flexmock()]
|
expected_results = [flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').and_return(
|
flexmock(module).should_receive('log_error_records').and_return(
|
||||||
expected_results[:1]
|
expected_results[:1]
|
||||||
).and_return(expected_results[1:])
|
).and_return(expected_results[1:2]).and_return(expected_results[2:])
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||||
config = {'repositories': [{'path': 'foo'}]}
|
config = {'repositories': [{'path': 'foo'}]}
|
||||||
@@ -601,8 +603,10 @@ def test_run_configuration_bails_for_on_error_hook_soft_failure():
|
|||||||
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
|
error = subprocess.CalledProcessError(borgmatic.hooks.command.SOFT_FAIL_EXIT_CODE, 'try again')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
flexmock(module.command).should_receive('execute_hooks').and_raise(error)
|
flexmock(module.command).should_receive('execute_hooks').and_raise(error)
|
||||||
expected_results = [flexmock()]
|
expected_results = [flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').and_return(expected_results)
|
flexmock(module).should_receive('log_error_records').and_return(
|
||||||
|
expected_results[:1]
|
||||||
|
).and_return(expected_results[1:])
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
flexmock(module).should_receive('run_actions').and_raise(OSError)
|
||||||
config = {'repositories': [{'path': 'foo'}]}
|
config = {'repositories': [{'path': 'foo'}]}
|
||||||
@@ -654,11 +658,14 @@ def test_run_configuration_retries_hard_error():
|
|||||||
levelno=logging.WARNING,
|
levelno=logging.WARNING,
|
||||||
log_command_error_output=True,
|
log_command_error_output=True,
|
||||||
).and_return([flexmock()])
|
).and_return([flexmock()])
|
||||||
error_logs = [flexmock()]
|
error_logs = [flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').with_args(
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
'Error running actions for repository',
|
'Error running actions for repository',
|
||||||
OSError,
|
OSError,
|
||||||
).and_return(error_logs)
|
).and_return(error_logs[:1]).ordered()
|
||||||
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
|
'Error running configuration',
|
||||||
|
).and_return(error_logs[1:]).ordered()
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
config = {'repositories': [{'path': 'foo'}], 'retries': 1}
|
config = {'repositories': [{'path': 'foo'}], 'retries': 1}
|
||||||
@@ -680,13 +687,16 @@ def test_run_configuration_retries_repositories_in_order():
|
|||||||
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_actions').and_raise(OSError).times(2)
|
flexmock(module).should_receive('run_actions').and_raise(OSError).times(2)
|
||||||
expected_results = [flexmock(), flexmock()]
|
expected_results = [flexmock(), flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').with_args(
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
'Error running actions for repository', OSError
|
'Error running actions for repository', OSError
|
||||||
).and_return(expected_results[:1]).ordered()
|
).and_return(expected_results[:1]).ordered()
|
||||||
flexmock(module).should_receive('log_error_records').with_args(
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
'Error running actions for repository', OSError
|
'Error running actions for repository', OSError
|
||||||
).and_return(expected_results[1:]).ordered()
|
).and_return(expected_results[1:2]).ordered()
|
||||||
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
|
'Error running configuration',
|
||||||
|
).and_return(expected_results[2:]).ordered()
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
config = {'repositories': [{'path': 'foo'}, {'path': 'bar'}]}
|
config = {'repositories': [{'path': 'foo'}, {'path': 'bar'}]}
|
||||||
@@ -728,6 +738,10 @@ def test_run_configuration_retries_round_robin():
|
|||||||
flexmock(module).should_receive('log_error_records').with_args(
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
'Error running actions for repository', OSError
|
'Error running actions for repository', OSError
|
||||||
).and_return(bar_error_logs).ordered()
|
).and_return(bar_error_logs).ordered()
|
||||||
|
config_error_logs = [flexmock()]
|
||||||
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
|
'Error running configuration',
|
||||||
|
).and_return(config_error_logs).ordered()
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
config = {
|
config = {
|
||||||
@@ -741,7 +755,7 @@ def test_run_configuration_retries_round_robin():
|
|||||||
|
|
||||||
results = list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
|
results = list(module.run_configuration('test.yaml', config, ['/tmp/test.yaml'], arguments))
|
||||||
|
|
||||||
assert results == foo_error_logs + bar_error_logs
|
assert results == foo_error_logs + bar_error_logs + config_error_logs
|
||||||
|
|
||||||
|
|
||||||
def test_run_configuration_with_one_retry():
|
def test_run_configuration_with_one_retry():
|
||||||
@@ -766,10 +780,13 @@ def test_run_configuration_with_one_retry():
|
|||||||
levelno=logging.WARNING,
|
levelno=logging.WARNING,
|
||||||
log_command_error_output=True,
|
log_command_error_output=True,
|
||||||
).and_return(flexmock()).ordered()
|
).and_return(flexmock()).ordered()
|
||||||
error_logs = [flexmock()]
|
error_logs = [flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').with_args(
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
'Error running actions for repository', OSError
|
'Error running actions for repository', OSError
|
||||||
).and_return(error_logs).ordered()
|
).and_return(error_logs[:1]).ordered()
|
||||||
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
|
'Error running configuration',
|
||||||
|
).and_return(error_logs[1:]).ordered()
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
config = {
|
config = {
|
||||||
@@ -818,10 +835,13 @@ def test_run_configuration_with_retry_wait_does_backoff_after_each_retry():
|
|||||||
).and_return([flexmock()]).ordered()
|
).and_return([flexmock()]).ordered()
|
||||||
|
|
||||||
flexmock(time).should_receive('sleep').with_args(30).and_return().ordered()
|
flexmock(time).should_receive('sleep').with_args(30).and_return().ordered()
|
||||||
error_logs = [flexmock()]
|
error_logs = [flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').with_args(
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
'Error running actions for repository', OSError
|
'Error running actions for repository', OSError
|
||||||
).and_return(error_logs).ordered()
|
).and_return(error_logs[:1]).ordered()
|
||||||
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
|
'Error running configuration',
|
||||||
|
).and_return(error_logs[1:]).ordered()
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
config = {
|
config = {
|
||||||
@@ -867,10 +887,13 @@ def test_run_configuration_with_multiple_repositories_retries_with_timeout():
|
|||||||
|
|
||||||
# Sleep before retrying bar (and failing)
|
# Sleep before retrying bar (and failing)
|
||||||
flexmock(time).should_receive('sleep').with_args(10).and_return().ordered()
|
flexmock(time).should_receive('sleep').with_args(10).and_return().ordered()
|
||||||
error_logs = [flexmock()]
|
error_logs = [flexmock(), flexmock()]
|
||||||
flexmock(module).should_receive('log_error_records').with_args(
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
'Error running actions for repository', OSError
|
'Error running actions for repository', OSError
|
||||||
).and_return(error_logs).ordered()
|
).and_return(error_logs[:1]).ordered()
|
||||||
|
flexmock(module).should_receive('log_error_records').with_args(
|
||||||
|
'Error running configuration',
|
||||||
|
).and_return(error_logs[1:]).ordered()
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks')
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
config = {
|
config = {
|
||||||
@@ -891,6 +914,9 @@ def test_run_configuration_with_multiple_repositories_retries_with_timeout():
|
|||||||
def test_run_actions_runs_repo_create():
|
def test_run_actions_runs_repo_create():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.repo_create).should_receive('run_repo_create').once()
|
flexmock(borgmatic.actions.repo_create).should_receive('run_repo_create').once()
|
||||||
|
|
||||||
@@ -914,6 +940,9 @@ def test_run_actions_runs_repo_create():
|
|||||||
def test_run_actions_adds_label_file_to_hook_context():
|
def test_run_actions_adds_label_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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
expected = flexmock()
|
expected = flexmock()
|
||||||
flexmock(borgmatic.actions.create).should_receive('run_create').with_args(
|
flexmock(borgmatic.actions.create).should_receive('run_create').with_args(
|
||||||
@@ -947,6 +976,9 @@ def test_run_actions_adds_label_file_to_hook_context():
|
|||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
expected = flexmock()
|
expected = flexmock()
|
||||||
flexmock(borgmatic.actions.create).should_receive('run_create').with_args(
|
flexmock(borgmatic.actions.create).should_receive('run_create').with_args(
|
||||||
@@ -980,6 +1012,9 @@ def test_run_actions_adds_log_file_to_hook_context():
|
|||||||
def test_run_actions_runs_transfer():
|
def test_run_actions_runs_transfer():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.transfer).should_receive('run_transfer').once()
|
flexmock(borgmatic.actions.transfer).should_receive('run_transfer').once()
|
||||||
|
|
||||||
@@ -1000,6 +1035,9 @@ def test_run_actions_runs_transfer():
|
|||||||
def test_run_actions_runs_create():
|
def test_run_actions_runs_create():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
expected = flexmock()
|
expected = flexmock()
|
||||||
flexmock(borgmatic.actions.create).should_receive('run_create').and_yield(expected).once()
|
flexmock(borgmatic.actions.create).should_receive('run_create').and_yield(expected).once()
|
||||||
@@ -1019,10 +1057,26 @@ def test_run_actions_runs_create():
|
|||||||
assert result == (expected,)
|
assert result == (expected,)
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_with_skip_actions_skips_create():
|
def test_run_actions_with_skip_actions_does_not_run_action_or_action_command_hooks():
|
||||||
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(['create'])
|
flexmock(module).should_receive('get_skip_actions').and_return(['create'])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
|
flexmock(module.command).should_receive('Before_after_hooks').with_args(
|
||||||
|
command_hooks=object,
|
||||||
|
before_after='action',
|
||||||
|
umask=object,
|
||||||
|
working_directory=object,
|
||||||
|
dry_run=object,
|
||||||
|
action_names=object,
|
||||||
|
configuration_filename=object,
|
||||||
|
repository_label=object,
|
||||||
|
log_file=object,
|
||||||
|
repositories=object,
|
||||||
|
repository=object,
|
||||||
|
).never()
|
||||||
flexmock(borgmatic.actions.create).should_receive('run_create').never()
|
flexmock(borgmatic.actions.create).should_receive('run_create').never()
|
||||||
|
|
||||||
tuple(
|
tuple(
|
||||||
@@ -1042,6 +1096,9 @@ def test_run_actions_with_skip_actions_skips_create():
|
|||||||
def test_run_actions_runs_recreate():
|
def test_run_actions_runs_recreate():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
|
|
||||||
flexmock(borgmatic.actions.recreate).should_receive('run_recreate').once()
|
flexmock(borgmatic.actions.recreate).should_receive('run_recreate').once()
|
||||||
@@ -1060,29 +1117,12 @@ def test_run_actions_runs_recreate():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_with_skip_actions_skips_recreate():
|
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
|
||||||
flexmock(module).should_receive('get_skip_actions').and_return(['recreate'])
|
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
|
||||||
flexmock(borgmatic.actions.recreate).should_receive('run_recreate').never()
|
|
||||||
|
|
||||||
tuple(
|
|
||||||
module.run_actions(
|
|
||||||
arguments={'global': flexmock(dry_run=False), 'recreate': flexmock()},
|
|
||||||
config_filename=flexmock(),
|
|
||||||
config={'repositories': [], 'skip_actions': ['recreate']},
|
|
||||||
config_paths=[],
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
repository={'path': 'repo'},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_runs_prune():
|
def test_run_actions_runs_prune():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.prune).should_receive('run_prune').once()
|
flexmock(borgmatic.actions.prune).should_receive('run_prune').once()
|
||||||
|
|
||||||
@@ -1100,29 +1140,12 @@ def test_run_actions_runs_prune():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_with_skip_actions_skips_prune():
|
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
|
||||||
flexmock(module).should_receive('get_skip_actions').and_return(['prune'])
|
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
|
||||||
flexmock(borgmatic.actions.prune).should_receive('run_prune').never()
|
|
||||||
|
|
||||||
tuple(
|
|
||||||
module.run_actions(
|
|
||||||
arguments={'global': flexmock(dry_run=False), 'prune': flexmock()},
|
|
||||||
config_filename=flexmock(),
|
|
||||||
config={'repositories': [], 'skip_actions': ['prune']},
|
|
||||||
config_paths=[],
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
repository={'path': 'repo'},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_runs_compact():
|
def test_run_actions_runs_compact():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.compact).should_receive('run_compact').once()
|
flexmock(borgmatic.actions.compact).should_receive('run_compact').once()
|
||||||
|
|
||||||
@@ -1140,29 +1163,12 @@ def test_run_actions_runs_compact():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_with_skip_actions_skips_compact():
|
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
|
||||||
flexmock(module).should_receive('get_skip_actions').and_return(['compact'])
|
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
|
||||||
flexmock(borgmatic.actions.compact).should_receive('run_compact').never()
|
|
||||||
|
|
||||||
tuple(
|
|
||||||
module.run_actions(
|
|
||||||
arguments={'global': flexmock(dry_run=False), 'compact': flexmock()},
|
|
||||||
config_filename=flexmock(),
|
|
||||||
config={'repositories': [], 'skip_actions': ['compact']},
|
|
||||||
config_paths=[],
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
repository={'path': 'repo'},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_runs_check_when_repository_enabled_for_checks():
|
def test_run_actions_runs_check_when_repository_enabled_for_checks():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(True)
|
flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(True)
|
||||||
flexmock(borgmatic.actions.check).should_receive('run_check').once()
|
flexmock(borgmatic.actions.check).should_receive('run_check').once()
|
||||||
@@ -1184,6 +1190,9 @@ def test_run_actions_runs_check_when_repository_enabled_for_checks():
|
|||||||
def test_run_actions_skips_check_when_repository_not_enabled_for_checks():
|
def test_run_actions_skips_check_when_repository_not_enabled_for_checks():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(False)
|
flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(False)
|
||||||
flexmock(borgmatic.actions.check).should_receive('run_check').never()
|
flexmock(borgmatic.actions.check).should_receive('run_check').never()
|
||||||
@@ -1202,30 +1211,12 @@ def test_run_actions_skips_check_when_repository_not_enabled_for_checks():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_with_skip_actions_skips_check():
|
|
||||||
flexmock(module).should_receive('add_custom_log_levels')
|
|
||||||
flexmock(module).should_receive('get_skip_actions').and_return(['check'])
|
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
|
||||||
flexmock(module.checks).should_receive('repository_enabled_for_checks').and_return(True)
|
|
||||||
flexmock(borgmatic.actions.check).should_receive('run_check').never()
|
|
||||||
|
|
||||||
tuple(
|
|
||||||
module.run_actions(
|
|
||||||
arguments={'global': flexmock(dry_run=False), 'check': flexmock()},
|
|
||||||
config_filename=flexmock(),
|
|
||||||
config={'repositories': [], 'skip_actions': ['check']},
|
|
||||||
config_paths=[],
|
|
||||||
local_path=flexmock(),
|
|
||||||
remote_path=flexmock(),
|
|
||||||
local_borg_version=flexmock(),
|
|
||||||
repository={'path': 'repo'},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_runs_extract():
|
def test_run_actions_runs_extract():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.extract).should_receive('run_extract').once()
|
flexmock(borgmatic.actions.extract).should_receive('run_extract').once()
|
||||||
|
|
||||||
@@ -1246,6 +1237,9 @@ def test_run_actions_runs_extract():
|
|||||||
def test_run_actions_runs_export_tar():
|
def test_run_actions_runs_export_tar():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.export_tar).should_receive('run_export_tar').once()
|
flexmock(borgmatic.actions.export_tar).should_receive('run_export_tar').once()
|
||||||
|
|
||||||
@@ -1266,6 +1260,9 @@ def test_run_actions_runs_export_tar():
|
|||||||
def test_run_actions_runs_mount():
|
def test_run_actions_runs_mount():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.mount).should_receive('run_mount').once()
|
flexmock(borgmatic.actions.mount).should_receive('run_mount').once()
|
||||||
|
|
||||||
@@ -1286,6 +1283,9 @@ def test_run_actions_runs_mount():
|
|||||||
def test_run_actions_runs_restore():
|
def test_run_actions_runs_restore():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.restore).should_receive('run_restore').once()
|
flexmock(borgmatic.actions.restore).should_receive('run_restore').once()
|
||||||
|
|
||||||
@@ -1306,6 +1306,9 @@ def test_run_actions_runs_restore():
|
|||||||
def test_run_actions_runs_repo_list():
|
def test_run_actions_runs_repo_list():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
expected = flexmock()
|
expected = flexmock()
|
||||||
flexmock(borgmatic.actions.repo_list).should_receive('run_repo_list').and_yield(expected).once()
|
flexmock(borgmatic.actions.repo_list).should_receive('run_repo_list').and_yield(expected).once()
|
||||||
@@ -1328,6 +1331,9 @@ def test_run_actions_runs_repo_list():
|
|||||||
def test_run_actions_runs_list():
|
def test_run_actions_runs_list():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
expected = flexmock()
|
expected = flexmock()
|
||||||
flexmock(borgmatic.actions.list).should_receive('run_list').and_yield(expected).once()
|
flexmock(borgmatic.actions.list).should_receive('run_list').and_yield(expected).once()
|
||||||
@@ -1350,6 +1356,9 @@ def test_run_actions_runs_list():
|
|||||||
def test_run_actions_runs_repo_info():
|
def test_run_actions_runs_repo_info():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
expected = flexmock()
|
expected = flexmock()
|
||||||
flexmock(borgmatic.actions.repo_info).should_receive('run_repo_info').and_yield(expected).once()
|
flexmock(borgmatic.actions.repo_info).should_receive('run_repo_info').and_yield(expected).once()
|
||||||
@@ -1372,6 +1381,9 @@ def test_run_actions_runs_repo_info():
|
|||||||
def test_run_actions_runs_info():
|
def test_run_actions_runs_info():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
expected = flexmock()
|
expected = flexmock()
|
||||||
flexmock(borgmatic.actions.info).should_receive('run_info').and_yield(expected).once()
|
flexmock(borgmatic.actions.info).should_receive('run_info').and_yield(expected).once()
|
||||||
@@ -1394,6 +1406,9 @@ def test_run_actions_runs_info():
|
|||||||
def test_run_actions_runs_break_lock():
|
def test_run_actions_runs_break_lock():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.break_lock).should_receive('run_break_lock').once()
|
flexmock(borgmatic.actions.break_lock).should_receive('run_break_lock').once()
|
||||||
|
|
||||||
@@ -1414,6 +1429,9 @@ def test_run_actions_runs_break_lock():
|
|||||||
def test_run_actions_runs_export_key():
|
def test_run_actions_runs_export_key():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.export_key).should_receive('run_export_key').once()
|
flexmock(borgmatic.actions.export_key).should_receive('run_export_key').once()
|
||||||
|
|
||||||
@@ -1434,6 +1452,9 @@ def test_run_actions_runs_export_key():
|
|||||||
def test_run_actions_runs_import_key():
|
def test_run_actions_runs_import_key():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.import_key).should_receive('run_import_key').once()
|
flexmock(borgmatic.actions.import_key).should_receive('run_import_key').once()
|
||||||
|
|
||||||
@@ -1454,6 +1475,9 @@ def test_run_actions_runs_import_key():
|
|||||||
def test_run_actions_runs_change_passphrase():
|
def test_run_actions_runs_change_passphrase():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.change_passphrase).should_receive('run_change_passphrase').once()
|
flexmock(borgmatic.actions.change_passphrase).should_receive('run_change_passphrase').once()
|
||||||
|
|
||||||
@@ -1477,6 +1501,9 @@ def test_run_actions_runs_change_passphrase():
|
|||||||
def test_run_actions_runs_delete():
|
def test_run_actions_runs_delete():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.delete).should_receive('run_delete').once()
|
flexmock(borgmatic.actions.delete).should_receive('run_delete').once()
|
||||||
|
|
||||||
@@ -1497,6 +1524,9 @@ def test_run_actions_runs_delete():
|
|||||||
def test_run_actions_runs_repo_delete():
|
def test_run_actions_runs_repo_delete():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.repo_delete).should_receive('run_repo_delete').once()
|
flexmock(borgmatic.actions.repo_delete).should_receive('run_repo_delete').once()
|
||||||
|
|
||||||
@@ -1520,6 +1550,9 @@ def test_run_actions_runs_repo_delete():
|
|||||||
def test_run_actions_runs_borg():
|
def test_run_actions_runs_borg():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.borg).should_receive('run_borg').once()
|
flexmock(borgmatic.actions.borg).should_receive('run_borg').once()
|
||||||
|
|
||||||
@@ -1540,6 +1573,9 @@ def test_run_actions_runs_borg():
|
|||||||
def test_run_actions_runs_multiple_actions_in_argument_order():
|
def test_run_actions_runs_multiple_actions_in_argument_order():
|
||||||
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([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
flexmock(borgmatic.actions.borg).should_receive('run_borg').once().ordered()
|
flexmock(borgmatic.actions.borg).should_receive('run_borg').once().ordered()
|
||||||
flexmock(borgmatic.actions.restore).should_receive('run_restore').once().ordered()
|
flexmock(borgmatic.actions.restore).should_receive('run_restore').once().ordered()
|
||||||
@@ -1562,6 +1598,50 @@ def test_run_actions_runs_multiple_actions_in_argument_order():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_actions_runs_action_hooks_for_one_action_at_a_time():
|
||||||
|
flexmock(module).should_receive('add_custom_log_levels')
|
||||||
|
flexmock(module).should_receive('get_skip_actions').and_return([])
|
||||||
|
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
|
||||||
|
flexmock()
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('Before_after_hooks').and_return(flexmock())
|
||||||
|
|
||||||
|
for action_name in ('borg', 'restore'):
|
||||||
|
flexmock(module.command).should_receive('Before_after_hooks').with_args(
|
||||||
|
command_hooks=object,
|
||||||
|
before_after='action',
|
||||||
|
umask=object,
|
||||||
|
working_directory=object,
|
||||||
|
dry_run=object,
|
||||||
|
action_names=(action_name,),
|
||||||
|
configuration_filename=object,
|
||||||
|
repository_label=object,
|
||||||
|
log_file=object,
|
||||||
|
repositories=object,
|
||||||
|
repository=object,
|
||||||
|
).and_return(flexmock()).once()
|
||||||
|
|
||||||
|
flexmock(borgmatic.actions.borg).should_receive('run_borg')
|
||||||
|
flexmock(borgmatic.actions.restore).should_receive('run_restore')
|
||||||
|
|
||||||
|
tuple(
|
||||||
|
module.run_actions(
|
||||||
|
arguments={
|
||||||
|
'global': flexmock(dry_run=False),
|
||||||
|
'borg': flexmock(),
|
||||||
|
'restore': flexmock(),
|
||||||
|
},
|
||||||
|
config_filename=flexmock(),
|
||||||
|
config={'repositories': []},
|
||||||
|
config_paths=[],
|
||||||
|
local_path=flexmock(),
|
||||||
|
remote_path=flexmock(),
|
||||||
|
local_borg_version=flexmock(),
|
||||||
|
repository={'path': 'repo'},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'resolve_env',
|
'resolve_env',
|
||||||
((True, False),),
|
((True, False),),
|
||||||
@@ -1852,7 +1932,12 @@ def test_collect_highlander_action_summary_logs_error_on_run_validate_failure():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_info_for_success():
|
def test_collect_configuration_run_summary_logs_info_for_success():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['finish']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return([])
|
flexmock(module).should_receive('run_configuration').and_return([])
|
||||||
@@ -1872,7 +1957,12 @@ def test_collect_configuration_run_summary_logs_info_for_success():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_executes_hooks_for_create():
|
def test_collect_configuration_run_summary_executes_hooks_for_create():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['finish']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return([])
|
flexmock(module).should_receive('run_configuration').and_return([])
|
||||||
@@ -1895,7 +1985,12 @@ def test_collect_configuration_run_summary_executes_hooks_for_create():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_info_for_success_with_extract():
|
def test_collect_configuration_run_summary_logs_info_for_success_with_extract():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['finish']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return([])
|
flexmock(module).should_receive('run_configuration').and_return([])
|
||||||
@@ -1938,7 +2033,12 @@ def test_collect_configuration_run_summary_logs_extract_with_repository_error():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_info_for_success_with_mount():
|
def test_collect_configuration_run_summary_logs_info_for_success_with_mount():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['finish']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return([])
|
flexmock(module).should_receive('run_configuration').and_return([])
|
||||||
@@ -1984,7 +2084,12 @@ def test_collect_configuration_run_summary_logs_mount_with_repository_error():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_missing_configs_error():
|
def test_collect_configuration_run_summary_logs_missing_configs_error():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['fail']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
arguments = {'global': flexmock(config_paths=[])}
|
arguments = {'global': flexmock(config_paths=[])}
|
||||||
expected_logs = (flexmock(),)
|
expected_logs = (flexmock(),)
|
||||||
@@ -1999,9 +2104,14 @@ def test_collect_configuration_run_summary_logs_missing_configs_error():
|
|||||||
assert logs == expected_logs
|
assert logs == expected_logs
|
||||||
|
|
||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_pre_hook_error():
|
def test_collect_configuration_run_summary_logs_before_hook_error():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['fail']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks').and_raise(ValueError)
|
flexmock(module.command).should_receive('execute_hooks').and_raise(ValueError)
|
||||||
expected_logs = (flexmock(),)
|
expected_logs = (flexmock(),)
|
||||||
flexmock(module).should_receive('log_error_records').and_return(expected_logs)
|
flexmock(module).should_receive('log_error_records').and_return(expected_logs)
|
||||||
@@ -2022,9 +2132,14 @@ def test_collect_configuration_run_summary_logs_pre_hook_error():
|
|||||||
assert logs == expected_logs
|
assert logs == expected_logs
|
||||||
|
|
||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_post_hook_error():
|
def test_collect_configuration_run_summary_logs_after_hook_error():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['finish']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks').and_return(None).and_raise(ValueError)
|
flexmock(module.command).should_receive('execute_hooks').and_return(None).and_raise(ValueError)
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return([])
|
flexmock(module).should_receive('run_configuration').and_return([])
|
||||||
@@ -2072,7 +2187,12 @@ def test_collect_configuration_run_summary_logs_for_list_with_archive_and_reposi
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_info_for_success_with_list():
|
def test_collect_configuration_run_summary_logs_info_for_success_with_list():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['finish']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return([])
|
flexmock(module).should_receive('run_configuration').and_return([])
|
||||||
@@ -2095,7 +2215,12 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_list():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_run_configuration_error_logs():
|
def test_collect_configuration_run_summary_logs_run_configuration_error_logs():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['fail']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return(
|
flexmock(module).should_receive('run_configuration').and_return(
|
||||||
@@ -2118,7 +2243,12 @@ def test_collect_configuration_run_summary_logs_run_configuration_error_logs():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_run_umount_error():
|
def test_collect_configuration_run_summary_logs_run_umount_error():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['fail']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return([])
|
flexmock(module).should_receive('run_configuration').and_return([])
|
||||||
@@ -2145,7 +2275,12 @@ def test_collect_configuration_run_summary_logs_run_umount_error():
|
|||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_outputs_merged_json_results():
|
def test_collect_configuration_run_summary_logs_outputs_merged_json_results():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
flexmock(module.command).should_receive('filter_hooks')
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, before='everything', action_names=object
|
||||||
|
)
|
||||||
|
flexmock(module.command).should_receive('filter_hooks').with_args(
|
||||||
|
object, after='everything', action_names=object, state_names=['finish']
|
||||||
|
)
|
||||||
flexmock(module.command).should_receive('execute_hooks')
|
flexmock(module.command).should_receive('execute_hooks')
|
||||||
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
flexmock(module).should_receive('Log_prefix').and_return(flexmock())
|
||||||
flexmock(module).should_receive('run_configuration').and_return(['foo', 'bar']).and_return(
|
flexmock(module).should_receive('run_configuration').and_return(['foo', 'bar']).and_return(
|
||||||
|
|||||||
@@ -167,6 +167,44 @@ def test_make_environment_with_pyinstaller_and_LD_LIBRARY_PATH_ORIG_copies_it_in
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
'before': 'action',
|
||||||
|
'states': ['finish'], # Not actually valid; only valid for "after".
|
||||||
|
'run': ['foo'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'after': 'action',
|
||||||
|
'run': ['bar'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'after': 'action',
|
||||||
|
'states': ['finish'],
|
||||||
|
'run': ['baz'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'after': 'action',
|
||||||
|
'states': ['fail'],
|
||||||
|
'run': ['quux'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{
|
||||||
|
'after': 'action',
|
||||||
|
'state_names': ['finish'],
|
||||||
|
},
|
||||||
|
(
|
||||||
|
{
|
||||||
|
'after': 'action',
|
||||||
|
'run': ['bar'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'after': 'action',
|
||||||
|
'states': ['finish'],
|
||||||
|
'run': ['baz'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_filter_hooks(hooks, filters, expected_hooks):
|
def test_filter_hooks(hooks, filters, expected_hooks):
|
||||||
@@ -336,14 +374,13 @@ def test_before_after_hooks_calls_command_hooks():
|
|||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
before='action',
|
before='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
after='action',
|
after='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
|
state_names=['finish'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('execute_hooks').twice()
|
flexmock(module).should_receive('execute_hooks').twice()
|
||||||
|
|
||||||
@@ -353,7 +390,6 @@ def test_before_after_hooks_calls_command_hooks():
|
|||||||
umask=1234,
|
umask=1234,
|
||||||
working_directory='/working',
|
working_directory='/working',
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
context1='stuff',
|
context1='stuff',
|
||||||
context2='such',
|
context2='such',
|
||||||
@@ -369,14 +405,13 @@ def test_before_after_hooks_with_before_error_runs_after_hook_and_raises():
|
|||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
before='action',
|
before='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
after='action',
|
after='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
|
state_names=['fail'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('execute_hooks').and_raise(OSError).and_return(None)
|
flexmock(module).should_receive('execute_hooks').and_raise(OSError).and_return(None)
|
||||||
flexmock(module).should_receive('considered_soft_failure').and_return(False)
|
flexmock(module).should_receive('considered_soft_failure').and_return(False)
|
||||||
@@ -388,7 +423,6 @@ def test_before_after_hooks_with_before_error_runs_after_hook_and_raises():
|
|||||||
umask=1234,
|
umask=1234,
|
||||||
working_directory='/working',
|
working_directory='/working',
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
context1='stuff',
|
context1='stuff',
|
||||||
context2='such',
|
context2='such',
|
||||||
@@ -396,7 +430,7 @@ def test_before_after_hooks_with_before_error_runs_after_hook_and_raises():
|
|||||||
assert False # This should never get called.
|
assert False # This should never get called.
|
||||||
|
|
||||||
|
|
||||||
def test_before_after_hooks_with_before_soft_failure_does_not_raise():
|
def test_before_after_hooks_with_before_soft_failure_raises():
|
||||||
commands = [
|
commands = [
|
||||||
{'before': 'repository', 'run': ['foo', 'bar']},
|
{'before': 'repository', 'run': ['foo', 'bar']},
|
||||||
{'after': 'repository', 'run': ['baz']},
|
{'after': 'repository', 'run': ['baz']},
|
||||||
@@ -404,30 +438,61 @@ def test_before_after_hooks_with_before_soft_failure_does_not_raise():
|
|||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
before='action',
|
before='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
after='action',
|
after='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
).and_return(flexmock()).once()
|
state_names=['finish'],
|
||||||
|
).never()
|
||||||
flexmock(module).should_receive('execute_hooks').and_raise(OSError)
|
flexmock(module).should_receive('execute_hooks').and_raise(OSError)
|
||||||
flexmock(module).should_receive('considered_soft_failure').and_return(True)
|
flexmock(module).should_receive('considered_soft_failure').and_return(True)
|
||||||
|
|
||||||
with module.Before_after_hooks(
|
with pytest.raises(OSError):
|
||||||
command_hooks=commands,
|
with module.Before_after_hooks(
|
||||||
before_after='action',
|
command_hooks=commands,
|
||||||
umask=1234,
|
before_after='action',
|
||||||
working_directory='/working',
|
umask=1234,
|
||||||
dry_run=False,
|
working_directory='/working',
|
||||||
hook_name='myhook',
|
dry_run=False,
|
||||||
|
action_names=['create'],
|
||||||
|
context1='stuff',
|
||||||
|
context2='such',
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_before_after_hooks_with_wrapped_code_error_runs_after_hook_and_raises():
|
||||||
|
commands = [
|
||||||
|
{'before': 'repository', 'run': ['foo', 'bar']},
|
||||||
|
{'after': 'repository', 'run': ['baz']},
|
||||||
|
]
|
||||||
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
|
commands,
|
||||||
|
before='action',
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
context1='stuff',
|
).and_return(flexmock()).once()
|
||||||
context2='such',
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
):
|
commands,
|
||||||
pass
|
after='action',
|
||||||
|
action_names=['create'],
|
||||||
|
state_names=['fail'],
|
||||||
|
).and_return(flexmock()).once()
|
||||||
|
flexmock(module).should_receive('execute_hooks').twice()
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
with module.Before_after_hooks(
|
||||||
|
command_hooks=commands,
|
||||||
|
before_after='action',
|
||||||
|
umask=1234,
|
||||||
|
working_directory='/working',
|
||||||
|
dry_run=False,
|
||||||
|
action_names=['create'],
|
||||||
|
context1='stuff',
|
||||||
|
context2='such',
|
||||||
|
):
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
|
||||||
def test_before_after_hooks_with_after_error_raises():
|
def test_before_after_hooks_with_after_error_raises():
|
||||||
@@ -438,14 +503,13 @@ def test_before_after_hooks_with_after_error_raises():
|
|||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
before='action',
|
before='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
after='action',
|
after='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
|
state_names=['finish'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('execute_hooks').and_return(None).and_raise(OSError)
|
flexmock(module).should_receive('execute_hooks').and_return(None).and_raise(OSError)
|
||||||
flexmock(module).should_receive('considered_soft_failure').and_return(False)
|
flexmock(module).should_receive('considered_soft_failure').and_return(False)
|
||||||
@@ -457,7 +521,6 @@ def test_before_after_hooks_with_after_error_raises():
|
|||||||
umask=1234,
|
umask=1234,
|
||||||
working_directory='/working',
|
working_directory='/working',
|
||||||
dry_run=False,
|
dry_run=False,
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
context1='stuff',
|
context1='stuff',
|
||||||
context2='such',
|
context2='such',
|
||||||
@@ -465,7 +528,7 @@ def test_before_after_hooks_with_after_error_raises():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_before_after_hooks_with_after_soft_failure_does_not_raise():
|
def test_before_after_hooks_with_after_soft_failure_raises():
|
||||||
commands = [
|
commands = [
|
||||||
{'before': 'repository', 'run': ['foo', 'bar']},
|
{'before': 'repository', 'run': ['foo', 'bar']},
|
||||||
{'after': 'repository', 'run': ['baz']},
|
{'after': 'repository', 'run': ['baz']},
|
||||||
@@ -473,30 +536,29 @@ def test_before_after_hooks_with_after_soft_failure_does_not_raise():
|
|||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
before='action',
|
before='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('filter_hooks').with_args(
|
flexmock(module).should_receive('filter_hooks').with_args(
|
||||||
commands,
|
commands,
|
||||||
after='action',
|
after='action',
|
||||||
hook_name='myhook',
|
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
|
state_names=['finish'],
|
||||||
).and_return(flexmock()).once()
|
).and_return(flexmock()).once()
|
||||||
flexmock(module).should_receive('execute_hooks').and_return(None).and_raise(OSError)
|
flexmock(module).should_receive('execute_hooks').and_return(None).and_raise(OSError)
|
||||||
flexmock(module).should_receive('considered_soft_failure').and_return(True)
|
flexmock(module).should_receive('considered_soft_failure').and_return(True)
|
||||||
|
|
||||||
with module.Before_after_hooks(
|
with pytest.raises(OSError):
|
||||||
command_hooks=commands,
|
with module.Before_after_hooks(
|
||||||
before_after='action',
|
command_hooks=commands,
|
||||||
umask=1234,
|
before_after='action',
|
||||||
working_directory='/working',
|
umask=1234,
|
||||||
dry_run=False,
|
working_directory='/working',
|
||||||
hook_name='myhook',
|
dry_run=False,
|
||||||
action_names=['create'],
|
action_names=['create'],
|
||||||
context1='stuff',
|
context1='stuff',
|
||||||
context2='such',
|
context2='such',
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_considered_soft_failure_treats_soft_fail_exit_code_as_soft_fail():
|
def test_considered_soft_failure_treats_soft_fail_exit_code_as_soft_fail():
|
||||||
@@ -513,3 +575,11 @@ def test_considered_soft_failure_does_not_treat_other_exit_code_as_soft_fail():
|
|||||||
|
|
||||||
def test_considered_soft_failure_does_not_treat_other_exception_type_as_soft_fail():
|
def test_considered_soft_failure_does_not_treat_other_exception_type_as_soft_fail():
|
||||||
assert not module.considered_soft_failure(Exception())
|
assert not module.considered_soft_failure(Exception())
|
||||||
|
|
||||||
|
|
||||||
|
def test_considered_soft_failure_caches_results_and_only_logs_once():
|
||||||
|
error = subprocess.CalledProcessError(module.SOFT_FAIL_EXIT_CODE, 'try again')
|
||||||
|
flexmock(module.logger).should_receive('info').once()
|
||||||
|
|
||||||
|
assert module.considered_soft_failure(error)
|
||||||
|
assert module.considered_soft_failure(error)
|
||||||
|
|||||||
Reference in New Issue
Block a user