From dcd567f4f00b757c400200cd0ab2cb020a8b311f Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Wed, 22 Oct 2025 11:10:42 -0700 Subject: [PATCH] Fix for a runtime directory error when the configured patterns contain a global exclude (#1150). --- NEWS | 1 + borgmatic/hooks/data_source/bootstrap.py | 20 +- borgmatic/hooks/data_source/btrfs.py | 12 +- borgmatic/hooks/data_source/config.py | 63 +++ borgmatic/hooks/data_source/lvm.py | 7 +- borgmatic/hooks/data_source/mariadb.py | 4 +- borgmatic/hooks/data_source/mongodb.py | 4 +- borgmatic/hooks/data_source/mysql.py | 4 +- borgmatic/hooks/data_source/postgresql.py | 4 +- borgmatic/hooks/data_source/sqlite.py | 4 +- borgmatic/hooks/data_source/zfs.py | 7 +- .../hooks/data_source/test_btrfs.py | 59 +++ .../integration/hooks/data_source/test_lvm.py | 81 ++++ .../integration/hooks/data_source/test_zfs.py | 48 +++ .../unit/hooks/data_source/test_bootstrap.py | 15 +- tests/unit/hooks/data_source/test_btrfs.py | 384 +++++++++--------- tests/unit/hooks/data_source/test_config.py | 113 ++++++ tests/unit/hooks/data_source/test_lvm.py | 97 +++-- tests/unit/hooks/data_source/test_mariadb.py | 37 ++ tests/unit/hooks/data_source/test_mongodb.py | 43 ++ tests/unit/hooks/data_source/test_mysql.py | 37 ++ .../unit/hooks/data_source/test_postgresql.py | 80 ++++ tests/unit/hooks/data_source/test_sqlite.py | 43 ++ tests/unit/hooks/data_source/test_zfs.py | 38 +- tox.ini | 8 +- 25 files changed, 939 insertions(+), 274 deletions(-) create mode 100644 tests/integration/hooks/data_source/test_btrfs.py create mode 100644 tests/integration/hooks/data_source/test_lvm.py create mode 100644 tests/integration/hooks/data_source/test_zfs.py diff --git a/NEWS b/NEWS index 6c416937..e2d698cd 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ databases when dumping "all". * #1150: Fix for a runtime directory error when the "create" action is used with the "--log-json" flag. + * #1150: Fix for a runtime directory error when the configured patterns contain a global exclude. * #1161: Fix a traceback (TypeError) in the "check" action with Python 3.14. * Add documentation search. * Change the URL of the local documentation development server to be more like the production URL. diff --git a/borgmatic/hooks/data_source/bootstrap.py b/borgmatic/hooks/data_source/bootstrap.py index 959c9db3..c1add4fe 100644 --- a/borgmatic/hooks/data_source/bootstrap.py +++ b/borgmatic/hooks/data_source/bootstrap.py @@ -7,6 +7,7 @@ import os import borgmatic.borg.pattern import borgmatic.config.paths +import borgmatic.hooks.data_source.config logger = logging.getLogger(__name__) @@ -58,20 +59,23 @@ def dump_data_sources( manifest_file, ) - patterns.extend( - borgmatic.borg.pattern.Pattern( - config_path, - source=borgmatic.borg.pattern.Pattern_source.HOOK, - ) - for config_path in config_paths - ) - patterns.append( + borgmatic.hooks.data_source.config.inject_pattern( + patterns, borgmatic.borg.pattern.Pattern( os.path.join(borgmatic_runtime_directory, 'bootstrap'), source=borgmatic.borg.pattern.Pattern_source.HOOK, ), ) + for config_path in config_paths: + borgmatic.hooks.data_source.config.inject_pattern( + patterns, + borgmatic.borg.pattern.Pattern( + config_path, + source=borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ) + return [] diff --git a/borgmatic/hooks/data_source/btrfs.py b/borgmatic/hooks/data_source/btrfs.py index 7defeb0c..95b0cc73 100644 --- a/borgmatic/hooks/data_source/btrfs.py +++ b/borgmatic/hooks/data_source/btrfs.py @@ -10,6 +10,7 @@ import subprocess import borgmatic.borg.pattern import borgmatic.config.paths import borgmatic.execute +import borgmatic.hooks.data_source.config import borgmatic.hooks.data_source.snapshot logger = logging.getLogger(__name__) @@ -330,14 +331,11 @@ def dump_data_sources( for pattern in subvolume.contained_patterns: snapshot_pattern = make_borg_snapshot_pattern(subvolume.path, pattern) + borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern) - # Attempt to update the pattern in place, since pattern order matters to Borg. - try: - patterns[patterns.index(pattern)] = snapshot_pattern - except ValueError: - patterns.append(snapshot_pattern) - - patterns.append(make_snapshot_exclude_pattern(subvolume.path)) + borgmatic.hooks.data_source.config.inject_pattern( + patterns, make_snapshot_exclude_pattern(subvolume.path) + ) return [] diff --git a/borgmatic/hooks/data_source/config.py b/borgmatic/hooks/data_source/config.py index 59a0bfc9..35bce138 100644 --- a/borgmatic/hooks/data_source/config.py +++ b/borgmatic/hooks/data_source/config.py @@ -3,6 +3,7 @@ import logging import shutil import subprocess +import borgmatic.borg.pattern from borgmatic.execute import execute_command_and_capture_output IS_A_HOOK = False @@ -102,3 +103,65 @@ def get_ip_from_container(container): raise ValueError( f"Could not determine ip address for container '{container}'; running in host mode or userspace networking?" ) + + +def inject_pattern(patterns, data_source_pattern): + ''' + Given a list of borgmatic.borg.pattern.Pattern instances representing the configured patterns, + insert the given data source pattern at the start of the list. The idea is that borgmatic is + injecting its own custom pattern specific to a data source hook into the user's configured + patterns so that the hook's data gets included in the backup. + + As part of this injection, if the data source pattern is a root pattern, also insert an + "include" version of the given root pattern, in an attempt to preempt any of the user's + configured exclude patterns that may follow. + ''' + if data_source_pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT: + patterns.insert( + 0, + borgmatic.borg.pattern.Pattern( + path=data_source_pattern.path, + type=borgmatic.borg.pattern.Pattern_type.INCLUDE, + style=data_source_pattern.style, + device=data_source_pattern.device, + source=borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ) + + patterns.insert(0, data_source_pattern) + + +def replace_pattern(patterns, pattern_to_replace, data_source_pattern): + ''' + Given a list of borgmatic.borg.pattern.Pattern instances representing the configured patterns, + replace the given pattern with the given data source pattern. The idea is that borgmatic is + replacing a configured pattern with its own modified pattern specific to a data source hook so + that the hook's data gets included in the backup. + + As part of this replacement, if the data source pattern is a root pattern, also insert an + "include" version of the given root pattern right after the replaced pattern, in an attempt to + preempt any of the user's configured exclude patterns that may follow. + + If the pattern to replace can't be found in the given patterns, then just inject the data source + pattern at the start of the list. + ''' + try: + index = patterns.index(pattern_to_replace) + except ValueError: + inject_pattern(patterns, data_source_pattern) + + return + + patterns[index] = data_source_pattern + + if data_source_pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT: + patterns.insert( + index + 1, + borgmatic.borg.pattern.Pattern( + path=data_source_pattern.path, + type=borgmatic.borg.pattern.Pattern_type.INCLUDE, + style=data_source_pattern.style, + device=data_source_pattern.device, + source=borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ) diff --git a/borgmatic/hooks/data_source/lvm.py b/borgmatic/hooks/data_source/lvm.py index fe272e1d..3cdc2cdc 100644 --- a/borgmatic/hooks/data_source/lvm.py +++ b/borgmatic/hooks/data_source/lvm.py @@ -10,6 +10,7 @@ import subprocess import borgmatic.borg.pattern import borgmatic.config.paths import borgmatic.execute +import borgmatic.hooks.data_source.config import borgmatic.hooks.data_source.snapshot logger = logging.getLogger(__name__) @@ -275,11 +276,7 @@ def dump_data_sources( normalized_runtime_directory, ) - # Attempt to update the pattern in place, since pattern order matters to Borg. - try: - patterns[patterns.index(pattern)] = snapshot_pattern - except ValueError: - patterns.append(snapshot_pattern) + borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern) return [] diff --git a/borgmatic/hooks/data_source/mariadb.py b/borgmatic/hooks/data_source/mariadb.py index 333dd23e..606eb3a4 100644 --- a/borgmatic/hooks/data_source/mariadb.py +++ b/borgmatic/hooks/data_source/mariadb.py @@ -7,6 +7,7 @@ import shlex import borgmatic.borg.pattern import borgmatic.config.paths import borgmatic.hooks.credential.parse +import borgmatic.hooks.data_source.config from borgmatic.execute import ( execute_command, execute_command_and_capture_output, @@ -366,7 +367,8 @@ def dump_data_sources( dump.write_data_source_dumps_metadata( borgmatic_runtime_directory, 'mariadb_databases', dumps_metadata ) - patterns.append( + borgmatic.hooks.data_source.config.inject_pattern( + patterns, borgmatic.borg.pattern.Pattern( os.path.join(borgmatic_runtime_directory, 'mariadb_databases'), source=borgmatic.borg.pattern.Pattern_source.HOOK, diff --git a/borgmatic/hooks/data_source/mongodb.py b/borgmatic/hooks/data_source/mongodb.py index 32416b27..7c94d5f7 100644 --- a/borgmatic/hooks/data_source/mongodb.py +++ b/borgmatic/hooks/data_source/mongodb.py @@ -5,6 +5,7 @@ import shlex import borgmatic.borg.pattern import borgmatic.config.paths import borgmatic.hooks.credential.parse +import borgmatic.hooks.data_source.config from borgmatic.execute import execute_command, execute_command_with_processes from borgmatic.hooks.data_source import config as database_config from borgmatic.hooks.data_source import dump @@ -100,7 +101,8 @@ def dump_data_sources( dump.write_data_source_dumps_metadata( borgmatic_runtime_directory, 'mongodb_databases', dumps_metadata ) - patterns.append( + borgmatic.hooks.data_source.config.inject_pattern( + patterns, borgmatic.borg.pattern.Pattern( os.path.join(borgmatic_runtime_directory, 'mongodb_databases'), source=borgmatic.borg.pattern.Pattern_source.HOOK, diff --git a/borgmatic/hooks/data_source/mysql.py b/borgmatic/hooks/data_source/mysql.py index 4b15cd13..a176a7ad 100644 --- a/borgmatic/hooks/data_source/mysql.py +++ b/borgmatic/hooks/data_source/mysql.py @@ -6,6 +6,7 @@ import shlex import borgmatic.borg.pattern import borgmatic.config.paths import borgmatic.hooks.credential.parse +import borgmatic.hooks.data_source.config import borgmatic.hooks.data_source.mariadb from borgmatic.execute import ( execute_command, @@ -297,7 +298,8 @@ def dump_data_sources( dump.write_data_source_dumps_metadata( borgmatic_runtime_directory, 'mysql_databases', dumps_metadata ) - patterns.append( + borgmatic.hooks.data_source.config.inject_pattern( + patterns, borgmatic.borg.pattern.Pattern( os.path.join(borgmatic_runtime_directory, 'mysql_databases'), source=borgmatic.borg.pattern.Pattern_source.HOOK, diff --git a/borgmatic/hooks/data_source/postgresql.py b/borgmatic/hooks/data_source/postgresql.py index ff70ee0c..3ef8e39a 100644 --- a/borgmatic/hooks/data_source/postgresql.py +++ b/borgmatic/hooks/data_source/postgresql.py @@ -8,6 +8,7 @@ import shlex import borgmatic.borg.pattern import borgmatic.config.paths import borgmatic.hooks.credential.parse +import borgmatic.hooks.data_source.config from borgmatic.execute import ( execute_command, execute_command_and_capture_output, @@ -260,7 +261,8 @@ def dump_data_sources( dump.write_data_source_dumps_metadata( borgmatic_runtime_directory, 'postgresql_databases', dumps_metadata ) - patterns.append( + borgmatic.hooks.data_source.config.inject_pattern( + patterns, borgmatic.borg.pattern.Pattern( os.path.join(borgmatic_runtime_directory, 'postgresql_databases'), source=borgmatic.borg.pattern.Pattern_source.HOOK, diff --git a/borgmatic/hooks/data_source/sqlite.py b/borgmatic/hooks/data_source/sqlite.py index c03b0431..d459e310 100644 --- a/borgmatic/hooks/data_source/sqlite.py +++ b/borgmatic/hooks/data_source/sqlite.py @@ -4,6 +4,7 @@ import shlex import borgmatic.borg.pattern import borgmatic.config.paths +import borgmatic.hooks.data_source.config from borgmatic.execute import execute_command, execute_command_with_processes from borgmatic.hooks.data_source import dump @@ -106,7 +107,8 @@ def dump_data_sources( dump.write_data_source_dumps_metadata( borgmatic_runtime_directory, 'sqlite_databases', dumps_metadata ) - patterns.append( + borgmatic.hooks.data_source.config.inject_pattern( + patterns, borgmatic.borg.pattern.Pattern( os.path.join(borgmatic_runtime_directory, 'sqlite_databases'), source=borgmatic.borg.pattern.Pattern_source.HOOK, diff --git a/borgmatic/hooks/data_source/zfs.py b/borgmatic/hooks/data_source/zfs.py index bf24b502..77b3ac9d 100644 --- a/borgmatic/hooks/data_source/zfs.py +++ b/borgmatic/hooks/data_source/zfs.py @@ -9,6 +9,7 @@ import subprocess import borgmatic.borg.pattern import borgmatic.config.paths import borgmatic.execute +import borgmatic.hooks.data_source.config import borgmatic.hooks.data_source.snapshot logger = logging.getLogger(__name__) @@ -306,11 +307,7 @@ def dump_data_sources( normalized_runtime_directory, ) - # Attempt to update the pattern in place, since pattern order matters to Borg. - try: - patterns[patterns.index(pattern)] = snapshot_pattern - except ValueError: - patterns.append(snapshot_pattern) + borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern) return [] diff --git a/tests/integration/hooks/data_source/test_btrfs.py b/tests/integration/hooks/data_source/test_btrfs.py new file mode 100644 index 00000000..f225622b --- /dev/null +++ b/tests/integration/hooks/data_source/test_btrfs.py @@ -0,0 +1,59 @@ +from flexmock import flexmock + +from borgmatic.borg.pattern import Pattern, Pattern_style, Pattern_type +from borgmatic.hooks.data_source import btrfs as module + + +def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns(): + patterns = [Pattern('/foo'), Pattern('/mnt/subvol1'), Pattern('/mnt/subvol2')] + config = {'btrfs': {}} + flexmock(module).should_receive('get_subvolumes').and_return( + ( + module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)), + module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)), + ), + ) + flexmock(module.os).should_receive('getpid').and_return(1234) + flexmock(module).should_receive('snapshot_subvolume').with_args( + 'btrfs', + '/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + ).once() + flexmock(module).should_receive('snapshot_subvolume').with_args( + 'btrfs', + '/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', + ).once() + + assert ( + module.dump_data_sources( + hook_config=config['btrfs'], + config=config, + config_paths=('test.yaml',), + borgmatic_runtime_directory='/run/borgmatic', + patterns=patterns, + dry_run=False, + ) + == [] + ) + + assert patterns == [ + Pattern( + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234', + Pattern_type.NO_RECURSE, + Pattern_style.FNMATCH, + ), + Pattern( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234', + Pattern_type.NO_RECURSE, + Pattern_style.FNMATCH, + ), + Pattern('/foo'), + Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1'), + Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', Pattern_type.INCLUDE), + Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2'), + Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', Pattern_type.INCLUDE), + ] + assert config == { + 'btrfs': {}, + } diff --git a/tests/integration/hooks/data_source/test_lvm.py b/tests/integration/hooks/data_source/test_lvm.py new file mode 100644 index 00000000..fb525dae --- /dev/null +++ b/tests/integration/hooks/data_source/test_lvm.py @@ -0,0 +1,81 @@ +from flexmock import flexmock + +from borgmatic.borg.pattern import Pattern, Pattern_type +from borgmatic.hooks.data_source import lvm as module + + +def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): + config = {'lvm': {}} + patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')] + logical_volumes = ( + module.Logical_volume( + name='lvolume1', + device_path='/dev/lvolume1', + mount_point='/mnt/lvolume1', + contained_patterns=(Pattern('/mnt/lvolume1/subdir'),), + ), + module.Logical_volume( + name='lvolume2', + device_path='/dev/lvolume2', + mount_point='/mnt/lvolume2', + contained_patterns=(Pattern('/mnt/lvolume2'),), + ), + ) + flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes) + flexmock(module.os).should_receive('getpid').and_return(1234) + flexmock(module).should_receive('snapshot_logical_volume').with_args( + 'lvcreate', + 'lvolume1_borgmatic-1234', + '/dev/lvolume1', + module.DEFAULT_SNAPSHOT_SIZE, + ).once() + flexmock(module).should_receive('snapshot_logical_volume').with_args( + 'lvcreate', + 'lvolume2_borgmatic-1234', + '/dev/lvolume2', + module.DEFAULT_SNAPSHOT_SIZE, + ).once() + flexmock(module).should_receive('get_snapshots').with_args( + 'lvs', + snapshot_name='lvolume1_borgmatic-1234', + ).and_return( + (module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),), + ) + flexmock(module).should_receive('get_snapshots').with_args( + 'lvs', + snapshot_name='lvolume2_borgmatic-1234', + ).and_return( + (module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),), + ) + flexmock(module.hashlib).should_receive('shake_256').and_return( + flexmock(hexdigest=lambda length: 'b33f'), + ) + flexmock(module).should_receive('mount_snapshot').with_args( + 'mount', + '/dev/lvolume1_snap', + '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1', + ).once() + flexmock(module).should_receive('mount_snapshot').with_args( + 'mount', + '/dev/lvolume2_snap', + '/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2', + ).once() + + assert ( + module.dump_data_sources( + hook_config=config['lvm'], + config=config, + config_paths=('test.yaml',), + borgmatic_runtime_directory='/run/borgmatic', + patterns=patterns, + dry_run=False, + ) + == [] + ) + + assert patterns == [ + Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'), + Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir', Pattern_type.INCLUDE), + Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'), + Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2', Pattern_type.INCLUDE), + ] diff --git a/tests/integration/hooks/data_source/test_zfs.py b/tests/integration/hooks/data_source/test_zfs.py new file mode 100644 index 00000000..63bab8f0 --- /dev/null +++ b/tests/integration/hooks/data_source/test_zfs.py @@ -0,0 +1,48 @@ +import os + +from flexmock import flexmock + +from borgmatic.borg.pattern import Pattern, Pattern_type +from borgmatic.hooks.data_source import zfs as module + + +def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): + dataset = flexmock( + name='dataset', + mount_point='/mnt/dataset', + contained_patterns=(Pattern('/mnt/dataset/subdir'),), + ) + flexmock(module).should_receive('get_datasets_to_backup').and_return((dataset,)) + flexmock(module.os).should_receive('getpid').and_return(1234) + full_snapshot_name = 'dataset@borgmatic-1234' + flexmock(module).should_receive('snapshot_dataset').with_args( + 'zfs', + full_snapshot_name, + ).once() + flexmock(module.hashlib).should_receive('shake_256').and_return( + flexmock(hexdigest=lambda length: 'b33f'), + ) + snapshot_mount_path = '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset' + flexmock(module).should_receive('mount_snapshot').with_args( + 'mount', + full_snapshot_name, + module.os.path.normpath(snapshot_mount_path), + ).once() + patterns = [Pattern('/mnt/dataset/subdir')] + + assert ( + module.dump_data_sources( + hook_config={}, + config={'source_directories': '/mnt/dataset', 'zfs': {}}, + config_paths=('test.yaml',), + borgmatic_runtime_directory='/run/borgmatic', + patterns=patterns, + dry_run=False, + ) + == [] + ) + + assert patterns == [ + Pattern(os.path.join(snapshot_mount_path, 'subdir')), + Pattern(os.path.join(snapshot_mount_path, 'subdir'), Pattern_type.INCLUDE), + ] diff --git a/tests/unit/hooks/data_source/test_bootstrap.py b/tests/unit/hooks/data_source/test_bootstrap.py index 612b60e5..6958f0f1 100644 --- a/tests/unit/hooks/data_source/test_bootstrap.py +++ b/tests/unit/hooks/data_source/test_bootstrap.py @@ -22,6 +22,18 @@ def test_dump_data_sources_creates_manifest_file(): {'borgmatic_version': '1.0.0', 'config_paths': ('test.yaml',)}, manifest_file, ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/bootstrap', source=module.borgmatic.borg.pattern.Pattern_source.HOOK + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + 'test.yaml', source=module.borgmatic.borg.pattern.Pattern_source.HOOK + ), + ).once() module.dump_data_sources( hook_config=None, @@ -36,6 +48,7 @@ def test_dump_data_sources_creates_manifest_file(): def test_dump_data_sources_with_store_config_files_false_does_not_create_manifest_file(): flexmock(module.os).should_receive('makedirs').never() flexmock(module.json).should_receive('dump').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() hook_config = {'store_config_files': False} module.dump_data_sources( @@ -51,7 +64,7 @@ def test_dump_data_sources_with_store_config_files_false_does_not_create_manifes def test_dump_data_sources_with_dry_run_does_not_create_manifest_file(): flexmock(module.os).should_receive('makedirs').never() flexmock(module.json).should_receive('dump').never() - + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() module.dump_data_sources( hook_config=None, config={}, diff --git a/tests/unit/hooks/data_source/test_btrfs.py b/tests/unit/hooks/data_source/test_btrfs.py index 8a093ce8..cc426403 100644 --- a/tests/unit/hooks/data_source/test_btrfs.py +++ b/tests/unit/hooks/data_source/test_btrfs.py @@ -399,8 +399,8 @@ def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_ assert module.make_borg_snapshot_pattern(subvolume_path, pattern) == expected_pattern -def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns(): - patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')] +def test_dump_data_sources_snapshots_each_subvolume_and_replaces_patterns(): + patterns = [Pattern('/foo'), Pattern('/mnt/subvol1'), Pattern('/mnt/subvol2')] config = {'btrfs': {}} flexmock(module).should_receive('get_subvolumes').and_return( ( @@ -409,26 +409,26 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns(): ), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', ) flexmock(module).should_receive('snapshot_subvolume').with_args( 'btrfs', '/mnt/subvol1', - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).once() flexmock(module).should_receive('snapshot_subvolume').with_args( 'btrfs', '/mnt/subvol2', - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', ).once() flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args( '/mnt/subvol1', ).and_return( Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, ), @@ -437,7 +437,7 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns(): '/mnt/subvol2', ).and_return( Pattern( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, ), @@ -445,11 +445,43 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns(): flexmock(module).should_receive('make_borg_snapshot_pattern').with_args( '/mnt/subvol1', object, - ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')) + ).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1')) flexmock(module).should_receive('make_borg_snapshot_pattern').with_args( '/mnt/subvol2', object, - ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2')) + ).and_return(Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/subvol1'), + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/subvol2'), + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234', + Pattern_type.NO_RECURSE, + Pattern_style.FNMATCH, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234', + Pattern_type.NO_RECURSE, + Pattern_style.FNMATCH, + ), + ).once() assert ( module.dump_data_sources( @@ -463,21 +495,6 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns(): == [] ) - assert patterns == [ - Pattern('/foo'), - Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'), - Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'), - Pattern( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - ] assert config == { 'btrfs': {}, } @@ -490,18 +507,18 @@ def test_dump_data_sources_uses_custom_btrfs_command_in_commands(): (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ) flexmock(module).should_receive('snapshot_subvolume').with_args( '/usr/local/bin/btrfs', '/mnt/subvol1', - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).once() flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args( '/mnt/subvol1', ).and_return( Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, ), @@ -509,7 +526,23 @@ def test_dump_data_sources_uses_custom_btrfs_command_in_commands(): flexmock(module).should_receive('make_borg_snapshot_pattern').with_args( '/mnt/subvol1', object, - ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')) + ).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/subvol1'), + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234', + Pattern_type.NO_RECURSE, + Pattern_style.FNMATCH, + ), + ).once() assert ( module.dump_data_sources( @@ -523,15 +556,6 @@ def test_dump_data_sources_uses_custom_btrfs_command_in_commands(): == [] ) - assert patterns == [ - Pattern('/foo'), - Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'), - Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - ] assert config == { 'btrfs': { 'btrfs_command': '/usr/local/bin/btrfs', @@ -550,18 +574,18 @@ def test_dump_data_sources_with_findmnt_command_warns(): (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),), ).once() flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ) flexmock(module).should_receive('snapshot_subvolume').with_args( 'btrfs', '/mnt/subvol1', - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).once() flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args( '/mnt/subvol1', ).and_return( Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH, ), @@ -569,7 +593,23 @@ def test_dump_data_sources_with_findmnt_command_warns(): flexmock(module).should_receive('make_borg_snapshot_pattern').with_args( '/mnt/subvol1', object, - ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')) + ).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/subvol1'), + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234', + Pattern_type.NO_RECURSE, + Pattern_style.FNMATCH, + ), + ).once() assert ( module.dump_data_sources( @@ -583,15 +623,6 @@ def test_dump_data_sources_with_findmnt_command_warns(): == [] ) - assert patterns == [ - Pattern('/foo'), - Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'), - Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - ] assert config == { 'btrfs': { 'findmnt_command': '/usr/local/bin/findmnt', @@ -606,10 +637,12 @@ def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update(): (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ) flexmock(module).should_receive('snapshot_subvolume').never() flexmock(module).should_receive('make_snapshot_exclude_pattern').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( @@ -623,7 +656,6 @@ def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update(): == [] ) - assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')] assert config == {'btrfs': {}} @@ -634,6 +666,8 @@ def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patter flexmock(module).should_receive('make_snapshot_path').never() flexmock(module).should_receive('snapshot_subvolume').never() flexmock(module).should_receive('make_snapshot_exclude_pattern').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( @@ -647,95 +681,9 @@ def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patter == [] ) - assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')] assert config == {'btrfs': {}} -def test_dump_data_sources_snapshots_adds_to_existing_exclude_patterns(): - patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')] - config = {'btrfs': {}, 'exclude_patterns': ['/bar']} - flexmock(module).should_receive('get_subvolumes').and_return( - ( - module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)), - module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)), - ), - ) - flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', - ) - flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', - ) - flexmock(module).should_receive('snapshot_subvolume').with_args( - 'btrfs', - '/mnt/subvol1', - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', - ).once() - flexmock(module).should_receive('snapshot_subvolume').with_args( - 'btrfs', - '/mnt/subvol2', - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', - ).once() - flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args( - '/mnt/subvol1', - ).and_return( - Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - ) - flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args( - '/mnt/subvol2', - ).and_return( - Pattern( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - ) - flexmock(module).should_receive('make_borg_snapshot_pattern').with_args( - '/mnt/subvol1', - object, - ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1')) - flexmock(module).should_receive('make_borg_snapshot_pattern').with_args( - '/mnt/subvol2', - object, - ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2')) - - assert ( - module.dump_data_sources( - hook_config=config['btrfs'], - config=config, - config_paths=('test.yaml',), - borgmatic_runtime_directory='/run/borgmatic', - patterns=patterns, - dry_run=False, - ) - == [] - ) - - assert patterns == [ - Pattern('/foo'), - Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'), - Pattern( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'), - Pattern( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234', - Pattern_type.NO_RECURSE, - Pattern_style.FNMATCH, - ), - ] - assert config == { - 'btrfs': {}, - 'exclude_patterns': ['/bar'], - } - - def test_remove_data_source_dumps_deletes_snapshots(): config = {'btrfs': {}} flexmock(module).should_receive('get_subvolumes').and_return( @@ -745,84 +693,90 @@ def test_remove_data_source_dumps_deletes_snapshots(): ), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return( - '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', ) flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1') flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2') flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol1/.borgmatic-*/mnt/subvol1', ).and_return( - ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'), + ( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', + ), ) flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol2/.borgmatic-*/mnt/subvol2', ).and_return( - ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'), + ( + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', + ), ) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-5678/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-5678/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', ).and_return(False) flexmock(module).should_receive('delete_snapshot').with_args( 'btrfs', - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).once() flexmock(module).should_receive('delete_snapshot').with_args( 'btrfs', - '/mnt/subvol1/.borgmatic-5678/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', ).once() flexmock(module).should_receive('delete_snapshot').with_args( 'btrfs', - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', ).once() flexmock(module).should_receive('delete_snapshot').with_args( 'btrfs', - '/mnt/subvol2/.borgmatic-5678/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', ).never() flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-1234', + '/mnt/subvol1/.borgmatic-snapshot-1234', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-5678', + '/mnt/subvol1/.borgmatic-snapshot-5678', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-1234', + '/mnt/subvol2/.borgmatic-snapshot-1234', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-5678', + '/mnt/subvol2/.borgmatic-snapshot-5678', ).and_return(True) flexmock(module.shutil).should_receive('rmtree').with_args( - '/mnt/subvol1/.borgmatic-1234', + '/mnt/subvol1/.borgmatic-snapshot-1234', ).once() flexmock(module.shutil).should_receive('rmtree').with_args( - '/mnt/subvol1/.borgmatic-5678', + '/mnt/subvol1/.borgmatic-snapshot-5678', ).once() flexmock(module.shutil).should_receive('rmtree').with_args( - '/mnt/subvol2/.borgmatic-1234', + '/mnt/subvol2/.borgmatic-snapshot-1234', ).once() flexmock(module.shutil).should_receive('rmtree').with_args( - '/mnt/subvol2/.borgmatic-5678', + '/mnt/subvol2/.borgmatic-snapshot-5678', ).never() module.remove_data_source_dumps( @@ -901,44 +855,50 @@ def test_remove_data_source_dumps_with_dry_run_skips_deletes(): ), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return( - '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', ) flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1') flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2') flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol1/.borgmatic-*/mnt/subvol1', ).and_return( - ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'), + ( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', + ), ) flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol2/.borgmatic-*/mnt/subvol2', ).and_return( - ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'), + ( + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', + ), ) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-5678/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-5678/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', ).and_return(False) flexmock(module).should_receive('delete_snapshot').never() flexmock(module.shutil).should_receive('rmtree').never() @@ -980,21 +940,21 @@ def test_remove_data_source_without_snapshots_skips_deletes(): ), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return( - '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', ) flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1') flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2') flexmock(module.glob).should_receive('glob').and_return(()) @@ -1020,44 +980,50 @@ def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bail ), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return( - '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', ) flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1') flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2') flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol1/.borgmatic-*/mnt/subvol1', ).and_return( - ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'), + ( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', + ), ) flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol2/.borgmatic-*/mnt/subvol2', ).and_return( - ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'), + ( + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', + ), ) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-5678/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-5678/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', ).and_return(False) flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError) flexmock(module.shutil).should_receive('rmtree').never() @@ -1080,44 +1046,50 @@ def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bail ), ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return( - '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', ) flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return( - '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', ) flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1') flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2') flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol1/.borgmatic-*/mnt/subvol1', ).and_return( - ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'), + ( + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', + ), ) flexmock(module.glob).should_receive('glob').with_args( '/mnt/subvol2/.borgmatic-*/mnt/subvol2', ).and_return( - ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'), + ( + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', + ), ) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-1234/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol1/.borgmatic-5678/mnt/subvol1', + '/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-1234/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2', ).and_return(True) flexmock(module.os.path).should_receive('isdir').with_args( - '/mnt/subvol2/.borgmatic-5678/mnt/subvol2', + '/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2', ).and_return(False) flexmock(module).should_receive('delete_snapshot').and_raise( module.subprocess.CalledProcessError(1, 'command', 'error'), @@ -1140,29 +1112,37 @@ def test_remove_data_source_dumps_with_root_subvolume_skips_duplicate_removal(): ) flexmock(module).should_receive('make_snapshot_path').with_args('/').and_return( - '/.borgmatic-1234', + '/.borgmatic-snapshot-1234', ) flexmock(module.borgmatic.config.paths).should_receive( 'replace_temporary_subdirectory_with_glob', ).with_args( - '/.borgmatic-1234', + '/.borgmatic-snapshot-1234', temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX, ).and_return('/.borgmatic-*') flexmock(module.glob).should_receive('glob').with_args('/.borgmatic-*').and_return( - ('/.borgmatic-1234', '/.borgmatic-5678'), + ('/.borgmatic-snapshot-1234', '/.borgmatic-snapshot-5678'), ) - flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-1234').and_return( + flexmock(module.os.path).should_receive('isdir').with_args( + '/.borgmatic-snapshot-1234' + ).and_return( True, ).and_return(False) - flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-5678').and_return( + flexmock(module.os.path).should_receive('isdir').with_args( + '/.borgmatic-snapshot-5678' + ).and_return( True, ).and_return(False) - flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-1234').once() - flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-5678').once() + flexmock(module).should_receive('delete_snapshot').with_args( + 'btrfs', '/.borgmatic-snapshot-1234' + ).once() + flexmock(module).should_receive('delete_snapshot').with_args( + 'btrfs', '/.borgmatic-snapshot-5678' + ).once() flexmock(module.os.path).should_receive('isdir').with_args('').and_return(False) diff --git a/tests/unit/hooks/data_source/test_config.py b/tests/unit/hooks/data_source/test_config.py index b69f24ea..00103a87 100644 --- a/tests/unit/hooks/data_source/test_config.py +++ b/tests/unit/hooks/data_source/test_config.py @@ -155,3 +155,116 @@ def test_get_ip_from_container_with_broken_output_errors(): module.get_ip_from_container('yolo') assert 'Could not decode JSON output' in str(exc_info.value) + + +def test_inject_pattern_prepends_pattern_in_list(): + patterns = [ + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/var'), + ] + + module.inject_pattern( + patterns, + module.borgmatic.borg.pattern.Pattern( + '/foo/bar', + type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE, + ), + ) + + assert patterns == [ + module.borgmatic.borg.pattern.Pattern( + '/foo/bar', + type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE, + ), + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/var'), + ] + + +def test_inject_pattern_with_root_pattern_prepends_it_along_with_corresponding_include_pattern(): + patterns = [ + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/var'), + ] + + module.inject_pattern( + patterns, + module.borgmatic.borg.pattern.Pattern('/foo/bar'), + ) + + assert patterns == [ + module.borgmatic.borg.pattern.Pattern('/foo/bar'), + module.borgmatic.borg.pattern.Pattern( + '/foo/bar', + type=module.borgmatic.borg.pattern.Pattern_type.INCLUDE, + ), + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/var'), + ] + + +def test_replace_pattern_swaps_out_pattern_in_place(): + patterns = [ + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/var'), + module.borgmatic.borg.pattern.Pattern('/lib'), + ] + + module.replace_pattern( + patterns, + module.borgmatic.borg.pattern.Pattern('/var'), + module.borgmatic.borg.pattern.Pattern( + '/foo/bar', + type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE, + ), + ) + + assert patterns == [ + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern( + '/foo/bar', + type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE, + ), + module.borgmatic.borg.pattern.Pattern('/lib'), + ] + + +def test_replace_pattern_with_unknown_pattern_falls_back_to_injecting(): + patterns = [ + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/var'), + module.borgmatic.borg.pattern.Pattern('/lib'), + ] + flexmock(module).should_receive('inject_pattern').with_args( + patterns, module.borgmatic.borg.pattern.Pattern('/foo/bar') + ).once() + + module.replace_pattern( + patterns, + module.borgmatic.borg.pattern.Pattern('/unknown'), + module.borgmatic.borg.pattern.Pattern('/foo/bar'), + ) + + +def test_replace_pattern_with_root_pattern_swaps_it_in_along_with_corresponding_include_pattern(): + patterns = [ + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/var'), + module.borgmatic.borg.pattern.Pattern('/lib'), + ] + + module.replace_pattern( + patterns, + module.borgmatic.borg.pattern.Pattern('/var'), + module.borgmatic.borg.pattern.Pattern('/foo/bar'), + ) + + assert patterns == [ + module.borgmatic.borg.pattern.Pattern('/etc'), + module.borgmatic.borg.pattern.Pattern('/foo/bar'), + module.borgmatic.borg.pattern.Pattern( + '/foo/bar', + type=module.borgmatic.borg.pattern.Pattern_type.INCLUDE, + ), + module.borgmatic.borg.pattern.Pattern('/lib'), + ] diff --git a/tests/unit/hooks/data_source/test_lvm.py b/tests/unit/hooks/data_source/test_lvm.py index 8984bb19..3c2615bc 100644 --- a/tests/unit/hooks/data_source/test_lvm.py +++ b/tests/unit/hooks/data_source/test_lvm.py @@ -294,7 +294,7 @@ def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_ ) -def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): +def test_dump_data_sources_snapshots_and_mounts_and_replaces_patterns(): config = {'lvm': {}} patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')] logical_volumes = ( @@ -360,6 +360,22 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): logical_volumes[1], '/run/borgmatic', ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume1/subdir'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume2'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -373,11 +389,6 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): == [] ) - assert patterns == [ - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'), - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'), - ] - def test_dump_data_sources_with_no_logical_volumes_skips_snapshots(): config = {'lvm': {}} @@ -385,6 +396,7 @@ def test_dump_data_sources_with_no_logical_volumes_skips_snapshots(): flexmock(module).should_receive('get_logical_volumes').and_return(()) flexmock(module).should_receive('snapshot_logical_volume').never() flexmock(module).should_receive('mount_snapshot').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never() assert ( module.dump_data_sources( @@ -398,8 +410,6 @@ def test_dump_data_sources_with_no_logical_volumes_skips_snapshots(): == [] ) - assert patterns == [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')] - def test_dump_data_sources_uses_snapshot_size_for_snapshot(): config = {'lvm': {'snapshot_size': '1000PB'}} @@ -467,6 +477,22 @@ def test_dump_data_sources_uses_snapshot_size_for_snapshot(): logical_volumes[1], '/run/borgmatic', ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume1/subdir'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume2'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -480,11 +506,6 @@ def test_dump_data_sources_uses_snapshot_size_for_snapshot(): == [] ) - assert patterns == [ - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'), - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'), - ] - def test_dump_data_sources_uses_custom_commands(): config = { @@ -559,6 +580,22 @@ def test_dump_data_sources_uses_custom_commands(): logical_volumes[1], '/run/borgmatic', ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume1/subdir'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume2'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -572,11 +609,6 @@ def test_dump_data_sources_uses_custom_commands(): == [] ) - assert patterns == [ - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'), - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'), - ] - def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patterns(): config = {'lvm': {}} @@ -601,6 +633,7 @@ def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patte flexmock(module).should_receive('snapshot_logical_volume').never() flexmock(module).should_receive('get_snapshots').never() flexmock(module).should_receive('mount_snapshot').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never() assert ( module.dump_data_sources( @@ -614,11 +647,6 @@ def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patte == [] ) - assert patterns == [ - Pattern('/mnt/lvolume1/subdir'), - Pattern('/mnt/lvolume2'), - ] - def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns(): config = {'lvm': {}} @@ -686,6 +714,22 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained logical_volumes[1], '/run/borgmatic', ).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume1/subdir'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/lvolume2'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -699,12 +743,6 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained == [] ) - assert patterns == [ - Pattern('/hmm'), - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'), - Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'), - ] - def test_dump_data_sources_with_missing_snapshot_errors(): config = {'lvm': {}} @@ -747,6 +785,7 @@ def test_dump_data_sources_with_missing_snapshot_errors(): snapshot_name='lvolume2_borgmatic-1234', ).never() flexmock(module).should_receive('mount_snapshot').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never() with pytest.raises(ValueError): module.dump_data_sources( diff --git a/tests/unit/hooks/data_source/test_mariadb.py b/tests/unit/hooks/data_source/test_mariadb.py index 439956fb..b74c7482 100644 --- a/tests/unit/hooks/data_source/test_mariadb.py +++ b/tests/unit/hooks/data_source/test_mariadb.py @@ -409,6 +409,13 @@ def test_dump_data_sources_dumps_each_database(): module.borgmatic.actions.restore.Dump('mariadb_databases', 'bar'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mariadb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -458,6 +465,13 @@ def test_dump_data_sources_dumps_with_password(): module.borgmatic.actions.restore.Dump('mariadb_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mariadb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( [database], @@ -509,6 +523,13 @@ def test_dump_data_sources_dumps_with_environment_password_transport_passes_pass module.borgmatic.actions.restore.Dump('mariadb_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mariadb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( [database], @@ -547,6 +568,13 @@ def test_dump_data_sources_dumps_all_databases_at_once(): module.borgmatic.actions.restore.Dump('mariadb_databases', 'all'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mariadb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -589,6 +617,13 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured module.borgmatic.actions.restore.Dump('mariadb_databases', 'bar'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mariadb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -614,6 +649,7 @@ def test_dump_data_sources_errors_for_missing_all_databases(): 'databases/localhost/all', ) flexmock(module).should_receive('database_names_to_dump').and_return(()) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() with pytest.raises(ValueError): assert module.dump_data_sources( @@ -637,6 +673,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run 'databases/localhost/all', ) flexmock(module).should_receive('database_names_to_dump').and_return(()) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( diff --git a/tests/unit/hooks/data_source/test_mongodb.py b/tests/unit/hooks/data_source/test_mongodb.py index 98f5ae2e..801c1e18 100644 --- a/tests/unit/hooks/data_source/test_mongodb.py +++ b/tests/unit/hooks/data_source/test_mongodb.py @@ -47,6 +47,13 @@ def test_dump_data_sources_runs_mongodump_for_each_database(): module.borgmatic.actions.restore.Dump('mongodb_databases', 'bar'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mongodb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -70,6 +77,7 @@ def test_dump_data_sources_with_dry_run_skips_mongodump(): flexmock(module.dump).should_receive('create_named_pipe_for_dump').never() flexmock(module).should_receive('execute_command').never() flexmock(module.dump).should_receive('write_data_source_dumps_metadata').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( @@ -118,6 +126,13 @@ def test_dump_data_sources_runs_mongodump_with_hostname_and_port(): ), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mongodb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -176,6 +191,13 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password(): module.borgmatic.actions.restore.Dump('mongodb_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mongodb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -207,6 +229,13 @@ def test_dump_data_sources_runs_mongodump_with_directory_format(): module.borgmatic.actions.restore.Dump('mongodb_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mongodb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -250,6 +279,13 @@ def test_dump_data_sources_runs_mongodump_with_options(): module.borgmatic.actions.restore.Dump('mongodb_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mongodb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -282,6 +318,13 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases(): module.borgmatic.actions.restore.Dump('mongodb_databases', 'all'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mongodb_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, diff --git a/tests/unit/hooks/data_source/test_mysql.py b/tests/unit/hooks/data_source/test_mysql.py index cdd4aeec..9b745196 100644 --- a/tests/unit/hooks/data_source/test_mysql.py +++ b/tests/unit/hooks/data_source/test_mysql.py @@ -296,6 +296,13 @@ def test_dump_data_sources_dumps_each_database(): module.borgmatic.actions.restore.Dump('mysql_databases', 'bar'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mysql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -345,6 +352,13 @@ def test_dump_data_sources_dumps_with_password(): module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mysql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( [database], @@ -396,6 +410,13 @@ def test_dump_data_sources_dumps_with_environment_password_transport_passes_pass module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mysql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( [database], @@ -435,6 +456,13 @@ def test_dump_data_sources_dumps_all_databases_at_once(): module.borgmatic.actions.restore.Dump('mysql_databases', 'all'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mysql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -477,6 +505,13 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured module.borgmatic.actions.restore.Dump('mysql_databases', 'bar'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/mysql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -502,6 +537,7 @@ def test_dump_data_sources_errors_for_missing_all_databases(): 'databases/localhost/all', ) flexmock(module).should_receive('database_names_to_dump').and_return(()) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() with pytest.raises(ValueError): assert module.dump_data_sources( @@ -525,6 +561,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run 'databases/localhost/all', ) flexmock(module).should_receive('database_names_to_dump').and_return(()) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( diff --git a/tests/unit/hooks/data_source/test_postgresql.py b/tests/unit/hooks/data_source/test_postgresql.py index 22f7ee4c..a49cc78f 100644 --- a/tests/unit/hooks/data_source/test_postgresql.py +++ b/tests/unit/hooks/data_source/test_postgresql.py @@ -281,6 +281,13 @@ def test_dump_data_sources_runs_pg_dump_for_each_database(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'bar'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -300,6 +307,7 @@ def test_dump_data_sources_raises_when_no_database_names_to_dump(): flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'}) flexmock(module).should_receive('make_dump_path').and_return('') flexmock(module).should_receive('database_names_to_dump').and_return(()) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() with pytest.raises(ValueError): module.dump_data_sources( @@ -317,6 +325,7 @@ def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump(): flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'}) flexmock(module).should_receive('make_dump_path').and_return('') flexmock(module).should_receive('database_names_to_dump').and_return(()) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( @@ -352,6 +361,13 @@ def test_dump_data_sources_with_duplicate_dump_skips_pg_dump(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'bar'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -383,6 +399,7 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump(): flexmock(module.dump).should_receive('create_named_pipe_for_dump').never() flexmock(module).should_receive('execute_command').never() flexmock(module.dump).should_receive('write_data_source_dumps_metadata').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( @@ -441,6 +458,13 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port(): ), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -494,6 +518,13 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -547,6 +578,13 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -595,6 +633,13 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -649,6 +694,13 @@ def test_dump_data_sources_runs_pg_dump_with_string_compression(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -703,6 +755,13 @@ def test_dump_data_sources_runs_pg_dump_with_integer_compression(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -756,6 +815,13 @@ def test_dump_data_sources_runs_pg_dump_with_options(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -795,6 +861,13 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'all'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, @@ -846,6 +919,13 @@ def test_dump_data_sources_runs_non_default_pg_dump(): module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/postgresql_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert module.dump_data_sources( databases, diff --git a/tests/unit/hooks/data_source/test_sqlite.py b/tests/unit/hooks/data_source/test_sqlite.py index 3531c975..fb093c40 100644 --- a/tests/unit/hooks/data_source/test_sqlite.py +++ b/tests/unit/hooks/data_source/test_sqlite.py @@ -33,6 +33,13 @@ def test_dump_data_sources_logs_and_skips_if_dump_already_exists(): module.borgmatic.actions.restore.Dump('sqlite_databases', 'database'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/sqlite_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -71,6 +78,13 @@ def test_dump_data_sources_dumps_each_database(): module.borgmatic.actions.restore.Dump('sqlite_databases', 'database2'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/sqlite_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -115,6 +129,13 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped(): module.borgmatic.actions.restore.Dump('sqlite_databases', 'database1'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/sqlite_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -164,6 +185,13 @@ def test_dump_data_sources_runs_non_default_sqlite_with_path_injection_attack_ge module.borgmatic.actions.restore.Dump('sqlite_databases', 'database1'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/sqlite_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -199,6 +227,13 @@ def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database(): module.borgmatic.actions.restore.Dump('sqlite_databases', 'database1'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/sqlite_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -236,6 +271,13 @@ def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases(): module.borgmatic.actions.restore.Dump('sqlite_databases', 'all'), ], ).once() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args( + object, + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/sqlite_databases', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -261,6 +303,7 @@ def test_dump_data_sources_does_not_dump_if_dry_run(): flexmock(module.dump).should_receive('create_named_pipe_for_dump').never() flexmock(module).should_receive('execute_command').never() flexmock(module.dump).should_receive('write_data_source_dumps_metadata').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never() assert ( module.dump_data_sources( diff --git a/tests/unit/hooks/data_source/test_zfs.py b/tests/unit/hooks/data_source/test_zfs.py index e973f71d..cdb39d94 100644 --- a/tests/unit/hooks/data_source/test_zfs.py +++ b/tests/unit/hooks/data_source/test_zfs.py @@ -1,5 +1,3 @@ -import os - import pytest from flexmock import flexmock @@ -302,7 +300,7 @@ def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_ ) -def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): +def test_dump_data_sources_snapshots_and_mounts_and_replaces_patterns(): dataset = flexmock( name='dataset', mount_point='/mnt/dataset', @@ -330,6 +328,14 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): '/run/borgmatic', ).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir')) patterns = [Pattern('/mnt/dataset/subdir')] + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/dataset/subdir'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() assert ( module.dump_data_sources( @@ -343,14 +349,13 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns(): == [] ) - assert patterns == [Pattern(os.path.join(snapshot_mount_path, 'subdir'))] - def test_dump_data_sources_with_no_datasets_skips_snapshots(): flexmock(module).should_receive('get_datasets_to_backup').and_return(()) flexmock(module.os).should_receive('getpid').and_return(1234) flexmock(module).should_receive('snapshot_dataset').never() flexmock(module).should_receive('mount_snapshot').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never() patterns = [Pattern('/mnt/dataset')] assert ( @@ -395,6 +400,14 @@ def test_dump_data_sources_uses_custom_commands(): dataset, '/run/borgmatic', ).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/dataset/subdir'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() patterns = [Pattern('/mnt/dataset/subdir')] hook_config = { 'zfs_command': '/usr/local/bin/zfs', @@ -416,8 +429,6 @@ def test_dump_data_sources_uses_custom_commands(): == [] ) - assert patterns == [Pattern(os.path.join(snapshot_mount_path, 'subdir'))] - def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_patterns(): flexmock(module).should_receive('get_datasets_to_backup').and_return( @@ -426,6 +437,7 @@ def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_patter flexmock(module.os).should_receive('getpid').and_return(1234) flexmock(module).should_receive('snapshot_dataset').never() flexmock(module).should_receive('mount_snapshot').never() + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never() patterns = [Pattern('/mnt/dataset')] assert ( @@ -440,8 +452,6 @@ def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_patter == [] ) - assert patterns == [Pattern('/mnt/dataset')] - def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns(): dataset = flexmock( @@ -470,6 +480,14 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained dataset, '/run/borgmatic', ).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir')) + flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args( + object, + Pattern('/mnt/dataset/subdir'), + module.borgmatic.borg.pattern.Pattern( + '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir', + source=module.borgmatic.borg.pattern.Pattern_source.HOOK, + ), + ).once() patterns = [Pattern('/hmm')] assert ( @@ -484,8 +502,6 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained == [] ) - assert patterns == [Pattern('/hmm'), Pattern(os.path.join(snapshot_mount_path, 'subdir'))] - def test_get_all_snapshots_parses_list_output(): flexmock(module.borgmatic.execute).should_receive( diff --git a/tox.ini b/tox.ini index da1a8b01..d25dfa9e 100644 --- a/tox.ini +++ b/tox.ini @@ -33,12 +33,18 @@ pass_env = COVERAGE_FILE commands = pytest {posargs} --no-cov tests/end-to-end -[testenv:lint-fix] +[testenv:lint] deps = [] skip_install = true commands = ruff check --diff {posargs} +[testenv:lint-fix] +deps = [] +skip_install = true +commands = + ruff check --fix {posargs} + [testenv:format] deps = [] skip_install = true