Prechádzať zdrojové kódy

improved tty-less progress reporting

Previously when running borg in a systemd service (and similar when piping to
a file and co.), these problems occurred:

- The carriage return both made it so that journald interpreted the output as
  binary, therefore not printing the text, while also not buffering
  correctly, so that log output was only available every once in a while
  in the form [40k blob data]. This can partially be worked around by
  using `journalctl -a` to view the logs, which at least prints the text,
  though only sporadically.

- The path was getting truncated to a short length, since the default
  get_terminal_size returns a column width of 80, which isn't relevant
  when printing to e.g. journald.

This commit fixes this by introducing a new code path for when stream is
not a tty, which always prints the full paths and ends lines with a linefeed.

This is based on unfinished PR #8939 by @infinisil, thanks for your suggestion!

Forward port of PR #9055 to master.
Thomas Waldmann 3 dní pred
rodič
commit
8cbe4b8d48

+ 10 - 1
src/borg/archive.py

@@ -149,6 +149,7 @@ Bytes sent to remote: {stats.tx_bytes}
     def show_progress(self, item=None, final=False, stream=None, dt=None):
     def show_progress(self, item=None, final=False, stream=None, dt=None):
         now = time.monotonic()
         now = time.monotonic()
         if dt is None or now - self.last_progress > dt:
         if dt is None or now - self.last_progress > dt:
+            stream = stream or sys.stderr
             self.last_progress = now
             self.last_progress = now
             if self.output_json:
             if self.output_json:
                 if not final:
                 if not final:
@@ -160,6 +161,14 @@ Bytes sent to remote: {stats.tx_bytes}
                 data.update({"time": time.time(), "type": "archive_progress", "finished": final})
                 data.update({"time": time.time(), "type": "archive_progress", "finished": final})
                 msg = json.dumps(data)
                 msg = json.dumps(data)
                 end = "\n"
                 end = "\n"
+            elif not stream.isatty():
+                # Non-TTY output: use normal linefeeds and do not truncate the path.
+                if not final:
+                    msg = "{0.osize_fmt} O {0.usize_fmt} U {0.nfiles} N ".format(self)
+                    msg += remove_surrogates(item.path) if item else ""
+                else:
+                    msg = ""
+                end = "\n"
             else:
             else:
                 columns, lines = get_terminal_size()
                 columns, lines = get_terminal_size()
                 if not final:
                 if not final:
@@ -174,7 +183,7 @@ Bytes sent to remote: {stats.tx_bytes}
                 else:
                 else:
                     msg = " " * columns
                     msg = " " * columns
                 end = "\r"
                 end = "\r"
-            print(msg, end=end, file=stream or sys.stderr, flush=True)
+            print(msg, end=end, file=stream, flush=True)
 
 
 
 
 def is_special(mode):
 def is_special(mode):

+ 40 - 15
src/borg/testsuite/archive_test.py

@@ -33,27 +33,52 @@ def test_stats_basic(stats):
     assert stats.usize == 20
     assert stats.usize == 20
 
 
 
 
-@pytest.mark.parametrize(
-    "item_path, update_size, expected_output",
-    [
-        ("", 0, "20 B O 20 B U 1 N "),  # test unchanged 'stats' fixture
-        ("foo", 10**3, "1.02 kB O 20 B U 1 N foo"),  # test updated original size and set item path
-        # test long item path which exceeds 80 characters
-        ("foo" * 40, 10**3, "1.02 kB O 20 B U 1 N foofoofoofoofoofoofoofoofo...foofoofoofoofoofoofoofoofoofoo"),
-    ],
-)
-def test_stats_progress(item_path, update_size, expected_output, stats, monkeypatch, columns=80):
+def test_stats_progress_tty(stats, monkeypatch, columns=80):
+    class TTYStringIO(StringIO):
+        def isatty(self):
+            return True
+
     monkeypatch.setenv("COLUMNS", str(columns))
     monkeypatch.setenv("COLUMNS", str(columns))
-    out = StringIO()
-    item = Item(path=item_path) if item_path else None
-    s = expected_output
+    out = TTYStringIO()
+    stats.show_progress(stream=out)
+    s = "20 B O 20 B U 1 N "
+    buf = " " * (columns - len(s))
+    assert out.getvalue() == s + buf + "\r"
+
+    out = TTYStringIO()
+    stats.update(10**3, unique=False)
+    stats.show_progress(item=Item(path="foo"), final=False, stream=out)
+    s = "1.02 kB O 20 B U 1 N foo"
+    buf = " " * (columns - len(s))
+    assert out.getvalue() == s + buf + "\r"
 
 
-    stats.update(update_size, unique=False)
-    stats.show_progress(item=item, stream=out)
+    out = TTYStringIO()
+    stats.show_progress(item=Item(path="foo" * 40), final=False, stream=out)
+    s = "1.02 kB O 20 B U 1 N foofoofoofoofoofoofoofoofo...foofoofoofoofoofoofoofoofoofoo"
     buf = " " * (columns - len(s))
     buf = " " * (columns - len(s))
     assert out.getvalue() == s + buf + "\r"
     assert out.getvalue() == s + buf + "\r"
 
 
 
 
+def test_stats_progress_file(stats, monkeypatch):
+    out = StringIO()
+    stats.show_progress(stream=out)
+    s = "20 B O 20 B U 1 N "
+    assert out.getvalue() == s + "\n"
+
+    out = StringIO()
+    stats.update(10**3, unique=False)
+    path = "foo"
+    stats.show_progress(item=Item(path=path), final=False, stream=out)
+    s = f"1.02 kB O 20 B U 1 N {path}"
+    assert out.getvalue() == s + "\n"
+
+    out = StringIO()
+    path = "foo" * 40
+    stats.show_progress(item=Item(path=path), final=False, stream=out)
+    s = f"1.02 kB O 20 B U 1 N {path}"
+    assert out.getvalue() == s + "\n"
+
+
 def test_stats_format(stats):
 def test_stats_format(stats):
     assert (
     assert (
         str(stats)
         str(stats)

+ 2 - 2
src/borg/testsuite/archiver/create_cmd_test.py

@@ -634,7 +634,7 @@ def test_progress_on(archivers, request):
     create_regular_file(archiver.input_path, "file1", size=1024 * 80)
     create_regular_file(archiver.input_path, "file1", size=1024 * 80)
     cmd(archiver, "repo-create", RK_ENCRYPTION)
     cmd(archiver, "repo-create", RK_ENCRYPTION)
     output = cmd(archiver, "create", "test4", "input", "--progress")
     output = cmd(archiver, "create", "test4", "input", "--progress")
-    assert "\r" in output
+    assert "0 B O 0 B U 0 N" in output
 
 
 
 
 def test_progress_off(archivers, request):
 def test_progress_off(archivers, request):
@@ -642,7 +642,7 @@ def test_progress_off(archivers, request):
     create_regular_file(archiver.input_path, "file1", size=1024 * 80)
     create_regular_file(archiver.input_path, "file1", size=1024 * 80)
     cmd(archiver, "repo-create", RK_ENCRYPTION)
     cmd(archiver, "repo-create", RK_ENCRYPTION)
     output = cmd(archiver, "create", "test5", "input")
     output = cmd(archiver, "create", "test5", "input")
-    assert "\r" not in output
+    assert "0 B O 0 B U 0 N" not in output
 
 
 
 
 def test_file_status(archivers, request):
 def test_file_status(archivers, request):