test_btrfs.py 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  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. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  224. flexmock()
  225. )
  226. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  227. config = {'btrfs': {}}
  228. flexmock(module).should_receive('get_subvolumes').and_return(
  229. (
  230. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  231. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  232. )
  233. )
  234. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  235. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  236. )
  237. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  238. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  239. )
  240. flexmock(module).should_receive('snapshot_subvolume').with_args(
  241. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  242. ).once()
  243. flexmock(module).should_receive('snapshot_subvolume').with_args(
  244. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  245. ).once()
  246. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  247. '/mnt/subvol1'
  248. ).and_return(
  249. Pattern(
  250. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  251. Pattern_type.NO_RECURSE,
  252. Pattern_style.FNMATCH,
  253. )
  254. )
  255. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  256. '/mnt/subvol2'
  257. ).and_return(
  258. Pattern(
  259. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  260. Pattern_type.NO_RECURSE,
  261. Pattern_style.FNMATCH,
  262. )
  263. )
  264. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  265. '/mnt/subvol1', object
  266. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  267. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  268. '/mnt/subvol2', object
  269. ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
  270. assert (
  271. module.dump_data_sources(
  272. hook_config=config['btrfs'],
  273. config=config,
  274. config_paths=('test.yaml',),
  275. borgmatic_runtime_directory='/run/borgmatic',
  276. patterns=patterns,
  277. dry_run=False,
  278. )
  279. == []
  280. )
  281. assert patterns == [
  282. Pattern('/foo'),
  283. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  284. Pattern(
  285. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  286. Pattern_type.NO_RECURSE,
  287. Pattern_style.FNMATCH,
  288. ),
  289. Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
  290. Pattern(
  291. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  292. Pattern_type.NO_RECURSE,
  293. Pattern_style.FNMATCH,
  294. ),
  295. ]
  296. assert config == {
  297. 'btrfs': {},
  298. }
  299. def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
  300. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  301. flexmock()
  302. )
  303. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  304. config = {'btrfs': {'btrfs_command': '/usr/local/bin/btrfs'}}
  305. flexmock(module).should_receive('get_subvolumes').and_return(
  306. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),)
  307. )
  308. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  309. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  310. )
  311. flexmock(module).should_receive('snapshot_subvolume').with_args(
  312. '/usr/local/bin/btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  313. ).once()
  314. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  315. '/mnt/subvol1'
  316. ).and_return(
  317. Pattern(
  318. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  319. Pattern_type.NO_RECURSE,
  320. Pattern_style.FNMATCH,
  321. )
  322. )
  323. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  324. '/mnt/subvol1', object
  325. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  326. assert (
  327. module.dump_data_sources(
  328. hook_config=config['btrfs'],
  329. config=config,
  330. config_paths=('test.yaml',),
  331. borgmatic_runtime_directory='/run/borgmatic',
  332. patterns=patterns,
  333. dry_run=False,
  334. )
  335. == []
  336. )
  337. assert patterns == [
  338. Pattern('/foo'),
  339. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  340. Pattern(
  341. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  342. Pattern_type.NO_RECURSE,
  343. Pattern_style.FNMATCH,
  344. ),
  345. ]
  346. assert config == {
  347. 'btrfs': {
  348. 'btrfs_command': '/usr/local/bin/btrfs',
  349. },
  350. }
  351. def test_dump_data_sources_uses_custom_findmnt_command_in_commands():
  352. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  353. flexmock()
  354. )
  355. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  356. config = {'btrfs': {'findmnt_command': '/usr/local/bin/findmnt'}}
  357. flexmock(module).should_receive('get_subvolumes').with_args(
  358. 'btrfs', '/usr/local/bin/findmnt', patterns
  359. ).and_return(
  360. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),)
  361. ).once()
  362. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  363. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  364. )
  365. flexmock(module).should_receive('snapshot_subvolume').with_args(
  366. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  367. ).once()
  368. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  369. '/mnt/subvol1'
  370. ).and_return(
  371. Pattern(
  372. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  373. Pattern_type.NO_RECURSE,
  374. Pattern_style.FNMATCH,
  375. )
  376. )
  377. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  378. '/mnt/subvol1', object
  379. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  380. assert (
  381. module.dump_data_sources(
  382. hook_config=config['btrfs'],
  383. config=config,
  384. config_paths=('test.yaml',),
  385. borgmatic_runtime_directory='/run/borgmatic',
  386. patterns=patterns,
  387. dry_run=False,
  388. )
  389. == []
  390. )
  391. assert patterns == [
  392. Pattern('/foo'),
  393. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  394. Pattern(
  395. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  396. Pattern_type.NO_RECURSE,
  397. Pattern_style.FNMATCH,
  398. ),
  399. ]
  400. assert config == {
  401. 'btrfs': {
  402. 'findmnt_command': '/usr/local/bin/findmnt',
  403. },
  404. }
  405. def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update():
  406. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  407. flexmock()
  408. )
  409. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  410. config = {'btrfs': {}}
  411. flexmock(module).should_receive('get_subvolumes').and_return(
  412. (module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),)
  413. )
  414. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  415. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  416. )
  417. flexmock(module).should_receive('snapshot_subvolume').never()
  418. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  419. assert (
  420. module.dump_data_sources(
  421. hook_config=config['btrfs'],
  422. config=config,
  423. config_paths=('test.yaml',),
  424. borgmatic_runtime_directory='/run/borgmatic',
  425. patterns=patterns,
  426. dry_run=True,
  427. )
  428. == []
  429. )
  430. assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
  431. assert config == {'btrfs': {}}
  432. def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patterns_update():
  433. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  434. flexmock()
  435. )
  436. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  437. config = {'btrfs': {}}
  438. flexmock(module).should_receive('get_subvolumes').and_return(())
  439. flexmock(module).should_receive('make_snapshot_path').never()
  440. flexmock(module).should_receive('snapshot_subvolume').never()
  441. flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
  442. assert (
  443. module.dump_data_sources(
  444. hook_config=config['btrfs'],
  445. config=config,
  446. config_paths=('test.yaml',),
  447. borgmatic_runtime_directory='/run/borgmatic',
  448. patterns=patterns,
  449. dry_run=False,
  450. )
  451. == []
  452. )
  453. assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
  454. assert config == {'btrfs': {}}
  455. def test_dump_data_sources_snapshots_adds_to_existing_exclude_patterns():
  456. flexmock(module.borgmatic.hooks.command).should_receive('Before_after_hooks').and_return(
  457. flexmock()
  458. )
  459. patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
  460. config = {'btrfs': {}, 'exclude_patterns': ['/bar']}
  461. flexmock(module).should_receive('get_subvolumes').and_return(
  462. (
  463. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  464. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  465. )
  466. )
  467. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  468. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  469. )
  470. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  471. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  472. )
  473. flexmock(module).should_receive('snapshot_subvolume').with_args(
  474. 'btrfs', '/mnt/subvol1', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  475. ).once()
  476. flexmock(module).should_receive('snapshot_subvolume').with_args(
  477. 'btrfs', '/mnt/subvol2', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  478. ).once()
  479. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  480. '/mnt/subvol1'
  481. ).and_return(
  482. Pattern(
  483. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  484. Pattern_type.NO_RECURSE,
  485. Pattern_style.FNMATCH,
  486. )
  487. )
  488. flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
  489. '/mnt/subvol2'
  490. ).and_return(
  491. Pattern(
  492. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  493. Pattern_type.NO_RECURSE,
  494. Pattern_style.FNMATCH,
  495. )
  496. )
  497. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  498. '/mnt/subvol1', object
  499. ).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
  500. flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
  501. '/mnt/subvol2', object
  502. ).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
  503. assert (
  504. module.dump_data_sources(
  505. hook_config=config['btrfs'],
  506. config=config,
  507. config_paths=('test.yaml',),
  508. borgmatic_runtime_directory='/run/borgmatic',
  509. patterns=patterns,
  510. dry_run=False,
  511. )
  512. == []
  513. )
  514. assert patterns == [
  515. Pattern('/foo'),
  516. Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
  517. Pattern(
  518. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
  519. Pattern_type.NO_RECURSE,
  520. Pattern_style.FNMATCH,
  521. ),
  522. Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
  523. Pattern(
  524. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
  525. Pattern_type.NO_RECURSE,
  526. Pattern_style.FNMATCH,
  527. ),
  528. ]
  529. assert config == {
  530. 'btrfs': {},
  531. 'exclude_patterns': ['/bar'],
  532. }
  533. def test_remove_data_source_dumps_deletes_snapshots():
  534. config = {'btrfs': {}}
  535. flexmock(module).should_receive('get_subvolumes').and_return(
  536. (
  537. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  538. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  539. )
  540. )
  541. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  542. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  543. )
  544. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  545. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  546. )
  547. flexmock(module.borgmatic.config.paths).should_receive(
  548. 'replace_temporary_subdirectory_with_glob'
  549. ).with_args(
  550. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  551. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  552. ).and_return(
  553. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  554. )
  555. flexmock(module.borgmatic.config.paths).should_receive(
  556. 'replace_temporary_subdirectory_with_glob'
  557. ).with_args(
  558. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  559. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  560. ).and_return(
  561. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  562. )
  563. flexmock(module.glob).should_receive('glob').with_args(
  564. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  565. ).and_return(
  566. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  567. )
  568. flexmock(module.glob).should_receive('glob').with_args(
  569. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  570. ).and_return(
  571. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  572. )
  573. flexmock(module.os.path).should_receive('isdir').with_args(
  574. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  575. ).and_return(True)
  576. flexmock(module.os.path).should_receive('isdir').with_args(
  577. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  578. ).and_return(True)
  579. flexmock(module.os.path).should_receive('isdir').with_args(
  580. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  581. ).and_return(True)
  582. flexmock(module.os.path).should_receive('isdir').with_args(
  583. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  584. ).and_return(False)
  585. flexmock(module).should_receive('delete_snapshot').with_args(
  586. 'btrfs', '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  587. ).once()
  588. flexmock(module).should_receive('delete_snapshot').with_args(
  589. 'btrfs', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  590. ).once()
  591. flexmock(module).should_receive('delete_snapshot').with_args(
  592. 'btrfs', '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  593. ).once()
  594. flexmock(module).should_receive('delete_snapshot').with_args(
  595. 'btrfs', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  596. ).never()
  597. flexmock(module.os.path).should_receive('isdir').with_args(
  598. '/mnt/subvol1/.borgmatic-1234'
  599. ).and_return(True)
  600. flexmock(module.os.path).should_receive('isdir').with_args(
  601. '/mnt/subvol1/.borgmatic-5678'
  602. ).and_return(True)
  603. flexmock(module.os.path).should_receive('isdir').with_args(
  604. '/mnt/subvol2/.borgmatic-1234'
  605. ).and_return(True)
  606. flexmock(module.os.path).should_receive('isdir').with_args(
  607. '/mnt/subvol2/.borgmatic-5678'
  608. ).and_return(True)
  609. flexmock(module.shutil).should_receive('rmtree').with_args(
  610. '/mnt/subvol1/.borgmatic-1234'
  611. ).once()
  612. flexmock(module.shutil).should_receive('rmtree').with_args(
  613. '/mnt/subvol1/.borgmatic-5678'
  614. ).once()
  615. flexmock(module.shutil).should_receive('rmtree').with_args(
  616. '/mnt/subvol2/.borgmatic-1234'
  617. ).once()
  618. flexmock(module.shutil).should_receive('rmtree').with_args(
  619. '/mnt/subvol2/.borgmatic-5678'
  620. ).never()
  621. module.remove_data_source_dumps(
  622. hook_config=config['btrfs'],
  623. config=config,
  624. borgmatic_runtime_directory='/run/borgmatic',
  625. dry_run=False,
  626. )
  627. def test_remove_data_source_dumps_without_hook_configuration_bails():
  628. flexmock(module).should_receive('get_subvolumes').never()
  629. flexmock(module).should_receive('make_snapshot_path').never()
  630. flexmock(module.borgmatic.config.paths).should_receive(
  631. 'replace_temporary_subdirectory_with_glob'
  632. ).never()
  633. flexmock(module).should_receive('delete_snapshot').never()
  634. flexmock(module.shutil).should_receive('rmtree').never()
  635. module.remove_data_source_dumps(
  636. hook_config=None,
  637. config={'source_directories': '/mnt/subvolume'},
  638. borgmatic_runtime_directory='/run/borgmatic',
  639. dry_run=False,
  640. )
  641. def test_remove_data_source_dumps_with_get_subvolumes_file_not_found_error_bails():
  642. config = {'btrfs': {}}
  643. flexmock(module).should_receive('get_subvolumes').and_raise(FileNotFoundError)
  644. flexmock(module).should_receive('make_snapshot_path').never()
  645. flexmock(module.borgmatic.config.paths).should_receive(
  646. 'replace_temporary_subdirectory_with_glob'
  647. ).never()
  648. flexmock(module).should_receive('delete_snapshot').never()
  649. flexmock(module.shutil).should_receive('rmtree').never()
  650. module.remove_data_source_dumps(
  651. hook_config=config['btrfs'],
  652. config=config,
  653. borgmatic_runtime_directory='/run/borgmatic',
  654. dry_run=False,
  655. )
  656. def test_remove_data_source_dumps_with_get_subvolumes_called_process_error_bails():
  657. config = {'btrfs': {}}
  658. flexmock(module).should_receive('get_subvolumes').and_raise(
  659. module.subprocess.CalledProcessError(1, 'command', 'error')
  660. )
  661. flexmock(module).should_receive('make_snapshot_path').never()
  662. flexmock(module.borgmatic.config.paths).should_receive(
  663. 'replace_temporary_subdirectory_with_glob'
  664. ).never()
  665. flexmock(module).should_receive('delete_snapshot').never()
  666. flexmock(module.shutil).should_receive('rmtree').never()
  667. module.remove_data_source_dumps(
  668. hook_config=config['btrfs'],
  669. config=config,
  670. borgmatic_runtime_directory='/run/borgmatic',
  671. dry_run=False,
  672. )
  673. def test_remove_data_source_dumps_with_dry_run_skips_deletes():
  674. config = {'btrfs': {}}
  675. flexmock(module).should_receive('get_subvolumes').and_return(
  676. (
  677. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  678. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  679. )
  680. )
  681. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  682. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  683. )
  684. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  685. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  686. )
  687. flexmock(module.borgmatic.config.paths).should_receive(
  688. 'replace_temporary_subdirectory_with_glob'
  689. ).with_args(
  690. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  691. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  692. ).and_return(
  693. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  694. )
  695. flexmock(module.borgmatic.config.paths).should_receive(
  696. 'replace_temporary_subdirectory_with_glob'
  697. ).with_args(
  698. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  699. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  700. ).and_return(
  701. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  702. )
  703. flexmock(module.glob).should_receive('glob').with_args(
  704. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  705. ).and_return(
  706. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  707. )
  708. flexmock(module.glob).should_receive('glob').with_args(
  709. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  710. ).and_return(
  711. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  712. )
  713. flexmock(module.os.path).should_receive('isdir').with_args(
  714. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  715. ).and_return(True)
  716. flexmock(module.os.path).should_receive('isdir').with_args(
  717. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  718. ).and_return(True)
  719. flexmock(module.os.path).should_receive('isdir').with_args(
  720. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  721. ).and_return(True)
  722. flexmock(module.os.path).should_receive('isdir').with_args(
  723. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  724. ).and_return(False)
  725. flexmock(module).should_receive('delete_snapshot').never()
  726. flexmock(module.shutil).should_receive('rmtree').never()
  727. module.remove_data_source_dumps(
  728. hook_config=config['btrfs'],
  729. config=config,
  730. borgmatic_runtime_directory='/run/borgmatic',
  731. dry_run=True,
  732. )
  733. def test_remove_data_source_dumps_without_subvolumes_skips_deletes():
  734. config = {'btrfs': {}}
  735. flexmock(module).should_receive('get_subvolumes').and_return(())
  736. flexmock(module).should_receive('make_snapshot_path').never()
  737. flexmock(module.borgmatic.config.paths).should_receive(
  738. 'replace_temporary_subdirectory_with_glob'
  739. ).never()
  740. flexmock(module).should_receive('delete_snapshot').never()
  741. flexmock(module.shutil).should_receive('rmtree').never()
  742. module.remove_data_source_dumps(
  743. hook_config=config['btrfs'],
  744. config=config,
  745. borgmatic_runtime_directory='/run/borgmatic',
  746. dry_run=False,
  747. )
  748. def test_remove_data_source_without_snapshots_skips_deletes():
  749. config = {'btrfs': {}}
  750. flexmock(module).should_receive('get_subvolumes').and_return(
  751. (
  752. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  753. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  754. )
  755. )
  756. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  757. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  758. )
  759. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  760. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  761. )
  762. flexmock(module.borgmatic.config.paths).should_receive(
  763. 'replace_temporary_subdirectory_with_glob'
  764. ).with_args(
  765. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  766. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  767. ).and_return(
  768. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  769. )
  770. flexmock(module.borgmatic.config.paths).should_receive(
  771. 'replace_temporary_subdirectory_with_glob'
  772. ).with_args(
  773. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  774. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  775. ).and_return(
  776. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  777. )
  778. flexmock(module.glob).should_receive('glob').and_return(())
  779. flexmock(module.os.path).should_receive('isdir').never()
  780. flexmock(module).should_receive('delete_snapshot').never()
  781. flexmock(module.shutil).should_receive('rmtree').never()
  782. module.remove_data_source_dumps(
  783. hook_config=config['btrfs'],
  784. config=config,
  785. borgmatic_runtime_directory='/run/borgmatic',
  786. dry_run=False,
  787. )
  788. def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bails():
  789. config = {'btrfs': {}}
  790. flexmock(module).should_receive('get_subvolumes').and_return(
  791. (
  792. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  793. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  794. )
  795. )
  796. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  797. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  798. )
  799. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  800. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  801. )
  802. flexmock(module.borgmatic.config.paths).should_receive(
  803. 'replace_temporary_subdirectory_with_glob'
  804. ).with_args(
  805. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  806. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  807. ).and_return(
  808. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  809. )
  810. flexmock(module.borgmatic.config.paths).should_receive(
  811. 'replace_temporary_subdirectory_with_glob'
  812. ).with_args(
  813. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  814. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  815. ).and_return(
  816. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  817. )
  818. flexmock(module.glob).should_receive('glob').with_args(
  819. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  820. ).and_return(
  821. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  822. )
  823. flexmock(module.glob).should_receive('glob').with_args(
  824. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  825. ).and_return(
  826. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  827. )
  828. flexmock(module.os.path).should_receive('isdir').with_args(
  829. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  830. ).and_return(True)
  831. flexmock(module.os.path).should_receive('isdir').with_args(
  832. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  833. ).and_return(True)
  834. flexmock(module.os.path).should_receive('isdir').with_args(
  835. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  836. ).and_return(True)
  837. flexmock(module.os.path).should_receive('isdir').with_args(
  838. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  839. ).and_return(False)
  840. flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError)
  841. flexmock(module.shutil).should_receive('rmtree').never()
  842. module.remove_data_source_dumps(
  843. hook_config=config['btrfs'],
  844. config=config,
  845. borgmatic_runtime_directory='/run/borgmatic',
  846. dry_run=False,
  847. )
  848. def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bails():
  849. config = {'btrfs': {}}
  850. flexmock(module).should_receive('get_subvolumes').and_return(
  851. (
  852. module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
  853. module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
  854. )
  855. )
  856. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
  857. '/mnt/subvol1/.borgmatic-1234/./mnt/subvol1'
  858. )
  859. flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
  860. '/mnt/subvol2/.borgmatic-1234/./mnt/subvol2'
  861. )
  862. flexmock(module.borgmatic.config.paths).should_receive(
  863. 'replace_temporary_subdirectory_with_glob'
  864. ).with_args(
  865. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
  866. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  867. ).and_return(
  868. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  869. )
  870. flexmock(module.borgmatic.config.paths).should_receive(
  871. 'replace_temporary_subdirectory_with_glob'
  872. ).with_args(
  873. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
  874. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  875. ).and_return(
  876. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  877. )
  878. flexmock(module.glob).should_receive('glob').with_args(
  879. '/mnt/subvol1/.borgmatic-*/mnt/subvol1'
  880. ).and_return(
  881. ('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1')
  882. )
  883. flexmock(module.glob).should_receive('glob').with_args(
  884. '/mnt/subvol2/.borgmatic-*/mnt/subvol2'
  885. ).and_return(
  886. ('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2')
  887. )
  888. flexmock(module.os.path).should_receive('isdir').with_args(
  889. '/mnt/subvol1/.borgmatic-1234/mnt/subvol1'
  890. ).and_return(True)
  891. flexmock(module.os.path).should_receive('isdir').with_args(
  892. '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'
  893. ).and_return(True)
  894. flexmock(module.os.path).should_receive('isdir').with_args(
  895. '/mnt/subvol2/.borgmatic-1234/mnt/subvol2'
  896. ).and_return(True)
  897. flexmock(module.os.path).should_receive('isdir').with_args(
  898. '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'
  899. ).and_return(False)
  900. flexmock(module).should_receive('delete_snapshot').and_raise(
  901. module.subprocess.CalledProcessError(1, 'command', 'error')
  902. )
  903. flexmock(module.shutil).should_receive('rmtree').never()
  904. module.remove_data_source_dumps(
  905. hook_config=config['btrfs'],
  906. config=config,
  907. borgmatic_runtime_directory='/run/borgmatic',
  908. dry_run=False,
  909. )
  910. def test_remove_data_source_dumps_with_root_subvolume_skips_duplicate_removal():
  911. config = {'btrfs': {}}
  912. flexmock(module).should_receive('get_subvolumes').and_return(
  913. (module.Subvolume('/', contained_patterns=(Pattern('/etc'),)),)
  914. )
  915. flexmock(module).should_receive('make_snapshot_path').with_args('/').and_return(
  916. '/.borgmatic-1234'
  917. )
  918. flexmock(module.borgmatic.config.paths).should_receive(
  919. 'replace_temporary_subdirectory_with_glob'
  920. ).with_args(
  921. '/.borgmatic-1234',
  922. temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
  923. ).and_return(
  924. '/.borgmatic-*'
  925. )
  926. flexmock(module.glob).should_receive('glob').with_args('/.borgmatic-*').and_return(
  927. ('/.borgmatic-1234', '/.borgmatic-5678')
  928. )
  929. flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-1234').and_return(
  930. True
  931. ).and_return(False)
  932. flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-5678').and_return(
  933. True
  934. ).and_return(False)
  935. flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-1234').once()
  936. flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-5678').once()
  937. flexmock(module.os.path).should_receive('isdir').with_args('').and_return(False)
  938. flexmock(module.shutil).should_receive('rmtree').never()
  939. module.remove_data_source_dumps(
  940. hook_config=config['btrfs'],
  941. config=config,
  942. borgmatic_runtime_directory='/run/borgmatic',
  943. dry_run=False,
  944. )