mirror of https://github.com/borgbackup/borg.git
diff_cmd converted
This commit is contained in:
parent
99bf56cfcd
commit
dfaea063a5
|
@ -2,321 +2,303 @@ import json
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
import time
|
import time
|
||||||
import unittest
|
|
||||||
|
|
||||||
from ...constants import * # NOQA
|
from ...constants import * # NOQA
|
||||||
from .. import are_symlinks_supported, are_hardlinks_supported
|
from .. import are_symlinks_supported, are_hardlinks_supported
|
||||||
from ..platform import is_win32, is_darwin
|
from ..platform import is_win32, is_darwin
|
||||||
from . import ArchiverTestCaseBase, RemoteArchiverTestCaseBase, ArchiverTestCaseBinaryBase, RK_ENCRYPTION, BORG_EXES
|
from . import cmd, create_regular_file, RK_ENCRYPTION, assert_line_exists
|
||||||
|
|
||||||
|
|
||||||
class ArchiverTestCase(ArchiverTestCaseBase):
|
def pytest_generate_tests(metafunc):
|
||||||
def test_basic_functionality(self):
|
# Generates tests that run on local and remote repos, as well as with a binary base.
|
||||||
# Setup files for the first snapshot
|
if "archivers" in metafunc.fixturenames:
|
||||||
self.create_regular_file("empty", size=0)
|
metafunc.parametrize("archivers", ["archiver", "remote_archiver", "binary_archiver"])
|
||||||
self.create_regular_file("file_unchanged", size=128)
|
|
||||||
self.create_regular_file("file_removed", size=256)
|
|
||||||
self.create_regular_file("file_removed2", size=512)
|
def test_basic_functionality(archivers, request):
|
||||||
self.create_regular_file("file_replaced", size=1024)
|
archiver = request.getfixturevalue(archivers)
|
||||||
os.mkdir("input/dir_replaced_with_file")
|
repo_location, input_path = archiver.repository_location, archiver.input_path
|
||||||
os.chmod("input/dir_replaced_with_file", stat.S_IFDIR | 0o755)
|
# Setup files for the first snapshot
|
||||||
os.mkdir("input/dir_removed")
|
create_regular_file(input_path, "empty", size=0)
|
||||||
|
create_regular_file(input_path, "file_unchanged", size=128)
|
||||||
|
create_regular_file(input_path, "file_removed", size=256)
|
||||||
|
create_regular_file(input_path, "file_removed2", size=512)
|
||||||
|
create_regular_file(input_path, "file_replaced", size=1024)
|
||||||
|
os.mkdir("input/dir_replaced_with_file")
|
||||||
|
os.chmod("input/dir_replaced_with_file", stat.S_IFDIR | 0o755)
|
||||||
|
os.mkdir("input/dir_removed")
|
||||||
|
if are_symlinks_supported():
|
||||||
|
os.mkdir("input/dir_replaced_with_link")
|
||||||
|
os.symlink("input/dir_replaced_with_file", "input/link_changed")
|
||||||
|
os.symlink("input/file_unchanged", "input/link_removed")
|
||||||
|
os.symlink("input/file_removed2", "input/link_target_removed")
|
||||||
|
os.symlink("input/empty", "input/link_target_contents_changed")
|
||||||
|
os.symlink("input/empty", "input/link_replaced_by_file")
|
||||||
|
if are_hardlinks_supported():
|
||||||
|
os.link("input/file_replaced", "input/hardlink_target_replaced")
|
||||||
|
os.link("input/empty", "input/hardlink_contents_changed")
|
||||||
|
os.link("input/file_removed", "input/hardlink_removed")
|
||||||
|
os.link("input/file_removed2", "input/hardlink_target_removed")
|
||||||
|
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "rcreate", RK_ENCRYPTION)
|
||||||
|
|
||||||
|
# Create the first snapshot
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "test0", "input")
|
||||||
|
|
||||||
|
# Setup files for the second snapshot
|
||||||
|
create_regular_file(input_path, "file_added", size=2048)
|
||||||
|
create_regular_file(input_path, "file_empty_added", size=0)
|
||||||
|
os.unlink("input/file_replaced")
|
||||||
|
create_regular_file(input_path, "file_replaced", contents=b"0" * 4096)
|
||||||
|
os.unlink("input/file_removed")
|
||||||
|
os.unlink("input/file_removed2")
|
||||||
|
os.rmdir("input/dir_replaced_with_file")
|
||||||
|
create_regular_file(input_path, "dir_replaced_with_file", size=8192)
|
||||||
|
os.chmod("input/dir_replaced_with_file", stat.S_IFREG | 0o755)
|
||||||
|
os.mkdir("input/dir_added")
|
||||||
|
os.rmdir("input/dir_removed")
|
||||||
|
if are_symlinks_supported():
|
||||||
|
os.rmdir("input/dir_replaced_with_link")
|
||||||
|
os.symlink("input/dir_added", "input/dir_replaced_with_link")
|
||||||
|
os.unlink("input/link_changed")
|
||||||
|
os.symlink("input/dir_added", "input/link_changed")
|
||||||
|
os.symlink("input/dir_added", "input/link_added")
|
||||||
|
os.unlink("input/link_replaced_by_file")
|
||||||
|
create_regular_file(input_path, "link_replaced_by_file", size=16384)
|
||||||
|
os.unlink("input/link_removed")
|
||||||
|
if are_hardlinks_supported():
|
||||||
|
os.unlink("input/hardlink_removed")
|
||||||
|
os.link("input/file_added", "input/hardlink_added")
|
||||||
|
|
||||||
|
with open("input/empty", "ab") as fd:
|
||||||
|
fd.write(b"appended_data")
|
||||||
|
|
||||||
|
# Create the second snapshot
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "test1a", "input")
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "test1b", "input", "--chunker-params", "16,18,17,4095")
|
||||||
|
|
||||||
|
def do_asserts(output, can_compare_ids, content_only=False):
|
||||||
|
lines: list = output.splitlines()
|
||||||
|
assert "file_replaced" in output # added to debug #3494
|
||||||
|
change = "modified.*B" if can_compare_ids else r"modified: \(can't get size\)"
|
||||||
|
assert_line_exists(lines, f"{change}.*input/file_replaced")
|
||||||
|
# File unchanged
|
||||||
|
assert "input/file_unchanged" not in output
|
||||||
|
|
||||||
|
# Directory replaced with a regular file
|
||||||
|
if "BORG_TESTS_IGNORE_MODES" not in os.environ and not is_win32 and not content_only:
|
||||||
|
assert_line_exists(lines, "[drwxr-xr-x -> -rwxr-xr-x].*input/dir_replaced_with_file")
|
||||||
|
|
||||||
|
# Basic directory cases
|
||||||
|
assert "added directory input/dir_added" in output
|
||||||
|
assert "removed directory input/dir_removed" in output
|
||||||
|
|
||||||
if are_symlinks_supported():
|
if are_symlinks_supported():
|
||||||
os.mkdir("input/dir_replaced_with_link")
|
# Basic symlink cases
|
||||||
os.symlink("input/dir_replaced_with_file", "input/link_changed")
|
assert_line_exists(lines, "changed link.*input/link_changed")
|
||||||
os.symlink("input/file_unchanged", "input/link_removed")
|
assert_line_exists(lines, "added link.*input/link_added")
|
||||||
os.symlink("input/file_removed2", "input/link_target_removed")
|
assert_line_exists(lines, "removed link.*input/link_removed")
|
||||||
os.symlink("input/empty", "input/link_target_contents_changed")
|
|
||||||
os.symlink("input/empty", "input/link_replaced_by_file")
|
# Symlink replacing or being replaced
|
||||||
|
if not content_only:
|
||||||
|
assert "input/dir_replaced_with_link" in output
|
||||||
|
assert "input/link_replaced_by_file" in output
|
||||||
|
|
||||||
|
# Symlink target removed. Should not affect the symlink at all.
|
||||||
|
assert "input/link_target_removed" not in output
|
||||||
|
|
||||||
|
# The inode has two links and the file contents changed. Borg
|
||||||
|
# should notice the changes in both links. However, the symlink
|
||||||
|
# pointing to the file is not changed.
|
||||||
|
change = "modified.*0 B" if can_compare_ids else r"modified: \(can't get size\)"
|
||||||
|
assert_line_exists(lines, f"{change}.*input/empty")
|
||||||
if are_hardlinks_supported():
|
if are_hardlinks_supported():
|
||||||
os.link("input/file_replaced", "input/hardlink_target_replaced")
|
assert_line_exists(lines, f"{change}.*input/hardlink_contents_changed")
|
||||||
os.link("input/empty", "input/hardlink_contents_changed")
|
|
||||||
os.link("input/file_removed", "input/hardlink_removed")
|
|
||||||
os.link("input/file_removed2", "input/hardlink_target_removed")
|
|
||||||
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "rcreate", RK_ENCRYPTION)
|
|
||||||
|
|
||||||
# Create the first snapshot
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "test0", "input")
|
|
||||||
|
|
||||||
# Setup files for the second snapshot
|
|
||||||
self.create_regular_file("file_added", size=2048)
|
|
||||||
self.create_regular_file("file_empty_added", size=0)
|
|
||||||
os.unlink("input/file_replaced")
|
|
||||||
self.create_regular_file("file_replaced", contents=b"0" * 4096)
|
|
||||||
os.unlink("input/file_removed")
|
|
||||||
os.unlink("input/file_removed2")
|
|
||||||
os.rmdir("input/dir_replaced_with_file")
|
|
||||||
self.create_regular_file("dir_replaced_with_file", size=8192)
|
|
||||||
os.chmod("input/dir_replaced_with_file", stat.S_IFREG | 0o755)
|
|
||||||
os.mkdir("input/dir_added")
|
|
||||||
os.rmdir("input/dir_removed")
|
|
||||||
if are_symlinks_supported():
|
if are_symlinks_supported():
|
||||||
os.rmdir("input/dir_replaced_with_link")
|
assert "input/link_target_contents_changed" not in output
|
||||||
os.symlink("input/dir_added", "input/dir_replaced_with_link")
|
|
||||||
os.unlink("input/link_changed")
|
# Added a new file and a hard link to it. Both links to the same
|
||||||
os.symlink("input/dir_added", "input/link_changed")
|
# inode should appear as separate files.
|
||||||
os.symlink("input/dir_added", "input/link_added")
|
assert "added: 2.05 kB input/file_added" in output
|
||||||
os.unlink("input/link_replaced_by_file")
|
|
||||||
self.create_regular_file("link_replaced_by_file", size=16384)
|
|
||||||
os.unlink("input/link_removed")
|
|
||||||
if are_hardlinks_supported():
|
if are_hardlinks_supported():
|
||||||
os.unlink("input/hardlink_removed")
|
assert "added: 2.05 kB input/hardlink_added" in output
|
||||||
os.link("input/file_added", "input/hardlink_added")
|
|
||||||
|
|
||||||
with open("input/empty", "ab") as fd:
|
# check if a diff between nonexistent and empty new file is found
|
||||||
fd.write(b"appended_data")
|
assert "added: 0 B input/file_empty_added" in output
|
||||||
|
|
||||||
# Create the second snapshot
|
# The inode has two links and both of them are deleted. They should
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "test1a", "input")
|
# appear as two deleted files.
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "test1b", "input", "--chunker-params", "16,18,17,4095")
|
assert "removed: 256 B input/file_removed" in output
|
||||||
|
if are_hardlinks_supported():
|
||||||
|
assert "removed: 256 B input/hardlink_removed" in output
|
||||||
|
|
||||||
def do_asserts(output, can_compare_ids, content_only=False):
|
if are_hardlinks_supported() and content_only:
|
||||||
lines: list = output.splitlines()
|
# Another link (marked previously as the source in borg) to the
|
||||||
assert "file_replaced" in output # added to debug #3494
|
# same inode was removed. This should only change the ctime since removing
|
||||||
change = "modified.*B" if can_compare_ids else r"modified: \(can't get size\)"
|
# the link would result in the decrementation of the inode's hard-link count.
|
||||||
self.assert_line_exists(lines, f"{change}.*input/file_replaced")
|
assert "input/hardlink_target_removed" not in output
|
||||||
# File unchanged
|
|
||||||
assert "input/file_unchanged" not in output
|
|
||||||
|
|
||||||
# Directory replaced with a regular file
|
# Another link (marked previously as the source in borg) to the
|
||||||
if "BORG_TESTS_IGNORE_MODES" not in os.environ and not is_win32 and not content_only:
|
# same inode was replaced with a new regular file. This should only change
|
||||||
self.assert_line_exists(lines, "[drwxr-xr-x -> -rwxr-xr-x].*input/dir_replaced_with_file")
|
# its ctime. This should not be reflected in the output if content-only is set
|
||||||
|
assert "input/hardlink_target_replaced" not in output
|
||||||
|
|
||||||
# Basic directory cases
|
def do_json_asserts(output, can_compare_ids, content_only=False):
|
||||||
assert "added directory input/dir_added" in output
|
def get_changes(filename, data):
|
||||||
assert "removed directory input/dir_removed" in output
|
chgsets = [j["changes"] for j in data if j["path"] == filename]
|
||||||
|
assert len(chgsets) < 2
|
||||||
|
# return a flattened list of changes for given filename
|
||||||
|
return sum(chgsets, [])
|
||||||
|
|
||||||
if are_symlinks_supported():
|
# convert output to list of dicts
|
||||||
# Basic symlink cases
|
joutput = [json.loads(line) for line in output.split("\n") if line]
|
||||||
self.assert_line_exists(lines, "changed link.*input/link_changed")
|
|
||||||
self.assert_line_exists(lines, "added link.*input/link_added")
|
|
||||||
self.assert_line_exists(lines, "removed link.*input/link_removed")
|
|
||||||
|
|
||||||
# Symlink replacing or being replaced
|
# File contents changed (deleted and replaced with a new file)
|
||||||
if not content_only:
|
expected = {"type": "modified", "added": 4096, "removed": 1024} if can_compare_ids else {"type": "modified"}
|
||||||
assert "input/dir_replaced_with_link" in output
|
assert expected in get_changes("input/file_replaced", joutput)
|
||||||
assert "input/link_replaced_by_file" in output
|
|
||||||
|
|
||||||
# Symlink target removed. Should not affect the symlink at all.
|
# File unchanged
|
||||||
assert "input/link_target_removed" not in output
|
assert not any(get_changes("input/file_unchanged", joutput))
|
||||||
|
|
||||||
# The inode has two links and the file contents changed. Borg
|
# Directory replaced with a regular file
|
||||||
# should notice the changes in both links. However, the symlink
|
if "BORG_TESTS_IGNORE_MODES" not in os.environ and not is_win32 and not content_only:
|
||||||
# pointing to the file is not changed.
|
assert {"type": "changed mode", "item1": "drwxr-xr-x", "item2": "-rwxr-xr-x"} in get_changes(
|
||||||
change = "modified.*0 B" if can_compare_ids else r"modified: \(can't get size\)"
|
"input/dir_replaced_with_file", joutput
|
||||||
self.assert_line_exists(lines, f"{change}.*input/empty")
|
)
|
||||||
if are_hardlinks_supported():
|
|
||||||
self.assert_line_exists(lines, f"{change}.*input/hardlink_contents_changed")
|
|
||||||
if are_symlinks_supported():
|
|
||||||
assert "input/link_target_contents_changed" not in output
|
|
||||||
|
|
||||||
# Added a new file and a hard link to it. Both links to the same
|
# Basic directory cases
|
||||||
# inode should appear as separate files.
|
assert {"type": "added directory"} in get_changes("input/dir_added", joutput)
|
||||||
assert "added: 2.05 kB input/file_added" in output
|
assert {"type": "removed directory"} in get_changes("input/dir_removed", joutput)
|
||||||
if are_hardlinks_supported():
|
|
||||||
assert "added: 2.05 kB input/hardlink_added" in output
|
|
||||||
|
|
||||||
# check if a diff between nonexistent and empty new file is found
|
if are_symlinks_supported():
|
||||||
assert "added: 0 B input/file_empty_added" in output
|
# Basic symlink cases
|
||||||
|
assert {"type": "changed link"} in get_changes("input/link_changed", joutput)
|
||||||
|
assert {"type": "added link"} in get_changes("input/link_added", joutput)
|
||||||
|
assert {"type": "removed link"} in get_changes("input/link_removed", joutput)
|
||||||
|
|
||||||
# The inode has two links and both of them are deleted. They should
|
# Symlink replacing or being replaced
|
||||||
# appear as two deleted files.
|
|
||||||
assert "removed: 256 B input/file_removed" in output
|
|
||||||
if are_hardlinks_supported():
|
|
||||||
assert "removed: 256 B input/hardlink_removed" in output
|
|
||||||
|
|
||||||
if are_hardlinks_supported() and content_only:
|
if not content_only:
|
||||||
# Another link (marked previously as the source in borg) to the
|
assert any(
|
||||||
# same inode was removed. This should only change the ctime since removing
|
chg["type"] == "changed mode" and chg["item1"].startswith("d") and chg["item2"].startswith("l")
|
||||||
# the link would result in the decrementation of the inode's hard-link count.
|
for chg in get_changes("input/dir_replaced_with_link", joutput)
|
||||||
assert "input/hardlink_target_removed" not in output
|
), get_changes("input/dir_replaced_with_link", joutput)
|
||||||
|
assert any(
|
||||||
|
chg["type"] == "changed mode" and chg["item1"].startswith("l") and chg["item2"].startswith("-")
|
||||||
|
for chg in get_changes("input/link_replaced_by_file", joutput)
|
||||||
|
), get_changes("input/link_replaced_by_file", joutput)
|
||||||
|
|
||||||
# Another link (marked previously as the source in borg) to the
|
# Symlink target removed. Should not affect the symlink at all.
|
||||||
# same inode was replaced with a new regular file. This should only change
|
assert not any(get_changes("input/link_target_removed", joutput))
|
||||||
# its ctime. This should not be reflected in the output if content-only is set
|
|
||||||
assert "input/hardlink_target_replaced" not in output
|
|
||||||
|
|
||||||
def do_json_asserts(output, can_compare_ids, content_only=False):
|
# The inode has two links and the file contents changed. Borg
|
||||||
def get_changes(filename, data):
|
# should notice the changes in both links. However, the symlink
|
||||||
chgsets = [j["changes"] for j in data if j["path"] == filename]
|
# pointing to the file is not changed.
|
||||||
assert len(chgsets) < 2
|
expected = {"type": "modified", "added": 13, "removed": 0} if can_compare_ids else {"type": "modified"}
|
||||||
# return a flattened list of changes for given filename
|
assert expected in get_changes("input/empty", joutput)
|
||||||
return sum(chgsets, [])
|
if are_hardlinks_supported():
|
||||||
|
assert expected in get_changes("input/hardlink_contents_changed", joutput)
|
||||||
|
if are_symlinks_supported():
|
||||||
|
assert not any(get_changes("input/link_target_contents_changed", joutput))
|
||||||
|
|
||||||
# convert output to list of dicts
|
# Added a new file and a hard link to it. Both links to the same
|
||||||
joutput = [json.loads(line) for line in output.split("\n") if line]
|
# inode should appear as separate files.
|
||||||
|
assert {"added": 2048, "removed": 0, "type": "added"} in get_changes("input/file_added", joutput)
|
||||||
|
if are_hardlinks_supported():
|
||||||
|
assert {"added": 2048, "removed": 0, "type": "added"} in get_changes("input/hardlink_added", joutput)
|
||||||
|
|
||||||
# File contents changed (deleted and replaced with a new file)
|
# check if a diff between nonexistent and empty new file is found
|
||||||
expected = {"type": "modified", "added": 4096, "removed": 1024} if can_compare_ids else {"type": "modified"}
|
assert {"added": 0, "removed": 0, "type": "added"} in get_changes("input/file_empty_added", joutput)
|
||||||
assert expected in get_changes("input/file_replaced", joutput)
|
|
||||||
|
|
||||||
# File unchanged
|
# The inode has two links and both of them are deleted. They should
|
||||||
assert not any(get_changes("input/file_unchanged", joutput))
|
# appear as two deleted files.
|
||||||
|
assert {"added": 0, "removed": 256, "type": "removed"} in get_changes("input/file_removed", joutput)
|
||||||
|
if are_hardlinks_supported():
|
||||||
|
assert {"added": 0, "removed": 256, "type": "removed"} in get_changes("input/hardlink_removed", joutput)
|
||||||
|
|
||||||
# Directory replaced with a regular file
|
if are_hardlinks_supported() and content_only:
|
||||||
if "BORG_TESTS_IGNORE_MODES" not in os.environ and not is_win32 and not content_only:
|
# Another link (marked previously as the source in borg) to the
|
||||||
assert {"type": "changed mode", "item1": "drwxr-xr-x", "item2": "-rwxr-xr-x"} in get_changes(
|
# same inode was removed. This should only change the ctime since removing
|
||||||
"input/dir_replaced_with_file", joutput
|
# the link would result in the decrementation of the inode's hard-link count.
|
||||||
)
|
assert not any(get_changes("input/hardlink_target_removed", joutput))
|
||||||
|
|
||||||
# Basic directory cases
|
# Another link (marked previously as the source in borg) to the
|
||||||
assert {"type": "added directory"} in get_changes("input/dir_added", joutput)
|
# same inode was replaced with a new regular file. This should only change
|
||||||
assert {"type": "removed directory"} in get_changes("input/dir_removed", joutput)
|
# its ctime. This should not be reflected in the output if content-only is set
|
||||||
|
assert not any(get_changes("input/hardlink_target_replaced", joutput))
|
||||||
|
|
||||||
if are_symlinks_supported():
|
output = cmd(archiver, f"--repo={repo_location}", "diff", "test0", "test1a")
|
||||||
# Basic symlink cases
|
do_asserts(output, True)
|
||||||
assert {"type": "changed link"} in get_changes("input/link_changed", joutput)
|
# We expect exit_code=1 due to the chunker params warning
|
||||||
assert {"type": "added link"} in get_changes("input/link_added", joutput)
|
output = cmd(archiver, f"--repo={repo_location}", "diff", "test0", "test1b", "--content-only", exit_code=1)
|
||||||
assert {"type": "removed link"} in get_changes("input/link_removed", joutput)
|
do_asserts(output, False, content_only=True)
|
||||||
|
|
||||||
# Symlink replacing or being replaced
|
output = cmd(archiver, f"--repo={repo_location}", "diff", "test0", "test1a", "--json-lines")
|
||||||
|
do_json_asserts(output, True)
|
||||||
|
|
||||||
if not content_only:
|
output = cmd(archiver, f"--repo={repo_location}", "diff", "test0", "test1a", "--json-lines", "--content-only")
|
||||||
assert any(
|
do_json_asserts(output, True, content_only=True)
|
||||||
chg["type"] == "changed mode" and chg["item1"].startswith("d") and chg["item2"].startswith("l")
|
|
||||||
for chg in get_changes("input/dir_replaced_with_link", joutput)
|
|
||||||
), get_changes("input/dir_replaced_with_link", joutput)
|
|
||||||
assert any(
|
|
||||||
chg["type"] == "changed mode" and chg["item1"].startswith("l") and chg["item2"].startswith("-")
|
|
||||||
for chg in get_changes("input/link_replaced_by_file", joutput)
|
|
||||||
), get_changes("input/link_replaced_by_file", joutput)
|
|
||||||
|
|
||||||
# Symlink target removed. Should not affect the symlink at all.
|
|
||||||
assert not any(get_changes("input/link_target_removed", joutput))
|
|
||||||
|
|
||||||
# The inode has two links and the file contents changed. Borg
|
|
||||||
# should notice the changes in both links. However, the symlink
|
|
||||||
# pointing to the file is not changed.
|
|
||||||
expected = {"type": "modified", "added": 13, "removed": 0} if can_compare_ids else {"type": "modified"}
|
|
||||||
assert expected in get_changes("input/empty", joutput)
|
|
||||||
if are_hardlinks_supported():
|
|
||||||
assert expected in get_changes("input/hardlink_contents_changed", joutput)
|
|
||||||
if are_symlinks_supported():
|
|
||||||
assert not any(get_changes("input/link_target_contents_changed", joutput))
|
|
||||||
|
|
||||||
# Added a new file and a hard link to it. Both links to the same
|
|
||||||
# inode should appear as separate files.
|
|
||||||
assert {"added": 2048, "removed": 0, "type": "added"} in get_changes("input/file_added", joutput)
|
|
||||||
if are_hardlinks_supported():
|
|
||||||
assert {"added": 2048, "removed": 0, "type": "added"} in get_changes("input/hardlink_added", joutput)
|
|
||||||
|
|
||||||
# check if a diff between nonexistent and empty new file is found
|
|
||||||
assert {"added": 0, "removed": 0, "type": "added"} in get_changes("input/file_empty_added", joutput)
|
|
||||||
|
|
||||||
# The inode has two links and both of them are deleted. They should
|
|
||||||
# appear as two deleted files.
|
|
||||||
assert {"added": 0, "removed": 256, "type": "removed"} in get_changes("input/file_removed", joutput)
|
|
||||||
if are_hardlinks_supported():
|
|
||||||
assert {"added": 0, "removed": 256, "type": "removed"} in get_changes("input/hardlink_removed", joutput)
|
|
||||||
|
|
||||||
if are_hardlinks_supported() and content_only:
|
|
||||||
# Another link (marked previously as the source in borg) to the
|
|
||||||
# same inode was removed. This should only change the ctime since removing
|
|
||||||
# the link would result in the decrementation of the inode's hard-link count.
|
|
||||||
assert not any(get_changes("input/hardlink_target_removed", joutput))
|
|
||||||
|
|
||||||
# Another link (marked previously as the source in borg) to the
|
|
||||||
# same inode was replaced with a new regular file. This should only change
|
|
||||||
# its ctime. This should not be reflected in the output if content-only is set
|
|
||||||
assert not any(get_changes("input/hardlink_target_replaced", joutput))
|
|
||||||
|
|
||||||
output = self.cmd(f"--repo={self.repository_location}", "diff", "test0", "test1a")
|
|
||||||
do_asserts(output, True)
|
|
||||||
# We expect exit_code=1 due to the chunker params warning
|
|
||||||
output = self.cmd(
|
|
||||||
f"--repo={self.repository_location}", "diff", "test0", "test1b", "--content-only", exit_code=1
|
|
||||||
)
|
|
||||||
do_asserts(output, False, content_only=True)
|
|
||||||
|
|
||||||
output = self.cmd(f"--repo={self.repository_location}", "diff", "test0", "test1a", "--json-lines")
|
|
||||||
do_json_asserts(output, True)
|
|
||||||
|
|
||||||
output = self.cmd(
|
|
||||||
f"--repo={self.repository_location}", "diff", "test0", "test1a", "--json-lines", "--content-only"
|
|
||||||
)
|
|
||||||
do_json_asserts(output, True, content_only=True)
|
|
||||||
|
|
||||||
def test_time_diffs(self):
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "rcreate", RK_ENCRYPTION)
|
|
||||||
self.create_regular_file("test_file", size=10)
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "archive1", "input")
|
|
||||||
time.sleep(0.1)
|
|
||||||
os.unlink("input/test_file")
|
|
||||||
if is_win32:
|
|
||||||
# Sleeping for 15s because Windows doesn't refresh ctime if file is deleted and recreated within 15 seconds.
|
|
||||||
time.sleep(15)
|
|
||||||
elif is_darwin:
|
|
||||||
time.sleep(1) # HFS has a 1s timestamp granularity
|
|
||||||
self.create_regular_file("test_file", size=15)
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "archive2", "input")
|
|
||||||
output = self.cmd(
|
|
||||||
f"--repo={self.repository_location}",
|
|
||||||
"diff",
|
|
||||||
"archive1",
|
|
||||||
"archive2",
|
|
||||||
"--format",
|
|
||||||
"'{mtime}{ctime} {path}{NL}'",
|
|
||||||
)
|
|
||||||
self.assert_in("mtime", output)
|
|
||||||
self.assert_in("ctime", output) # Should show up on windows as well since it is a new file.
|
|
||||||
if is_darwin:
|
|
||||||
time.sleep(1) # HFS has a 1s timestamp granularity
|
|
||||||
os.chmod("input/test_file", 0o777)
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "archive3", "input")
|
|
||||||
output = self.cmd(
|
|
||||||
f"--repo={self.repository_location}",
|
|
||||||
"diff",
|
|
||||||
"archive2",
|
|
||||||
"archive3",
|
|
||||||
"--format",
|
|
||||||
"'{mtime}{ctime} {path}{NL}'",
|
|
||||||
)
|
|
||||||
self.assert_not_in("mtime", output)
|
|
||||||
# Checking platform because ctime should not be shown on windows since it wasn't recreated.
|
|
||||||
if not is_win32:
|
|
||||||
self.assert_in("ctime", output)
|
|
||||||
else:
|
|
||||||
self.assert_not_in("ctime", output)
|
|
||||||
|
|
||||||
def test_sort_option(self):
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "rcreate", RK_ENCRYPTION)
|
|
||||||
|
|
||||||
self.create_regular_file("a_file_removed", size=8)
|
|
||||||
self.create_regular_file("f_file_removed", size=16)
|
|
||||||
self.create_regular_file("c_file_changed", size=32)
|
|
||||||
self.create_regular_file("e_file_changed", size=64)
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "test0", "input")
|
|
||||||
|
|
||||||
os.unlink("input/a_file_removed")
|
|
||||||
os.unlink("input/f_file_removed")
|
|
||||||
os.unlink("input/c_file_changed")
|
|
||||||
os.unlink("input/e_file_changed")
|
|
||||||
self.create_regular_file("c_file_changed", size=512)
|
|
||||||
self.create_regular_file("e_file_changed", size=1024)
|
|
||||||
self.create_regular_file("b_file_added", size=128)
|
|
||||||
self.create_regular_file("d_file_added", size=256)
|
|
||||||
self.cmd(f"--repo={self.repository_location}", "create", "test1", "input")
|
|
||||||
|
|
||||||
output = self.cmd(f"--repo={self.repository_location}", "diff", "test0", "test1", "--sort", "--content-only")
|
|
||||||
expected = [
|
|
||||||
"a_file_removed",
|
|
||||||
"b_file_added",
|
|
||||||
"c_file_changed",
|
|
||||||
"d_file_added",
|
|
||||||
"e_file_changed",
|
|
||||||
"f_file_removed",
|
|
||||||
]
|
|
||||||
assert isinstance(output, str)
|
|
||||||
outputs = output.splitlines()
|
|
||||||
assert len(outputs) == len(expected)
|
|
||||||
assert all(x in line for x, line in zip(expected, outputs))
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteArchiverTestCase(RemoteArchiverTestCaseBase, ArchiverTestCase):
|
def test_time_diffs(archivers, request):
|
||||||
"""run the same tests, but with a remote repository"""
|
archiver = request.getfixturevalue(archivers)
|
||||||
|
repo_location, input_path = archiver.repository_location, archiver.input_path
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "rcreate", RK_ENCRYPTION)
|
||||||
|
create_regular_file(input_path, "test_file", size=10)
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "archive1", "input")
|
||||||
|
time.sleep(0.1)
|
||||||
|
os.unlink("input/test_file")
|
||||||
|
if is_win32:
|
||||||
|
# Sleeping for 15s because Windows doesn't refresh ctime if file is deleted and recreated within 15 seconds.
|
||||||
|
time.sleep(15)
|
||||||
|
elif is_darwin:
|
||||||
|
time.sleep(1) # HFS has a 1s timestamp granularity
|
||||||
|
create_regular_file(input_path, "test_file", size=15)
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "archive2", "input")
|
||||||
|
output = cmd(
|
||||||
|
archiver, f"--repo={repo_location}", "diff", "archive1", "archive2", "--format", "'{mtime}{ctime} {path}{NL}'"
|
||||||
|
)
|
||||||
|
assert "mtime" in output
|
||||||
|
assert "ctime" in output # Should show up on Windows as well since it is a new file.
|
||||||
|
if is_darwin:
|
||||||
|
time.sleep(1) # HFS has a 1s timestamp granularity
|
||||||
|
os.chmod("input/test_file", 0o777)
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "archive3", "input")
|
||||||
|
output = cmd(
|
||||||
|
archiver, f"--repo={repo_location}", "diff", "archive2", "archive3", "--format", "'{mtime}{ctime} {path}{NL}'"
|
||||||
|
)
|
||||||
|
assert "mtime" not in output
|
||||||
|
# Checking platform because ctime should not be shown on Windows since it wasn't recreated.
|
||||||
|
if not is_win32:
|
||||||
|
assert "ctime" in output
|
||||||
|
else:
|
||||||
|
assert "ctime" not in output
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless("binary" in BORG_EXES, "no borg.exe available")
|
def test_sort_option(archivers, request):
|
||||||
class ArchiverTestCaseBinary(ArchiverTestCaseBinaryBase, ArchiverTestCase):
|
archiver = request.getfixturevalue(archivers)
|
||||||
"""runs the same tests, but via the borg binary"""
|
repo_location, input_path = archiver.repository_location, archiver.input_path
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "rcreate", RK_ENCRYPTION)
|
||||||
|
|
||||||
|
create_regular_file(input_path, "a_file_removed", size=8)
|
||||||
|
create_regular_file(input_path, "f_file_removed", size=16)
|
||||||
|
create_regular_file(input_path, "c_file_changed", size=32)
|
||||||
|
create_regular_file(input_path, "e_file_changed", size=64)
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "test0", "input")
|
||||||
|
|
||||||
|
os.unlink("input/a_file_removed")
|
||||||
|
os.unlink("input/f_file_removed")
|
||||||
|
os.unlink("input/c_file_changed")
|
||||||
|
os.unlink("input/e_file_changed")
|
||||||
|
create_regular_file(input_path, "c_file_changed", size=512)
|
||||||
|
create_regular_file(input_path, "e_file_changed", size=1024)
|
||||||
|
create_regular_file(input_path, "b_file_added", size=128)
|
||||||
|
create_regular_file(input_path, "d_file_added", size=256)
|
||||||
|
cmd(archiver, f"--repo={repo_location}", "create", "test1", "input")
|
||||||
|
|
||||||
|
output = cmd(archiver, f"--repo={repo_location}", "diff", "test0", "test1", "--sort", "--content-only")
|
||||||
|
expected = ["a_file_removed", "b_file_added", "c_file_changed", "d_file_added", "e_file_changed", "f_file_removed"]
|
||||||
|
assert isinstance(output, str)
|
||||||
|
outputs = output.splitlines()
|
||||||
|
assert len(outputs) == len(expected)
|
||||||
|
assert all(x in line for x, line in zip(expected, outputs))
|
||||||
|
|
Loading…
Reference in New Issue