|
@@ -1,6 +1,7 @@
|
|
|
import json
|
|
|
import os
|
|
|
import stat
|
|
|
+import time
|
|
|
import unittest
|
|
|
|
|
|
from ...constants import * # NOQA
|
|
@@ -70,18 +71,19 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
self.cmd(f"--repo={self.repository_location}", "create", "test1a", "input")
|
|
|
self.cmd(f"--repo={self.repository_location}", "create", "test1b", "input", "--chunker-params", "16,18,17,4095")
|
|
|
|
|
|
- def do_asserts(output, can_compare_ids):
|
|
|
+ def do_asserts(output, can_compare_ids, content_only=False):
|
|
|
# File contents changed (deleted and replaced with a new file)
|
|
|
change = "B" if can_compare_ids else "{:<19}".format("modified")
|
|
|
+ lines = output.splitlines()
|
|
|
assert "file_replaced" in output # added to debug #3494
|
|
|
- assert f"{change} input/file_replaced" in output
|
|
|
+ self.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:
|
|
|
- assert "[drwxr-xr-x -> -rwxr-xr-x] input/dir_replaced_with_file" in output
|
|
|
+ if "BORG_TESTS_IGNORE_MODES" not in os.environ and not is_win32 and not content_only:
|
|
|
+ self.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
|
|
@@ -89,13 +91,13 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
|
|
|
if are_symlinks_supported():
|
|
|
# Basic symlink cases
|
|
|
- assert "changed link input/link_changed" in output
|
|
|
- assert "added link input/link_added" in output
|
|
|
- assert "removed link input/link_removed" in output
|
|
|
+ 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
|
|
|
- assert "] input/dir_replaced_with_link" in output
|
|
|
- assert "] input/link_replaced_by_file" in output
|
|
|
+ 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
|
|
@@ -104,9 +106,9 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
# should notice the changes in both links. However, the symlink
|
|
|
# pointing to the file is not changed.
|
|
|
change = "0 B" if can_compare_ids else "{:<19}".format("modified")
|
|
|
- assert f"{change} input/empty" in output
|
|
|
+ self.assert_line_exists(lines, f"{change}.*input/empty")
|
|
|
if are_hardlinks_supported():
|
|
|
- assert f"{change} input/hardlink_contents_changed" in output
|
|
|
+ self.assert_line_exists(lines, f"{change}.*input/hardlink_contents_changed")
|
|
|
if are_symlinks_supported():
|
|
|
assert "input/link_target_contents_changed" not in output
|
|
|
|
|
@@ -125,18 +127,18 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
if are_hardlinks_supported():
|
|
|
assert "removed 256 B input/hardlink_removed" in output
|
|
|
|
|
|
- # Another link (marked previously as the source in borg) to the
|
|
|
- # same inode was removed. This should not change this link at all.
|
|
|
- if are_hardlinks_supported():
|
|
|
+ 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 "input/hardlink_target_removed" not in output
|
|
|
|
|
|
- # Another link (marked previously as the source in borg) to the
|
|
|
- # same inode was replaced with a new regular file. This should not
|
|
|
- # change this link at all.
|
|
|
- if are_hardlinks_supported():
|
|
|
+ # 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 "input/hardlink_target_replaced" not in output
|
|
|
|
|
|
- def do_json_asserts(output, can_compare_ids):
|
|
|
+ def do_json_asserts(output, can_compare_ids, content_only=False):
|
|
|
def get_changes(filename, data):
|
|
|
chgsets = [j["changes"] for j in data if j["path"] == filename]
|
|
|
assert len(chgsets) < 2
|
|
@@ -154,7 +156,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
assert not any(get_changes("input/file_unchanged", joutput))
|
|
|
|
|
|
# Directory replaced with a regular file
|
|
|
- if "BORG_TESTS_IGNORE_MODES" not in os.environ and not is_win32:
|
|
|
+ if "BORG_TESTS_IGNORE_MODES" not in os.environ and not is_win32 and not content_only:
|
|
|
assert {"type": "mode", "old_mode": "drwxr-xr-x", "new_mode": "-rwxr-xr-x"} in get_changes(
|
|
|
"input/dir_replaced_with_file", joutput
|
|
|
)
|
|
@@ -170,14 +172,16 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
assert {"type": "removed link"} in get_changes("input/link_removed", joutput)
|
|
|
|
|
|
# Symlink replacing or being replaced
|
|
|
- assert any(
|
|
|
- chg["type"] == "mode" and chg["new_mode"].startswith("l")
|
|
|
- for chg in get_changes("input/dir_replaced_with_link", joutput)
|
|
|
- )
|
|
|
- assert any(
|
|
|
- chg["type"] == "mode" and chg["old_mode"].startswith("l")
|
|
|
- for chg in get_changes("input/link_replaced_by_file", joutput)
|
|
|
- )
|
|
|
+
|
|
|
+ if not content_only:
|
|
|
+ assert any(
|
|
|
+ chg["type"] == "mode" and chg["new_mode"].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"] == "mode" and chg["old_mode"].startswith("l")
|
|
|
+ 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))
|
|
@@ -207,21 +211,56 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
if are_hardlinks_supported():
|
|
|
assert {"type": "removed", "size": 256} in get_changes("input/hardlink_removed", joutput)
|
|
|
|
|
|
- # Another link (marked previously as the source in borg) to the
|
|
|
- # same inode was removed. This should not change this link at all.
|
|
|
- if are_hardlinks_supported():
|
|
|
+ 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 not
|
|
|
- # change this link at all.
|
|
|
- if are_hardlinks_supported():
|
|
|
+ # 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))
|
|
|
|
|
|
- do_asserts(self.cmd(f"--repo={self.repository_location}", "diff", "test0", "test1a"), True)
|
|
|
+ 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
|
|
|
- do_asserts(self.cmd(f"--repo={self.repository_location}", "diff", "test0", "test1b", exit_code=1), False)
|
|
|
- do_json_asserts(self.cmd(f"--repo={self.repository_location}", "diff", "test0", "test1a", "--json-lines"), True)
|
|
|
+ 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)
|
|
|
+ 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")
|
|
|
+ self.assert_in("mtime", output)
|
|
|
+ self.assert_in("ctime", output) # Should show up on windows as well since it is a new file.
|
|
|
+ os.chmod("input/test_file", 777)
|
|
|
+ self.cmd(f"--repo={self.repository_location}", "create", "archive3", "input")
|
|
|
+ output = self.cmd(f"--repo={self.repository_location}", "diff", "archive2", "archive3")
|
|
|
+ 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)
|
|
@@ -242,7 +281,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
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")
|
|
|
+ output = self.cmd(f"--repo={self.repository_location}", "diff", "test0", "test1", "--sort", "--content-only")
|
|
|
expected = [
|
|
|
"a_file_removed",
|
|
|
"b_file_added",
|
|
@@ -251,7 +290,6 @@ class ArchiverTestCase(ArchiverTestCaseBase):
|
|
|
"e_file_changed",
|
|
|
"f_file_removed",
|
|
|
]
|
|
|
-
|
|
|
assert all(x in line for x, line in zip(expected, output.splitlines()))
|
|
|
|
|
|
|