From 35feeb4615f080342bbf3c992c27916e35d8087c Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Tue, 30 Sep 2025 21:56:04 -0700 Subject: [PATCH] Fix restore and get end-to-end tests passing (#1105). --- borgmatic/actions/restore.py | 9 ++++ tests/end-to-end/commands/fake_btrfs.py | 35 ++++++---------- tests/end-to-end/commands/fake_findmnt.py | 42 ------------------- .../hooks/data_source/test_btrfs.py | 3 +- tests/unit/actions/test_create.py | 5 +++ tests/unit/actions/test_restore.py | 18 ++++++++ 6 files changed, 46 insertions(+), 66 deletions(-) delete mode 100644 tests/end-to-end/commands/fake_findmnt.py diff --git a/borgmatic/actions/restore.py b/borgmatic/actions/restore.py index 95f5539b..8f9efe62 100644 --- a/borgmatic/actions/restore.py +++ b/borgmatic/actions/restore.py @@ -5,6 +5,7 @@ import pathlib import shutil import tempfile +import borgmatic.actions.pattern import borgmatic.borg.extract import borgmatic.borg.list import borgmatic.borg.mount @@ -536,13 +537,20 @@ def run_restore( return logger.info(f'Restoring data sources from archive {restore_arguments.archive}') + working_directory = borgmatic.config.paths.get_working_directory(config) with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory: + patterns = borgmatic.actions.pattern.process_patterns( + borgmatic.actions.pattern.collect_patterns(config), + config, + working_directory, + ) borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured( 'remove_data_source_dumps', config, borgmatic.hooks.dispatch.Hook_type.DATA_SOURCE, borgmatic_runtime_directory, + patterns, global_arguments.dry_run, ) @@ -625,6 +633,7 @@ def run_restore( config, borgmatic.hooks.dispatch.Hook_type.DATA_SOURCE, borgmatic_runtime_directory, + patterns, global_arguments.dry_run, ) diff --git a/tests/end-to-end/commands/fake_btrfs.py b/tests/end-to-end/commands/fake_btrfs.py index 4902b66a..d35aff8c 100644 --- a/tests/end-to-end/commands/fake_btrfs.py +++ b/tests/end-to-end/commands/fake_btrfs.py @@ -12,9 +12,8 @@ def parse_arguments(*unparsed_arguments): subvolume_parser = action_parsers.add_parser('subvolume') subvolume_subparser = subvolume_parser.add_subparsers(dest='subaction') - list_parser = subvolume_subparser.add_parser('list') - list_parser.add_argument('-s', dest='snapshots_only', action='store_true') - list_parser.add_argument('subvolume_path') + show_parser = subvolume_subparser.add_parser('show') + show_parser.add_argument('subvolume_path') snapshot_parser = subvolume_subparser.add_parser('snapshot') snapshot_parser.add_argument('-r', dest='read_only', action='store_true') @@ -24,6 +23,9 @@ def parse_arguments(*unparsed_arguments): delete_parser = subvolume_subparser.add_parser('delete') delete_parser.add_argument('snapshot_path') + ensure_deleted_parser = subvolume_subparser.add_parser('ensure_deleted') + ensure_deleted_parser.add_argument('snapshot_path') + property_parser = action_parsers.add_parser('property') property_subparser = property_parser.add_subparsers(dest='subaction') get_parser = property_subparser.add_parser('get') @@ -34,13 +36,6 @@ def parse_arguments(*unparsed_arguments): return (global_parser, global_parser.parse_args(unparsed_arguments)) -BUILTIN_SUBVOLUME_LIST_LINES = ( - '261 gen 29 top level 5 path sub', - '262 gen 29 top level 5 path other', -) -SUBVOLUME_LIST_LINE_PREFIX = '263 gen 29 top level 5 path ' - - def load_snapshots(): try: return json.load(open('/tmp/fake_btrfs.json')) @@ -52,18 +47,12 @@ def save_snapshots(snapshot_paths): json.dump(snapshot_paths, open('/tmp/fake_btrfs.json', 'w')) -def print_subvolume_list(arguments, snapshot_paths): +def print_subvolume_show(arguments): assert arguments.subvolume_path == '/e2e/mnt/subvolume' - if not arguments.snapshots_only: - for line in BUILTIN_SUBVOLUME_LIST_LINES: - print(line) - - for snapshot_path in snapshot_paths: - print( - SUBVOLUME_LIST_LINE_PREFIX - + snapshot_path[snapshot_path.index('.borgmatic-snapshot-') :], - ) + # borgmatic doesn't currently parse the output of "btrfs subvolume show"—it's just checking the + # exit code—so what we print in response doesn't matter in this test. + print('Totally legit btrfs subvolume!') def main(): @@ -74,8 +63,8 @@ def main(): global_parser.print_help() sys.exit(1) - if arguments.subaction == 'list': - print_subvolume_list(arguments, snapshot_paths) + if arguments.subaction == 'show': + print_subvolume_show(arguments) elif arguments.subaction == 'snapshot': snapshot_paths.append(arguments.snapshot_path) save_snapshots(snapshot_paths) @@ -95,6 +84,8 @@ def main(): if snapshot_path.endswith('/' + arguments.snapshot_path) ] save_snapshots(snapshot_paths) + elif arguments.subaction == 'ensure_deleted': + assert arguments.snapshot_path not in snapshot_paths elif arguments.action == 'property' and arguments.subaction == 'get': print(f'{arguments.property_name}=false') diff --git a/tests/end-to-end/commands/fake_findmnt.py b/tests/end-to-end/commands/fake_findmnt.py deleted file mode 100644 index e6b60a6c..00000000 --- a/tests/end-to-end/commands/fake_findmnt.py +++ /dev/null @@ -1,42 +0,0 @@ -import argparse -import sys - - -def parse_arguments(*unparsed_arguments): - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument('-t', dest='type') - parser.add_argument('--json', action='store_true') - parser.add_argument('--list', action='store_true') - - return parser.parse_args(unparsed_arguments) - - -BUILTIN_FILESYSTEM_MOUNT_OUTPUT = '''{ - "filesystems": [ - { - "target": "/e2e/mnt/subvolume", - "source": "/dev/loop0", - "fstype": "btrfs", - "options": "rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/" - } - ] - } - ''' - - -def print_filesystem_mounts(): - print(BUILTIN_FILESYSTEM_MOUNT_OUTPUT) - - -def main(): - arguments = parse_arguments(*sys.argv[1:]) - - assert arguments.type == 'btrfs' - assert arguments.json - assert arguments.list - - print_filesystem_mounts() - - -if __name__ == '__main__': - main() diff --git a/tests/end-to-end/hooks/data_source/test_btrfs.py b/tests/end-to-end/hooks/data_source/test_btrfs.py index 5dfb2655..0bdab903 100644 --- a/tests/end-to-end/hooks/data_source/test_btrfs.py +++ b/tests/end-to-end/hooks/data_source/test_btrfs.py @@ -24,7 +24,6 @@ def generate_configuration(config_path, repository_path): + 'encryption_passphrase: "test"\n' + 'btrfs:\n' + ' btrfs_command: python3 /app/tests/end-to-end/commands/fake_btrfs.py\n' - + ' findmnt_command: python3 /app/tests/end-to-end/commands/fake_findmnt.py\n' ) config_file = open(config_path, 'w') config_file.write(config) @@ -55,7 +54,7 @@ def test_btrfs_create_and_list(): # Assert that the snapshot has been deleted. assert not subprocess.check_output( - 'python3 /app/tests/end-to-end/commands/fake_btrfs.py subvolume list -s /e2e/mnt/subvolume'.split( + 'python3 /app/tests/end-to-end/commands/fake_btrfs.py subvolume ensure_deleted /e2e/mnt/subvolume'.split( ' ', ), ) diff --git a/tests/unit/actions/test_create.py b/tests/unit/actions/test_create.py index ddb9b703..e8ad6032 100644 --- a/tests/unit/actions/test_create.py +++ b/tests/unit/actions/test_create.py @@ -18,6 +18,7 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository(): flexmock(module.borgmatic.hooks.dispatch).should_receive( 'call_hooks_even_if_unconfigured', ).and_return({}) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap') @@ -60,6 +61,7 @@ def test_run_create_runs_with_selected_repository(): flexmock(module.borgmatic.hooks.dispatch).should_receive( 'call_hooks_even_if_unconfigured', ).and_return({}) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap') @@ -207,6 +209,7 @@ def test_run_create_produces_json(): flexmock(module.borgmatic.hooks.dispatch).should_receive( 'call_hooks_even_if_unconfigured', ).and_return({}) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap') @@ -252,6 +255,7 @@ def test_run_create_with_active_dumps_roundtrips_via_checkpoint_archive(): flexmock(module.borgmatic.hooks.dispatch).should_receive( 'call_hooks_even_if_unconfigured', ).and_return({}) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap') @@ -335,6 +339,7 @@ def test_run_create_with_active_dumps_json_updates_archive_info(): flexmock(module.borgmatic.hooks.dispatch).should_receive( 'call_hooks_even_if_unconfigured', ).and_return({}) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap') diff --git a/tests/unit/actions/test_restore.py b/tests/unit/actions/test_restore.py index 372bfbc0..5ba26bb3 100644 --- a/tests/unit/actions/test_restore.py +++ b/tests/unit/actions/test_restore.py @@ -1176,6 +1176,9 @@ def test_run_restore_restores_each_data_source(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) + flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) + flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured') flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( flexmock(), @@ -1245,6 +1248,9 @@ def test_run_restore_bails_for_non_matching_repository(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) + flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) + flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(module.borgmatic.hooks.dispatch).should_receive( 'call_hooks_even_if_unconfigured', ).never() @@ -1274,6 +1280,9 @@ def test_run_restore_restores_data_source_by_falling_back_to_all_name(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) + flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) + flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured') flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( flexmock(), @@ -1334,6 +1343,9 @@ def test_run_restore_restores_data_source_configured_with_all_name(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) + flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) + flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured') flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( flexmock(), @@ -1416,6 +1428,9 @@ def test_run_restore_skips_missing_data_source(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) + flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) + flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured') flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( flexmock(), @@ -1498,6 +1513,9 @@ def test_run_restore_restores_data_sources_from_different_hooks(): flexmock(module.borgmatic.config.paths).should_receive( 'make_runtime_directory_glob', ).replace_with(lambda path: path) + flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None) + flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(()) + flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([]) flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured') flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return( flexmock(),