"""根因分析测试。""" import _bootstrap # noqa: F401 import unittest from datetime import datetime, timezone from fault_log_analyzer.models import Event, FaultLog from fault_log_analyzer.root_cause import RootCauseAnalyzer BASE = datetime(2025, 1, 1, 10, 18, 0, tzinfo=timezone.utc) def _log(msg, host="h-1", trace=None, at=None, fid="fl-1"): return FaultLog( fault_log_id=fid, host_id=host, message=msg, trace_id=trace, occurred_at=at or BASE, ) def _event(host="h-1", metric="disk_used_percent", value=97.2, threshold=90.0, at=None): return Event( event_id="e-1", host_id=host, metric=metric, value=value, threshold=threshold, fired_at=at or BASE, ) class TestLogHints(unittest.TestCase): def test_disk_hint(self): rc = RootCauseAnalyzer().analyze(_log("no space left on device"), []) self.assertEqual(rc.cause_type, "disk_full") self.assertGreater(rc.confidence, 0.0) def test_memory_hint(self): rc = RootCauseAnalyzer().analyze(_log("out of memory killed"), []) self.assertEqual(rc.cause_type, "memory_exhausted") def test_unknown_when_no_signal(self): rc = RootCauseAnalyzer().analyze(_log("hello world"), []) self.assertEqual(rc.cause_type, "unknown") self.assertEqual(rc.confidence, 0.0) class TestMetricEvents(unittest.TestCase): def test_metric_event_rule(self): rc = RootCauseAnalyzer().analyze(_log("some error"), [_event()]) self.assertEqual(rc.cause_type, "disk_full") self.assertGreater(rc.confidence, 0.0) def test_event_outside_window_ignored(self): later = datetime(2025, 1, 1, 11, 0, 0, tzinfo=timezone.utc) rc = RootCauseAnalyzer().analyze(_log("some error"), [_event(at=later)]) self.assertEqual(rc.cause_type, "unknown") def test_different_host_event_ignored(self): rc = RootCauseAnalyzer().analyze(_log("some error"), [_event(host="h-2")]) self.assertEqual(rc.cause_type, "unknown") def test_memory_metric_rule(self): event = _event(metric="mem_used_percent", value=95.0, threshold=90.0) rc = RootCauseAnalyzer().analyze(_log("some error"), [event]) self.assertEqual(rc.cause_type, "memory_exhausted") class TestNaiveDatetimeRobustness(unittest.TestCase): """跨模块时间比较防御性归一:上游可能传入 naive datetime。""" def test_naive_fault_log_and_naive_event(self): naive = datetime(2025, 1, 1, 10, 18, 0) log = _log("no space left on device", at=naive) event = _event(value=97.2, at=naive) rc = RootCauseAnalyzer().analyze(log, [event]) self.assertEqual(rc.cause_type, "disk_full") def test_naive_event_with_aware_log(self): naive_event = _event(value=97.2, at=datetime(2025, 1, 1, 10, 18, 0)) rc = RootCauseAnalyzer().analyze(_log("some error"), [naive_event]) self.assertEqual(rc.cause_type, "disk_full") class TestTraceAssociation(unittest.TestCase): def test_trace_association(self): log = _log("request failed", trace="tr-1") related = FaultLog( fault_log_id="fl-2", host_id="h-1", message="out of memory", trace_id="tr-1", occurred_at=BASE, ) rc = RootCauseAnalyzer().analyze(log, [], related_logs=[related]) self.assertEqual(rc.cause_type, "memory_exhausted") def test_evidence_collected(self): rc = RootCauseAnalyzer().analyze(_log("no space left on device"), [_event()]) types = {e["type"] for e in rc.evidence} self.assertIn("log", types) self.assertIn("event", types) if __name__ == "__main__": unittest.main()