diff options
Diffstat (limited to 'app/tests/test_logging_kit.py')
| -rw-r--r-- | app/tests/test_logging_kit.py | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/app/tests/test_logging_kit.py b/app/tests/test_logging_kit.py new file mode 100644 index 0000000..1945e5e --- /dev/null +++ b/app/tests/test_logging_kit.py @@ -0,0 +1,118 @@ +"""Tests for app/logging_kit.py — log naming, teeing, and retention.""" + +import io +import os +import tempfile +import time +import unittest +from pathlib import Path + +import logging_kit + + +class _TempDir(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.dir = Path(self.tmp.name) + + +class DayStreamTests(_TempDir): + def test_creates_dated_stream_and_appends(self): + handle = logging_kit.day_stream("tui", log_dir=self.dir) + self.assertIsNotNone(handle) + logging_kit.write_line(handle, "first") + logging_kit.write_line(handle, "second") + handle.close() + files = list(self.dir.glob("tui_*.log")) + self.assertEqual(len(files), 1) + self.assertRegex(files[0].name, r"^tui_\d{8}\.log$") + self.assertEqual(files[0].read_text(encoding="utf-8"), + "first\nsecond\n") + + def test_unwritable_dir_returns_none(self): + blocker = self.dir / "file" # a file where the directory would be + blocker.write_text("x", encoding="utf-8") + self.assertIsNone( + logging_kit.day_stream("tui", log_dir=blocker)) + + +class RunArtifactTests(_TempDir): + def test_creates_timestamped_artifact(self): + path, handle = logging_kit.run_artifact("audiocpp_build", + log_dir=self.dir) + self.assertIsNotNone(handle) + self.assertRegex(path.name, r"^audiocpp_build_\d{8}_\d{6}\.log$") + logging_kit.write_line(handle, "out") + handle.close() + self.assertEqual(path.read_text(encoding="utf-8"), "out\n") + + def test_returns_path_even_when_unwritable(self): + blocker = self.dir / "file" + blocker.write_text("x", encoding="utf-8") + path, handle = logging_kit.run_artifact("x", log_dir=blocker) + self.assertIsNone(handle) + self.assertEqual(path.parent, blocker) + + +class WriteLineTests(unittest.TestCase): + def test_none_handle_is_a_noop(self): + logging_kit.write_line(None, "x") # must not raise + + def test_broken_handle_is_swallowed(self): + class Broken: + def write(self, _): + raise OSError("nope") + + def flush(self): + raise OSError("nope") + + logging_kit.write_line(Broken(), "x") # must not raise + + +class TeeWriterTests(unittest.TestCase): + def test_mirrors_to_both_sides(self): + logf, inner = io.StringIO(), io.StringIO() + writer = logging_kit.TeeWriter(logf, inner) + self.assertEqual(writer.write("hello"), 5) + writer.flush() + self.assertEqual(logf.getvalue(), "hello") + self.assertEqual(inner.getvalue(), "hello") + + def test_empty_write_returns_zero(self): + self.assertEqual(logging_kit.TeeWriter(io.StringIO()).write(""), 0) + + def test_none_sides_and_errors_are_swallowed(self): + writer = logging_kit.TeeWriter(None, None) + self.assertEqual(writer.write("x"), 1) # must not raise + writer.flush() + self.assertFalse(writer.isatty()) + + +class PruneTests(_TempDir): + def test_deletes_old_logs_keeps_new_server_logs_and_pid_files(self): + old = self.dir / "tui_20200101.log" + old.write_text("x", encoding="utf-8") + new = self.dir / "audiobook_20990101.log" + new.write_text("x", encoding="utf-8") + server = self.dir / "audiocpp-server.log" + server.write_text("x", encoding="utf-8") + pid = self.dir / "audiocpp-server.pid" + pid.write_text("1", encoding="utf-8") + ancient = time.time() - 100 * 86400 + os.utime(old, (ancient, ancient)) + os.utime(server, (ancient, ancient)) + + logging_kit.prune_logs(log_dir=self.dir) + + self.assertFalse(old.exists()) + self.assertTrue(new.exists()) + self.assertTrue(server.exists()) # owned by a server process + self.assertTrue(pid.exists()) # not a log + + def test_missing_dir_is_a_noop(self): + logging_kit.prune_logs(log_dir=self.dir / "nope") # must not raise + + +if __name__ == "__main__": + unittest.main() |
