create_cmd_test.py 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. import errno
  2. import json
  3. import os
  4. import tempfile
  5. import shutil
  6. import socket
  7. import stat
  8. import subprocess
  9. import time
  10. import pytest
  11. from ... import platform
  12. from ...constants import * # NOQA
  13. from ...constants import zeros
  14. from ...manifest import Manifest
  15. from ...platform import is_win32, is_darwin
  16. from ...repository import Repository
  17. from ...helpers import CommandError, BackupPermissionError
  18. from .. import has_lchflags
  19. from .. import changedir
  20. from .. import (
  21. are_symlinks_supported,
  22. are_hardlinks_supported,
  23. are_fifos_supported,
  24. is_utime_fully_supported,
  25. is_birthtime_fully_supported,
  26. same_ts_ns,
  27. is_root,
  28. )
  29. from . import (
  30. cmd,
  31. generate_archiver_tests,
  32. create_test_files,
  33. assert_dirs_equal,
  34. create_regular_file,
  35. requires_hardlinks,
  36. _create_test_caches,
  37. _create_test_tagged,
  38. _create_test_keep_tagged,
  39. _assert_test_caches,
  40. _assert_test_tagged,
  41. _assert_test_keep_tagged,
  42. RK_ENCRYPTION,
  43. )
  44. pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA
  45. def test_basic_functionality(archivers, request):
  46. archiver = request.getfixturevalue(archivers)
  47. if archiver.EXE:
  48. pytest.skip("test_basic_functionality seems incompatible with fakeroot and/or the binary.")
  49. have_root = create_test_files(archiver.input_path)
  50. # Fork required to test --show-rc output.
  51. output = cmd(archiver, "repo-create", RK_ENCRYPTION, "--show-version", "--show-rc", fork=True)
  52. assert "borgbackup version" in output
  53. assert "terminating with success status, rc 0" in output
  54. cmd(archiver, "create", "test", "input")
  55. output = cmd(archiver, "create", "--stats", "test.2", "input")
  56. assert "Archive name: test.2" in output
  57. with changedir("output"):
  58. cmd(archiver, "extract", "test")
  59. list_output = cmd(archiver, "repo-list")
  60. assert "test" in list_output
  61. assert "test.2" in list_output
  62. expected = [
  63. "input",
  64. "input/bdev",
  65. "input/cdev",
  66. "input/dir2",
  67. "input/dir2/file2",
  68. "input/empty",
  69. "input/file1",
  70. "input/flagfile",
  71. ]
  72. if are_fifos_supported():
  73. expected.append("input/fifo1")
  74. if are_symlinks_supported():
  75. expected.append("input/link1")
  76. if are_hardlinks_supported():
  77. expected.append("input/hardlink")
  78. if not have_root:
  79. # We could not create these device files without (fake)root.
  80. expected.remove("input/bdev")
  81. expected.remove("input/cdev")
  82. if has_lchflags:
  83. # remove the file we did not back up, so input and output become equal
  84. expected.remove("input/flagfile") # this file is UF_NODUMP
  85. os.remove(os.path.join("input", "flagfile"))
  86. list_output = cmd(archiver, "list", "test", "--short")
  87. for name in expected:
  88. assert name in list_output
  89. assert_dirs_equal("input", "output/input")
  90. info_output = cmd(archiver, "info", "-a", "test")
  91. item_count = 5 if has_lchflags else 6 # one file is UF_NODUMP
  92. assert "Number of files: %d" % item_count in info_output
  93. shutil.rmtree(archiver.cache_path)
  94. info_output2 = cmd(archiver, "info", "-a", "test")
  95. def filter(output):
  96. # Filter for interesting 'info' output; ignore cache-rebuilding related messages.
  97. prefixes = ["Name:", "Fingerprint:", "Number of files:", "This archive:", "All archives:", "Chunk index:"]
  98. result = []
  99. for line in output.splitlines():
  100. for prefix in prefixes:
  101. if line.startswith(prefix):
  102. result.append(line)
  103. return "\n".join(result)
  104. # The interesting parts of info_output2 and info_output should be the same.
  105. assert filter(info_output) == filter(info_output2)
  106. def test_archived_paths(archivers, request):
  107. # As Borg comes from the POSIX (Linux/UNIX) world, much assumes path separators
  108. # to be slashes "/", e.g., in archived items or for pattern matching.
  109. # To make our lives easier and to support cross-platform extraction, we always use slashes.
  110. # Similarly, archived paths are expected to be full but relative (have no leading slash).
  111. archiver = request.getfixturevalue(archivers)
  112. full_path = os.path.abspath(os.path.join(archiver.input_path, "test"))
  113. # remove windows drive letter, if any:
  114. posix_path = full_path[2:] if full_path[1] == ":" else full_path
  115. # only needed on Windows in case there are backslashes:
  116. posix_path = posix_path.replace("\\", "/")
  117. # no leading slash in borg archives:
  118. archived_path = posix_path.lstrip("/")
  119. create_regular_file(archiver.input_path, "test")
  120. cmd(archiver, "repo-create", "--encryption=none")
  121. cmd(archiver, "create", "test", "input", posix_path)
  122. # "input" directory is recursed into, "input/test" is discovered and joined by borg's recursion.
  123. # posix_path was directly given as a cli argument and should end up as archive_path in the borg archive.
  124. expected_paths = sorted(["input", "input/test", archived_path])
  125. # check path in archived items:
  126. archive_list = cmd(archiver, "list", "test", "--short")
  127. assert expected_paths == sorted([path for path in archive_list.splitlines() if path])
  128. # check path in archived items (json):
  129. archive_list = cmd(archiver, "list", "test", "--json-lines")
  130. assert expected_paths == sorted([json.loads(line)["path"] for line in archive_list.splitlines() if line])
  131. @requires_hardlinks
  132. def test_create_duplicate_root(archivers, request):
  133. archiver = request.getfixturevalue(archivers)
  134. # setup for #5603
  135. path_a = os.path.join(archiver.input_path, "a")
  136. path_b = os.path.join(archiver.input_path, "b")
  137. os.mkdir(path_a)
  138. os.mkdir(path_b)
  139. hl_a = os.path.join(path_a, "hardlink")
  140. hl_b = os.path.join(path_b, "hardlink")
  141. create_regular_file(archiver.input_path, hl_a, contents=b"123456")
  142. os.link(hl_a, hl_b)
  143. cmd(archiver, "repo-create", "--encryption=none")
  144. cmd(archiver, "create", "test", "input", "input") # give input twice!
  145. # test if created archive has 'input' contents twice:
  146. archive_list = cmd(archiver, "list", "test", "--json-lines")
  147. paths = [json.loads(line)["path"] for line in archive_list.split("\n") if line]
  148. # we have all fs items exactly once!
  149. assert sorted(paths) == ["input", "input/a", "input/a/hardlink", "input/b", "input/b/hardlink"]
  150. def test_create_unreadable_parent(archiver):
  151. parent_dir = os.path.join(archiver.input_path, "parent")
  152. root_dir = os.path.join(archiver.input_path, "parent", "root")
  153. os.mkdir(parent_dir)
  154. os.mkdir(root_dir)
  155. os.chmod(parent_dir, 0o111) # --x--x--x == parent dir traversable, but not readable
  156. try:
  157. cmd(archiver, "repo-create", "--encryption=none")
  158. # issue #7746: we *can* read root_dir and we *can* traverse parent_dir, so this should work:
  159. cmd(archiver, "create", "test", root_dir)
  160. finally:
  161. os.chmod(parent_dir, 0o771) # otherwise cleanup after this test fails
  162. @pytest.mark.skipif(is_win32, reason="unix sockets not available on windows")
  163. def test_unix_socket(archivers, request, monkeypatch):
  164. archiver = request.getfixturevalue(archivers)
  165. cmd(archiver, "repo-create", RK_ENCRYPTION)
  166. try:
  167. with tempfile.TemporaryDirectory() as temp_dir:
  168. sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  169. sock.bind(os.path.join(temp_dir, "unix-socket"))
  170. except PermissionError as err:
  171. if err.errno == errno.EPERM:
  172. pytest.skip("unix sockets disabled or not supported")
  173. elif err.errno == errno.EACCES:
  174. pytest.skip("permission denied to create unix sockets")
  175. cmd(archiver, "create", "test", "input")
  176. sock.close()
  177. with changedir("output"):
  178. cmd(archiver, "extract", "test")
  179. print(f"{temp_dir}/unix-socket")
  180. assert not os.path.exists(f"{temp_dir}/unix-socket")
  181. @pytest.mark.skipif(not is_utime_fully_supported(), reason="cannot setup and execute test without utime")
  182. @pytest.mark.skipif(not is_birthtime_fully_supported(), reason="cannot setup and execute test without birth time")
  183. def test_nobirthtime(archivers, request):
  184. archiver = request.getfixturevalue(archivers)
  185. create_test_files(archiver.input_path)
  186. birthtime, mtime, atime = 946598400, 946684800, 946771200
  187. os.utime("input/file1", (atime, birthtime))
  188. os.utime("input/file1", (atime, mtime))
  189. cmd(archiver, "repo-create", RK_ENCRYPTION)
  190. cmd(archiver, "create", "test", "input", "--nobirthtime")
  191. with changedir("output"):
  192. cmd(archiver, "extract", "test")
  193. sti = os.stat("input/file1")
  194. sto = os.stat("output/input/file1")
  195. assert same_ts_ns(sti.st_birthtime * 1e9, birthtime * 1e9)
  196. assert same_ts_ns(sto.st_birthtime * 1e9, mtime * 1e9)
  197. assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns)
  198. assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9)
  199. def test_create_stdin(archivers, request):
  200. archiver = request.getfixturevalue(archivers)
  201. cmd(archiver, "repo-create", RK_ENCRYPTION)
  202. input_data = b"\x00foo\n\nbar\n \n"
  203. cmd(archiver, "create", "test", "-", input=input_data)
  204. item = json.loads(cmd(archiver, "list", "test", "--json-lines"))
  205. assert item["size"] == len(input_data)
  206. assert item["path"] == "stdin"
  207. extracted_data = cmd(archiver, "extract", "test", "--stdout", binary_output=True)
  208. assert extracted_data == input_data
  209. def test_create_erroneous_file(archivers, request):
  210. archiver = request.getfixturevalue(archivers)
  211. chunk_size = 1000 # fixed chunker with this size
  212. create_regular_file(archiver.input_path, os.path.join(archiver.input_path, "file1"), size=chunk_size * 2)
  213. create_regular_file(archiver.input_path, os.path.join(archiver.input_path, "file2"), size=chunk_size * 2)
  214. create_regular_file(archiver.input_path, os.path.join(archiver.input_path, "file3"), size=chunk_size * 2)
  215. cmd(archiver, "repo-create", RK_ENCRYPTION)
  216. flist = "".join(f"input/file{n}\n" for n in range(1, 4))
  217. out = cmd(
  218. archiver,
  219. "create",
  220. f"--chunker-params=fail,{chunk_size},rrrEEErrrr",
  221. "--paths-from-stdin",
  222. "--list",
  223. "test",
  224. input=flist.encode(),
  225. exit_code=0,
  226. )
  227. assert "retry: 3 of " in out
  228. assert "E input/file2" not in out # we managed to read it in the 3rd retry (after 3 failed reads)
  229. # repo looking good overall? checks for rc == 0.
  230. cmd(archiver, "check", "--debug")
  231. # check files in created archive
  232. out = cmd(archiver, "list", "test")
  233. assert "input/file1" in out
  234. assert "input/file2" in out
  235. assert "input/file3" in out
  236. @pytest.mark.skipif(is_root(), reason="test must not be run as (fake)root")
  237. def test_create_no_permission_file(archivers, request):
  238. archiver = request.getfixturevalue(archivers)
  239. file_path = os.path.join(archiver.input_path, "file")
  240. create_regular_file(archiver.input_path, file_path + "1", size=1000)
  241. create_regular_file(archiver.input_path, file_path + "2", size=1000)
  242. create_regular_file(archiver.input_path, file_path + "3", size=1000)
  243. # revoke read permissions on file2 for everybody, including us:
  244. if is_win32:
  245. subprocess.run(["icacls.exe", file_path + "2", "/deny", "everyone:(R)"])
  246. else:
  247. # note: this will NOT take away read permissions for root
  248. os.chmod(file_path + "2", 0o000)
  249. cmd(archiver, "repo-create", RK_ENCRYPTION)
  250. flist = "".join(f"input/file{n}\n" for n in range(1, 4))
  251. expected_ec = BackupPermissionError("open", OSError(13, "permission denied")).exit_code
  252. if expected_ec == EXIT_ERROR: # workaround, TODO: fix it
  253. expected_ec = EXIT_WARNING
  254. out = cmd(
  255. archiver,
  256. "create",
  257. "--paths-from-stdin",
  258. "--list",
  259. "test",
  260. input=flist.encode(),
  261. exit_code=expected_ec, # WARNING status: could not back up file2.
  262. )
  263. assert "retry: 1 of " not in out # retries were NOT attempted!
  264. assert "E input/file2" in out # no permissions!
  265. # repo looking good overall? checks for rc == 0.
  266. cmd(archiver, "check", "--debug")
  267. # check files in created archive
  268. out = cmd(archiver, "list", "test")
  269. assert "input/file1" in out
  270. assert "input/file2" not in out # it skipped file2
  271. assert "input/file3" in out
  272. def test_sanitized_stdin_name(archivers, request):
  273. archiver = request.getfixturevalue(archivers)
  274. cmd(archiver, "repo-create", RK_ENCRYPTION)
  275. cmd(archiver, "create", "--stdin-name", "./a//path", "test", "-", input=b"")
  276. item = json.loads(cmd(archiver, "list", "test", "--json-lines"))
  277. assert item["path"] == "a/path"
  278. def test_dotdot_stdin_name(archivers, request):
  279. archiver = request.getfixturevalue(archivers)
  280. cmd(archiver, "repo-create", RK_ENCRYPTION)
  281. output = cmd(archiver, "create", "--stdin-name", "foo/../bar", "test", "-", input=b"", exit_code=2)
  282. assert output.endswith("'..' element in path 'foo/../bar'" + os.linesep)
  283. def test_dot_stdin_name(archivers, request):
  284. archiver = request.getfixturevalue(archivers)
  285. cmd(archiver, "repo-create", RK_ENCRYPTION)
  286. output = cmd(archiver, "create", "--stdin-name", "./", "test", "-", input=b"", exit_code=2)
  287. assert output.endswith("'./' is not a valid file name" + os.linesep)
  288. def test_create_content_from_command(archivers, request):
  289. archiver = request.getfixturevalue(archivers)
  290. cmd(archiver, "repo-create", RK_ENCRYPTION)
  291. input_data = "some test content"
  292. name = "a/b/c"
  293. cmd(archiver, "create", "--stdin-name", name, "--content-from-command", "test", "--", "echo", input_data)
  294. item = json.loads(cmd(archiver, "list", "test", "--json-lines"))
  295. assert item["size"] == len(input_data) + 1 # `echo` adds newline
  296. assert item["path"] == name
  297. extracted_data = cmd(archiver, "extract", "test", "--stdout")
  298. assert extracted_data == input_data + "\n"
  299. def test_create_content_from_command_with_failed_command(archivers, request):
  300. archiver = request.getfixturevalue(archivers)
  301. cmd(archiver, "repo-create", RK_ENCRYPTION)
  302. if archiver.FORK_DEFAULT:
  303. expected_ec = CommandError().exit_code
  304. output = cmd(
  305. archiver, "create", "--content-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=expected_ec
  306. )
  307. assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
  308. else:
  309. with pytest.raises(CommandError):
  310. cmd(archiver, "create", "--content-from-command", "test", "--", "sh", "-c", "exit 73;")
  311. archive_list = json.loads(cmd(archiver, "repo-list", "--json"))
  312. assert archive_list["archives"] == []
  313. def test_create_content_from_command_missing_command(archivers, request):
  314. archiver = request.getfixturevalue(archivers)
  315. cmd(archiver, "repo-create", RK_ENCRYPTION)
  316. output = cmd(archiver, "create", "test", "--content-from-command", exit_code=2)
  317. assert output.endswith("No command given." + os.linesep)
  318. def test_create_paths_from_stdin(archivers, request):
  319. archiver = request.getfixturevalue(archivers)
  320. cmd(archiver, "repo-create", RK_ENCRYPTION)
  321. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  322. create_regular_file(archiver.input_path, "dir1/file2", size=1024 * 80)
  323. create_regular_file(archiver.input_path, "dir1/file3", size=1024 * 80)
  324. create_regular_file(archiver.input_path, "file4", size=1024 * 80)
  325. input_data = b"input/file1\0input/dir1\0input/file4"
  326. cmd(archiver, "create", "test", "--paths-from-stdin", "--paths-delimiter", "\\0", input=input_data)
  327. archive_list = cmd(archiver, "list", "test", "--json-lines")
  328. paths = [json.loads(line)["path"] for line in archive_list.split("\n") if line]
  329. assert paths == ["input/file1", "input/dir1", "input/file4"]
  330. def test_create_paths_from_command(archivers, request):
  331. archiver = request.getfixturevalue(archivers)
  332. cmd(archiver, "repo-create", RK_ENCRYPTION)
  333. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  334. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  335. create_regular_file(archiver.input_path, "file3", size=1024 * 80)
  336. create_regular_file(archiver.input_path, "file4", size=1024 * 80)
  337. input_data = "input/file1\ninput/file2\ninput/file3"
  338. if is_win32:
  339. with open("filenames.cmd", "w") as script:
  340. for filename in input_data.splitlines():
  341. script.write(f"@echo {filename}\n")
  342. cmd(archiver, "create", "--paths-from-command", "test", "--", "filenames.cmd" if is_win32 else "echo", input_data)
  343. archive_list = cmd(archiver, "list", "test", "--json-lines")
  344. paths = [json.loads(line)["path"] for line in archive_list.split("\n") if line]
  345. assert paths == ["input/file1", "input/file2", "input/file3"]
  346. def test_create_paths_from_command_with_failed_command(archivers, request):
  347. archiver = request.getfixturevalue(archivers)
  348. cmd(archiver, "repo-create", RK_ENCRYPTION)
  349. if archiver.FORK_DEFAULT:
  350. expected_ec = CommandError().exit_code
  351. output = cmd(
  352. archiver, "create", "--paths-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=expected_ec
  353. )
  354. assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
  355. else:
  356. with pytest.raises(CommandError):
  357. cmd(archiver, "create", "--paths-from-command", "test", "--", "sh", "-c", "exit 73;")
  358. archive_list = json.loads(cmd(archiver, "repo-list", "--json"))
  359. assert archive_list["archives"] == []
  360. def test_create_paths_from_command_missing_command(archivers, request):
  361. archiver = request.getfixturevalue(archivers)
  362. cmd(archiver, "repo-create", RK_ENCRYPTION)
  363. output = cmd(archiver, "create", "test", "--paths-from-command", exit_code=2)
  364. assert output.endswith("No command given." + os.linesep)
  365. def test_create_without_root(archivers, request):
  366. """test create without a root"""
  367. archiver = request.getfixturevalue(archivers)
  368. cmd(archiver, "repo-create", RK_ENCRYPTION)
  369. cmd(archiver, "create", "test", exit_code=2)
  370. def test_create_pattern_root(archivers, request):
  371. """test create with only a root pattern"""
  372. archiver = request.getfixturevalue(archivers)
  373. cmd(archiver, "repo-create", RK_ENCRYPTION)
  374. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  375. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  376. output = cmd(archiver, "create", "test", "-v", "--list", "--pattern=R input")
  377. assert "A input/file1" in output
  378. assert "A input/file2" in output
  379. def test_create_pattern(archivers, request):
  380. """test file patterns during create"""
  381. archiver = request.getfixturevalue(archivers)
  382. cmd(archiver, "repo-create", RK_ENCRYPTION)
  383. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  384. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  385. create_regular_file(archiver.input_path, "file_important", size=1024 * 80)
  386. output = cmd(
  387. archiver, "create", "-v", "--list", "--pattern=+input/file_important", "--pattern=-input/file*", "test", "input"
  388. )
  389. assert "A input/file_important" in output
  390. assert "- input/file1" in output
  391. assert "- input/file2" in output
  392. def test_create_pattern_file(archivers, request):
  393. """test file patterns during create"""
  394. archiver = request.getfixturevalue(archivers)
  395. cmd(archiver, "repo-create", RK_ENCRYPTION)
  396. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  397. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  398. create_regular_file(archiver.input_path, "otherfile", size=1024 * 80)
  399. create_regular_file(archiver.input_path, "file_important", size=1024 * 80)
  400. output = cmd(
  401. archiver,
  402. "create",
  403. "-v",
  404. "--list",
  405. "--pattern=-input/otherfile",
  406. "--patterns-from=" + archiver.patterns_file_path,
  407. "test",
  408. "input",
  409. )
  410. assert "A input/file_important" in output
  411. assert "- input/file1" in output
  412. assert "- input/file2" in output
  413. assert "- input/otherfile" in output
  414. def test_create_pattern_exclude_folder_but_recurse(archivers, request):
  415. """test when patterns exclude a parent folder, but include a child"""
  416. archiver = request.getfixturevalue(archivers)
  417. patterns_file_path2 = os.path.join(archiver.tmpdir, "patterns2")
  418. with open(patterns_file_path2, "wb") as fd:
  419. fd.write(b"+ input/x/b\n- input/x*\n")
  420. cmd(archiver, "repo-create", RK_ENCRYPTION)
  421. create_regular_file(archiver.input_path, "x/a/foo_a", size=1024 * 80)
  422. create_regular_file(archiver.input_path, "x/b/foo_b", size=1024 * 80)
  423. create_regular_file(archiver.input_path, "y/foo_y", size=1024 * 80)
  424. output = cmd(archiver, "create", "-v", "--list", "--patterns-from=" + patterns_file_path2, "test", "input")
  425. assert "- input/x/a/foo_a" in output
  426. assert "A input/x/b/foo_b" in output
  427. assert "A input/y/foo_y" in output
  428. def test_create_pattern_exclude_folder_no_recurse(archivers, request):
  429. """test when patterns exclude a parent folder, but include a child"""
  430. archiver = request.getfixturevalue(archivers)
  431. patterns_file_path2 = os.path.join(archiver.tmpdir, "patterns2")
  432. with open(patterns_file_path2, "wb") as fd:
  433. fd.write(b"+ input/x/b\n! input/x*\n")
  434. cmd(archiver, "repo-create", RK_ENCRYPTION)
  435. create_regular_file(archiver.input_path, "x/a/foo_a", size=1024 * 80)
  436. create_regular_file(archiver.input_path, "x/b/foo_b", size=1024 * 80)
  437. create_regular_file(archiver.input_path, "y/foo_y", size=1024 * 80)
  438. output = cmd(archiver, "create", "-v", "--list", "--patterns-from=" + patterns_file_path2, "test", "input")
  439. assert "input/x/a/foo_a" not in output
  440. assert "input/x/a" not in output
  441. assert "A input/y/foo_y" in output
  442. def test_create_pattern_intermediate_folders_first(archivers, request):
  443. """test that intermediate folders appear first when patterns exclude a parent folder but include a child"""
  444. archiver = request.getfixturevalue(archivers)
  445. patterns_file_path2 = os.path.join(archiver.tmpdir, "patterns2")
  446. with open(patterns_file_path2, "wb") as fd:
  447. fd.write(b"+ input/x/a\n+ input/x/b\n- input/x*\n")
  448. cmd(archiver, "repo-create", RK_ENCRYPTION)
  449. create_regular_file(archiver.input_path, "x/a/foo_a", size=1024 * 80)
  450. create_regular_file(archiver.input_path, "x/b/foo_b", size=1024 * 80)
  451. with changedir("input"):
  452. cmd(archiver, "create", "--patterns-from=" + patterns_file_path2, "test", ".")
  453. # list the archive and verify that the "intermediate" folders appear before
  454. # their contents
  455. out = cmd(archiver, "list", "test", "--format", "{type} {path}{NL}")
  456. out_list = out.splitlines()
  457. assert "d x/a" in out_list
  458. assert "d x/b" in out_list
  459. assert out_list.index("d x/a") < out_list.index("- x/a/foo_a")
  460. assert out_list.index("d x/b") < out_list.index("- x/b/foo_b")
  461. def test_create_archivename_with_placeholder(archivers, request):
  462. archiver = request.getfixturevalue(archivers)
  463. create_test_files(archiver.input_path)
  464. cmd(archiver, "repo-create", RK_ENCRYPTION)
  465. ts = "1999-12-31T23:59:59"
  466. name_given = "test-{now}" # placeholder in archive name gets replaced by borg
  467. name_expected = f"test-{ts}" # placeholder in f-string gets replaced by python
  468. cmd(archiver, "create", f"--timestamp={ts}", name_given, "input")
  469. list_output = cmd(archiver, "repo-list")
  470. assert name_expected in list_output
  471. def test_exclude_caches(archivers, request):
  472. archiver = request.getfixturevalue(archivers)
  473. _create_test_caches(archiver)
  474. cmd(archiver, "create", "test", "input", "--exclude-caches")
  475. _assert_test_caches(archiver)
  476. def test_exclude_tagged(archivers, request):
  477. archiver = request.getfixturevalue(archivers)
  478. _create_test_tagged(archiver)
  479. cmd(archiver, "create", "test", "input", "--exclude-if-present", ".NOBACKUP", "--exclude-if-present", "00-NOBACKUP")
  480. _assert_test_tagged(archiver)
  481. def test_exclude_keep_tagged(archivers, request):
  482. archiver = request.getfixturevalue(archivers)
  483. _create_test_keep_tagged(archiver)
  484. cmd(
  485. archiver,
  486. "create",
  487. "test",
  488. "input",
  489. "--exclude-if-present",
  490. ".NOBACKUP1",
  491. "--exclude-if-present",
  492. ".NOBACKUP2",
  493. "--exclude-caches",
  494. "--keep-exclude-tags",
  495. )
  496. _assert_test_keep_tagged(archiver)
  497. def test_path_sanitation(archivers, request):
  498. archiver = request.getfixturevalue(archivers)
  499. cmd(archiver, "repo-create", RK_ENCRYPTION)
  500. create_regular_file(archiver.input_path, "dir1/dir2/file", size=1024 * 80)
  501. with changedir("input/dir1/dir2"):
  502. cmd(archiver, "create", "test", "../../../input/dir1/../dir1/dir2/..")
  503. output = cmd(archiver, "list", "test")
  504. assert ".." not in output
  505. assert " input/dir1/dir2/file" in output
  506. def test_exclude_sanitation(archivers, request):
  507. archiver = request.getfixturevalue(archivers)
  508. cmd(archiver, "repo-create", RK_ENCRYPTION)
  509. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  510. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  511. with changedir("input"):
  512. cmd(archiver, "create", "test1", ".", "--exclude=file1")
  513. with changedir("output"):
  514. cmd(archiver, "extract", "test1")
  515. assert sorted(os.listdir("output")) == ["file2"]
  516. with changedir("input"):
  517. cmd(archiver, "create", "test2", ".", "--exclude=./file1")
  518. with changedir("output"):
  519. cmd(archiver, "extract", "test2")
  520. assert sorted(os.listdir("output")) == ["file2"]
  521. cmd(archiver, "create", "test3", "input", "--exclude=input/./file1")
  522. with changedir("output"):
  523. cmd(archiver, "extract", "test3")
  524. assert sorted(os.listdir("output/input")) == ["file2"]
  525. def test_repeated_files(archivers, request):
  526. archiver = request.getfixturevalue(archivers)
  527. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  528. cmd(archiver, "repo-create", RK_ENCRYPTION)
  529. cmd(archiver, "create", "test", "input", "input")
  530. @pytest.mark.skipif("BORG_TESTS_IGNORE_MODES" in os.environ, reason="modes unreliable")
  531. @pytest.mark.skipif(is_win32, reason="modes unavailable on Windows")
  532. def test_umask(archivers, request):
  533. archiver = request.getfixturevalue(archivers)
  534. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  535. cmd(archiver, "repo-create", RK_ENCRYPTION)
  536. cmd(archiver, "create", "test", "input")
  537. mode = os.stat(archiver.repository_path).st_mode
  538. assert stat.S_IMODE(mode) == 0o700
  539. def test_create_dry_run(archivers, request):
  540. archiver = request.getfixturevalue(archivers)
  541. cmd(archiver, "repo-create", RK_ENCRYPTION)
  542. cmd(archiver, "create", "--dry-run", "test", "input")
  543. # Make sure no archive has been created
  544. with Repository(archiver.repository_path) as repository:
  545. manifest = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
  546. assert manifest.archives.count() == 0
  547. def test_progress_on(archivers, request):
  548. archiver = request.getfixturevalue(archivers)
  549. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  550. cmd(archiver, "repo-create", RK_ENCRYPTION)
  551. output = cmd(archiver, "create", "test4", "input", "--progress")
  552. assert "0 B O 0 B U 0 N" in output
  553. def test_progress_off(archivers, request):
  554. archiver = request.getfixturevalue(archivers)
  555. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  556. cmd(archiver, "repo-create", RK_ENCRYPTION)
  557. output = cmd(archiver, "create", "test5", "input")
  558. assert "0 B O 0 B U 0 N" not in output
  559. def test_file_status(archivers, request):
  560. """test that various file status show expected results
  561. clearly incomplete: only tests for the weird "unchanged" status for now"""
  562. archiver = request.getfixturevalue(archivers)
  563. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  564. time.sleep(1) # file2 must have newer timestamps than file1
  565. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  566. cmd(archiver, "repo-create", RK_ENCRYPTION)
  567. output = cmd(archiver, "create", "--list", "test", "input")
  568. assert "A input/file1" in output
  569. assert "A input/file2" in output
  570. # should find first file as unmodified
  571. output = cmd(archiver, "create", "--list", "test", "input")
  572. assert "U input/file1" in output
  573. # although surprising, this is expected. For why, see:
  574. # https://borgbackup.readthedocs.org/en/latest/faq.html#i-am-seeing-a-added-status-for-a-unchanged-file
  575. assert "A input/file2" in output
  576. @pytest.mark.skipif(
  577. is_win32, reason="ctime attribute is file creation time on Windows"
  578. ) # see https://docs.python.org/3/library/os.html#os.stat_result.st_ctime
  579. def test_file_status_cs_cache_mode(archivers, request):
  580. archiver = request.getfixturevalue(archivers)
  581. """test that a changed file with faked "previous" mtime still gets backed up in ctime,size cache_mode"""
  582. create_regular_file(archiver.input_path, "file1", contents=b"123")
  583. time.sleep(1) # file2 must have newer timestamps than file1
  584. create_regular_file(archiver.input_path, "file2", size=10)
  585. cmd(archiver, "repo-create", RK_ENCRYPTION)
  586. cmd(archiver, "create", "test", "input", "--list", "--files-cache=ctime,size")
  587. # modify file1, but cheat with the mtime (and atime) and also keep same size:
  588. st = os.stat("input/file1")
  589. create_regular_file(archiver.input_path, "file1", contents=b"321")
  590. os.utime("input/file1", ns=(st.st_atime_ns, st.st_mtime_ns))
  591. # this mode uses ctime for change detection, so it should find file1 as modified
  592. output = cmd(archiver, "create", "test", "input", "--list", "--files-cache=ctime,size")
  593. assert "M input/file1" in output
  594. def test_file_status_ms_cache_mode(archivers, request):
  595. """test that a chmod'ed file with no content changes does not get chunked again in mtime,size cache_mode"""
  596. archiver = request.getfixturevalue(archivers)
  597. create_regular_file(archiver.input_path, "file1", size=10)
  598. time.sleep(1) # file2 must have newer timestamps than file1
  599. create_regular_file(archiver.input_path, "file2", size=10)
  600. cmd(archiver, "repo-create", RK_ENCRYPTION)
  601. cmd(archiver, "create", "--list", "--files-cache=mtime,size", "test", "input")
  602. # change mode of file1, no content change:
  603. st = os.stat("input/file1")
  604. os.chmod("input/file1", st.st_mode ^ stat.S_IRWXO) # this triggers a ctime change, but mtime is unchanged
  605. # this mode uses mtime for change detection, so it should find file1 as unmodified
  606. output = cmd(archiver, "create", "--list", "--files-cache=mtime,size", "test", "input")
  607. assert "U input/file1" in output
  608. def test_file_status_rc_cache_mode(archivers, request):
  609. """test that files get rechunked unconditionally in rechunk,ctime cache mode"""
  610. archiver = request.getfixturevalue(archivers)
  611. create_regular_file(archiver.input_path, "file1", size=10)
  612. time.sleep(1) # file2 must have newer timestamps than file1
  613. create_regular_file(archiver.input_path, "file2", size=10)
  614. cmd(archiver, "repo-create", RK_ENCRYPTION)
  615. cmd(archiver, "create", "--list", "--files-cache=rechunk,ctime", "test", "input")
  616. # no changes here, but this mode rechunks unconditionally
  617. output = cmd(archiver, "create", "--list", "--files-cache=rechunk,ctime", "test", "input")
  618. assert "A input/file1" in output
  619. def test_file_status_excluded(archivers, request):
  620. """test that excluded paths are listed"""
  621. archiver = request.getfixturevalue(archivers)
  622. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  623. time.sleep(1) # file2 must have newer timestamps than file1
  624. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  625. if has_lchflags:
  626. create_regular_file(archiver.input_path, "file3", size=1024 * 80)
  627. platform.set_flags(os.path.join(archiver.input_path, "file3"), stat.UF_NODUMP)
  628. cmd(archiver, "repo-create", RK_ENCRYPTION)
  629. output = cmd(archiver, "create", "--list", "test", "input")
  630. assert "A input/file1" in output
  631. assert "A input/file2" in output
  632. if has_lchflags:
  633. assert "- input/file3" in output
  634. # should find second file as excluded
  635. output = cmd(archiver, "create", "test", "input", "--list", "--exclude", "*/file2")
  636. assert "U input/file1" in output
  637. assert "- input/file2" in output
  638. if has_lchflags:
  639. assert "- input/file3" in output
  640. def test_file_status_counters(archivers, request):
  641. """Test file status counters in the stats of `borg create --stats`"""
  642. archiver = request.getfixturevalue(archivers)
  643. def to_dict(borg_create_output):
  644. borg_create_output = borg_create_output.strip().splitlines()
  645. borg_create_output = [line.split(":", 1) for line in borg_create_output]
  646. borg_create_output = {
  647. key: int(value)
  648. for key, value in borg_create_output
  649. if key in ("Added files", "Unchanged files", "Modified files")
  650. }
  651. return borg_create_output
  652. # Test case set up: create a repository
  653. cmd(archiver, "repo-create", RK_ENCRYPTION)
  654. # Archive an empty dir
  655. result = cmd(archiver, "create", "--stats", "test_archive", archiver.input_path)
  656. result = to_dict(result)
  657. assert result["Added files"] == 0
  658. assert result["Unchanged files"] == 0
  659. assert result["Modified files"] == 0
  660. # Archive a dir with two added files
  661. create_regular_file(archiver.input_path, "testfile1", contents=b"test1")
  662. time.sleep(1.0 if is_darwin else 0.01) # testfile2 must have newer timestamps than testfile1
  663. create_regular_file(archiver.input_path, "testfile2", contents=b"test2")
  664. result = cmd(archiver, "create", "--stats", "test_archive", archiver.input_path)
  665. result = to_dict(result)
  666. assert result["Added files"] == 2
  667. assert result["Unchanged files"] == 0
  668. assert result["Modified files"] == 0
  669. # Archive a dir with 1 unmodified file and 1 modified
  670. create_regular_file(archiver.input_path, "testfile1", contents=b"new data")
  671. result = cmd(archiver, "create", "--stats", "test_archive", archiver.input_path)
  672. result = to_dict(result)
  673. # Should process testfile2 as added because of
  674. # https://borgbackup.readthedocs.io/en/stable/faq.html#i-am-seeing-a-added-status-for-an-unchanged-file
  675. assert result["Added files"] == 1
  676. assert result["Unchanged files"] == 0
  677. assert result["Modified files"] == 1
  678. def test_create_json(archivers, request):
  679. archiver = request.getfixturevalue(archivers)
  680. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  681. cmd(archiver, "repo-create", RK_ENCRYPTION)
  682. create_info = json.loads(cmd(archiver, "create", "--json", "test", "input"))
  683. # The usual keys
  684. assert "encryption" in create_info
  685. assert "repository" in create_info
  686. assert "cache" in create_info
  687. assert "last_modified" in create_info["repository"]
  688. archive = create_info["archive"]
  689. assert archive["name"] == "test"
  690. assert isinstance(archive["command_line"], str)
  691. assert isinstance(archive["duration"], float)
  692. assert len(archive["id"]) == 64
  693. assert "stats" in archive
  694. def test_create_topical(archivers, request):
  695. archiver = request.getfixturevalue(archivers)
  696. create_regular_file(archiver.input_path, "file1", size=1024 * 80)
  697. time.sleep(1) # file2 must have newer timestamps than file1
  698. create_regular_file(archiver.input_path, "file2", size=1024 * 80)
  699. cmd(archiver, "repo-create", RK_ENCRYPTION)
  700. # no listing by default
  701. output = cmd(archiver, "create", "test", "input")
  702. assert "file1" not in output
  703. # shouldn't be listed even if unchanged
  704. output = cmd(archiver, "create", "test", "input")
  705. assert "file1" not in output
  706. # should list the file as unchanged
  707. output = cmd(archiver, "create", "test", "input", "--list", "--filter=U")
  708. assert "file1" in output
  709. # should *not* list the file as changed
  710. output = cmd(archiver, "create", "test", "input", "--list", "--filter=AM")
  711. assert "file1" not in output
  712. # change the file
  713. create_regular_file(archiver.input_path, "file1", size=1024 * 100)
  714. # should list the file as changed
  715. output = cmd(archiver, "create", "test", "input", "--list", "--filter=AM")
  716. assert "file1" in output
  717. # @pytest.mark.skipif(not are_fifos_supported() or is_cygwin, reason="FIFOs not supported, hangs on cygwin")
  718. @pytest.mark.skip(reason="This test is problematic and should be skipped")
  719. def test_create_read_special_symlink(archivers, request):
  720. archiver = request.getfixturevalue(archivers)
  721. from threading import Thread
  722. def fifo_feeder(fifo_fn, data):
  723. fd = os.open(fifo_fn, os.O_WRONLY)
  724. try:
  725. os.write(fd, data)
  726. finally:
  727. os.close(fd)
  728. cmd(archiver, "repo-create", RK_ENCRYPTION)
  729. data = b"foobar" * 1000
  730. fifo_fn = os.path.join(archiver.input_path, "fifo")
  731. link_fn = os.path.join(archiver.input_path, "link_fifo")
  732. os.mkfifo(fifo_fn)
  733. os.symlink(fifo_fn, link_fn)
  734. t = Thread(target=fifo_feeder, args=(fifo_fn, data))
  735. t.start()
  736. try:
  737. cmd(archiver, "create", "--read-special", "test", "input/link_fifo")
  738. finally:
  739. # In case `borg create` failed to open FIFO, read all data to avoid join() hanging.
  740. fd = os.open(fifo_fn, os.O_RDONLY | os.O_NONBLOCK)
  741. try:
  742. os.read(fd, len(data))
  743. except OSError:
  744. # fails on FreeBSD 13 with BlockingIOError
  745. pass
  746. finally:
  747. os.close(fd)
  748. t.join()
  749. with changedir("output"):
  750. cmd(archiver, "extract", "test")
  751. fifo_fn = "input/link_fifo"
  752. with open(fifo_fn, "rb") as f:
  753. extracted_data = f.read()
  754. assert extracted_data == data
  755. @pytest.mark.skipif(not are_symlinks_supported(), reason="symlinks not supported")
  756. def test_create_read_special_broken_symlink(archivers, request):
  757. archiver = request.getfixturevalue(archivers)
  758. os.symlink("somewhere does not exist", os.path.join(archiver.input_path, "link"))
  759. cmd(archiver, "repo-create", RK_ENCRYPTION)
  760. cmd(archiver, "create", "--read-special", "test", "input")
  761. output = cmd(archiver, "list", "test")
  762. assert "input/link -> somewhere does not exist" in output
  763. def test_create_dotslash_hack(archivers, request):
  764. archiver = request.getfixturevalue(archivers)
  765. os.makedirs(os.path.join(archiver.input_path, "first", "secondA", "thirdA"))
  766. os.makedirs(os.path.join(archiver.input_path, "first", "secondB", "thirdB"))
  767. cmd(archiver, "repo-create", RK_ENCRYPTION)
  768. cmd(archiver, "create", "test", "input/first/./") # hack!
  769. output = cmd(archiver, "list", "test")
  770. # dir levels left of slashdot (= input, first) not in archive:
  771. assert "input" not in output
  772. assert "input/first" not in output
  773. assert "input/first/secondA" not in output
  774. assert "input/first/secondA/thirdA" not in output
  775. assert "input/first/secondB" not in output
  776. assert "input/first/secondB/thirdB" not in output
  777. assert "first" not in output
  778. assert "first/secondA" not in output
  779. assert "first/secondA/thirdA" not in output
  780. assert "first/secondB" not in output
  781. assert "first/secondB/thirdB" not in output
  782. # dir levels right of slashdot are in archive:
  783. assert "secondA" in output
  784. assert "secondA/thirdA" in output
  785. assert "secondB" in output
  786. assert "secondB/thirdB" in output
  787. def test_log_json(archivers, request):
  788. archiver = request.getfixturevalue(archivers)
  789. create_test_files(archiver.input_path)
  790. cmd(archiver, "repo-create", RK_ENCRYPTION)
  791. log = cmd(archiver, "create", "test", "input", "--log-json", "--list", "--debug")
  792. messages = {} # type -> message, one of each kind
  793. for line in log.splitlines():
  794. msg = json.loads(line)
  795. messages[msg["type"]] = msg
  796. file_status = messages["file_status"]
  797. assert "status" in file_status
  798. assert file_status["path"].startswith("input")
  799. log_message = messages["log_message"]
  800. assert isinstance(log_message["time"], float)
  801. assert log_message["levelname"] == "DEBUG" # there should only be DEBUG messages
  802. assert isinstance(log_message["message"], str)
  803. def test_common_options(archivers, request):
  804. archiver = request.getfixturevalue(archivers)
  805. create_test_files(archiver.input_path)
  806. cmd(archiver, "repo-create", RK_ENCRYPTION)
  807. log = cmd(archiver, "--debug", "create", "test", "input")
  808. assert "security: read previous location" in log
  809. def test_create_big_zeros_files(archivers, request):
  810. """Test creating an archive from 10 files with 10MB zeros each."""
  811. archiver = request.getfixturevalue(archivers)
  812. # Create 10 files with 10,000,000 bytes of zeros each
  813. count, size = 10, 10 * 1000 * 1000
  814. assert size <= len(zeros)
  815. for i in range(count):
  816. create_regular_file(archiver.input_path, f"zeros_{i}", contents=memoryview(zeros)[:size])
  817. # Create repository and archive
  818. cmd(archiver, "repo-create", RK_ENCRYPTION)
  819. cmd(archiver, "create", "test", "input")
  820. # Extract the archive to verify contents
  821. with tempfile.TemporaryDirectory() as extract_path:
  822. with changedir(extract_path):
  823. cmd(archiver, "extract", "test")
  824. # Verify that the extracted files have the correct contents
  825. for i in range(count):
  826. extracted_file_path = os.path.join(extract_path, "input", f"zeros_{i}")
  827. with open(extracted_file_path, "rb") as f:
  828. extracted_data = f.read()
  829. # Verify the file contains only zeros and has the correct size
  830. assert extracted_data == bytes(size)
  831. assert len(extracted_data) == size
  832. # Also verify the directory structure matches
  833. assert_dirs_equal(archiver.input_path, os.path.join(extract_path, "input"))
  834. # Remove input files
  835. for i in range(count):
  836. os.unlink(os.path.join(archiver.input_path, f"zeros_{i}"))
  837. def test_create_big_random_files(archivers, request):
  838. """Test creating an archive from 10 files with 10MB random data each."""
  839. archiver = request.getfixturevalue(archivers)
  840. # Create 10 files with 10,000,000 bytes of random data each
  841. count, size = 10, 10 * 1000 * 1000
  842. random_data = {}
  843. for i in range(count):
  844. data = os.urandom(size)
  845. random_data[i] = data
  846. create_regular_file(archiver.input_path, f"random_{i}", contents=data)
  847. # Create repository and archive
  848. cmd(archiver, "repo-create", RK_ENCRYPTION)
  849. cmd(archiver, "create", "test", "input")
  850. # Extract the archive to verify contents
  851. with tempfile.TemporaryDirectory() as extract_path:
  852. with changedir(extract_path):
  853. cmd(archiver, "extract", "test")
  854. # Verify that the extracted files have the correct contents
  855. for i in range(count):
  856. extracted_file_path = os.path.join(extract_path, "input", f"random_{i}")
  857. with open(extracted_file_path, "rb") as f:
  858. extracted_data = f.read()
  859. # Verify the file contains the original random data and has the correct size
  860. assert extracted_data == random_data[i]
  861. assert len(extracted_data) == size
  862. # Also verify the directory structure matches
  863. assert_dirs_equal(archiver.input_path, os.path.join(extract_path, "input"))
  864. # Remove input files
  865. for i in range(count):
  866. os.unlink(os.path.join(archiver.input_path, f"random_{i}"))
  867. def test_create_with_compression_algorithms(archivers, request):
  868. """Test creating archives with different compression algorithms."""
  869. archiver = request.getfixturevalue(archivers)
  870. # Create test files: 5 files with zeros (highly compressible) and 5 with random data (incompressible)
  871. count, size = 5, 1 * 1000 * 1000 # 1MB per file
  872. random_data = {}
  873. # Create zeros files
  874. for i in range(count):
  875. create_regular_file(archiver.input_path, f"zeros_{i}", contents=memoryview(zeros)[:size])
  876. # Create random files
  877. for i in range(count):
  878. data = os.urandom(size)
  879. random_data[i] = data
  880. create_regular_file(archiver.input_path, f"random_{i}", contents=data)
  881. # Create repository
  882. cmd(archiver, "repo-create", RK_ENCRYPTION)
  883. # Test different compression algorithms
  884. algorithms = [
  885. "none", # No compression
  886. "lz4", # Fast compression
  887. "zlib,6", # Medium compression
  888. "zstd,3", # Good compression/speed balance
  889. "lzma,6", # High compression
  890. ]
  891. for algo in algorithms:
  892. # Create archive with specific compression algorithm
  893. archive_name = f"test_{algo.replace(',', '_')}"
  894. cmd(archiver, "create", "--compression", algo, archive_name, "input")
  895. # Extract the archive to verify contents
  896. with tempfile.TemporaryDirectory() as extract_path:
  897. with changedir(extract_path):
  898. cmd(archiver, "extract", archive_name)
  899. # Verify zeros files
  900. for i in range(count):
  901. extracted_file_path = os.path.join(extract_path, "input", f"zeros_{i}")
  902. with open(extracted_file_path, "rb") as f:
  903. extracted_data = f.read()
  904. # Verify the file contains only zeros and has the correct size
  905. assert extracted_data == bytes(size)
  906. assert len(extracted_data) == size
  907. # Verify random files
  908. for i in range(count):
  909. extracted_file_path = os.path.join(extract_path, "input", f"random_{i}")
  910. with open(extracted_file_path, "rb") as f:
  911. extracted_data = f.read()
  912. # Verify the file contains the original random data and has the correct size
  913. assert extracted_data == random_data[i]
  914. assert len(extracted_data) == size
  915. # Also verify the directory structure matches
  916. assert_dirs_equal(archiver.input_path, os.path.join(extract_path, "input"))
  917. # Remove input files
  918. for i in range(count):
  919. os.unlink(os.path.join(archiver.input_path, f"zeros_{i}"))
  920. os.unlink(os.path.join(archiver.input_path, f"random_{i}"))
  921. def test_exclude_nodump_dir_with_file(archivers, request):
  922. """A directory flagged NODUMP and its contents must not be archived."""
  923. archiver = request.getfixturevalue(archivers)
  924. if not has_lchflags:
  925. pytest.skip("platform does not support setting UF_NODUMP")
  926. # Prepare input tree: input/nd directory (NODUMP) containing a file.
  927. create_regular_file(archiver.input_path, "nd/file_in_ndir", contents=b"hello")
  928. platform.set_flags(os.path.join(archiver.input_path, "nd"), stat.UF_NODUMP)
  929. # Create repo and archive
  930. cmd(archiver, "repo-create", RK_ENCRYPTION)
  931. cmd(archiver, "create", "test", "input")
  932. # Verify: neither the directory nor its contained file are present in the archive
  933. list_output = cmd(archiver, "list", "test", "--short")
  934. assert "input/nd\n" not in list_output
  935. assert "input/nd/file_in_ndir\n" not in list_output