When the "read_special" option is true or database hooks are enabled, auto-exclude special files for a "create" action to prevent Borg from hanging (#587).

This commit is contained in:
Dan Helfman 2022-10-03 12:58:13 -07:00
parent 90be5b84b1
commit ae036aebd7
7 changed files with 404 additions and 102 deletions

2
NEWS
View File

@ -1,4 +1,6 @@
1.7.3.dev0
* #587: When the "read_special" option is true or database hooks are enabled, auto-exclude special
files for a "create" action to prevent Borg from hanging.
* #587: Warn when ignoring a configured "read_special" value of false, as true is needed when
database hooks are enabled.

View File

@ -3,6 +3,7 @@ import itertools
import logging
import os
import pathlib
import stat
import tempfile
from borgmatic.borg import environment, feature, flags, state
@ -104,16 +105,21 @@ def deduplicate_directories(directory_devices, additional_directory_devices):
return tuple(sorted(deduplicated))
def write_pattern_file(patterns=None, sources=None):
def write_pattern_file(patterns=None, sources=None, pattern_file=None):
'''
Given a sequence of patterns and an optional sequence of source directories, write them to a
named temporary file (with the source directories as additional roots) and return the file.
If an optional open pattern file is given, overwrite it instead of making a new temporary file.
Return None if no patterns are provided.
'''
if not patterns:
return None
pattern_file = tempfile.NamedTemporaryFile('w')
if pattern_file is None:
pattern_file = tempfile.NamedTemporaryFile('w')
else:
pattern_file.seek(0)
pattern_file.write(
'\n'.join(tuple(patterns) + tuple(f'R {source}' for source in (sources or [])))
)
@ -187,7 +193,7 @@ def make_exclude_flags(location_config, exclude_filename=None):
DEFAULT_ARCHIVE_NAME_FORMAT = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
def borgmatic_source_directories(borgmatic_source_directory):
def collect_borgmatic_source_directories(borgmatic_source_directory):
'''
Return a list of borgmatic-specific source directories used for state like database backups.
'''
@ -218,6 +224,58 @@ def pattern_root_directories(patterns=None):
]
def special_file(path):
'''
Return whether the given path is a special file (character device, block device, or named pipe
/ FIFO).
'''
mode = os.stat(path).st_mode
return stat.S_ISCHR(mode) or stat.S_ISBLK(mode) or stat.S_ISFIFO(mode)
def any_parent_directories(path, candidate_parents):
'''
Return whether any of the given candidate parent directories are an actual parent of the given
path. This includes grandparents, etc.
'''
for parent in candidate_parents:
if pathlib.PurePosixPath(parent) in pathlib.PurePath(path).parents:
return True
return False
def collect_special_file_paths(
create_command, local_path, working_directory, borg_environment, skip_directories
):
'''
Given a Borg create command as a tuple, a local Borg path, a working directory, and a dict of
environment variables to pass to Borg, and a sequence of parent directories to skip, collect the
paths for any special files (character devices, block devices, and named pipes / FIFOs) that
Borg would encounter during a create. These are all paths that could cause Borg to hang if its
--read-special flag is used.
'''
paths_output = execute_command(
create_command + ('--dry-run', '--list'),
output_log_level=None,
borg_local_path=local_path,
working_directory=working_directory,
extra_environment=borg_environment,
)
paths = tuple(
path_line.split(' ', 1)[1]
for path_line in paths_output.split('\n')
if path_line and path_line.startswith('- ')
)
return tuple(
path
for path in paths
if special_file(path) and not any_parent_directories(path, skip_directories)
)
def create_archive(
dry_run,
repository,
@ -239,11 +297,13 @@ def create_archive(
If a sequence of stream processes is given (instances of subprocess.Popen), then execute the
create command while also triggering the given processes to produce output.
'''
borgmatic_source_directories = expand_directories(
collect_borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
)
sources = deduplicate_directories(
map_directories_to_devices(
expand_directories(
location_config.get('source_directories', [])
+ borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
tuple(location_config.get('source_directories', ())) + borgmatic_source_directories
)
),
additional_directory_devices=map_directories_to_devices(
@ -265,6 +325,7 @@ def create_archive(
upload_rate_limit = storage_config.get('upload_rate_limit', None)
umask = storage_config.get('umask', None)
lock_wait = storage_config.get('lock_wait', None)
read_special = True if (location_config.get('read_special') or stream_processes) else False
files_cache = location_config.get('files_cache')
archive_name_format = storage_config.get('archive_name_format', DEFAULT_ARCHIVE_NAME_FORMAT)
extra_borg_options = storage_config.get('extra_borg_options', {}).get('create', '')
@ -300,7 +361,7 @@ def create_archive(
f'{repository}: Ignoring configured "read_special" value of false, as true is needed for database hooks.'
)
full_command = (
create_command = (
tuple(local_path.split(' '))
+ ('create',)
+ make_pattern_flags(location_config, pattern_file.name if pattern_file else None)
@ -318,19 +379,14 @@ def create_archive(
+ atime_flags
+ (('--noctime',) if location_config.get('ctime') is False else ())
+ (('--nobirthtime',) if location_config.get('birthtime') is False else ())
+ (('--read-special',) if (location_config.get('read_special') or stream_processes) else ())
+ (('--read-special',) if read_special else ())
+ noflags_flags
+ (('--files-cache', files_cache) if files_cache else ())
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--list', '--filter', 'AME-') if list_files and not json and not progress else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (('--stats',) if stats and not json and not dry_run else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--dry-run',) if dry_run else ())
+ (('--progress',) if progress else ())
+ (('--json',) if json else ())
+ (tuple(extra_borg_options.split(' ')) if extra_borg_options else ())
+ flags.make_repository_archive_flags(repository, archive_name_format, local_borg_version)
+ (sources if not pattern_file else ())
@ -349,9 +405,39 @@ def create_archive(
borg_environment = environment.make_environment(storage_config)
# If read_special is enabled, exclude files that might cause Borg to hang.
if read_special:
special_file_paths = collect_special_file_paths(
create_command,
local_path,
working_directory,
borg_environment,
skip_directories=borgmatic_source_directories,
)
logger.warning(
f'{repository}: Excluding special files to prevent Borg from hanging: {", ".join(special_file_paths)}'
)
exclude_file = write_pattern_file(
expand_home_directories(
tuple(location_config.get('exclude_patterns') or ()) + special_file_paths
),
pattern_file=exclude_file,
)
if exclude_file:
create_command += make_exclude_flags(location_config, exclude_file.name)
create_command += (
(('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (('--stats',) if stats and not json and not dry_run else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--progress',) if progress else ())
+ (('--json',) if json else ())
)
if stream_processes:
return execute_command_with_processes(
full_command,
create_command,
stream_processes,
output_log_level,
output_file,
@ -361,7 +447,7 @@ def create_archive(
)
return execute_command(
full_command,
create_command,
output_log_level,
output_file,
borg_local_path=local_path,

View File

@ -70,8 +70,8 @@ def parse_overrides(raw_overrides):
def apply_overrides(config, raw_overrides):
'''
Given a sequence of configuration file override strings in the form of "section.option=value"
and a configuration dict, parse each override and set it the configuration dict.
Given a configuration dict and a sequence of configuration file override strings in the form of
"section.option=value", parse each override and set it the configuration dict.
'''
overrides = parse_overrides(raw_overrides)

View File

@ -197,7 +197,7 @@ def execute_command(
if output_log_level is None:
output = subprocess.check_output(
command, shell=shell, env=environment, cwd=working_directory
command, stderr=subprocess.STDOUT, shell=shell, env=environment, cwd=working_directory
)
return output.decode() if output is not None else None

View File

@ -9,20 +9,24 @@ import pytest
def write_configuration(
config_path, repository_path, borgmatic_source_directory, postgresql_dump_format='custom'
source_directory,
config_path,
repository_path,
borgmatic_source_directory,
postgresql_dump_format='custom',
):
'''
Write out borgmatic configuration into a file at the config path. Set the options so as to work
for testing. This includes injecting the given repository path, borgmatic source directory for
storing database dumps, dump format (for PostgreSQL), and encryption passphrase.
'''
config = '''
config = f'''
location:
source_directories:
- {}
- {source_directory}
repositories:
- {}
borgmatic_source_directory: {}
- {repository_path}
borgmatic_source_directory: {borgmatic_source_directory}
storage:
encryption_passphrase: "test"
@ -33,7 +37,7 @@ hooks:
hostname: postgresql
username: postgres
password: test
format: {}
format: {postgresql_dump_format}
- name: all
hostname: postgresql
username: postgres
@ -57,9 +61,7 @@ hooks:
hostname: mongodb
username: root
password: test
'''.format(
config_path, repository_path, borgmatic_source_directory, postgresql_dump_format
)
'''
with open(config_path, 'w') as config_file:
config_file.write(config)
@ -71,11 +73,16 @@ def test_database_dump_and_restore():
repository_path = os.path.join(temporary_directory, 'test.borg')
borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
# Write out a special file to ensure that it gets properly excluded and Borg doesn't hang on it.
os.mkfifo(os.path.join(temporary_directory, 'special_file'))
original_working_directory = os.getcwd()
try:
config_path = os.path.join(temporary_directory, 'test.yaml')
write_configuration(config_path, repository_path, borgmatic_source_directory)
write_configuration(
temporary_directory, config_path, repository_path, borgmatic_source_directory
)
subprocess.check_call(
['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']
@ -114,6 +121,7 @@ def test_database_dump_and_restore_with_directory_format():
try:
config_path = os.path.join(temporary_directory, 'test.yaml')
write_configuration(
temporary_directory,
config_path,
repository_path,
borgmatic_source_directory,
@ -146,7 +154,9 @@ def test_database_dump_with_error_causes_borgmatic_to_exit():
try:
config_path = os.path.join(temporary_directory, 'test.yaml')
write_configuration(config_path, repository_path, borgmatic_source_directory)
write_configuration(
temporary_directory, config_path, repository_path, borgmatic_source_directory
)
subprocess.check_call(
['borgmatic', '-v', '2', '--config', config_path, 'init', '--encryption', 'repokey']

View File

@ -134,6 +134,15 @@ def test_write_pattern_file_with_empty_exclude_patterns_does_not_raise():
module.write_pattern_file([])
def test_write_pattern_file_overwrites_existing_file():
pattern_file = flexmock(name='filename', flush=lambda: None)
pattern_file.should_receive('seek').with_args(0).once()
pattern_file.should_receive('write').with_args('R /foo\n+ /foo/bar')
flexmock(module.tempfile).should_receive('NamedTemporaryFile').never()
module.write_pattern_file(['R /foo', '+ /foo/bar'], pattern_file=pattern_file)
@pytest.mark.parametrize(
'filename_lists,opened_filenames',
(
@ -267,25 +276,25 @@ def test_make_exclude_flags_is_empty_when_config_has_no_excludes():
assert exclude_flags == ()
def test_borgmatic_source_directories_set_when_directory_exists():
def test_collect_borgmatic_source_directories_set_when_directory_exists():
flexmock(module.os.path).should_receive('exists').and_return(True)
flexmock(module.os.path).should_receive('expanduser')
assert module.borgmatic_source_directories('/tmp') == ['/tmp']
assert module.collect_borgmatic_source_directories('/tmp') == ['/tmp']
def test_borgmatic_source_directories_empty_when_directory_does_not_exist():
def test_collect_borgmatic_source_directories_empty_when_directory_does_not_exist():
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.os.path).should_receive('expanduser')
assert module.borgmatic_source_directories('/tmp') == []
assert module.collect_borgmatic_source_directories('/tmp') == []
def test_borgmatic_source_directories_defaults_when_directory_not_given():
def test_collect_borgmatic_source_directories_defaults_when_directory_not_given():
flexmock(module.os.path).should_receive('exists').and_return(True)
flexmock(module.os.path).should_receive('expanduser')
assert module.borgmatic_source_directories(None) == [
assert module.collect_borgmatic_source_directories(None) == [
module.state.DEFAULT_BORGMATIC_SOURCE_DIRECTORY
]
@ -300,12 +309,93 @@ def test_pattern_root_directories_parses_roots_and_ignores_others():
) == ['/root', '/baz']
@pytest.mark.parametrize(
'character_device,block_device,fifo,expected_result',
(
(False, False, False, False),
(True, False, False, True),
(False, True, False, True),
(True, True, False, True),
(False, False, True, True),
(False, True, True, True),
(True, False, True, True),
),
)
def test_special_file_looks_at_file_type(character_device, block_device, fifo, expected_result):
flexmock(module.os).should_receive('stat').and_return(flexmock(st_mode=flexmock()))
flexmock(module.stat).should_receive('S_ISCHR').and_return(character_device)
flexmock(module.stat).should_receive('S_ISBLK').and_return(block_device)
flexmock(module.stat).should_receive('S_ISFIFO').and_return(fifo)
assert module.special_file('/dev/special') == expected_result
def test_any_parent_directories_treats_parents_as_match():
module.any_parent_directories('/foo/bar.txt', ('/foo', '/etc'))
def test_any_parent_directories_treats_grandparents_as_match():
module.any_parent_directories('/foo/bar/baz.txt', ('/foo', '/etc'))
def test_any_parent_directories_treats_unrelated_paths_as_non_match():
module.any_parent_directories('/foo/bar.txt', ('/usr', '/etc'))
def test_collect_special_file_paths_parses_special_files_from_borg_dry_run_file_list():
flexmock(module).should_receive('execute_command').and_return(
'Processing files ...\n- /foo\n- /bar\n- /baz'
)
flexmock(module).should_receive('special_file').and_return(True)
flexmock(module).should_receive('any_parent_directories').and_return(False)
assert module.collect_special_file_paths(
('borg', 'create'),
local_path=None,
working_directory=None,
borg_environment=None,
skip_directories=flexmock(),
) == ('/foo', '/bar', '/baz')
def test_collect_special_file_paths_excludes_requested_directories():
flexmock(module).should_receive('execute_command').and_return('- /foo\n- /bar\n- /baz')
flexmock(module).should_receive('special_file').and_return(True)
flexmock(module).should_receive('any_parent_directories').and_return(False).and_return(
True
).and_return(False)
assert module.collect_special_file_paths(
('borg', 'create'),
local_path=None,
working_directory=None,
borg_environment=None,
skip_directories=flexmock(),
) == ('/foo', '/baz')
def test_collect_special_file_paths_excludes_non_special_files():
flexmock(module).should_receive('execute_command').and_return('- /foo\n- /bar\n- /baz')
flexmock(module).should_receive('special_file').and_return(True).and_return(False).and_return(
True
)
flexmock(module).should_receive('any_parent_directories').and_return(False)
assert module.collect_special_file_paths(
('borg', 'create'),
local_path=None,
working_directory=None,
borg_environment=None,
skip_directories=flexmock(),
) == ('/foo', '/baz')
DEFAULT_ARCHIVE_NAME = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
REPO_ARCHIVE_WITH_PATHS = (f'repo::{DEFAULT_ARCHIVE_NAME}', 'foo', 'bar')
def test_create_archive_calls_borg_with_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -344,7 +434,7 @@ def test_create_archive_calls_borg_with_parameters():
def test_create_archive_calls_borg_with_environment():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -385,7 +475,7 @@ def test_create_archive_calls_borg_with_environment():
def test_create_archive_with_patterns_calls_borg_with_patterns_including_converted_source_directories():
pattern_flags = ('--patterns-from', 'patterns')
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -427,7 +517,7 @@ def test_create_archive_with_patterns_calls_borg_with_patterns_including_convert
def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
exclude_flags = ('--exclude-from', 'excludes')
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -468,7 +558,7 @@ def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
def test_create_archive_with_log_info_calls_borg_with_info_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -485,7 +575,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--info') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--info',),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
@ -508,7 +598,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -525,7 +615,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--json') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--json',),
output_log_level=None,
output_file=None,
borg_local_path='borg',
@ -549,7 +639,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -566,7 +656,7 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--debug', '--show-rc') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--debug', '--show-rc'),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
@ -589,7 +679,7 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -606,7 +696,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--json') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--json',),
output_log_level=None,
output_file=None,
borg_local_path='borg',
@ -630,7 +720,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
def test_create_archive_with_dry_run_calls_borg_with_dry_run_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -671,7 +761,7 @@ def test_create_archive_with_dry_run_calls_borg_with_dry_run_parameter():
def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_parameter():
# --dry-run and --stats are mutually exclusive, see:
# https://borgbackup.readthedocs.io/en/stable/usage/create.html#description
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -688,7 +778,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_paramete
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--info', '--dry-run') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create', '--dry-run') + REPO_ARCHIVE_WITH_PATHS + ('--info',),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
@ -712,7 +802,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_paramete
def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_interval_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -751,7 +841,7 @@ def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_inte
def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -790,7 +880,7 @@ def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_param
def test_create_archive_with_compression_calls_borg_with_compression_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -834,7 +924,7 @@ def test_create_archive_with_compression_calls_borg_with_compression_parameters(
def test_create_archive_with_upload_rate_limit_calls_borg_with_upload_ratelimit_parameters(
feature_available, option_flag
):
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -873,7 +963,7 @@ def test_create_archive_with_upload_rate_limit_calls_borg_with_upload_ratelimit_
def test_create_archive_with_working_directory_calls_borg_with_working_directory():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -915,7 +1005,7 @@ def test_create_archive_with_working_directory_calls_borg_with_working_directory
def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -960,7 +1050,7 @@ def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_par
def test_create_archive_with_numeric_ids_calls_borg_with_numeric_ids_parameter(
feature_available, option_flag
):
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1000,7 +1090,7 @@ def test_create_archive_with_numeric_ids_calls_borg_with_numeric_ids_parameter(
def test_create_archive_with_read_special_calls_borg_with_read_special_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1016,8 +1106,72 @@ def test_create_archive_with_read_special_calls_borg_with_read_special_parameter
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('collect_special_file_paths').and_return(())
create_command = ('borg', 'create', '--read-special') + REPO_ARCHIVE_WITH_PATHS
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--read-special') + REPO_ARCHIVE_WITH_PATHS,
create_command + ('--dry-run', '--list'),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
working_directory=None,
extra_environment=None,
)
flexmock(module).should_receive('execute_command').with_args(
create_command,
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
working_directory=None,
extra_environment=None,
)
module.create_archive(
dry_run=False,
repository='repo',
location_config={
'source_directories': ['foo', 'bar'],
'repositories': ['repo'],
'read_special': True,
'exclude_patterns': None,
},
storage_config={},
local_borg_version='1.2.3',
)
def test_create_archive_with_read_special_adds_special_files_to_excludes():
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
flexmock(module).should_receive('pattern_root_directories').and_return([])
flexmock(module.os.path).should_receive('expanduser').and_raise(TypeError)
flexmock(module).should_receive('expand_home_directories').and_return(())
flexmock(module).should_receive('write_pattern_file').and_return(None).and_return(
None
).and_return(flexmock(name='/excludes'))
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module).should_receive('ensure_files_readable')
flexmock(module).should_receive('make_pattern_flags').and_return(())
flexmock(module).should_receive('make_exclude_flags').and_return(()).and_return(
'--exclude-from', '/excludes'
)
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('collect_special_file_paths').and_return(())
create_command = ('borg', 'create', '--read-special') + REPO_ARCHIVE_WITH_PATHS
flexmock(module).should_receive('execute_command').with_args(
create_command + ('--dry-run', '--list'),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
working_directory=None,
extra_environment=None,
)
flexmock(module).should_receive('execute_command').with_args(
create_command + ('--exclude-from', '/excludes'),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
@ -1047,7 +1201,7 @@ def test_create_archive_with_basic_option_calls_borg_with_corresponding_paramete
option_name, option_value
):
option_flag = '--no' + option_name.replace('', '') if option_value is False else None
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1098,7 +1252,7 @@ def test_create_archive_with_basic_option_calls_borg_with_corresponding_paramete
def test_create_archive_with_atime_option_calls_borg_with_corresponding_parameter(
option_value, feature_available, option_flag
):
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1149,7 +1303,7 @@ def test_create_archive_with_atime_option_calls_borg_with_corresponding_paramete
def test_create_archive_with_flags_option_calls_borg_with_corresponding_parameter(
option_value, feature_available, option_flag
):
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1189,7 +1343,7 @@ def test_create_archive_with_flags_option_calls_borg_with_corresponding_paramete
def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1229,7 +1383,7 @@ def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters(
def test_create_archive_with_local_path_calls_borg_via_local_path():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1269,7 +1423,7 @@ def test_create_archive_with_local_path_calls_borg_via_local_path():
def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1309,7 +1463,7 @@ def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters(
def test_create_archive_with_umask_calls_borg_with_umask_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1348,7 +1502,7 @@ def test_create_archive_with_umask_calls_borg_with_umask_parameters():
def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1387,7 +1541,7 @@ def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_output_log_level():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1404,7 +1558,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_o
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--stats') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--stats',),
output_log_level=logging.WARNING,
output_file=None,
borg_local_path='borg',
@ -1427,7 +1581,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_o
def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_and_info_output_log_level():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1444,7 +1598,7 @@ def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--info', '--stats') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--info', '--stats'),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
@ -1468,7 +1622,7 @@ def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_
def test_create_archive_with_files_calls_borg_with_list_parameter_and_warning_output_log_level():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1508,7 +1662,7 @@ def test_create_archive_with_files_calls_borg_with_list_parameter_and_warning_ou
def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_and_info_output_log_level():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1525,7 +1679,7 @@ def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_a
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--list', '--filter', 'AME-', '--info') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create', '--list', '--filter', 'AME-') + REPO_ARCHIVE_WITH_PATHS + ('--info',),
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
@ -1549,7 +1703,7 @@ def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_a
def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_parameter_and_no_list():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1566,7 +1720,7 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--info', '--progress') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--info', '--progress',),
output_log_level=logging.INFO,
output_file=module.DO_NOT_CAPTURE,
borg_local_path='borg',
@ -1590,7 +1744,7 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
def test_create_archive_with_progress_calls_borg_with_progress_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1607,7 +1761,7 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--progress') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--progress',),
output_log_level=logging.INFO,
output_file=module.DO_NOT_CAPTURE,
borg_local_path='borg',
@ -1631,7 +1785,7 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progress_parameter():
processes = flexmock()
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1647,9 +1801,23 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('collect_special_file_paths').and_return(())
create_command = (
('borg', 'create', '--one-file-system', '--read-special')
+ REPO_ARCHIVE_WITH_PATHS
+ ('--progress',)
)
flexmock(module).should_receive('execute_command_with_processes').with_args(
('borg', 'create', '--one-file-system', '--read-special', '--progress')
+ REPO_ARCHIVE_WITH_PATHS,
create_command + ('--dry-run', '--list'),
processes=processes,
output_log_level=logging.INFO,
output_file=module.DO_NOT_CAPTURE,
borg_local_path='borg',
working_directory=None,
extra_environment=None,
)
flexmock(module).should_receive('execute_command_with_processes').with_args(
create_command,
processes=processes,
output_log_level=logging.INFO,
output_file=module.DO_NOT_CAPTURE,
@ -1673,9 +1841,9 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
)
def test_create_archive_with_stream_processes_ignores_read_special_false_logs_warning():
def test_create_archive_with_stream_processes_ignores_read_special_false_and_logs_warnings():
processes = flexmock()
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1685,15 +1853,31 @@ def test_create_archive_with_stream_processes_ignores_read_special_false_logs_wa
flexmock(module).should_receive('write_pattern_file').and_return(None)
flexmock(module.feature).should_receive('available').and_return(True)
flexmock(module).should_receive('ensure_files_readable')
flexmock(module.logger).should_receive('warning').once()
flexmock(module.logger).should_receive('warning').twice()
flexmock(module).should_receive('make_pattern_flags').and_return(())
flexmock(module).should_receive('make_exclude_flags').and_return(())
flexmock(module.flags).should_receive('make_repository_archive_flags').and_return(
(f'repo::{DEFAULT_ARCHIVE_NAME}',)
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('collect_special_file_paths').and_return(())
create_command = (
'borg',
'create',
'--one-file-system',
'--read-special',
) + REPO_ARCHIVE_WITH_PATHS
flexmock(module).should_receive('execute_command_with_processes').with_args(
('borg', 'create', '--one-file-system', '--read-special') + REPO_ARCHIVE_WITH_PATHS,
create_command + ('--dry-run', '--list'),
processes=processes,
output_log_level=logging.INFO,
output_file=None,
borg_local_path='borg',
working_directory=None,
extra_environment=None,
)
flexmock(module).should_receive('execute_command_with_processes').with_args(
create_command,
processes=processes,
output_log_level=logging.INFO,
output_file=None,
@ -1718,7 +1902,7 @@ def test_create_archive_with_stream_processes_ignores_read_special_false_logs_wa
def test_create_archive_with_json_calls_borg_with_json_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1735,7 +1919,7 @@ def test_create_archive_with_json_calls_borg_with_json_parameter():
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--json') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--json',),
output_log_level=None,
output_file=None,
borg_local_path='borg',
@ -1760,7 +1944,7 @@ def test_create_archive_with_json_calls_borg_with_json_parameter():
def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1777,7 +1961,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter()
)
flexmock(module.environment).should_receive('make_environment')
flexmock(module).should_receive('execute_command').with_args(
('borg', 'create', '--json') + REPO_ARCHIVE_WITH_PATHS,
('borg', 'create') + REPO_ARCHIVE_WITH_PATHS + ('--json',),
output_log_level=None,
output_file=None,
borg_local_path='borg',
@ -1803,7 +1987,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter()
def test_create_archive_with_source_directories_glob_expands():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1843,7 +2027,7 @@ def test_create_archive_with_source_directories_glob_expands():
def test_create_archive_with_non_matching_source_directories_glob_passes_through():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo*',))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1883,7 +2067,7 @@ def test_create_archive_with_non_matching_source_directories_glob_passes_through
def test_create_archive_with_glob_calls_borg_with_expanded_directories():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1922,7 +2106,7 @@ def test_create_archive_with_glob_calls_borg_with_expanded_directories():
def test_create_archive_with_archive_name_format_calls_borg_with_archive_name():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -1962,7 +2146,7 @@ def test_create_archive_with_archive_name_format_calls_borg_with_archive_name():
def test_create_archive_with_archive_name_format_accepts_borg_placeholders():
repository_archive_pattern = 'repo::Documents_{hostname}-{now}'
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -2002,7 +2186,7 @@ def test_create_archive_with_archive_name_format_accepts_borg_placeholders():
def test_create_archive_with_repository_accepts_borg_placeholders():
repository_archive_pattern = '{fqdn}::Documents_{hostname}-{now}'
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -2041,7 +2225,7 @@ def test_create_archive_with_repository_accepts_borg_placeholders():
def test_create_archive_with_extra_borg_options_calls_borg_with_extra_options():
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -2079,9 +2263,9 @@ def test_create_archive_with_extra_borg_options_calls_borg_with_extra_options():
)
def test_create_archive_with_stream_processes_calls_borg_with_processes():
def test_create_archive_with_stream_processes_calls_borg_with_processes_and_read_special():
processes = flexmock()
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
flexmock(module).should_receive('collect_borgmatic_source_directories').and_return([])
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
flexmock(module).should_receive('map_directories_to_devices').and_return({})
flexmock(module).should_receive('expand_directories').and_return(())
@ -2097,8 +2281,24 @@ def test_create_archive_with_stream_processes_calls_borg_with_processes():
(f'repo::{DEFAULT_ARCHIVE_NAME}',)