diff --git a/borgmatic/hooks/data_source/btrfs.py b/borgmatic/hooks/data_source/btrfs.py index eb7afd23..7defeb0c 100644 --- a/borgmatic/hooks/data_source/btrfs.py +++ b/borgmatic/hooks/data_source/btrfs.py @@ -50,12 +50,16 @@ def path_is_a_subvolume(btrfs_command, path): return True +@functools.cache def get_subvolume_property(btrfs_command, subvolume_path, property_name): ''' Given a btrfs command, a subvolume path, and a property name to lookup, return the value of the corresponding property. Raise subprocess.CalledProcessError if the btrfs command errors. + + As a performance optimization, multiple calls to this function with the same arguments are + cached. ''' output = borgmatic.execute.execute_command_and_capture_output( ( diff --git a/tests/unit/hooks/data_source/test_btrfs.py b/tests/unit/hooks/data_source/test_btrfs.py index e9e96607..8a093ce8 100644 --- a/tests/unit/hooks/data_source/test_btrfs.py +++ b/tests/unit/hooks/data_source/test_btrfs.py @@ -38,6 +38,7 @@ def test_path_is_a_subvolume_caches_result_after_first_call(): def test_get_subvolume_property_with_invalid_btrfs_output_errors(): + module.get_subvolume_property.cache_clear() flexmock(module.borgmatic.execute).should_receive( 'execute_command_and_capture_output', ).and_return('invalid') @@ -47,6 +48,7 @@ def test_get_subvolume_property_with_invalid_btrfs_output_errors(): def test_get_subvolume_property_with_true_output_returns_true_bool(): + module.get_subvolume_property.cache_clear() flexmock(module.borgmatic.execute).should_receive( 'execute_command_and_capture_output', ).and_return('ro=true') @@ -55,6 +57,7 @@ def test_get_subvolume_property_with_true_output_returns_true_bool(): def test_get_subvolume_property_with_false_output_returns_false_bool(): + module.get_subvolume_property.cache_clear() flexmock(module.borgmatic.execute).should_receive( 'execute_command_and_capture_output', ).and_return('ro=false') @@ -63,6 +66,7 @@ def test_get_subvolume_property_with_false_output_returns_false_bool(): def test_get_subvolume_property_passes_through_general_value(): + module.get_subvolume_property.cache_clear() flexmock(module.borgmatic.execute).should_receive( 'execute_command_and_capture_output', ).and_return('thing=value') @@ -70,6 +74,16 @@ def test_get_subvolume_property_passes_through_general_value(): assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value' +def test_get_subvolume_property_caches_result_after_first_call(): + module.get_subvolume_property.cache_clear() + flexmock(module.borgmatic.execute).should_receive( + 'execute_command_and_capture_output', + ).and_return('thing=value').once() + + assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value' + assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value' + + def test_get_containing_subvolume_path_with_subvolume_self_returns_it(): flexmock(module).should_receive('path_is_a_subvolume').with_args( 'btrfs', '/foo/bar/baz'