mirror of https://github.com/borgbackup/borg.git
977 lines
42 KiB
Python
977 lines
42 KiB
Python
import errno
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from random import randbytes
|
|
import shutil
|
|
import socket
|
|
import stat
|
|
import subprocess
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from ... import platform
|
|
from ...constants import * # NOQA
|
|
from ...manifest import Manifest
|
|
from ...platform import is_cygwin, is_win32, is_darwin
|
|
from ...repository import Repository
|
|
from ...helpers import CommandError
|
|
from .. import has_lchflags
|
|
from .. import changedir
|
|
from .. import (
|
|
are_symlinks_supported,
|
|
are_hardlinks_supported,
|
|
are_fifos_supported,
|
|
is_utime_fully_supported,
|
|
is_birthtime_fully_supported,
|
|
same_ts_ns,
|
|
is_root,
|
|
)
|
|
from . import (
|
|
cmd,
|
|
generate_archiver_tests,
|
|
create_test_files,
|
|
assert_dirs_equal,
|
|
create_regular_file,
|
|
requires_hardlinks,
|
|
_create_test_caches,
|
|
_create_test_tagged,
|
|
_create_test_keep_tagged,
|
|
_assert_test_caches,
|
|
_assert_test_tagged,
|
|
_assert_test_keep_tagged,
|
|
RK_ENCRYPTION,
|
|
)
|
|
|
|
pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA
|
|
|
|
|
|
def test_basic_functionality(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
if archiver.EXE:
|
|
pytest.skip("test_basic_functionality seems incompatible with fakeroot and/or the binary.")
|
|
have_root = create_test_files(archiver.input_path)
|
|
# fork required to test show-rc output
|
|
output = cmd(archiver, "rcreate", RK_ENCRYPTION, "--show-version", "--show-rc", fork=True)
|
|
assert "borgbackup version" in output
|
|
assert "terminating with success status, rc 0" in output
|
|
|
|
cmd(archiver, "create", "--exclude-nodump", "test", "input")
|
|
output = cmd(archiver, "create", "--exclude-nodump", "--stats", "test.2", "input")
|
|
assert "Archive name: test.2" in output
|
|
|
|
with changedir("output"):
|
|
cmd(archiver, "extract", "test")
|
|
|
|
list_output = cmd(archiver, "rlist", "--short")
|
|
assert "test" in list_output
|
|
assert "test.2" in list_output
|
|
|
|
expected = [
|
|
"input",
|
|
"input/bdev",
|
|
"input/cdev",
|
|
"input/dir2",
|
|
"input/dir2/file2",
|
|
"input/empty",
|
|
"input/file1",
|
|
"input/flagfile",
|
|
]
|
|
if are_fifos_supported():
|
|
expected.append("input/fifo1")
|
|
if are_symlinks_supported():
|
|
expected.append("input/link1")
|
|
if are_hardlinks_supported():
|
|
expected.append("input/hardlink")
|
|
if not have_root:
|
|
# we could not create these device files without (fake)root
|
|
expected.remove("input/bdev")
|
|
expected.remove("input/cdev")
|
|
if has_lchflags:
|
|
# remove the file we did not back up, so input and output become equal
|
|
expected.remove("input/flagfile") # this file is UF_NODUMP
|
|
os.remove(os.path.join("input", "flagfile"))
|
|
|
|
list_output = cmd(archiver, "list", "test", "--short")
|
|
for name in expected:
|
|
assert name in list_output
|
|
assert_dirs_equal("input", "output/input")
|
|
|
|
info_output = cmd(archiver, "info", "-a", "test")
|
|
item_count = 5 if has_lchflags else 6 # one file is UF_NODUMP
|
|
assert "Number of files: %d" % item_count in info_output
|
|
shutil.rmtree(archiver.cache_path)
|
|
info_output2 = cmd(archiver, "info", "-a", "test")
|
|
|
|
def filter(output):
|
|
# filter for interesting "info" output, ignore cache rebuilding related stuff
|
|
prefixes = ["Name:", "Fingerprint:", "Number of files:", "This archive:", "All archives:", "Chunk index:"]
|
|
result = []
|
|
for line in output.splitlines():
|
|
for prefix in prefixes:
|
|
if line.startswith(prefix):
|
|
result.append(line)
|
|
return "\n".join(result)
|
|
|
|
# the interesting parts of info_output2 and info_output should be same
|
|
assert filter(info_output) == filter(info_output2)
|
|
|
|
|
|
def test_archived_paths(archivers, request):
|
|
# As borg comes from the POSIX (Linux, UNIX) world, a lot of stuff assumes path separators
|
|
# to be slashes "/", e.g.: in archived items, for pattern matching.
|
|
# To make our lives easier and to support cross-platform extraction we always use slashes.
|
|
# Similarly, archived paths are expected to be full, but relative (have no leading slash).
|
|
archiver = request.getfixturevalue(archivers)
|
|
full_path = os.path.abspath(os.path.join(archiver.input_path, "test"))
|
|
# remove windows drive letter, if any:
|
|
posix_path = full_path[2:] if full_path[1] == ":" else full_path
|
|
# only needed on Windows in case there are backslashes:
|
|
posix_path = posix_path.replace("\\", "/")
|
|
# no leading slash in borg archives:
|
|
archived_path = posix_path.lstrip("/")
|
|
create_regular_file(archiver.input_path, "test")
|
|
cmd(archiver, "rcreate", "--encryption=none")
|
|
cmd(archiver, "create", "test", "input", posix_path)
|
|
# "input" directory is recursed into, "input/test" is discovered and joined by borg's recursion.
|
|
# posix_path was directly given as a cli argument and should end up as archive_path in the borg archive.
|
|
expected_paths = sorted(["input", "input/test", archived_path])
|
|
|
|
# check path in archived items:
|
|
archive_list = cmd(archiver, "list", "test", "--short")
|
|
assert expected_paths == sorted([path for path in archive_list.splitlines() if path])
|
|
|
|
# check path in archived items (json):
|
|
archive_list = cmd(archiver, "list", "test", "--json-lines")
|
|
assert expected_paths == sorted([json.loads(line)["path"] for line in archive_list.splitlines() if line])
|
|
|
|
|
|
@requires_hardlinks
|
|
def test_create_duplicate_root(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
# setup for #5603
|
|
path_a = os.path.join(archiver.input_path, "a")
|
|
path_b = os.path.join(archiver.input_path, "b")
|
|
os.mkdir(path_a)
|
|
os.mkdir(path_b)
|
|
hl_a = os.path.join(path_a, "hardlink")
|
|
hl_b = os.path.join(path_b, "hardlink")
|
|
create_regular_file(archiver.input_path, hl_a, contents=b"123456")
|
|
os.link(hl_a, hl_b)
|
|
cmd(archiver, "rcreate", "--encryption=none")
|
|
cmd(archiver, "create", "test", "input", "input") # give input twice!
|
|
# test if created archive has 'input' contents twice:
|
|
archive_list = cmd(archiver, "list", "test", "--json-lines")
|
|
paths = [json.loads(line)["path"] for line in archive_list.split("\n") if line]
|
|
# we have all fs items exactly once!
|
|
assert sorted(paths) == ["input", "input/a", "input/a/hardlink", "input/b", "input/b/hardlink"]
|
|
|
|
|
|
def test_create_unreadable_parent(archiver):
|
|
parent_dir = os.path.join(archiver.input_path, "parent")
|
|
root_dir = os.path.join(archiver.input_path, "parent", "root")
|
|
os.mkdir(parent_dir)
|
|
os.mkdir(root_dir)
|
|
os.chmod(parent_dir, 0o111) # --x--x--x == parent dir traversable, but not readable
|
|
try:
|
|
cmd(archiver, "rcreate", "--encryption=none")
|
|
# issue #7746: we *can* read root_dir and we *can* traverse parent_dir, so this should work:
|
|
cmd(archiver, "create", "test", root_dir)
|
|
finally:
|
|
os.chmod(parent_dir, 0o771) # otherwise cleanup after this test fails
|
|
|
|
|
|
@pytest.mark.skipif(is_win32, reason="unix sockets not available on windows")
|
|
def test_unix_socket(archivers, request, monkeypatch):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
try:
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
sock.bind(os.path.join(temp_dir, "unix-socket"))
|
|
except PermissionError as err:
|
|
if err.errno == errno.EPERM:
|
|
pytest.skip("unix sockets disabled or not supported")
|
|
elif err.errno == errno.EACCES:
|
|
pytest.skip("permission denied to create unix sockets")
|
|
cmd(archiver, "create", "test", "input")
|
|
sock.close()
|
|
with changedir("output"):
|
|
cmd(archiver, "extract", "test")
|
|
print(f"{temp_dir}/unix-socket")
|
|
assert not os.path.exists(f"{temp_dir}/unix-socket")
|
|
|
|
|
|
@pytest.mark.skipif(not is_utime_fully_supported(), reason="cannot setup and execute test without utime")
|
|
@pytest.mark.skipif(not is_birthtime_fully_supported(), reason="cannot setup and execute test without birth time")
|
|
def test_nobirthtime(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_test_files(archiver.input_path)
|
|
birthtime, mtime, atime = 946598400, 946684800, 946771200
|
|
os.utime("input/file1", (atime, birthtime))
|
|
os.utime("input/file1", (atime, mtime))
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "test", "input", "--nobirthtime")
|
|
with changedir("output"):
|
|
cmd(archiver, "extract", "test")
|
|
sti = os.stat("input/file1")
|
|
sto = os.stat("output/input/file1")
|
|
assert same_ts_ns(sti.st_birthtime * 1e9, birthtime * 1e9)
|
|
assert same_ts_ns(sto.st_birthtime * 1e9, mtime * 1e9)
|
|
assert same_ts_ns(sti.st_mtime_ns, sto.st_mtime_ns)
|
|
assert same_ts_ns(sto.st_mtime_ns, mtime * 1e9)
|
|
|
|
|
|
def test_create_stdin(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
input_data = b"\x00foo\n\nbar\n \n"
|
|
cmd(archiver, "create", "test", "-", input=input_data)
|
|
item = json.loads(cmd(archiver, "list", "test", "--json-lines"))
|
|
assert item["size"] == len(input_data)
|
|
assert item["path"] == "stdin"
|
|
extracted_data = cmd(archiver, "extract", "test", "--stdout", binary_output=True)
|
|
assert extracted_data == input_data
|
|
|
|
|
|
def test_create_stdin_checkpointing(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
chunk_size = 1000 # fixed chunker with this size, also volume based checkpointing after that volume
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
input_data = b"X" * (chunk_size * 2 - 1) # one full and one partial chunk
|
|
cmd(
|
|
archiver,
|
|
"create",
|
|
f"--chunker-params=fixed,{chunk_size}",
|
|
f"--checkpoint-volume={chunk_size}",
|
|
"test",
|
|
"-",
|
|
input=input_data,
|
|
)
|
|
# repo looking good overall? checks for rc == 0.
|
|
cmd(archiver, "check", "--debug")
|
|
# verify that there are no part files in final archive
|
|
out = cmd(archiver, "list", "test")
|
|
assert "stdin.borg_part" not in out
|
|
# verify full file
|
|
out = cmd(archiver, "extract", "test", "stdin", "--stdout", binary_output=True)
|
|
assert out == input_data
|
|
|
|
|
|
def test_create_erroneous_file(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
chunk_size = 1000 # fixed chunker with this size, also volume based checkpointing after that volume
|
|
create_regular_file(archiver.input_path, os.path.join(archiver.input_path, "file1"), size=chunk_size * 2)
|
|
create_regular_file(archiver.input_path, os.path.join(archiver.input_path, "file2"), size=chunk_size * 2)
|
|
create_regular_file(archiver.input_path, os.path.join(archiver.input_path, "file3"), size=chunk_size * 2)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
flist = "".join(f"input/file{n}\n" for n in range(1, 4))
|
|
out = cmd(
|
|
archiver,
|
|
"create",
|
|
f"--chunker-params=fail,{chunk_size},rrrEEErrrr",
|
|
"--paths-from-stdin",
|
|
"--list",
|
|
"test",
|
|
input=flist.encode(),
|
|
exit_code=0,
|
|
)
|
|
assert "retry: 3 of " in out
|
|
assert "E input/file2" not in out # we managed to read it in the 3rd retry (after 3 failed reads)
|
|
# repo looking good overall? checks for rc == 0.
|
|
cmd(archiver, "check", "--debug")
|
|
# check files in created archive
|
|
out = cmd(archiver, "list", "test")
|
|
assert "input/file1" in out
|
|
assert "input/file2" in out
|
|
assert "input/file3" in out
|
|
|
|
|
|
@pytest.mark.skipif(is_root(), reason="test must not be run as (fake)root")
|
|
def test_create_no_permission_file(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
file_path = os.path.join(archiver.input_path, "file")
|
|
create_regular_file(archiver.input_path, file_path + "1", size=1000)
|
|
create_regular_file(archiver.input_path, file_path + "2", size=1000)
|
|
create_regular_file(archiver.input_path, file_path + "3", size=1000)
|
|
# revoke read permissions on file2 for everybody, including us:
|
|
if is_win32:
|
|
subprocess.run(["icacls.exe", file_path + "2", "/deny", "everyone:(R)"])
|
|
else:
|
|
# note: this will NOT take away read permissions for root
|
|
os.chmod(file_path + "2", 0o000)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
flist = "".join(f"input/file{n}\n" for n in range(1, 4))
|
|
out = cmd(
|
|
archiver,
|
|
"create",
|
|
"--paths-from-stdin",
|
|
"--list",
|
|
"test",
|
|
input=flist.encode(),
|
|
exit_code=1, # WARNING status: could not back up file2.
|
|
)
|
|
assert "retry: 1 of " not in out # retries were NOT attempted!
|
|
assert "E input/file2" in out # no permissions!
|
|
# repo looking good overall? checks for rc == 0.
|
|
cmd(archiver, "check", "--debug")
|
|
# check files in created archive
|
|
out = cmd(archiver, "list", "test")
|
|
assert "input/file1" in out
|
|
assert "input/file2" not in out # it skipped file2
|
|
assert "input/file3" in out
|
|
|
|
|
|
def test_sanitized_stdin_name(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "--stdin-name", "./a//path", "test", "-", input=b"")
|
|
item = json.loads(cmd(archiver, "list", "test", "--json-lines"))
|
|
assert item["path"] == "a/path"
|
|
|
|
|
|
def test_dotdot_stdin_name(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "--stdin-name", "foo/../bar", "test", "-", input=b"", exit_code=2)
|
|
assert output.endswith("'..' element in path 'foo/../bar'" + os.linesep)
|
|
|
|
|
|
def test_dot_stdin_name(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "--stdin-name", "./", "test", "-", input=b"", exit_code=2)
|
|
assert output.endswith("'./' is not a valid file name" + os.linesep)
|
|
|
|
|
|
def test_create_content_from_command(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
input_data = "some test content"
|
|
name = "a/b/c"
|
|
cmd(archiver, "create", "--stdin-name", name, "--content-from-command", "test", "--", "echo", input_data)
|
|
item = json.loads(cmd(archiver, "list", "test", "--json-lines"))
|
|
assert item["size"] == len(input_data) + 1 # `echo` adds newline
|
|
assert item["path"] == name
|
|
extracted_data = cmd(archiver, "extract", "test", "--stdout")
|
|
assert extracted_data == input_data + "\n"
|
|
|
|
|
|
def test_create_content_from_command_with_failed_command(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
if archiver.FORK_DEFAULT:
|
|
output = cmd(archiver, "create", "--content-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=2)
|
|
assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
|
|
else:
|
|
with pytest.raises(CommandError):
|
|
cmd(archiver, "create", "--content-from-command", "test", "--", "sh", "-c", "exit 73;")
|
|
archive_list = json.loads(cmd(archiver, "rlist", "--json"))
|
|
assert archive_list["archives"] == []
|
|
|
|
|
|
def test_create_content_from_command_missing_command(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "test", "--content-from-command", exit_code=2)
|
|
assert output.endswith("No command given." + os.linesep)
|
|
|
|
|
|
def test_create_paths_from_stdin(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "dir1/file2", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "dir1/file3", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file4", size=1024 * 80)
|
|
input_data = b"input/file1\0input/dir1\0input/file4"
|
|
cmd(archiver, "create", "test", "--paths-from-stdin", "--paths-delimiter", "\\0", input=input_data)
|
|
archive_list = cmd(archiver, "list", "test", "--json-lines")
|
|
paths = [json.loads(line)["path"] for line in archive_list.split("\n") if line]
|
|
assert paths == ["input/file1", "input/dir1", "input/file4"]
|
|
|
|
|
|
def test_create_paths_from_command(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file3", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file4", size=1024 * 80)
|
|
input_data = "input/file1\ninput/file2\ninput/file3"
|
|
if is_win32:
|
|
with open("filenames.cmd", "w") as script:
|
|
for filename in input_data.splitlines():
|
|
script.write(f"@echo {filename}\n")
|
|
cmd(archiver, "create", "--paths-from-command", "test", "--", "filenames.cmd" if is_win32 else "echo", input_data)
|
|
archive_list = cmd(archiver, "list", "test", "--json-lines")
|
|
paths = [json.loads(line)["path"] for line in archive_list.split("\n") if line]
|
|
assert paths == ["input/file1", "input/file2", "input/file3"]
|
|
|
|
|
|
def test_create_paths_from_command_with_failed_command(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
if archiver.FORK_DEFAULT:
|
|
output = cmd(archiver, "create", "--paths-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=2)
|
|
assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
|
|
else:
|
|
with pytest.raises(CommandError):
|
|
cmd(archiver, "create", "--paths-from-command", "test", "--", "sh", "-c", "exit 73;")
|
|
archive_list = json.loads(cmd(archiver, "rlist", "--json"))
|
|
assert archive_list["archives"] == []
|
|
|
|
|
|
def test_create_paths_from_command_missing_command(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "test", "--paths-from-command", exit_code=2)
|
|
assert output.endswith("No command given." + os.linesep)
|
|
|
|
|
|
def test_create_without_root(archivers, request):
|
|
"""test create without a root"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "test", exit_code=2)
|
|
|
|
|
|
def test_create_pattern_root(archivers, request):
|
|
"""test create with only a root pattern"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
output = cmd(archiver, "create", "test", "-v", "--list", "--pattern=R input")
|
|
assert "A input/file1" in output
|
|
assert "A input/file2" in output
|
|
|
|
|
|
def test_create_pattern(archivers, request):
|
|
"""test file patterns during create"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file_important", size=1024 * 80)
|
|
output = cmd(
|
|
archiver, "create", "-v", "--list", "--pattern=+input/file_important", "--pattern=-input/file*", "test", "input"
|
|
)
|
|
assert "A input/file_important" in output
|
|
assert "- input/file1" in output
|
|
assert "- input/file2" in output
|
|
|
|
|
|
def test_create_pattern_file(archivers, request):
|
|
"""test file patterns during create"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "otherfile", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file_important", size=1024 * 80)
|
|
output = cmd(
|
|
archiver,
|
|
"create",
|
|
"-v",
|
|
"--list",
|
|
"--pattern=-input/otherfile",
|
|
"--patterns-from=" + archiver.patterns_file_path,
|
|
"test",
|
|
"input",
|
|
)
|
|
assert "A input/file_important" in output
|
|
assert "- input/file1" in output
|
|
assert "- input/file2" in output
|
|
assert "- input/otherfile" in output
|
|
|
|
|
|
def test_create_pattern_exclude_folder_but_recurse(archivers, request):
|
|
"""test when patterns exclude a parent folder, but include a child"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
patterns_file_path2 = os.path.join(archiver.tmpdir, "patterns2")
|
|
with open(patterns_file_path2, "wb") as fd:
|
|
fd.write(b"+ input/x/b\n- input/x*\n")
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "x/a/foo_a", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "x/b/foo_b", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "y/foo_y", size=1024 * 80)
|
|
output = cmd(archiver, "create", "-v", "--list", "--patterns-from=" + patterns_file_path2, "test", "input")
|
|
assert "- input/x/a/foo_a" in output
|
|
assert "A input/x/b/foo_b" in output
|
|
assert "A input/y/foo_y" in output
|
|
|
|
|
|
def test_create_pattern_exclude_folder_no_recurse(archivers, request):
|
|
"""test when patterns exclude a parent folder, but include a child"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
patterns_file_path2 = os.path.join(archiver.tmpdir, "patterns2")
|
|
with open(patterns_file_path2, "wb") as fd:
|
|
fd.write(b"+ input/x/b\n! input/x*\n")
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "x/a/foo_a", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "x/b/foo_b", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "y/foo_y", size=1024 * 80)
|
|
output = cmd(archiver, "create", "-v", "--list", "--patterns-from=" + patterns_file_path2, "test", "input")
|
|
assert "input/x/a/foo_a" not in output
|
|
assert "input/x/a" not in output
|
|
assert "A input/y/foo_y" in output
|
|
|
|
|
|
def test_create_pattern_intermediate_folders_first(archivers, request):
|
|
"""test that intermediate folders appear first when patterns exclude a parent folder but include a child"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
patterns_file_path2 = os.path.join(archiver.tmpdir, "patterns2")
|
|
with open(patterns_file_path2, "wb") as fd:
|
|
fd.write(b"+ input/x/a\n+ input/x/b\n- input/x*\n")
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "x/a/foo_a", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "x/b/foo_b", size=1024 * 80)
|
|
with changedir("input"):
|
|
cmd(archiver, "create", "--patterns-from=" + patterns_file_path2, "test", ".")
|
|
# list the archive and verify that the "intermediate" folders appear before
|
|
# their contents
|
|
out = cmd(archiver, "list", "test", "--format", "{type} {path}{NL}")
|
|
out_list = out.splitlines()
|
|
assert "d x/a" in out_list
|
|
assert "d x/b" in out_list
|
|
assert out_list.index("d x/a") < out_list.index("- x/a/foo_a")
|
|
assert out_list.index("d x/b") < out_list.index("- x/b/foo_b")
|
|
|
|
|
|
def test_create_no_cache_sync(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_test_files(archiver.input_path)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "rdelete", "--cache-only")
|
|
create_json = json.loads(
|
|
cmd(archiver, "create", "--no-cache-sync", "--json", "--error", "test", "input")
|
|
) # ignore experimental warning
|
|
info_json = json.loads(cmd(archiver, "info", "-a", "test", "--json"))
|
|
create_stats = create_json["cache"]["stats"]
|
|
info_stats = info_json["cache"]["stats"]
|
|
assert create_stats == info_stats
|
|
cmd(archiver, "rdelete", "--cache-only")
|
|
cmd(archiver, "create", "--no-cache-sync", "test2", "input")
|
|
cmd(archiver, "rinfo")
|
|
cmd(archiver, "check")
|
|
|
|
|
|
def test_create_archivename_with_placeholder(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_test_files(archiver.input_path)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
ts = "1999-12-31T23:59:59"
|
|
name_given = "test-{now}" # placeholder in archive name gets replaced by borg
|
|
name_expected = f"test-{ts}" # placeholder in f-string gets replaced by python
|
|
cmd(archiver, "create", f"--timestamp={ts}", name_given, "input")
|
|
list_output = cmd(archiver, "rlist", "--short")
|
|
assert name_expected in list_output
|
|
|
|
|
|
def test_exclude_caches(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
_create_test_caches(archiver)
|
|
cmd(archiver, "create", "test", "input", "--exclude-caches")
|
|
_assert_test_caches(archiver)
|
|
|
|
|
|
def test_exclude_tagged(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
_create_test_tagged(archiver)
|
|
cmd(archiver, "create", "test", "input", "--exclude-if-present", ".NOBACKUP", "--exclude-if-present", "00-NOBACKUP")
|
|
_assert_test_tagged(archiver)
|
|
|
|
|
|
def test_exclude_keep_tagged(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
_create_test_keep_tagged(archiver)
|
|
cmd(
|
|
archiver,
|
|
"create",
|
|
"test",
|
|
"input",
|
|
"--exclude-if-present",
|
|
".NOBACKUP1",
|
|
"--exclude-if-present",
|
|
".NOBACKUP2",
|
|
"--exclude-caches",
|
|
"--keep-exclude-tags",
|
|
)
|
|
_assert_test_keep_tagged(archiver)
|
|
|
|
|
|
def test_path_sanitation(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "dir1/dir2/file", size=1024 * 80)
|
|
with changedir("input/dir1/dir2"):
|
|
cmd(archiver, "create", "test", "../../../input/dir1/../dir1/dir2/..")
|
|
output = cmd(archiver, "list", "test")
|
|
assert ".." not in output
|
|
assert " input/dir1/dir2/file" in output
|
|
|
|
|
|
def test_exclude_sanitation(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
with changedir("input"):
|
|
cmd(archiver, "create", "test1", ".", "--exclude=file1")
|
|
with changedir("output"):
|
|
cmd(archiver, "extract", "test1")
|
|
assert sorted(os.listdir("output")) == ["file2"]
|
|
with changedir("input"):
|
|
cmd(archiver, "create", "test2", ".", "--exclude=./file1")
|
|
with changedir("output"):
|
|
cmd(archiver, "extract", "test2")
|
|
assert sorted(os.listdir("output")) == ["file2"]
|
|
cmd(archiver, "create", "test3", "input", "--exclude=input/./file1")
|
|
with changedir("output"):
|
|
cmd(archiver, "extract", "test3")
|
|
assert sorted(os.listdir("output/input")) == ["file2"]
|
|
|
|
|
|
def test_repeated_files(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "test", "input", "input")
|
|
|
|
|
|
@pytest.mark.skipif("BORG_TESTS_IGNORE_MODES" in os.environ, reason="modes unreliable")
|
|
@pytest.mark.skipif(is_win32, reason="modes unavailable on Windows")
|
|
def test_umask(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "test", "input")
|
|
mode = os.stat(archiver.repository_path).st_mode
|
|
assert stat.S_IMODE(mode) == 0o700
|
|
|
|
|
|
def test_create_dry_run(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "--dry-run", "test", "input")
|
|
# Make sure no archive has been created
|
|
with Repository(archiver.repository_path) as repository:
|
|
manifest = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
|
|
assert len(manifest.archives) == 0
|
|
|
|
|
|
def test_progress_on(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "test4", "input", "--progress")
|
|
assert "\r" in output
|
|
|
|
|
|
def test_progress_off(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "test5", "input")
|
|
assert "\r" not in output
|
|
|
|
|
|
def test_file_status(archivers, request):
|
|
"""test that various file status show expected results
|
|
clearly incomplete: only tests for the weird "unchanged" status for now"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
time.sleep(1) # file2 must have newer timestamps than file1
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "--list", "test", "input")
|
|
assert "A input/file1" in output
|
|
assert "A input/file2" in output
|
|
# should find first file as unmodified
|
|
output = cmd(archiver, "create", "--list", "test2", "input")
|
|
assert "U input/file1" in output
|
|
# although surprising, this is expected. For why, see:
|
|
# https://borgbackup.readthedocs.org/en/latest/faq.html#i-am-seeing-a-added-status-for-a-unchanged-file
|
|
assert "A input/file2" in output
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
is_win32, reason="ctime attribute is file creation time on Windows"
|
|
) # see https://docs.python.org/3/library/os.html#os.stat_result.st_ctime
|
|
def test_file_status_cs_cache_mode(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
"""test that a changed file with faked "previous" mtime still gets backed up in ctime,size cache_mode"""
|
|
create_regular_file(archiver.input_path, "file1", contents=b"123")
|
|
time.sleep(1) # file2 must have newer timestamps than file1
|
|
create_regular_file(archiver.input_path, "file2", size=10)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "test1", "input", "--list", "--files-cache=ctime,size")
|
|
# modify file1, but cheat with the mtime (and atime) and also keep same size:
|
|
st = os.stat("input/file1")
|
|
create_regular_file(archiver.input_path, "file1", contents=b"321")
|
|
os.utime("input/file1", ns=(st.st_atime_ns, st.st_mtime_ns))
|
|
# this mode uses ctime for change detection, so it should find file1 as modified
|
|
output = cmd(archiver, "create", "test2", "input", "--list", "--files-cache=ctime,size")
|
|
assert "M input/file1" in output
|
|
|
|
|
|
def test_file_status_ms_cache_mode(archivers, request):
|
|
"""test that a chmod'ed file with no content changes does not get chunked again in mtime,size cache_mode"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=10)
|
|
time.sleep(1) # file2 must have newer timestamps than file1
|
|
create_regular_file(archiver.input_path, "file2", size=10)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "--list", "--files-cache=mtime,size", "test1", "input")
|
|
# change mode of file1, no content change:
|
|
st = os.stat("input/file1")
|
|
os.chmod("input/file1", st.st_mode ^ stat.S_IRWXO) # this triggers a ctime change, but mtime is unchanged
|
|
# this mode uses mtime for change detection, so it should find file1 as unmodified
|
|
output = cmd(archiver, "create", "--list", "--files-cache=mtime,size", "test2", "input")
|
|
assert "U input/file1" in output
|
|
|
|
|
|
def test_file_status_rc_cache_mode(archivers, request):
|
|
"""test that files get rechunked unconditionally in rechunk,ctime cache mode"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=10)
|
|
time.sleep(1) # file2 must have newer timestamps than file1
|
|
create_regular_file(archiver.input_path, "file2", size=10)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "--list", "--files-cache=rechunk,ctime", "test1", "input")
|
|
# no changes here, but this mode rechunks unconditionally
|
|
output = cmd(archiver, "create", "--list", "--files-cache=rechunk,ctime", "test2", "input")
|
|
assert "A input/file1" in output
|
|
|
|
|
|
def test_file_status_excluded(archivers, request):
|
|
"""test that excluded paths are listed"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
time.sleep(1) # file2 must have newer timestamps than file1
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
if has_lchflags:
|
|
create_regular_file(archiver.input_path, "file3", size=1024 * 80)
|
|
platform.set_flags(os.path.join(archiver.input_path, "file3"), stat.UF_NODUMP)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
output = cmd(archiver, "create", "--list", "--exclude-nodump", "test", "input")
|
|
assert "A input/file1" in output
|
|
assert "A input/file2" in output
|
|
if has_lchflags:
|
|
assert "- input/file3" in output
|
|
# should find second file as excluded
|
|
output = cmd(archiver, "create", "test1", "input", "--list", "--exclude-nodump", "--exclude", "*/file2")
|
|
assert "U input/file1" in output
|
|
assert "- input/file2" in output
|
|
if has_lchflags:
|
|
assert "- input/file3" in output
|
|
|
|
|
|
def test_file_status_counters(archivers, request):
|
|
"""Test file status counters in the stats of `borg create --stats`"""
|
|
archiver = request.getfixturevalue(archivers)
|
|
|
|
def to_dict(borg_create_output):
|
|
borg_create_output = borg_create_output.strip().splitlines()
|
|
borg_create_output = [line.split(":", 1) for line in borg_create_output]
|
|
borg_create_output = {
|
|
key: int(value)
|
|
for key, value in borg_create_output
|
|
if key in ("Added files", "Unchanged files", "Modified files")
|
|
}
|
|
return borg_create_output
|
|
|
|
# Test case set up: create a repository
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
# Archive an empty dir
|
|
result = cmd(archiver, "create", "--stats", "test_archive", archiver.input_path)
|
|
result = to_dict(result)
|
|
assert result["Added files"] == 0
|
|
assert result["Unchanged files"] == 0
|
|
assert result["Modified files"] == 0
|
|
# Archive a dir with two added files
|
|
create_regular_file(archiver.input_path, "testfile1", contents=b"test1")
|
|
time.sleep(1.0 if is_darwin else 0.01) # testfile2 must have newer timestamps than testfile1
|
|
create_regular_file(archiver.input_path, "testfile2", contents=b"test2")
|
|
result = cmd(archiver, "create", "--stats", "test_archive2", archiver.input_path)
|
|
result = to_dict(result)
|
|
assert result["Added files"] == 2
|
|
assert result["Unchanged files"] == 0
|
|
assert result["Modified files"] == 0
|
|
# Archive a dir with 1 unmodified file and 1 modified
|
|
create_regular_file(archiver.input_path, "testfile1", contents=b"new data")
|
|
result = cmd(archiver, "create", "--stats", "test_archive3", archiver.input_path)
|
|
result = to_dict(result)
|
|
# Should process testfile2 as added because of
|
|
# https://borgbackup.readthedocs.io/en/stable/faq.html#i-am-seeing-a-added-status-for-an-unchanged-file
|
|
assert result["Added files"] == 1
|
|
assert result["Unchanged files"] == 0
|
|
assert result["Modified files"] == 1
|
|
|
|
|
|
def test_create_json(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_info = json.loads(cmd(archiver, "create", "--json", "test", "input"))
|
|
# The usual keys
|
|
assert "encryption" in create_info
|
|
assert "repository" in create_info
|
|
assert "cache" in create_info
|
|
assert "last_modified" in create_info["repository"]
|
|
|
|
archive = create_info["archive"]
|
|
assert archive["name"] == "test"
|
|
assert isinstance(archive["command_line"], str)
|
|
assert isinstance(archive["duration"], float)
|
|
assert len(archive["id"]) == 64
|
|
assert "stats" in archive
|
|
|
|
|
|
def test_create_topical(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
|
time.sleep(1) # file2 must have newer timestamps than file1
|
|
create_regular_file(archiver.input_path, "file2", size=1024 * 80)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
# no listing by default
|
|
output = cmd(archiver, "create", "test", "input")
|
|
assert "file1" not in output
|
|
# shouldn't be listed even if unchanged
|
|
output = cmd(archiver, "create", "test0", "input")
|
|
assert "file1" not in output
|
|
# should list the file as unchanged
|
|
output = cmd(archiver, "create", "test1", "input", "--list", "--filter=U")
|
|
assert "file1" in output
|
|
# should *not* list the file as changed
|
|
output = cmd(archiver, "create", "test2", "input", "--list", "--filter=AM")
|
|
assert "file1" not in output
|
|
# change the file
|
|
create_regular_file(archiver.input_path, "file1", size=1024 * 100)
|
|
# should list the file as changed
|
|
output = cmd(archiver, "create", "test3", "input", "--list", "--filter=AM")
|
|
assert "file1" in output
|
|
|
|
|
|
@pytest.mark.skipif(not are_fifos_supported() or is_cygwin, reason="FIFOs not supported, hangs on cygwin")
|
|
def test_create_read_special_symlink(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
from threading import Thread
|
|
|
|
def fifo_feeder(fifo_fn, data):
|
|
fd = os.open(fifo_fn, os.O_WRONLY)
|
|
try:
|
|
os.write(fd, data)
|
|
finally:
|
|
os.close(fd)
|
|
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
data = b"foobar" * 1000
|
|
|
|
fifo_fn = os.path.join(archiver.input_path, "fifo")
|
|
link_fn = os.path.join(archiver.input_path, "link_fifo")
|
|
os.mkfifo(fifo_fn)
|
|
os.symlink(fifo_fn, link_fn)
|
|
|
|
t = Thread(target=fifo_feeder, args=(fifo_fn, data))
|
|
t.start()
|
|
try:
|
|
cmd(archiver, "create", "--read-special", "test", "input/link_fifo")
|
|
finally:
|
|
# In case `borg create` failed to open FIFO, read all data to avoid join() hanging.
|
|
fd = os.open(fifo_fn, os.O_RDONLY | os.O_NONBLOCK)
|
|
try:
|
|
os.read(fd, len(data))
|
|
except OSError:
|
|
# fails on FreeBSD 13 with BlockingIOError
|
|
pass
|
|
finally:
|
|
os.close(fd)
|
|
t.join()
|
|
with changedir("output"):
|
|
cmd(archiver, "extract", "test")
|
|
fifo_fn = "input/link_fifo"
|
|
with open(fifo_fn, "rb") as f:
|
|
extracted_data = f.read()
|
|
assert extracted_data == data
|
|
|
|
|
|
@pytest.mark.skipif(not are_symlinks_supported(), reason="symlinks not supported")
|
|
def test_create_read_special_broken_symlink(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
os.symlink("somewhere does not exist", os.path.join(archiver.input_path, "link"))
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
cmd(archiver, "create", "--read-special", "test", "input")
|
|
output = cmd(archiver, "list", "test")
|
|
assert "input/link -> somewhere does not exist" in output
|
|
|
|
|
|
def test_log_json(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_test_files(archiver.input_path)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
log = cmd(archiver, "create", "test", "input", "--log-json", "--list", "--debug")
|
|
messages = {} # type -> message, one of each kind
|
|
for line in log.splitlines():
|
|
msg = json.loads(line)
|
|
messages[msg["type"]] = msg
|
|
|
|
file_status = messages["file_status"]
|
|
assert "status" in file_status
|
|
assert file_status["path"].startswith("input")
|
|
|
|
log_message = messages["log_message"]
|
|
assert isinstance(log_message["time"], float)
|
|
assert log_message["levelname"] == "DEBUG" # there should only be DEBUG messages
|
|
assert isinstance(log_message["message"], str)
|
|
|
|
|
|
def test_common_options(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
create_test_files(archiver.input_path)
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
log = cmd(archiver, "--debug", "create", "test", "input")
|
|
assert "security: read previous location" in log
|
|
|
|
|
|
def test_hashing_time(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
|
|
def extract_hashing_time(borg_create_output):
|
|
borg_create_output = borg_create_output.strip().splitlines()
|
|
borg_create_output = [line.split(":", 1) for line in borg_create_output]
|
|
hashing_time = [line for line in borg_create_output if line[0] == "Time spent in hashing"].pop()
|
|
hashing_time = hashing_time[1]
|
|
hashing_time = float(hashing_time.removesuffix(" seconds"))
|
|
return hashing_time
|
|
|
|
# Test case set up: create a repository and a file
|
|
cmd(archiver, "rcreate", "--encryption=none")
|
|
create_regular_file(archiver.input_path, "testfile", contents=randbytes(50000000))
|
|
# Archive
|
|
result = cmd(archiver, "create", "--stats", "test_archive", archiver.input_path)
|
|
hashing_time = extract_hashing_time(result)
|
|
assert hashing_time > 0.0
|
|
|
|
|
|
def test_chunking_time(archivers, request):
|
|
archiver = request.getfixturevalue(archivers)
|
|
|
|
def extract_chunking_time(borg_create_output):
|
|
borg_create_output = borg_create_output.strip().splitlines()
|
|
borg_create_output = [line.split(":", 1) for line in borg_create_output]
|
|
chunking_time = [line for line in borg_create_output if line[0] == "Time spent in chunking"].pop()
|
|
chunking_time = chunking_time[1]
|
|
chunking_time = float(chunking_time.removesuffix(" seconds"))
|
|
return chunking_time
|
|
|
|
# Test case set up: create a repository and a file
|
|
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
|
create_regular_file(archiver.input_path, "testfile", contents=randbytes(50000000))
|
|
# Archive
|
|
result = cmd(archiver, "create", "--stats", "test_archive", archiver.input_path)
|
|
chunking_time = extract_chunking_time(result)
|
|
assert chunking_time > 0.0
|