From 78db924b707412e62b42c44ce095c8ba8662ed86 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 8 May 2026 18:14:15 -0700 Subject: [PATCH] Get tests passing (#1267). --- borgmatic/config/paths.py | 8 +- .../configuration/runtime-directory.md | 4 +- tests/unit/config/test_paths.py | 170 +++++++----------- 3 files changed, 70 insertions(+), 112 deletions(-) diff --git a/borgmatic/config/paths.py b/borgmatic/config/paths.py index 24429c84..0ba5368f 100644 --- a/borgmatic/config/paths.py +++ b/borgmatic/config/paths.py @@ -121,11 +121,13 @@ class Runtime_directory: ''' Given a configuration dict and the Borg ID for a repository, determine the borgmatic runtime directory, creating a secure, temporary directory within it if necessary. Defaults to - $XDG_RUNTIME_DIR/[repository_id]/./borgmatic or $RUNTIME_DIRECTORY/[repository_id]/./borgmatic or + $XDG_RUNTIME_DIR/borgmatic-[repository_id]/./borgmatic or + $RUNTIME_DIRECTORY/borgmatic-[repository_id]/./borgmatic or $TMPDIR/borgmatic-[random]/./borgmatic or $TEMP/borgmatic-[random]/./borgmatic or /tmp/borgmatic-[random]/./borgmatic where "[random]" is a randomly generated string and - "[repository_id]" is the Borg repository ID. Both are intended to avoid path collisions, and - the random string helps avoid temporary file attacks. + "[repository_id]" is the Borg repository ID being operated on. Both strings are intended to + avoid path collisions, and the random string helps avoid predictable temporary path attacks + in shared temporary directories. The "/./" is taking advantage of a Borg feature such that the part of the path before the "/./" does not get stored in the file path within an archive. That way, the path of the runtime diff --git a/docs/reference/configuration/runtime-directory.md b/docs/reference/configuration/runtime-directory.md index 59b5429a..1fa396d9 100644 --- a/docs/reference/configuration/runtime-directory.md +++ b/docs/reference/configuration/runtime-directory.md @@ -32,8 +32,8 @@ You can see the runtime directory path that borgmatic selects by running with Regardless of the runtime directory selected, borgmatic stores its files within a `borgmatic` subdirectory of the runtime directory. Additionally, in the case of `TMPDIR`, `TEMP`, and the hard-coded `/tmp`, borgmatic creates a randomly -named subdirectory in an effort to reduce path collisions and temporary file -attacks in shared system temporary directories. +named subdirectory in an effort to reduce path collisions and predictable +temporary path attacks in shared temporary directories. New in version 2.1.6 When constructing the runtime directory, borgmatic now creates a subdirectory named diff --git a/tests/unit/config/test_paths.py b/tests/unit/config/test_paths.py index eb2d3790..06e9b4c7 100644 --- a/tests/unit/config/test_paths.py +++ b/tests/unit/config/test_paths.py @@ -63,64 +63,45 @@ def test_replace_temporary_subdirectory_with_glob_uses_custom_temporary_director def test_runtime_directory_uses_config_option(): + flexmock(module).should_receive('Fixed_name_temporary_directory').and_return(flexmock(cleanup=lambda: None)) flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) flexmock(module.os).should_receive('makedirs') config = {'user_runtime_directory': '/run', 'borgmatic_source_directory': '/nope'} - with module.Runtime_directory(config) as borgmatic_runtime_directory: - assert borgmatic_runtime_directory == '/run/./borgmatic' - - -def test_runtime_directory_uses_config_option_without_adding_duplicate_borgmatic_subdirectory(): - flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os).should_receive('makedirs') - config = {'user_runtime_directory': '/run/borgmatic', 'borgmatic_source_directory': '/nope'} - - with module.Runtime_directory(config) as borgmatic_runtime_directory: - assert borgmatic_runtime_directory == '/run/./borgmatic' + with module.Runtime_directory(config, repository_id='id') as borgmatic_runtime_directory: + assert borgmatic_runtime_directory == '/run/borgmatic-id/./borgmatic' def test_runtime_directory_with_relative_config_option_errors(): flexmock(module.os).should_receive('makedirs').never() config = {'user_runtime_directory': 'run', 'borgmatic_source_directory': '/nope'} - with pytest.raises(ValueError), module.Runtime_directory(config): + with pytest.raises(ValueError), module.Runtime_directory(config, repository_id='id'): pass -def test_runtime_directory_falls_back_to_xdg_runtime_dir(): +def test_runtime_directory_falls_back_to_xdg_runtime_dir(monkeypatch): + flexmock(module).should_receive('Fixed_name_temporary_directory').and_return(flexmock(cleanup=lambda: None)) flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return( - '/run', - ) + monkeypatch.setenv('XDG_RUNTIME_DIR', '/run') flexmock(module.os).should_receive('makedirs') - with module.Runtime_directory({}) as borgmatic_runtime_directory: - assert borgmatic_runtime_directory == '/run/./borgmatic' + with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory: + assert borgmatic_runtime_directory == '/run/borgmatic-id/./borgmatic' -def test_runtime_directory_falls_back_to_xdg_runtime_dir_without_adding_duplicate_borgmatic_subdirectory(): - flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return( - '/run/borgmatic', - ) - flexmock(module.os).should_receive('makedirs') - - with module.Runtime_directory({}) as borgmatic_runtime_directory: - assert borgmatic_runtime_directory == '/run/./borgmatic' - - -def test_runtime_directory_with_relative_xdg_runtime_dir_errors(): - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return('run') +def test_runtime_directory_with_relative_xdg_runtime_dir_errors(monkeypatch): + monkeypatch.setenv('XDG_RUNTIME_DIR', 'run') flexmock(module.os).should_receive('makedirs').never() - with pytest.raises(ValueError), module.Runtime_directory({}): + with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'): pass -def test_runtime_directory_falls_back_to_runtime_directory(): +def test_runtime_directory_falls_back_to_runtime_directory(monkeypatch): + flexmock(module).should_receive('Fixed_name_temporary_directory').and_return(flexmock(cleanup=lambda: None)) flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( @@ -128,26 +109,12 @@ def test_runtime_directory_falls_back_to_runtime_directory(): ) flexmock(module.os).should_receive('makedirs') - with module.Runtime_directory({}) as borgmatic_runtime_directory: - assert borgmatic_runtime_directory == '/run/./borgmatic' + with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory: + assert borgmatic_runtime_directory == '/run/borgmatic-id/./borgmatic' -def test_runtime_directory_falls_back_to_runtime_directory_without_adding_duplicate_borgmatic_subdirectory(): - flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) - flexmock(module).should_receive('resolve_systemd_directory').with_args( - module.Systemd_directories.RUNTIME_DIRECTORY - ).and_return( - '/run/borgmatic', - ) - flexmock(module.os).should_receive('makedirs') - - with module.Runtime_directory({}) as borgmatic_runtime_directory: - assert borgmatic_runtime_directory == '/run/./borgmatic' - - -def test_runtime_directory_with_relative_runtime_directory_errors(): - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) +def test_runtime_directory_with_relative_runtime_directory_errors(monkeypatch): + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( @@ -155,19 +122,19 @@ def test_runtime_directory_with_relative_runtime_directory_errors(): ) flexmock(module.os).should_receive('makedirs').never() - with pytest.raises(ValueError), module.Runtime_directory({}): + with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'): pass -def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_that_get_cleaned_up(): +def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_that_get_cleaned_up(monkeypatch): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( None, ) - flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('/run') + monkeypatch.setenv('TMPDIR', '/run') temporary_directory = flexmock(name='/run/borgmatic-1234') temporary_directory.should_receive('cleanup').once() flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args( @@ -176,35 +143,35 @@ def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_ ).and_return(temporary_directory) flexmock(module.os).should_receive('makedirs') - with module.Runtime_directory({}) as borgmatic_runtime_directory: + with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory: assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic' -def test_runtime_directory_with_relative_tmpdir_errors(): - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) +def test_runtime_directory_with_relative_tmpdir_errors(monkeypatch): + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( None, ) - flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('run') + monkeypatch.setenv('TMPDIR', 'run') flexmock(module.tempfile).should_receive('TemporaryDirectory').never() flexmock(module.os).should_receive('makedirs').never() - with pytest.raises(ValueError), module.Runtime_directory({}): + with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'): pass -def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_that_get_cleaned_up(): +def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_that_get_cleaned_up(monkeypatch): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( None, ) - flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None) - flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('/run') + monkeypatch.delenv('TMPDIR', raising=False) + monkeypatch.setenv('TEMP', '/run') temporary_directory = flexmock(name='/run/borgmatic-1234') temporary_directory.should_receive('cleanup').once() flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args( @@ -213,36 +180,36 @@ def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_th ).and_return(temporary_directory) flexmock(module.os).should_receive('makedirs') - with module.Runtime_directory({}) as borgmatic_runtime_directory: + with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory: assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic' -def test_runtime_directory_with_relative_temp_errors(): - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) +def test_runtime_directory_with_relative_temp_errors(monkeypatch): + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( None, ) - flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None) - flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('run') + monkeypatch.delenv('TMPDIR', raising=False) + monkeypatch.setenv('TEMP', 'run') flexmock(module.tempfile).should_receive('TemporaryDirectory').never() flexmock(module.os).should_receive('makedirs') - with pytest.raises(ValueError), module.Runtime_directory({}): + with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'): pass -def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_subdirectory_that_get_cleaned_up(): +def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_subdirectory_that_get_cleaned_up(monkeypatch): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( None, ) - flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None) - flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return(None) + monkeypatch.delenv('TMPDIR', raising=False) + monkeypatch.delenv('TEMP', raising=False) temporary_directory = flexmock(name='/tmp/borgmatic-1234') temporary_directory.should_receive('cleanup').once() flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args( @@ -251,20 +218,20 @@ def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_ ).and_return(temporary_directory) flexmock(module.os).should_receive('makedirs') - with module.Runtime_directory({}) as borgmatic_runtime_directory: + with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory: assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic' -def test_runtime_directory_with_erroring_cleanup_does_not_raise(): +def test_runtime_directory_with_erroring_cleanup_does_not_raise(monkeypatch): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None) + monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.RUNTIME_DIRECTORY ).and_return( None, ) - flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None) - flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return(None) + monkeypatch.delenv('TMPDIR', raising=False) + monkeypatch.delenv('TEMP', raising=False) temporary_directory = flexmock(name='/tmp/borgmatic-1234') temporary_directory.should_receive('cleanup').and_raise(OSError).once() flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args( @@ -273,7 +240,7 @@ def test_runtime_directory_with_erroring_cleanup_does_not_raise(): ).and_return(temporary_directory) flexmock(module.os).should_receive('makedirs') - with module.Runtime_directory({}) as borgmatic_runtime_directory: + with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory: assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic' @@ -291,7 +258,6 @@ def test_make_runtime_directory_glob(borgmatic_runtime_directory, expected_glob) def test_get_borgmatic_state_directory_uses_config_option(): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').never() assert ( module.get_borgmatic_state_directory( @@ -301,16 +267,16 @@ def test_get_borgmatic_state_directory_uses_config_option(): ) -def test_get_borgmatic_state_directory_falls_back_to_xdg_state_home(): +def test_get_borgmatic_state_directory_falls_back_to_xdg_state_home(monkeypatch): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_STATE_HOME').and_return('/tmp') + monkeypatch.setenv('XDG_STATE_HOME', '/tmp') assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic' -def test_get_borgmatic_state_directory_falls_back_to_state_directory(): +def test_get_borgmatic_state_directory_falls_back_to_state_directory(monkeypatch): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').with_args('XDG_STATE_HOME').and_return(None) + monkeypatch.delenv('XDG_STATE_HOME', raising=False) flexmock(module).should_receive('resolve_systemd_directory').with_args( module.Systemd_directories.STATE_DIRECTORY ).and_return( @@ -320,32 +286,26 @@ def test_get_borgmatic_state_directory_falls_back_to_state_directory(): assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic' -def test_get_borgmatic_state_directory_defaults_to_hard_coded_path(): +def test_get_borgmatic_state_directory_defaults_to_hard_coded_path(monkeypatch): flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path) - flexmock(module.os.environ).should_receive('get').and_return(None) + monkeypatch.delenv('XDG_STATE_HOME', raising=False) assert module.get_borgmatic_state_directory({}) == '~/.local/state/borgmatic' -def test_resolve_systemd_directory_none(): - flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return( - None - ) - flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return(None) +def test_resolve_systemd_directory_none(monkeypatch): + monkeypatch.delenv('RUNTIME_DIRECTORY', raising=False) + monkeypatch.delenv('STATE_DIRECTORY', raising=False) assert module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY) is None assert module.resolve_systemd_directory(module.Systemd_directories.STATE_DIRECTORY) is None -def test_resolve_systemd_directory_single(): +def test_resolve_systemd_directory_single(monkeypatch): runtime_dir = '/run/borgmatic' state_dir = '/var/lib/borgmatic' - flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return( - runtime_dir - ) - flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return( - state_dir - ) + monkeypatch.setenv('RUNTIME_DIRECTORY', runtime_dir) + monkeypatch.setenv('STATE_DIRECTORY', state_dir) assert ( module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY) @@ -354,16 +314,12 @@ def test_resolve_systemd_directory_single(): assert module.resolve_systemd_directory(module.Systemd_directories.STATE_DIRECTORY) == state_dir -def test_resolve_systemd_directory_multiple(): +def test_resolve_systemd_directory_multiple(monkeypatch): runtime_dirs = '/run/borgmatic:/run/second:/run/third' state_dirs = '/var/lib/borgmatic:/var/lib/second:/var/lib/third' - flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return( - runtime_dirs - ) - flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return( - state_dirs - ) + monkeypatch.setenv('RUNTIME_DIRECTORY', runtime_dirs) + monkeypatch.setenv('STATE_DIRECTORY', state_dirs) assert ( module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY)