test_btrfs.py 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. import pytest
  2. from flexmock import flexmock
  3. from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
  4. from borgmatic.hooks.data_source import btrfs as module
  5. def test_get_subvolume_mount_points_parses_findmnt_output():
  6. flexmock(module.borgmatic.execute).should_receive(
  7. 'execute_command_and_capture_output'
  8. ).and_return(
  9. '''{
  10. "filesystems": [
  11. {
  12. "target": "/mnt0",
  13. "source": "/dev/loop0",
  14. "fstype": "btrfs",
  15. "options": "rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/"
  16. },
  17. {
  18. "target": "/mnt1",
  19. "source": "/dev/loop0",
  20. "fstype": "btrfs",
  21. "options": "rw,relatime,ssd,space_cache=v2,subvolid=5,subvol=/"
  22. }
  23. ]
  24. }
  25. '''
  26. )
  27. assert module.get_subvolume_mount_points('findmnt') == ('/mnt0', '/mnt1')
  28. def test_get_subvolume_mount_points_with_invalid_findmnt_json_errors():
  29. flexmock(module.borgmatic.execute).should_receive(
  30. 'execute_command_and_capture_output'
  31. ).and_return('{')
  32. with pytest.raises(ValueError):
  33. module.get_subvolume_mount_points('findmnt')
  34. def test_get_subvolume_mount_points_with_findmnt_json_missing_filesystems_errors():
  35. flexmock(module.borgmatic.execute).should_receive(
  36. 'execute_command_and_capture_output'
  37. ).and_return('{"wtf": "something is wrong here"}')
  38. with pytest.raises(ValueError):
  39. module.get_subvolume_mount_points('findmnt')
  40. def test_get_subvolume_property_with_invalid_btrfs_output_errors():
  41. flexmock(module.borgmatic.execute).should_receive(
  42. 'execute_command_and_capture_output'
  43. ).and_return('invalid')
  44. with pytest.raises(ValueError):
  45. module.get_subvolume_property('btrfs', '/foo', 'ro')
  46. def test_get_subvolume_property_with_true_output_returns_true_bool():
  47. flexmock(module.borgmatic.execute).should_receive(
  48. 'execute_command_and_capture_output'
  49. ).and_return('ro=true')
  50. assert module.get_subvolume_property('btrfs', '/foo', 'ro') is True
  51. def test_get_subvolume_property_with_false_output_returns_false_bool():
  52. flexmock(module.borgmatic.execute).should_receive(
  53. 'execute_command_and_capture_output'
  54. ).and_return('ro=false')
  55. assert module.get_subvolume_property('btrfs', '/foo', 'ro') is False
  56. def test_get_subvolume_property_passes_through_general_value():
  57. flexmock(module.borgmatic.execute).should_receive(
  58. 'execute_command_and_capture_output'
  59. ).and_return('thing=value')
  60. assert module.get_subvolume_property('btrfs', '/foo', 'thing') == 'value'
  61. def test_omit_read_only_subvolume_mount_points_filters_out_read_only():
  62. flexmock(module).should_receive('get_subvolume_property').with_args(
  63. 'btrfs', '/foo', 'ro'
  64. ).and_return(False)
  65. flexmock(module).should_receive('get_subvolume_property').with_args(
  66. 'btrfs', '/bar', 'ro'
  67. ).and_return(True)
  68. flexmock(module).should_receive('get_subvolume_property').with_args(
  69. 'btrfs', '/baz', 'ro'
  70. ).and_return(False)
  71. assert module.omit_read_only_subvolume_mount_points('btrfs', ('/foo', '/bar', '/baz')) == (
  72. '/foo',
  73. '/baz',
  74. )
  75. def test_get_subvolumes_collects_subvolumes_matching_patterns():
  76. flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
  77. flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
  78. ('/mnt1', '/mnt2')
  79. )
  80. contained_pattern = Pattern(
  81. '/mnt1',
  82. type=Pattern_type.ROOT,
  83. source=Pattern_source.CONFIG,
  84. )
  85. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  86. 'get_contained_patterns'
  87. ).with_args('/mnt1', object).and_return((contained_pattern,))
  88. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  89. 'get_contained_patterns'
  90. ).with_args('/mnt2', object).and_return(())
  91. assert module.get_subvolumes(
  92. 'btrfs',
  93. 'findmnt',
  94. patterns=[
  95. Pattern('/mnt1'),
  96. Pattern('/mnt3'),
  97. ],
  98. ) == (module.Subvolume('/mnt1', contained_patterns=(contained_pattern,)),)
  99. def test_get_subvolumes_skips_non_root_patterns():
  100. flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
  101. flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
  102. ('/mnt1', '/mnt2')
  103. )
  104. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  105. 'get_contained_patterns'
  106. ).with_args('/mnt1', object).and_return(
  107. (
  108. Pattern(
  109. '/mnt1',
  110. type=Pattern_type.EXCLUDE,
  111. source=Pattern_source.CONFIG,
  112. ),
  113. )
  114. )
  115. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  116. 'get_contained_patterns'
  117. ).with_args('/mnt2', object).and_return(())
  118. assert (
  119. module.get_subvolumes(
  120. 'btrfs',
  121. 'findmnt',
  122. patterns=[
  123. Pattern('/mnt1'),
  124. Pattern('/mnt3'),
  125. ],
  126. )
  127. == ()
  128. )
  129. def test_get_subvolumes_skips_non_config_patterns():
  130. flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
  131. flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
  132. ('/mnt1', '/mnt2')
  133. )
  134. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  135. 'get_contained_patterns'
  136. ).with_args('/mnt1', object).and_return(
  137. (
  138. Pattern(
  139. '/mnt1',
  140. type=Pattern_type.ROOT,
  141. source=Pattern_source.HOOK,
  142. ),
  143. )
  144. )
  145. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  146. 'get_contained_patterns'
  147. ).with_args('/mnt2', object).and_return(())
  148. assert (
  149. module.get_subvolumes(
  150. 'btrfs',
  151. 'findmnt',
  152. patterns=[
  153. Pattern('/mnt1'),
  154. Pattern('/mnt3'),
  155. ],
  156. )
  157. == ()
  158. )
  159. def test_get_subvolumes_without_patterns_collects_all_subvolumes():
  160. flexmock(module).should_receive('get_subvolume_mount_points').and_return(('/mnt1', '/mnt2'))
  161. flexmock(module).should_receive('omit_read_only_subvolume_mount_points').and_return(
  162. ('/mnt1', '/mnt2')
  163. )
  164. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  165. 'get_contained_patterns'
  166. ).with_args('/mnt1', object).and_return((Pattern('/mnt1'),))
  167. flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
  168. 'get_contained_patterns'
  169. ).with_args('/mnt2', object).and_return((Pattern('/mnt2'),))
  170. assert module.get_subvolumes('btrfs', 'findmnt') == (
  171. module.Subvolume('/mnt1', contained_patterns=(Pattern('/mnt1'),)),
  172. module.Subvolume('/mnt2', contained_patterns=(Pattern('/mnt2'),)),
  173. )
  174. @pytest.mark.parametrize(
  175. 'subvolume_path,expected_snapshot_path',
  176. (
  177. ('/foo/bar', '/foo/bar/.borgmatic-snapshot-1234/foo/bar'),
  178. ('/', '/.borgmatic-snapshot-1234'),
  179. ),
  180. )
  181. def test_make_snapshot_path_includes_stripped_subvolume_path(
  182. subvolume_path, expected_snapshot_path
  183. ):
  184. flexmock(module.os).should_receive('getpid').and_return(1234)
  185. assert module.make_snapshot_path(subvolume_path) == expected_snapshot_path
  186. @pytest.mark.parametrize(
  187. 'subvolume_path,pattern,expected_pattern',
  188. (
  189. (
  190. '/foo/bar',
  191. Pattern('/foo/bar/baz'),
  192. Pattern('/foo/bar/.borgmatic-snapshot-1234/./foo/bar/baz'),
  193. ),
  194. ('/foo/bar', Pattern('/foo/bar'), Pattern('/foo/bar/.borgmatic-snapshot-1234/./foo/bar')),
  195. (
  196. '/foo/bar',
  197. Pattern('^/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  198. Pattern(
  199. '^/foo/bar/.borgmatic-snapshot-1234/./foo/bar',
  200. Pattern_type.INCLUDE,
  201. Pattern_style.REGULAR_EXPRESSION,
  202. ),
  203. ),
  204. (
  205. '/foo/bar',
  206. Pattern('/foo/bar', Pattern_type.INCLUDE, Pattern_style.REGULAR_EXPRESSION),
  207. Pattern(
  208. '/foo/bar/.borgmatic-snapshot-1234/./foo/bar',
  209. Pattern_type.INCLUDE,
  210. Pattern_style.REGULAR_EXPRESSION,
  211. ),
  212. ),
  213. ('/', Pattern('/foo'), Pattern('/.borgmatic-snapshot-1234/./foo')),
  214. ('/', Pattern('/'), Pattern('/.borgmatic-snapshot-1234/./')),
  215. ),
  216. )
  217. def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_path(
  218. subvolume_path, pattern, expected_pattern
  219. ):
  220. flexmock(module.os).should_receive('getpid').and_return(1234)
  221. assert module.make_borg_snapshot_pattern(subvolume_path, pattern) == expected_pattern
  222. def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
  223. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  224. config = {'btrfs': {}}
  225. flexmock(module).should_receive('get_subvolumes').and_return(
  226. (
  227. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  228. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  229. )
  230. )
  231. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  232. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  233. )
  234. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  235. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  236. )
  237. flexmock(module).should_receive('snapshot_subvolume').with_args(
  238. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  239. ).once()
  240. flexmock(module).should_receive('snapshot_subvolume').with_args(
  241. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  242. ).once()
  243. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  244. '/mnt/subvol1'
  245. ).and_return(
  246. Pattern(
  247. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  248. Pattern_type.NO_RECURSE,
  249. Pattern_style.FNMATCH,
  250. )
  251. )
  252. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  253. '/mnt/subvol2'
  254. ).and_return(
  255. Pattern(
  256. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  257. Pattern_type.NO_RECURSE,
  258. Pattern_style.FNMATCH,
  259. )
  260. )
  261. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  262. '/mnt/subvol1', object
  263. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  264. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  265. '/mnt/subvol2', object
  266. ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
  267. assert (
  268. module.dump_data_sources(
  269. hook_config=config['btrfs'],
  270. config=config,
  271. config_paths=('test.yaml',),
  272. borgmatic_runtime_directory='/run/borgmatic',
  273. patterns=patterns,
  274. dry_run=False,
  275. )
  276. == []
  277. )
  278. assert patterns == [
  279. Pattern('/foo'),
  280. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  281. Pattern(
  282. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  283. Pattern_type.NO_RECURSE,
  284. Pattern_style.FNMATCH,
  285. ),
  286. Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
  287. Pattern(
  288. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  289. Pattern_type.NO_RECURSE,
  290. Pattern_style.FNMATCH,
  291. ),
  292. ]
  293. assert config == {
  294. 'btrfs': {},
  295. }
  296. def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
  297. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  298. config = {'btrfs': {'btrfs_command': '/usr/local/bin/btrfs'}}
  299. flexmock(module).should_receive('get_subvolumes').and_return(
  300. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),)
  301. )
  302. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  303. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  304. )
  305. flexmock(module).should_receive('snapshot_subvolume').with_args(
  306. '/usr/local/bin/btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  307. ).once()
  308. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  309. '/mnt/subvol1'
  310. ).and_return(
  311. Pattern(
  312. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  313. Pattern_type.NO_RECURSE,
  314. Pattern_style.FNMATCH,
  315. )
  316. )
  317. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  318. '/mnt/subvol1', object
  319. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  320. assert (
  321. module.dump_data_sources(
  322. hook_config=config['btrfs'],
  323. config=config,
  324. config_paths=('test.yaml',),
  325. borgmatic_runtime_directory='/run/borgmatic',
  326. patterns=patterns,
  327. dry_run=False,
  328. )
  329. == []
  330. )
  331. assert patterns == [
  332. Pattern('/foo'),
  333. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  334. Pattern(
  335. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  336. Pattern_type.NO_RECURSE,
  337. Pattern_style.FNMATCH,
  338. ),
  339. ]
  340. assert config == {
  341. 'btrfs': {
  342. 'btrfs_command': '/usr/local/bin/btrfs',
  343. },
  344. }
  345. def test_dump_data_sources_uses_custom_findmnt_command_in_commands():
  346. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  347. config = {'btrfs': {'findmnt_command': '/usr/local/bin/findmnt'}}
  348. flexmock(module).should_receive('get_subvolumes').with_args(
  349. 'btrfs', '/usr/local/bin/findmnt', patterns
  350. ).and_return(
  351. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),)
  352. ).once()
  353. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  354. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  355. )
  356. flexmock(module).should_receive('snapshot_subvolume').with_args(
  357. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  358. ).once()
  359. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  360. '/mnt/subvol1'
  361. ).and_return(
  362. Pattern(
  363. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  364. Pattern_type.NO_RECURSE,
  365. Pattern_style.FNMATCH,
  366. )
  367. )
  368. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  369. '/mnt/subvol1', object
  370. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  371. assert (
  372. module.dump_data_sources(
  373. hook_config=config['btrfs'],
  374. config=config,
  375. config_paths=('test.yaml',),
  376. borgmatic_runtime_directory='/run/borgmatic',
  377. patterns=patterns,
  378. dry_run=False,
  379. )
  380. == []
  381. )
  382. assert patterns == [
  383. Pattern('/foo'),
  384. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  385. Pattern(
  386. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  387. Pattern_type.NO_RECURSE,
  388. Pattern_style.FNMATCH,
  389. ),
  390. ]
  391. assert config == {
  392. 'btrfs': {
  393. 'findmnt_command': '/usr/local/bin/findmnt',
  394. },
  395. }
  396. def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update():
  397. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  398. config = {'btrfs': {}}
  399. flexmock(module).should_receive('get_subvolumes').and_return(
  400. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),)
  401. )
  402. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  403. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  404. )
  405. flexmock(module).should_receive('snapshot_subvolume').never()
  406. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  407. assert (
  408. module.dump_data_sources(
  409. hook_config=config['btrfs'],
  410. config=config,
  411. config_paths=('test.yaml',),
  412. borgmatic_runtime_directory='/run/borgmatic',
  413. patterns=patterns,
  414. dry_run=True,
  415. )
  416. == []
  417. )
  418. assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
  419. assert config == {'btrfs': {}}
  420. def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patterns_update():
  421. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  422. config = {'btrfs': {}}
  423. flexmock(module).should_receive('get_subvolumes').and_return(())
  424. flexmock(module).should_receive('make_snapshot_path').never()
  425. flexmock(module).should_receive('snapshot_subvolume').never()
  426. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  427. assert (
  428. module.dump_data_sources(
  429. hook_config=config['btrfs'],
  430. config=config,
  431. config_paths=('test.yaml',),
  432. borgmatic_runtime_directory='/run/borgmatic',
  433. patterns=patterns,
  434. dry_run=False,
  435. )
  436. == []
  437. )
  438. assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
  439. assert config == {'btrfs': {}}
  440. def test_dump_data_sources_snapshots_adds_to_existing_exclude_patterns():
  441. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  442. config = {'btrfs': {}, 'exclude_patterns': ['/bar']}
  443. flexmock(module).should_receive('get_subvolumes').and_return(
  444. (
  445. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  446. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  447. )
  448. )
  449. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  450. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  451. )
  452. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  453. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  454. )
  455. flexmock(module).should_receive('snapshot_subvolume').with_args(
  456. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  457. ).once()
  458. flexmock(module).should_receive('snapshot_subvolume').with_args(
  459. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  460. ).once()
  461. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  462. '/mnt/subvol1'
  463. ).and_return(
  464. Pattern(
  465. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  466. Pattern_type.NO_RECURSE,
  467. Pattern_style.FNMATCH,
  468. )
  469. )
  470. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  471. '/mnt/subvol2'
  472. ).and_return(
  473. Pattern(
  474. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  475. Pattern_type.NO_RECURSE,
  476. Pattern_style.FNMATCH,
  477. )
  478. )
  479. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  480. '/mnt/subvol1', object
  481. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  482. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  483. '/mnt/subvol2', object
  484. ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
  485. assert (
  486. module.dump_data_sources(
  487. hook_config=config['btrfs'],
  488. config=config,
  489. config_paths=('test.yaml',),
  490. borgmatic_runtime_directory='/run/borgmatic',
  491. patterns=patterns,
  492. dry_run=False,
  493. )
  494. == []
  495. )
  496. assert patterns == [
  497. Pattern('/foo'),
  498. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  499. Pattern(
  500. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  501. Pattern_type.NO_RECURSE,
  502. Pattern_style.FNMATCH,
  503. ),
  504. Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
  505. Pattern(
  506. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  507. Pattern_type.NO_RECURSE,
  508. Pattern_style.FNMATCH,
  509. ),
  510. ]
  511. assert config == {
  512. 'btrfs': {},
  513. 'exclude_patterns': ['/bar'],
  514. }
  515. def test_remove_data_source_dumps_deletes_snapshots():
  516. config = {'btrfs': {}}
  517. flexmock(module).should_receive('get_subvolumes').and_return(
  518. (
  519. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  520. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  521. )
  522. )
  523. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  524. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  525. )
  526. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  527. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  528. )
  529. flexmock(module.borgmatic.config.paths).should_receive(
  530. 'replace_temporary_subdirectory_with_glob'
  531. ).with_args(
  532. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  533. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  534. ).and_return(
  535. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  536. )
  537. flexmock(module.borgmatic.config.paths).should_receive(
  538. 'replace_temporary_subdirectory_with_glob'
  539. ).with_args(
  540. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  541. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  542. ).and_return(
  543. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  544. )
  545. flexmock(module.glob).should_receive('glob').with_args(
  546. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  547. ).and_return(
  548. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  549. )
  550. flexmock(module.glob).should_receive('glob').with_args(
  551. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  552. ).and_return(
  553. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  554. )
  555. flexmock(module.os.path).should_receive('isdir').with_args(
  556. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  557. ).and_return(True)
  558. flexmock(module.os.path).should_receive('isdir').with_args(
  559. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  560. ).and_return(True)
  561. flexmock(module.os.path).should_receive('isdir').with_args(
  562. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  563. ).and_return(True)
  564. flexmock(module.os.path).should_receive('isdir').with_args(
  565. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  566. ).and_return(False)
  567. flexmock(module).should_receive('delete_snapshot').with_args(
  568. 'btrfs', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  569. ).once()
  570. flexmock(module).should_receive('delete_snapshot').with_args(
  571. 'btrfs', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  572. ).once()
  573. flexmock(module).should_receive('delete_snapshot').with_args(
  574. 'btrfs', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  575. ).once()
  576. flexmock(module).should_receive('delete_snapshot').with_args(
  577. 'btrfs', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  578. ).never()
  579. flexmock(module.os.path).should_receive('isdir').with_args(
  580. '/mnt/subvol1/.borgmatic-1234'
  581. ).and_return(True)
  582. flexmock(module.os.path).should_receive('isdir').with_args(
  583. '/mnt/subvol1/.borgmatic-5678'
  584. ).and_return(True)
  585. flexmock(module.os.path).should_receive('isdir').with_args(
  586. '/mnt/subvol2/.borgmatic-1234'
  587. ).and_return(True)
  588. flexmock(module.os.path).should_receive('isdir').with_args(
  589. '/mnt/subvol2/.borgmatic-5678'
  590. ).and_return(True)
  591. flexmock(module.shutil).should_receive('rmtree').with_args(
  592. '/mnt/subvol1/.borgmatic-1234'
  593. ).once()
  594. flexmock(module.shutil).should_receive('rmtree').with_args(
  595. '/mnt/subvol1/.borgmatic-5678'
  596. ).once()
  597. flexmock(module.shutil).should_receive('rmtree').with_args(
  598. '/mnt/subvol2/.borgmatic-1234'
  599. ).once()
  600. flexmock(module.shutil).should_receive('rmtree').with_args(
  601. '/mnt/subvol2/.borgmatic-5678'
  602. ).never()
  603. module.remove_data_source_dumps(
  604. hook_config=config['btrfs'],
  605. config=config,
  606. borgmatic_runtime_directory='/run/borgmatic',
  607. dry_run=False,
  608. )
  609. def test_remove_data_source_dumps_without_hook_configuration_bails():
  610. flexmock(module).should_receive('get_subvolumes').never()
  611. flexmock(module).should_receive('make_snapshot_path').never()
  612. flexmock(module.borgmatic.config.paths).should_receive(
  613. 'replace_temporary_subdirectory_with_glob'
  614. ).never()
  615. flexmock(module).should_receive('delete_snapshot').never()
  616. flexmock(module.shutil).should_receive('rmtree').never()
  617. module.remove_data_source_dumps(
  618. hook_config=None,
  619. config={'source_directories': '/mnt/subvolume'},
  620. borgmatic_runtime_directory='/run/borgmatic',
  621. dry_run=False,
  622. )
  623. def test_remove_data_source_dumps_with_get_subvolumes_file_not_found_error_bails():
  624. config = {'btrfs': {}}
  625. flexmock(module).should_receive('get_subvolumes').and_raise(FileNotFoundError)
  626. flexmock(module).should_receive('make_snapshot_path').never()
  627. flexmock(module.borgmatic.config.paths).should_receive(
  628. 'replace_temporary_subdirectory_with_glob'
  629. ).never()
  630. flexmock(module).should_receive('delete_snapshot').never()
  631. flexmock(module.shutil).should_receive('rmtree').never()
  632. module.remove_data_source_dumps(
  633. hook_config=config['btrfs'],
  634. config=config,
  635. borgmatic_runtime_directory='/run/borgmatic',
  636. dry_run=False,
  637. )
  638. def test_remove_data_source_dumps_with_get_subvolumes_called_process_error_bails():
  639. config = {'btrfs': {}}
  640. flexmock(module).should_receive('get_subvolumes').and_raise(
  641. module.subprocess.CalledProcessError(1, 'command', 'error')
  642. )
  643. flexmock(module).should_receive('make_snapshot_path').never()
  644. flexmock(module.borgmatic.config.paths).should_receive(
  645. 'replace_temporary_subdirectory_with_glob'
  646. ).never()
  647. flexmock(module).should_receive('delete_snapshot').never()
  648. flexmock(module.shutil).should_receive('rmtree').never()
  649. module.remove_data_source_dumps(
  650. hook_config=config['btrfs'],
  651. config=config,
  652. borgmatic_runtime_directory='/run/borgmatic',
  653. dry_run=False,
  654. )
  655. def test_remove_data_source_dumps_with_dry_run_skips_deletes():
  656. config = {'btrfs': {}}
  657. flexmock(module).should_receive('get_subvolumes').and_return(
  658. (
  659. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  660. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  661. )
  662. )
  663. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  664. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  665. )
  666. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  667. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  668. )
  669. flexmock(module.borgmatic.config.paths).should_receive(
  670. 'replace_temporary_subdirectory_with_glob'
  671. ).with_args(
  672. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  673. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  674. ).and_return(
  675. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  676. )
  677. flexmock(module.borgmatic.config.paths).should_receive(
  678. 'replace_temporary_subdirectory_with_glob'
  679. ).with_args(
  680. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  681. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  682. ).and_return(
  683. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  684. )
  685. flexmock(module.glob).should_receive('glob').with_args(
  686. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  687. ).and_return(
  688. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  689. )
  690. flexmock(module.glob).should_receive('glob').with_args(
  691. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  692. ).and_return(
  693. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  694. )
  695. flexmock(module.os.path).should_receive('isdir').with_args(
  696. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  697. ).and_return(True)
  698. flexmock(module.os.path).should_receive('isdir').with_args(
  699. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  700. ).and_return(True)
  701. flexmock(module.os.path).should_receive('isdir').with_args(
  702. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  703. ).and_return(True)
  704. flexmock(module.os.path).should_receive('isdir').with_args(
  705. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  706. ).and_return(False)
  707. flexmock(module).should_receive('delete_snapshot').never()
  708. flexmock(module.shutil).should_receive('rmtree').never()
  709. module.remove_data_source_dumps(
  710. hook_config=config['btrfs'],
  711. config=config,
  712. borgmatic_runtime_directory='/run/borgmatic',
  713. dry_run=True,
  714. )
  715. def test_remove_data_source_dumps_without_subvolumes_skips_deletes():
  716. config = {'btrfs': {}}
  717. flexmock(module).should_receive('get_subvolumes').and_return(())
  718. flexmock(module).should_receive('make_snapshot_path').never()
  719. flexmock(module.borgmatic.config.paths).should_receive(
  720. 'replace_temporary_subdirectory_with_glob'
  721. ).never()
  722. flexmock(module).should_receive('delete_snapshot').never()
  723. flexmock(module.shutil).should_receive('rmtree').never()
  724. module.remove_data_source_dumps(
  725. hook_config=config['btrfs'],
  726. config=config,
  727. borgmatic_runtime_directory='/run/borgmatic',
  728. dry_run=False,
  729. )
  730. def test_remove_data_source_without_snapshots_skips_deletes():
  731. config = {'btrfs': {}}
  732. flexmock(module).should_receive('get_subvolumes').and_return(
  733. (
  734. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  735. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  736. )
  737. )
  738. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  739. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  740. )
  741. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  742. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  743. )
  744. flexmock(module.borgmatic.config.paths).should_receive(
  745. 'replace_temporary_subdirectory_with_glob'
  746. ).with_args(
  747. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  748. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  749. ).and_return(
  750. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  751. )
  752. flexmock(module.borgmatic.config.paths).should_receive(
  753. 'replace_temporary_subdirectory_with_glob'
  754. ).with_args(
  755. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  756. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  757. ).and_return(
  758. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  759. )
  760. flexmock(module.glob).should_receive('glob').and_return(())
  761. flexmock(module.os.path).should_receive('isdir').never()
  762. flexmock(module).should_receive('delete_snapshot').never()
  763. flexmock(module.shutil).should_receive('rmtree').never()
  764. module.remove_data_source_dumps(
  765. hook_config=config['btrfs'],
  766. config=config,
  767. borgmatic_runtime_directory='/run/borgmatic',
  768. dry_run=False,
  769. )
  770. def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bails():
  771. config = {'btrfs': {}}
  772. flexmock(module).should_receive('get_subvolumes').and_return(
  773. (
  774. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  775. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  776. )
  777. )
  778. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  779. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  780. )
  781. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  782. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  783. )
  784. flexmock(module.borgmatic.config.paths).should_receive(
  785. 'replace_temporary_subdirectory_with_glob'
  786. ).with_args(
  787. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  788. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  789. ).and_return(
  790. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  791. )
  792. flexmock(module.borgmatic.config.paths).should_receive(
  793. 'replace_temporary_subdirectory_with_glob'
  794. ).with_args(
  795. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  796. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  797. ).and_return(
  798. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  799. )
  800. flexmock(module.glob).should_receive('glob').with_args(
  801. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  802. ).and_return(
  803. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  804. )
  805. flexmock(module.glob).should_receive('glob').with_args(
  806. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  807. ).and_return(
  808. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  809. )
  810. flexmock(module.os.path).should_receive('isdir').with_args(
  811. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  812. ).and_return(True)
  813. flexmock(module.os.path).should_receive('isdir').with_args(
  814. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  815. ).and_return(True)
  816. flexmock(module.os.path).should_receive('isdir').with_args(
  817. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  818. ).and_return(True)
  819. flexmock(module.os.path).should_receive('isdir').with_args(
  820. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  821. ).and_return(False)
  822. flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError)
  823. flexmock(module.shutil).should_receive('rmtree').never()
  824. module.remove_data_source_dumps(
  825. hook_config=config['btrfs'],
  826. config=config,
  827. borgmatic_runtime_directory='/run/borgmatic',
  828. dry_run=False,
  829. )
  830. def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bails():
  831. config = {'btrfs': {}}
  832. flexmock(module).should_receive('get_subvolumes').and_return(
  833. (
  834. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  835. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  836. )
  837. )
  838. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  839. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  840. )
  841. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  842. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  843. )
  844. flexmock(module.borgmatic.config.paths).should_receive(
  845. 'replace_temporary_subdirectory_with_glob'
  846. ).with_args(
  847. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  848. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  849. ).and_return(
  850. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  851. )
  852. flexmock(module.borgmatic.config.paths).should_receive(
  853. 'replace_temporary_subdirectory_with_glob'
  854. ).with_args(
  855. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  856. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  857. ).and_return(
  858. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  859. )
  860. flexmock(module.glob).should_receive('glob').with_args(
  861. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  862. ).and_return(
  863. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  864. )
  865. flexmock(module.glob).should_receive('glob').with_args(
  866. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  867. ).and_return(
  868. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  869. )
  870. flexmock(module.os.path).should_receive('isdir').with_args(
  871. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  872. ).and_return(True)
  873. flexmock(module.os.path).should_receive('isdir').with_args(
  874. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  875. ).and_return(True)
  876. flexmock(module.os.path).should_receive('isdir').with_args(
  877. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  878. ).and_return(True)
  879. flexmock(module.os.path).should_receive('isdir').with_args(
  880. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  881. ).and_return(False)
  882. flexmock(module).should_receive('delete_snapshot').and_raise(
  883. module.subprocess.CalledProcessError(1, 'command', 'error')
  884. )
  885. flexmock(module.shutil).should_receive('rmtree').never()
  886. module.remove_data_source_dumps(
  887. hook_config=config['btrfs'],
  888. config=config,
  889. borgmatic_runtime_directory='/run/borgmatic',
  890. dry_run=False,
  891. )
  892. def test_remove_data_source_dumps_with_root_subvolume_skips_duplicate_removal():
  893. config = {'btrfs': {}}
  894. flexmock(module).should_receive('get_subvolumes').and_return(
  895. (module.Subvolume('/', contained_patterns=(Pattern('/etc'),)),)
  896. )
  897. flexmock(module).should_receive('make_snapshot_path').with_args('/').and_return(
  898. '/.borgmatic-1234'
  899. )
  900. flexmock(module.borgmatic.config.paths).should_receive(
  901. 'replace_temporary_subdirectory_with_glob'
  902. ).with_args(
  903. '/.borgmatic-1234',
  904. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  905. ).and_return(
  906. '/.borgmatic-*'
  907. )
  908. flexmock(module.glob).should_receive('glob').with_args('/.borgmatic-*').and_return(
  909. ('/.borgmatic-1234', '/.borgmatic-5678')
  910. )
  911. flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-1234').and_return(
  912. True
  913. ).and_return(False)
  914. flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-5678').and_return(
  915. True
  916. ).and_return(False)
  917. flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-1234').once()
  918. flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-5678').once()
  919. flexmock(module.os.path).should_receive('isdir').with_args('').and_return(False)
  920. flexmock(module.shutil).should_receive('rmtree').never()
  921. module.remove_data_source_dumps(
  922. hook_config=config['btrfs'],
  923. config=config,
  924. borgmatic_runtime_directory='/run/borgmatic',
  925. dry_run=False,
  926. )