test_btrfs.py 41 KB

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