test_pattern.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. import io
  2. import sys
  3. import pytest
  4. from flexmock import flexmock
  5. from borgmatic.actions import pattern as module
  6. from borgmatic.borg.pattern import Pattern, Pattern_source, Pattern_style, Pattern_type
  7. @pytest.mark.parametrize(
  8. 'pattern_line,expected_pattern',
  9. (
  10. ('R /foo', Pattern('/foo', source=Pattern_source.CONFIG)),
  11. ('P sh', Pattern('sh', Pattern_type.PATTERN_STYLE, source=Pattern_source.CONFIG)),
  12. ('+ /foo*', Pattern('/foo*', Pattern_type.INCLUDE, source=Pattern_source.CONFIG)),
  13. (
  14. '+ sh:/foo*',
  15. Pattern(
  16. '/foo*',
  17. Pattern_type.INCLUDE,
  18. Pattern_style.SHELL,
  19. source=Pattern_source.CONFIG,
  20. ),
  21. ),
  22. ),
  23. )
  24. def test_parse_pattern_transforms_pattern_line_to_instance(pattern_line, expected_pattern):
  25. assert module.parse_pattern(pattern_line) == expected_pattern
  26. def test_parse_pattern_with_invalid_pattern_line_errors():
  27. with pytest.raises(ValueError):
  28. module.parse_pattern('/foo')
  29. def test_collect_patterns_converts_source_directories():
  30. assert module.collect_patterns({'source_directories': ['/foo', '/bar']}) == (
  31. Pattern('/foo', source=Pattern_source.CONFIG),
  32. Pattern('/bar', source=Pattern_source.CONFIG),
  33. )
  34. def test_collect_patterns_parses_config_patterns():
  35. flexmock(module).should_receive('parse_pattern').with_args('R /foo').and_return(Pattern('/foo'))
  36. flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
  37. flexmock(module).should_receive('parse_pattern').with_args('').never()
  38. flexmock(module).should_receive('parse_pattern').with_args(' ').never()
  39. flexmock(module).should_receive('parse_pattern').with_args('R /bar').and_return(Pattern('/bar'))
  40. assert module.collect_patterns({'patterns': ['R /foo', '# comment', '', ' ', 'R /bar']}) == (
  41. Pattern('/foo'),
  42. Pattern('/bar'),
  43. )
  44. def test_collect_patterns_converts_exclude_patterns():
  45. assert module.collect_patterns({'exclude_patterns': ['/foo', '/bar', 'sh:**/baz']}) == (
  46. Pattern(
  47. '/foo',
  48. Pattern_type.NO_RECURSE,
  49. Pattern_style.FNMATCH,
  50. source=Pattern_source.CONFIG,
  51. ),
  52. Pattern(
  53. '/bar',
  54. Pattern_type.NO_RECURSE,
  55. Pattern_style.FNMATCH,
  56. source=Pattern_source.CONFIG,
  57. ),
  58. Pattern(
  59. '**/baz',
  60. Pattern_type.NO_RECURSE,
  61. Pattern_style.SHELL,
  62. source=Pattern_source.CONFIG,
  63. ),
  64. )
  65. def test_collect_patterns_reads_config_patterns_from_file():
  66. builtins = flexmock(sys.modules['builtins'])
  67. builtins.should_receive('open').with_args('file1.txt', encoding='utf-8').and_return(
  68. io.StringIO('R /foo')
  69. )
  70. builtins.should_receive('open').with_args('file2.txt', encoding='utf-8').and_return(
  71. io.StringIO('R /bar\n# comment\n\n \nR /baz'),
  72. )
  73. flexmock(module).should_receive('parse_pattern').with_args('R /foo').and_return(Pattern('/foo'))
  74. flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
  75. flexmock(module).should_receive('parse_pattern').with_args('').never()
  76. flexmock(module).should_receive('parse_pattern').with_args(' ').never()
  77. flexmock(module).should_receive('parse_pattern').with_args('R /bar').and_return(Pattern('/bar'))
  78. flexmock(module).should_receive('parse_pattern').with_args('R /baz').and_return(Pattern('/baz'))
  79. assert module.collect_patterns({'patterns_from': ['file1.txt', 'file2.txt']}) == (
  80. Pattern('/foo'),
  81. Pattern('/bar'),
  82. Pattern('/baz'),
  83. )
  84. def test_collect_patterns_errors_on_missing_config_patterns_from_file():
  85. builtins = flexmock(sys.modules['builtins'])
  86. builtins.should_receive('open').with_args('file1.txt', encoding='utf-8').and_raise(
  87. FileNotFoundError
  88. )
  89. flexmock(module).should_receive('parse_pattern').never()
  90. with pytest.raises(ValueError):
  91. module.collect_patterns({'patterns_from': ['file1.txt', 'file2.txt']})
  92. def test_collect_patterns_reads_config_exclude_from_file():
  93. builtins = flexmock(sys.modules['builtins'])
  94. builtins.should_receive('open').with_args('file1.txt', encoding='utf-8').and_return(
  95. io.StringIO('/foo')
  96. )
  97. builtins.should_receive('open').with_args('file2.txt', encoding='utf-8').and_return(
  98. io.StringIO('/bar\n# comment\n\n \n/baz'),
  99. )
  100. flexmock(module).should_receive('parse_pattern').with_args(
  101. '! /foo',
  102. default_style=Pattern_style.FNMATCH,
  103. ).and_return(Pattern('/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
  104. flexmock(module).should_receive('parse_pattern').with_args(
  105. '! /bar',
  106. default_style=Pattern_style.FNMATCH,
  107. ).and_return(Pattern('/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
  108. flexmock(module).should_receive('parse_pattern').with_args('# comment').never()
  109. flexmock(module).should_receive('parse_pattern').with_args('').never()
  110. flexmock(module).should_receive('parse_pattern').with_args(' ').never()
  111. flexmock(module).should_receive('parse_pattern').with_args(
  112. '! /baz',
  113. default_style=Pattern_style.FNMATCH,
  114. ).and_return(Pattern('/baz', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH))
  115. assert module.collect_patterns({'exclude_from': ['file1.txt', 'file2.txt']}) == (
  116. Pattern('/foo', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
  117. Pattern('/bar', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
  118. Pattern('/baz', Pattern_type.NO_RECURSE, Pattern_style.FNMATCH),
  119. )
  120. def test_collect_patterns_errors_on_missing_config_exclude_from_file():
  121. builtins = flexmock(sys.modules['builtins'])
  122. builtins.should_receive('open').with_args('file1.txt', encoding='utf-8').and_raise(OSError)
  123. flexmock(module).should_receive('parse_pattern').never()
  124. with pytest.raises(ValueError):
  125. module.collect_patterns({'exclude_from': ['file1.txt', 'file2.txt']})
  126. def test_expand_directory_with_basic_path_passes_it_through():
  127. flexmock(module.os.path).should_receive('expanduser').and_return('foo')
  128. flexmock(module.glob).should_receive('glob').and_return([])
  129. paths = module.expand_directory('foo', None)
  130. assert paths == ['foo']
  131. def test_expand_directory_with_glob_expands():
  132. flexmock(module.os.path).should_receive('expanduser').and_return('foo*')
  133. flexmock(module.glob).should_receive('glob').and_return(['foo', 'food'])
  134. paths = module.expand_directory('foo*', None)
  135. assert paths == ['foo', 'food']
  136. def test_expand_directory_strips_off_working_directory():
  137. flexmock(module.os.path).should_receive('expanduser').and_return('foo')
  138. flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo').and_return([]).once()
  139. paths = module.expand_directory('foo', working_directory='/working/dir')
  140. assert paths == ['foo']
  141. def test_expand_directory_globs_working_directory_and_strips_it_off():
  142. flexmock(module.os.path).should_receive('expanduser').and_return('foo*')
  143. flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo*').and_return(
  144. ['/working/dir/foo', '/working/dir/food'],
  145. ).once()
  146. paths = module.expand_directory('foo*', working_directory='/working/dir')
  147. assert paths == ['foo', 'food']
  148. def test_expand_directory_with_slashdot_hack_globs_working_directory_and_strips_it_off():
  149. flexmock(module.os.path).should_receive('expanduser').and_return('./foo*')
  150. flexmock(module.glob).should_receive('glob').with_args('/working/dir/./foo*').and_return(
  151. ['/working/dir/./foo', '/working/dir/./food'],
  152. ).once()
  153. paths = module.expand_directory('./foo*', working_directory='/working/dir')
  154. assert paths == ['./foo', './food']
  155. def test_expand_directory_with_working_directory_matching_start_of_directory_does_not_strip_it_off():
  156. flexmock(module.os.path).should_receive('expanduser').and_return('/working/dir/foo')
  157. flexmock(module.glob).should_receive('glob').with_args('/working/dir/foo').and_return(
  158. ['/working/dir/foo'],
  159. ).once()
  160. paths = module.expand_directory('/working/dir/foo', working_directory='/working/dir')
  161. assert paths == ['/working/dir/foo']
  162. def test_expand_patterns_flattens_expanded_directories():
  163. flexmock(module).should_receive('expand_directory').with_args('~/foo', None).and_return(
  164. ['/root/foo'],
  165. )
  166. flexmock(module).should_receive('expand_directory').with_args('bar*', None).and_return(
  167. ['bar', 'barf'],
  168. )
  169. paths = module.expand_patterns((Pattern('~/foo'), Pattern('bar*')))
  170. assert paths == (Pattern('/root/foo'), Pattern('bar'), Pattern('barf'))
  171. def test_expand_patterns_with_working_directory_passes_it_through():
  172. flexmock(module).should_receive('expand_directory').with_args('foo', '/working/dir').and_return(
  173. ['/working/dir/foo'],
  174. )
  175. patterns = module.expand_patterns((Pattern('foo'),), working_directory='/working/dir')
  176. assert patterns == (Pattern('/working/dir/foo'),)
  177. def test_expand_patterns_does_not_expand_skip_paths():
  178. flexmock(module).should_receive('expand_directory').with_args('/foo', None).and_return(['/foo'])
  179. flexmock(module).should_receive('expand_directory').with_args('/bar*', None).never()
  180. patterns = module.expand_patterns((Pattern('/foo'), Pattern('/bar*')), skip_paths=('/bar*',))
  181. assert patterns == (Pattern('/foo'), Pattern('/bar*'))
  182. def test_expand_patterns_considers_none_as_no_patterns():
  183. assert module.expand_patterns(None) == ()
  184. def test_expand_patterns_expands_tildes_and_globs_in_root_patterns():
  185. flexmock(module.os.path).should_receive('expanduser').never()
  186. flexmock(module).should_receive('expand_directory').and_return(
  187. ['/root/foo/one', '/root/foo/two'],
  188. )
  189. paths = module.expand_patterns((Pattern('~/foo/*'),))
  190. assert paths == (Pattern('/root/foo/one'), Pattern('/root/foo/two'))
  191. def test_expand_patterns_expands_only_tildes_in_non_root_patterns():
  192. flexmock(module).should_receive('expand_directory').never()
  193. flexmock(module.os.path).should_receive('expanduser').and_return('/root/bar/*')
  194. paths = module.expand_patterns((Pattern('~/bar/*', Pattern_type.INCLUDE),))
  195. assert paths == (Pattern('/root/bar/*', Pattern_type.INCLUDE),)
  196. def test_get_existent_path_or_parent_passes_through_existent_path():
  197. flexmock(module.os.path).should_receive('exists').and_return(True)
  198. assert module.get_existent_path_or_parent('/foo/bar/baz') == '/foo/bar/baz'
  199. def test_get_existent_path_or_parent_with_non_existent_path_returns_none():
  200. flexmock(module.os.path).should_receive('exists').and_return(False)
  201. assert module.get_existent_path_or_parent('/foo/bar/baz') is None
  202. def test_get_existent_path_or_parent_with_non_existent_path_returns_existent_parent():
  203. flexmock(module.os.path).should_receive('exists').with_args('/foo/bar/baz*').and_return(False)
  204. flexmock(module.os.path).should_receive('exists').with_args('/foo/bar').and_return(True)
  205. flexmock(module.os.path).should_receive('exists').with_args('/foo').never()
  206. flexmock(module.os.path).should_receive('exists').with_args('/').never()
  207. assert module.get_existent_path_or_parent('/foo/bar/baz*') == '/foo/bar'
  208. def test_get_existent_path_or_parent_with_non_existent_path_returns_existent_grandparent():
  209. flexmock(module.os.path).should_receive('exists').with_args('/foo/bar/baz*').and_return(False)
  210. flexmock(module.os.path).should_receive('exists').with_args('/foo/bar').and_return(False)
  211. flexmock(module.os.path).should_receive('exists').with_args('/foo').and_return(True)
  212. flexmock(module.os.path).should_receive('exists').with_args('/').never()
  213. assert module.get_existent_path_or_parent('/foo/bar/baz*') == '/foo'
  214. def test_get_existent_path_or_parent_with_end_to_end_test_prefix_returns_none():
  215. flexmock(module.os.path).should_receive('exists').never()
  216. assert module.get_existent_path_or_parent('/e2e/foo/bar/baz') is None
  217. def test_device_map_patterns_gives_device_id_per_path():
  218. flexmock(module).should_receive('get_existent_path_or_parent').replace_with(lambda path: path)
  219. flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
  220. flexmock(module.os).should_receive('stat').with_args('/bar').and_return(flexmock(st_dev=66))
  221. device_map = module.device_map_patterns(
  222. (
  223. Pattern('/foo'),
  224. Pattern('^/bar', type=Pattern_type.INCLUDE, style=Pattern_style.REGULAR_EXPRESSION),
  225. ),
  226. )
  227. assert device_map == (
  228. Pattern('/foo', device=55),
  229. Pattern(
  230. '^/bar',
  231. type=Pattern_type.INCLUDE,
  232. style=Pattern_style.REGULAR_EXPRESSION,
  233. device=66,
  234. ),
  235. )
  236. def test_device_map_patterns_with_missing_path_does_not_error():
  237. flexmock(module).should_receive('get_existent_path_or_parent').with_args('/foo').and_return(
  238. '/foo',
  239. )
  240. flexmock(module).should_receive('get_existent_path_or_parent').with_args('/bar').and_return(
  241. None,
  242. )
  243. flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
  244. flexmock(module.os).should_receive('stat').with_args('/bar').never()
  245. device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar')))
  246. assert device_map == (
  247. Pattern('/foo', device=55),
  248. Pattern('/bar'),
  249. )
  250. def test_device_map_patterns_uses_working_directory_to_construct_path():
  251. flexmock(module).should_receive('get_existent_path_or_parent').replace_with(lambda path: path)
  252. flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
  253. flexmock(module.os).should_receive('stat').with_args('/working/dir/bar').and_return(
  254. flexmock(st_dev=66),
  255. )
  256. device_map = module.device_map_patterns(
  257. (Pattern('/foo'), Pattern('bar')),
  258. working_directory='/working/dir',
  259. )
  260. assert device_map == (
  261. Pattern('/foo', device=55),
  262. Pattern('bar', device=66),
  263. )
  264. def test_device_map_patterns_with_existing_device_id_does_not_overwrite_it():
  265. flexmock(module).should_receive('get_existent_path_or_parent').replace_with(lambda path: path)
  266. flexmock(module.os).should_receive('stat').with_args('/foo').and_return(flexmock(st_dev=55))
  267. flexmock(module.os).should_receive('stat').with_args('/bar').and_return(flexmock(st_dev=100))
  268. device_map = module.device_map_patterns((Pattern('/foo'), Pattern('/bar', device=66)))
  269. assert device_map == (
  270. Pattern('/foo', device=55),
  271. Pattern('/bar', device=66),
  272. )
  273. @pytest.mark.parametrize(
  274. 'patterns,borgmatic_runtime_directory,expected_patterns,one_file_system',
  275. (
  276. (
  277. (Pattern('/', device=1), Pattern('/root', device=1)),
  278. '/root',
  279. (Pattern('/', device=1),),
  280. False,
  281. ),
  282. # No deduplication is expected when borgmatic runtime directory is None.
  283. (
  284. (Pattern('/', device=1), Pattern('/root', device=1)),
  285. None,
  286. (Pattern('/', device=1), Pattern('/root', device=1)),
  287. False,
  288. ),
  289. (
  290. (Pattern('/', device=1), Pattern('/root/', device=1)),
  291. '/root',
  292. (Pattern('/', device=1),),
  293. False,
  294. ),
  295. (
  296. (Pattern('/', device=1), Pattern('/root', device=2)),
  297. '/root',
  298. (Pattern('/', device=1),),
  299. False,
  300. ),
  301. (
  302. (Pattern('/', device=1), Pattern('/root', device=2)),
  303. None,
  304. (Pattern('/', device=1), Pattern('/root', device=2)),
  305. False,
  306. ),
  307. (
  308. (Pattern('/root', device=1), Pattern('/', device=1)),
  309. '/root',
  310. (Pattern('/', device=1),),
  311. False,
  312. ),
  313. (
  314. (Pattern('/root', device=1), Pattern('/', device=1)),
  315. None,
  316. (Pattern('/root', device=1), Pattern('/', device=1)),
  317. False,
  318. ),
  319. (
  320. (Pattern('/root', device=1), Pattern('/root/foo', device=1)),
  321. '/root/foo',
  322. (Pattern('/root', device=1),),
  323. False,
  324. ),
  325. (
  326. (Pattern('/root', device=1), Pattern('/root/foo', device=1)),
  327. None,
  328. (Pattern('/root', device=1), Pattern('/root/foo', device=1)),
  329. False,
  330. ),
  331. # No deduplication is expected when the runtime directory doesn't match the patterns.
  332. (
  333. (Pattern('/root', device=1), Pattern('/root/foo', device=1)),
  334. '/other',
  335. (Pattern('/root', device=1), Pattern('/root/foo', device=1)),
  336. False,
  337. ),
  338. (
  339. (Pattern('/root/', device=1), Pattern('/root/foo', device=1)),
  340. '/root/foo',
  341. (Pattern('/root/', device=1),),
  342. False,
  343. ),
  344. (
  345. (Pattern('/root', device=1), Pattern('/root/foo/', device=1)),
  346. '/root/foo',
  347. (Pattern('/root', device=1),),
  348. False,
  349. ),
  350. (
  351. (Pattern('/root', device=1), Pattern('/root/foo', device=2)),
  352. '/root/foo',
  353. (Pattern('/root', device=1),),
  354. False,
  355. ),
  356. (
  357. (Pattern('/root/foo', device=1), Pattern('/root', device=1)),
  358. '/root/foo',
  359. (Pattern('/root', device=1),),
  360. False,
  361. ),
  362. (
  363. (Pattern('/root', device=None), Pattern('/root/foo', device=None)),
  364. '/root/foo',
  365. (Pattern('/root'), Pattern('/root/foo')),
  366. False,
  367. ),
  368. (
  369. (
  370. Pattern('/root', device=1),
  371. Pattern('/etc', device=1),
  372. Pattern('/root/foo/bar', device=1),
  373. ),
  374. '/root/foo/bar',
  375. (Pattern('/root', device=1), Pattern('/etc', device=1)),
  376. False,
  377. ),
  378. (
  379. (
  380. Pattern('/root', device=1),
  381. Pattern('/root/foo', device=1),
  382. Pattern('/root/foo/bar', device=1),
  383. ),
  384. '/root/foo/bar',
  385. (Pattern('/root', device=1),),
  386. False,
  387. ),
  388. (
  389. (
  390. Pattern('/root', device=1),
  391. Pattern('/root/foo', device=1),
  392. Pattern('/root/foo/bar', device=1),
  393. ),
  394. None,
  395. (
  396. Pattern('/root', device=1),
  397. Pattern('/root/foo', device=1),
  398. Pattern('/root/foo/bar', device=1),
  399. ),
  400. False,
  401. ),
  402. (
  403. (
  404. Pattern('/root', device=1),
  405. Pattern('/root/foo', device=1),
  406. Pattern('/root/foo/bar', device=1),
  407. ),
  408. '/other',
  409. (
  410. Pattern('/root', device=1),
  411. Pattern('/root/foo', device=1),
  412. Pattern('/root/foo/bar', device=1),
  413. ),
  414. False,
  415. ),
  416. (
  417. (Pattern('/dup', device=1), Pattern('/dup', device=1)),
  418. '/dup',
  419. (Pattern('/dup', device=1),),
  420. False,
  421. ),
  422. (
  423. (Pattern('/foo', device=1), Pattern('/bar', device=1)),
  424. '/bar',
  425. (Pattern('/foo', device=1), Pattern('/bar', device=1)),
  426. False,
  427. ),
  428. (
  429. (Pattern('/foo', device=1), Pattern('/bar', device=2)),
  430. '/bar',
  431. (Pattern('/foo', device=1), Pattern('/bar', device=2)),
  432. False,
  433. ),
  434. ((Pattern('/root/foo', device=1),), '/root/foo', (Pattern('/root/foo', device=1),), False),
  435. (
  436. (Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
  437. '/root',
  438. (Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
  439. False,
  440. ),
  441. (
  442. (Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
  443. '/root',
  444. (Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
  445. False,
  446. ),
  447. (
  448. (Pattern('/', device=1), Pattern('/root', device=1)),
  449. '/root',
  450. (Pattern('/', device=1),),
  451. True,
  452. ),
  453. (
  454. (Pattern('/', device=1), Pattern('/root/', device=1)),
  455. '/root',
  456. (Pattern('/', device=1),),
  457. True,
  458. ),
  459. (
  460. (Pattern('/', device=1), Pattern('/root', device=2)),
  461. '/root',
  462. (Pattern('/', device=1), Pattern('/root', device=2)),
  463. True,
  464. ),
  465. (
  466. (Pattern('/root', device=1), Pattern('/', device=1)),
  467. '/root',
  468. (Pattern('/', device=1),),
  469. True,
  470. ),
  471. (
  472. (Pattern('/root', device=1), Pattern('/root/foo', device=1)),
  473. '/root/foo',
  474. (Pattern('/root', device=1),),
  475. True,
  476. ),
  477. (
  478. (Pattern('/root/', device=1), Pattern('/root/foo', device=1)),
  479. '/root/foo',
  480. (Pattern('/root/', device=1),),
  481. True,
  482. ),
  483. (
  484. (Pattern('/root', device=1), Pattern('/root/foo/', device=1)),
  485. '/root/foo',
  486. (Pattern('/root', device=1),),
  487. True,
  488. ),
  489. (
  490. (Pattern('/root', device=1), Pattern('/root/foo', device=2)),
  491. '/root/foo',
  492. (Pattern('/root', device=1), Pattern('/root/foo', device=2)),
  493. True,
  494. ),
  495. (
  496. (Pattern('/root/foo', device=1), Pattern('/root', device=1)),
  497. '/root/foo',
  498. (Pattern('/root', device=1),),
  499. True,
  500. ),
  501. (
  502. (Pattern('/root', device=None), Pattern('/root/foo', device=None)),
  503. '/root/foo',
  504. (Pattern('/root'), Pattern('/root/foo')),
  505. True,
  506. ),
  507. (
  508. (
  509. Pattern('/root', device=1),
  510. Pattern('/etc', device=1),
  511. Pattern('/root/foo/bar', device=1),
  512. ),
  513. '/root/foo/bar',
  514. (Pattern('/root', device=1), Pattern('/etc', device=1)),
  515. True,
  516. ),
  517. (
  518. (
  519. Pattern('/root', device=1),
  520. Pattern('/root/foo', device=1),
  521. Pattern('/root/foo/bar', device=1),
  522. ),
  523. '/root/foo/bar',
  524. (Pattern('/root', device=1),),
  525. True,
  526. ),
  527. (
  528. (Pattern('/dup', device=1), Pattern('/dup', device=1)),
  529. '/dup',
  530. (Pattern('/dup', device=1),),
  531. True,
  532. ),
  533. (
  534. (Pattern('/foo', device=1), Pattern('/bar', device=1)),
  535. '/bar',
  536. (Pattern('/foo', device=1), Pattern('/bar', device=1)),
  537. True,
  538. ),
  539. (
  540. (Pattern('/foo', device=1), Pattern('/bar', device=2)),
  541. '/bar',
  542. (Pattern('/foo', device=1), Pattern('/bar', device=2)),
  543. True,
  544. ),
  545. ((Pattern('/root/foo', device=1),), '/root/foo', (Pattern('/root/foo', device=1),), True),
  546. (
  547. (Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
  548. '/root',
  549. (Pattern('/', device=1), Pattern('/root', Pattern_type.INCLUDE, device=1)),
  550. True,
  551. ),
  552. (
  553. (Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
  554. '/root',
  555. (Pattern('/root', Pattern_type.INCLUDE, device=1), Pattern('/', device=1)),
  556. True,
  557. ),
  558. ),
  559. )
  560. def test_deduplicate_runtime_directory_patterns_omits_child_paths_based_on_device_and_one_file_system(
  561. patterns,
  562. borgmatic_runtime_directory,
  563. expected_patterns,
  564. one_file_system,
  565. ):
  566. assert (
  567. module.deduplicate_runtime_directory_patterns(
  568. patterns, {'one_file_system': one_file_system}, borgmatic_runtime_directory
  569. )
  570. == expected_patterns
  571. )
  572. def test_process_patterns_includes_patterns():
  573. flexmock(module).should_receive('deduplicate_runtime_directory_patterns').and_return(
  574. (Pattern('foo'), Pattern('bar')),
  575. )
  576. flexmock(module).should_receive('device_map_patterns').and_return({})
  577. flexmock(module).should_receive('expand_patterns').with_args(
  578. (Pattern('foo'), Pattern('bar')),
  579. working_directory='/working',
  580. skip_paths=set(),
  581. ).and_return(()).once()
  582. assert module.process_patterns(
  583. (Pattern('foo'), Pattern('bar')),
  584. config={},
  585. working_directory='/working',
  586. ) == [Pattern('foo'), Pattern('bar')]
  587. def test_process_patterns_skips_expand_for_requested_paths():
  588. skip_paths = {flexmock()}
  589. flexmock(module).should_receive('deduplicate_runtime_directory_patterns').and_return(
  590. (Pattern('foo'), Pattern('bar')),
  591. )
  592. flexmock(module).should_receive('device_map_patterns').and_return({})
  593. flexmock(module).should_receive('expand_patterns').with_args(
  594. (Pattern('foo'), Pattern('bar')),
  595. working_directory='/working',
  596. skip_paths=skip_paths,
  597. ).and_return(()).once()
  598. assert module.process_patterns(
  599. (Pattern('foo'), Pattern('bar')),
  600. config={},
  601. working_directory='/working',
  602. skip_expand_paths=skip_paths,
  603. ) == [Pattern('foo'), Pattern('bar')]