test_compat_alt.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. """Test alt"""
  2. import os
  3. import string
  4. import py
  5. import pytest
  6. import utils
  7. # These tests are for the alternate processing in YADM_COMPATIBILITY=1 mode
  8. pytestmark = pytest.mark.deprecated
  9. # These test IDs are broken. During the writing of these tests, problems have
  10. # been discovered in the way yadm orders matching files.
  11. BROKEN_TEST_IDS = [
  12. 'test_wild[tracked-##C.S.H.U-C-S%-H%-U]',
  13. 'test_wild[tracked-##C.S.H.U-C-S-H%-U]',
  14. 'test_wild[encrypted-##C.S.H.U-C-S%-H%-U]',
  15. 'test_wild[encrypted-##C.S.H.U-C-S-H%-U]',
  16. ]
  17. PRECEDENCE = [
  18. '##',
  19. '##$tst_sys',
  20. '##$tst_sys.$tst_host',
  21. '##$tst_sys.$tst_host.$tst_user',
  22. '##$tst_class',
  23. '##$tst_class.$tst_sys',
  24. '##$tst_class.$tst_sys.$tst_host',
  25. '##$tst_class.$tst_sys.$tst_host.$tst_user',
  26. ]
  27. WILD_TEMPLATES = [
  28. '##$tst_class',
  29. '##$tst_class.$tst_sys',
  30. '##$tst_class.$tst_sys.$tst_host',
  31. '##$tst_class.$tst_sys.$tst_host.$tst_user',
  32. ]
  33. TEST_PATHS = [utils.ALT_FILE1, utils.ALT_FILE2, utils.ALT_DIR]
  34. WILD_TESTED = set()
  35. @pytest.mark.parametrize('precedence_index', range(len(PRECEDENCE)))
  36. @pytest.mark.parametrize(
  37. 'tracked, encrypt, exclude', [
  38. (False, False, False),
  39. (True, False, False),
  40. (False, True, False),
  41. (False, True, True),
  42. ], ids=[
  43. 'untracked',
  44. 'tracked',
  45. 'encrypted',
  46. 'excluded',
  47. ])
  48. @pytest.mark.usefixtures('ds1_copy')
  49. def test_alt(runner, yadm_y, paths,
  50. tst_sys, tst_host, tst_user,
  51. tracked, encrypt, exclude,
  52. precedence_index):
  53. """Test alternate linking
  54. This test is done by iterating for the number of templates in PRECEDENCE.
  55. With each iteration, another file is left off the list. So with each
  56. iteration, the template with the "highest precedence" is left out. The file
  57. using the highest precedence should be the one linked.
  58. """
  59. # set the class
  60. tst_class = 'testclass'
  61. utils.set_local(paths, 'class', tst_class)
  62. # process the templates in PRECEDENCE
  63. precedence = list()
  64. for template in PRECEDENCE:
  65. precedence.append(
  66. string.Template(template).substitute(
  67. tst_class=tst_class,
  68. tst_host=tst_host,
  69. tst_sys=tst_sys,
  70. tst_user=tst_user,
  71. )
  72. )
  73. # create files using a subset of files
  74. for suffix in precedence[0:precedence_index+1]:
  75. utils.create_alt_files(paths, suffix, tracked=tracked,
  76. encrypt=encrypt, exclude=exclude)
  77. # run alt to trigger linking
  78. env = os.environ.copy()
  79. env['YADM_COMPATIBILITY'] = '1'
  80. run = runner(yadm_y('alt'), env=env)
  81. assert run.success
  82. assert run.err == ''
  83. linked = utils.parse_alt_output(run.out)
  84. # assert the proper linking has occurred
  85. for file_path in TEST_PATHS:
  86. source_file = file_path + precedence[precedence_index]
  87. if tracked or (encrypt and not exclude):
  88. assert paths.work.join(file_path).islink()
  89. target = py.path.local(paths.work.join(file_path).readlink())
  90. if target.isfile():
  91. assert paths.work.join(file_path).read() == source_file
  92. assert str(paths.work.join(source_file)) in linked
  93. else:
  94. assert paths.work.join(file_path).join(
  95. utils.CONTAINED).read() == source_file
  96. assert str(paths.work.join(source_file)) in linked
  97. else:
  98. assert not paths.work.join(file_path).exists()
  99. assert str(paths.work.join(source_file)) not in linked
  100. def short_template(template):
  101. """Translate template into something short for test IDs"""
  102. return string.Template(template).substitute(
  103. tst_class='C',
  104. tst_host='H',
  105. tst_sys='S',
  106. tst_user='U',
  107. )
  108. @pytest.mark.parametrize('wild_user', [True, False], ids=['U%', 'U'])
  109. @pytest.mark.parametrize('wild_host', [True, False], ids=['H%', 'H'])
  110. @pytest.mark.parametrize('wild_sys', [True, False], ids=['S%', 'S'])
  111. @pytest.mark.parametrize('wild_class', [True, False], ids=['C%', 'C'])
  112. @pytest.mark.parametrize('template', WILD_TEMPLATES, ids=short_template)
  113. @pytest.mark.parametrize(
  114. 'tracked, encrypt', [
  115. (True, False),
  116. (False, True),
  117. ], ids=[
  118. 'tracked',
  119. 'encrypted',
  120. ])
  121. @pytest.mark.usefixtures('ds1_copy')
  122. def test_wild(request, runner, yadm_y, paths,
  123. tst_sys, tst_host, tst_user,
  124. tracked, encrypt,
  125. wild_class, wild_host, wild_sys, wild_user,
  126. template):
  127. """Test wild linking
  128. These tests are done by creating permutations of the possible files using
  129. WILD_TEMPLATES. Each case is then tested (while skipping the already tested
  130. permutations for efficiency).
  131. """
  132. if request.node.name in BROKEN_TEST_IDS:
  133. pytest.xfail(
  134. 'This test is known to be broken. '
  135. 'This bug only affects deprecated features.')
  136. tst_class = 'testclass'
  137. # determine the "wild" version of the suffix
  138. str_class = '%' if wild_class else tst_class
  139. str_host = '%' if wild_host else tst_host
  140. str_sys = '%' if wild_sys else tst_sys
  141. str_user = '%' if wild_user else tst_user
  142. wild_suffix = string.Template(template).substitute(
  143. tst_class=str_class,
  144. tst_host=str_host,
  145. tst_sys=str_sys,
  146. tst_user=str_user,
  147. )
  148. # determine the "standard" version of the suffix
  149. std_suffix = string.Template(template).substitute(
  150. tst_class=tst_class,
  151. tst_host=tst_host,
  152. tst_sys=tst_sys,
  153. tst_user=tst_user,
  154. )
  155. # skip over duplicate tests (this seems to be the simplest way to cover the
  156. # permutations of tests, while skipping duplicates.)
  157. test_key = f'{tracked}{encrypt}{wild_suffix}{std_suffix}'
  158. if test_key in WILD_TESTED:
  159. return
  160. WILD_TESTED.add(test_key)
  161. # set the class
  162. utils.set_local(paths, 'class', tst_class)
  163. # create files using the wild suffix
  164. utils.create_alt_files(paths, wild_suffix, tracked=tracked,
  165. encrypt=encrypt, exclude=False)
  166. # run alt to trigger linking
  167. env = os.environ.copy()
  168. env['YADM_COMPATIBILITY'] = '1'
  169. run = runner(yadm_y('alt'), env=env)
  170. assert run.success
  171. assert run.err == ''
  172. linked = utils.parse_alt_output(run.out)
  173. # assert the proper linking has occurred
  174. for file_path in TEST_PATHS:
  175. source_file = file_path + wild_suffix
  176. assert paths.work.join(file_path).islink()
  177. target = py.path.local(paths.work.join(file_path).readlink())
  178. if target.isfile():
  179. assert paths.work.join(file_path).read() == source_file
  180. assert str(paths.work.join(source_file)) in linked
  181. else:
  182. assert paths.work.join(file_path).join(
  183. utils.CONTAINED).read() == source_file
  184. assert str(paths.work.join(source_file)) in linked
  185. # create files using the standard suffix
  186. utils.create_alt_files(paths, std_suffix, tracked=tracked,
  187. encrypt=encrypt, exclude=False)
  188. # run alt to trigger linking
  189. env = os.environ.copy()
  190. env['YADM_COMPATIBILITY'] = '1'
  191. run = runner(yadm_y('alt'), env=env)
  192. assert run.success
  193. assert run.err == ''
  194. linked = utils.parse_alt_output(run.out)
  195. # assert the proper linking has occurred
  196. for file_path in TEST_PATHS:
  197. source_file = file_path + std_suffix
  198. assert paths.work.join(file_path).islink()
  199. target = py.path.local(paths.work.join(file_path).readlink())
  200. if target.isfile():
  201. assert paths.work.join(file_path).read() == source_file
  202. assert str(paths.work.join(source_file)) in linked
  203. else:
  204. assert paths.work.join(file_path).join(
  205. utils.CONTAINED).read() == source_file
  206. assert str(paths.work.join(source_file)) in linked
  207. @pytest.mark.usefixtures('ds1_copy')
  208. def test_local_override(runner, yadm_y, paths,
  209. tst_sys, tst_host, tst_user):
  210. """Test local overrides"""
  211. # define local overrides
  212. utils.set_local(paths, 'class', 'or-class')
  213. utils.set_local(paths, 'hostname', 'or-hostname')
  214. utils.set_local(paths, 'os', 'or-os')
  215. utils.set_local(paths, 'user', 'or-user')
  216. # create files, the first would normally be the most specific version
  217. # however, the second is the overridden version which should be preferred.
  218. utils.create_alt_files(
  219. paths, f'##or-class.{tst_sys}.{tst_host}.{tst_user}')
  220. utils.create_alt_files(
  221. paths, '##or-class.or-os.or-hostname.or-user')
  222. # run alt to trigger linking
  223. env = os.environ.copy()
  224. env['YADM_COMPATIBILITY'] = '1'
  225. run = runner(yadm_y('alt'), env=env)
  226. assert run.success
  227. assert run.err == ''
  228. linked = utils.parse_alt_output(run.out)
  229. # assert the proper linking has occurred
  230. for file_path in TEST_PATHS:
  231. source_file = file_path + '##or-class.or-os.or-hostname.or-user'
  232. assert paths.work.join(file_path).islink()
  233. target = py.path.local(paths.work.join(file_path).readlink())
  234. if target.isfile():
  235. assert paths.work.join(file_path).read() == source_file
  236. assert str(paths.work.join(source_file)) in linked
  237. else:
  238. assert paths.work.join(file_path).join(
  239. utils.CONTAINED).read() == source_file
  240. assert str(paths.work.join(source_file)) in linked
  241. @pytest.mark.parametrize('suffix', ['AAA', 'ZZZ', 'aaa', 'zzz'])
  242. @pytest.mark.usefixtures('ds1_copy')
  243. def test_class_case(runner, yadm_y, paths, tst_sys, suffix):
  244. """Test range of class cases"""
  245. # set the class
  246. utils.set_local(paths, 'class', suffix)
  247. # create files
  248. endings = [suffix]
  249. if tst_sys == 'Linux':
  250. # Only create all of these side-by-side on Linux, which is
  251. # unquestionably case-sensitive. This would break tests on
  252. # case-insensitive systems.
  253. endings = ['AAA', 'ZZZ', 'aaa', 'zzz']
  254. for ending in endings:
  255. utils.create_alt_files(paths, f'##{ending}')
  256. # run alt to trigger linking
  257. env = os.environ.copy()
  258. env['YADM_COMPATIBILITY'] = '1'
  259. run = runner(yadm_y('alt'), env=env)
  260. assert run.success
  261. assert run.err == ''
  262. linked = utils.parse_alt_output(run.out)
  263. # assert the proper linking has occurred
  264. for file_path in TEST_PATHS:
  265. source_file = file_path + f'##{suffix}'
  266. assert paths.work.join(file_path).islink()
  267. target = py.path.local(paths.work.join(file_path).readlink())
  268. if target.isfile():
  269. assert paths.work.join(file_path).read() == source_file
  270. assert str(paths.work.join(source_file)) in linked
  271. else:
  272. assert paths.work.join(file_path).join(
  273. utils.CONTAINED).read() == source_file
  274. assert str(paths.work.join(source_file)) in linked
  275. @pytest.mark.parametrize('autoalt', [None, 'true', 'false'])
  276. @pytest.mark.usefixtures('ds1_copy')
  277. def test_auto_alt(runner, yadm_y, paths, autoalt):
  278. """Test setting auto-alt"""
  279. # set the value of auto-alt
  280. if autoalt:
  281. os.system(' '.join(yadm_y('config', 'yadm.auto-alt', autoalt)))
  282. # create file
  283. suffix = '##'
  284. utils.create_alt_files(paths, suffix)
  285. # run status to possibly trigger linking
  286. env = os.environ.copy()
  287. env['YADM_COMPATIBILITY'] = '1'
  288. run = runner(yadm_y('status'), env=env)
  289. assert run.success
  290. assert run.err == ''
  291. linked = utils.parse_alt_output(run.out)
  292. # assert the proper linking has occurred
  293. for file_path in TEST_PATHS:
  294. source_file = file_path + suffix
  295. if autoalt == 'false':
  296. assert not paths.work.join(file_path).exists()
  297. else:
  298. assert paths.work.join(file_path).islink()
  299. target = py.path.local(paths.work.join(file_path).readlink())
  300. if target.isfile():
  301. assert paths.work.join(file_path).read() == source_file
  302. # no linking output when run via auto-alt
  303. assert str(paths.work.join(source_file)) not in linked
  304. else:
  305. assert paths.work.join(file_path).join(
  306. utils.CONTAINED).read() == source_file
  307. # no linking output when run via auto-alt
  308. assert str(paths.work.join(source_file)) not in linked
  309. @pytest.mark.parametrize('delimiter', ['.', '_'])
  310. @pytest.mark.usefixtures('ds1_copy')
  311. def test_delimiter(runner, yadm_y, paths,
  312. tst_sys, tst_host, tst_user, delimiter):
  313. """Test delimiters used"""
  314. suffix = '##' + delimiter.join([tst_sys, tst_host, tst_user])
  315. # create file
  316. utils.create_alt_files(paths, suffix)
  317. # run alt to trigger linking
  318. env = os.environ.copy()
  319. env['YADM_COMPATIBILITY'] = '1'
  320. run = runner(yadm_y('alt'), env=env)
  321. assert run.success
  322. assert run.err == ''
  323. linked = utils.parse_alt_output(run.out)
  324. # assert the proper linking has occurred
  325. # only a delimiter of '.' is valid
  326. for file_path in TEST_PATHS:
  327. source_file = file_path + suffix
  328. if delimiter == '.':
  329. assert paths.work.join(file_path).islink()
  330. target = py.path.local(paths.work.join(file_path).readlink())
  331. if target.isfile():
  332. assert paths.work.join(file_path).read() == source_file
  333. assert str(paths.work.join(source_file)) in linked
  334. else:
  335. assert paths.work.join(file_path).join(
  336. utils.CONTAINED).read() == source_file
  337. assert str(paths.work.join(source_file)) in linked
  338. else:
  339. assert not paths.work.join(file_path).exists()
  340. assert str(paths.work.join(source_file)) not in linked
  341. @pytest.mark.usefixtures('ds1_copy')
  342. def test_invalid_links_removed(runner, yadm_y, paths):
  343. """Links to invalid alternative files are removed
  344. This test ensures that when an already linked alternative becomes invalid
  345. due to a change in class, the alternate link is removed.
  346. """
  347. # set the class
  348. tst_class = 'testclass'
  349. utils.set_local(paths, 'class', tst_class)
  350. # create files which match the test class
  351. utils.create_alt_files(paths, f'##{tst_class}')
  352. # run alt to trigger linking
  353. env = os.environ.copy()
  354. env['YADM_COMPATIBILITY'] = '1'
  355. run = runner(yadm_y('alt'), env=env)
  356. assert run.success
  357. assert run.err == ''
  358. linked = utils.parse_alt_output(run.out)
  359. # assert the proper linking has occurred
  360. for file_path in TEST_PATHS:
  361. source_file = file_path + '##' + tst_class
  362. assert paths.work.join(file_path).islink()
  363. target = py.path.local(paths.work.join(file_path).readlink())
  364. if target.isfile():
  365. assert paths.work.join(file_path).read() == source_file
  366. assert str(paths.work.join(source_file)) in linked
  367. else:
  368. assert paths.work.join(file_path).join(
  369. utils.CONTAINED).read() == source_file
  370. assert str(paths.work.join(source_file)) in linked
  371. # change the class so there are no valid alternates
  372. utils.set_local(paths, 'class', 'changedclass')
  373. # run alt to trigger linking
  374. env = os.environ.copy()
  375. env['YADM_COMPATIBILITY'] = '1'
  376. run = runner(yadm_y('alt'), env=env)
  377. assert run.success
  378. assert run.err == ''
  379. linked = utils.parse_alt_output(run.out)
  380. # assert the linking is removed
  381. for file_path in TEST_PATHS:
  382. source_file = file_path + '##' + tst_class
  383. assert not paths.work.join(file_path).exists()
  384. assert str(paths.work.join(source_file)) not in linked