Browse Source

Cache Btrfs get property commands (#1105).

Dan Helfman 1 month ago
parent
commit
75405bed89
2 changed files with 18 additions and 0 deletions
  1. 4 0
      borgmatic/hooks/data_source/btrfs.py
  2. 14 0
      tests/unit/hooks/data_source/test_btrfs.py

+ 4 - 0
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(
         (

+ 14 - 0
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'