Ctrl K
ComunidadGuías…

tests/test_log_retention.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""`peach.log_retention`:日志目录统一保留半年,按月切段,大小不设限。"""
from __future__ import annotations

import os
import tempfile
import unittest
from datetime import datetime, timedelta
from pathlib import Path

from peach import log_retention

NOW = datetime(2026, 9, 5, 12, 0, 0)


def touch(path: Path, when: datetime, content: bytes = b"line\n") -> None:
    path.write_bytes(content)
    stamp = when.timestamp()
    os.utime(path, (stamp, stamp))


class SweepTests(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.logs = Path(self.tmp.name).resolve() / "logs"
        self.logs.mkdir()

    def tearDown(self):
        self.tmp.cleanup()

    def test_a_file_nobody_wrote_to_for_half_a_year_is_deleted_whole(self):
        touch(self.logs / "tray-http.out.log", NOW - timedelta(days=184))
        touch(self.logs / "tray-http.out.until-20260131.log", NOW - timedelta(days=200))
        actions = log_retention.sweep(self.logs, now=NOW)
        self.assertEqual(sorted(p.name for p in self.logs.iterdir()), [])
        self.assertEqual(sorted(actions), ["deleted tray-http.out.log",
                                           "deleted tray-http.out.until-20260131.log"])

    def test_a_file_last_written_in_an_earlier_month_becomes_a_dated_segment(self):
        touch(self.logs / "tray-https.err.log", datetime(2026, 8, 30, 23, 0), b"august\n")
        actions = log_retention.sweep(self.logs, now=NOW)
        self.assertEqual(actions, ["rotated tray-https.err.log -> tray-https.err.until-20260830.log"])
        self.assertFalse((self.logs / "tray-https.err.log").exists(), "下一次写入从空文件开始")
        self.assertEqual((self.logs / "tray-https.err.until-20260830.log").read_bytes(), b"august\n")

    def test_a_file_written_this_month_stays_put(self):
        touch(self.logs / "windows-source-sync.log", NOW - timedelta(days=3))
        self.assertEqual(log_retention.sweep(self.logs, now=NOW), [])
        self.assertTrue((self.logs / "windows-source-sync.log").exists())

    def test_segments_are_never_cut_again_until_they_expire(self):
        segment = self.logs / "tray-scan.out.until-20260731.log"
        touch(segment, datetime(2026, 7, 31, 8, 0))
        self.assertEqual(log_retention.sweep(self.logs, now=NOW), [])
        self.assertTrue(segment.exists())
        self.assertEqual(log_retention.sweep(self.logs, now=datetime(2027, 2, 1)),
                         ["deleted tray-scan.out.until-20260731.log"])

    def test_a_second_cut_on_the_same_day_appends_instead_of_overwriting(self):
        touch(self.logs / "tray-http.out.until-20260830.log", datetime(2026, 8, 30, 9, 0), b"first\n")
        touch(self.logs / "tray-http.out.log", datetime(2026, 8, 30, 22, 0), b"second\n")
        log_retention.sweep(self.logs, now=NOW)
        self.assertEqual((self.logs / "tray-http.out.until-20260830.log").read_bytes(), b"first\nsecond\n")
        self.assertFalse((self.logs / "tray-http.out.log").exists())

    def test_only_log_files_are_touched_and_a_missing_directory_is_fine(self):
        touch(self.logs / "peach-tray.lock", NOW - timedelta(days=400))
        touch(self.logs / "notes.txt", NOW - timedelta(days=400))
        self.assertEqual(log_retention.sweep(self.logs, now=NOW), [])
        self.assertEqual(log_retention.sweep(self.logs / "absent", now=NOW), [])

    def test_the_retention_is_half_a_year(self):
        self.assertEqual(log_retention.RETENTION, timedelta(days=183))

    def test_idle_logs_of_finished_processes_go_and_the_rest_stay(self):
        entry = b'{"stage": "entry", "pid": 1}\n'
        touch(self.logs / "crash-11.log", NOW, b"")
        touch(self.logs / "errors-11.log", NOW, entry)
        touch(self.logs / "crash-12.log", NOW, b"Fatal Python error: Segmentation fault\n")
        touch(self.logs / "errors-12.log", NOW, entry + b'{"stage": "logging", "message": "x"}\n')
        touch(self.logs / "crash-13.log", NOW, b"")
        touch(self.logs / "errors-13.log", NOW, entry)
        actions = log_retention.sweep(self.logs, now=NOW, alive=lambda pid: pid == 13)
        self.assertEqual(actions, ["removed 2 idle process logs"])
        self.assertEqual(sorted(path.name for path in self.logs.iterdir()),
                         ["crash-12.log", "crash-13.log", "errors-12.log", "errors-13.log"])


if __name__ == "__main__":
    unittest.main()