fault-log-analyzer/tests/test_cluster.py

90 lines
2.8 KiB
Python

import _bootstrap # noqa: F401
import unittest
from datetime import datetime, timezone
from fault_log_analyzer.cluster import (
ClusterEngine,
TfidfVectorizer,
cosine_distance,
cosine_similarity,
dbscan,
)
from fault_log_analyzer.models import FaultLog
def make_log(fid, message, host="h-1"):
return FaultLog(
fault_log_id=fid,
host_id=host,
fingerprint="fp",
level="ERROR",
message=message,
occurred_at=datetime(2025, 1, 1, tzinfo=timezone.utc),
)
class TestTfidfVectorizer(unittest.TestCase):
def test_fit_transform(self):
vec = TfidfVectorizer().fit([["a", "b"], ["b", "c"]])
out = vec.transform([["a", "b"], ["b", "c"]])
self.assertEqual(len(out), 2)
self.assertTrue(all(isinstance(v, dict) for v in out))
def test_empty_documents(self):
vec = TfidfVectorizer().fit([])
self.assertEqual(vec.transform([]), [])
class TestCosineSimilarity(unittest.TestCase):
def test_identical(self):
self.assertEqual(cosine_similarity({"a": 1.0}, {"a": 1.0}), 1.0)
def test_disjoint(self):
self.assertEqual(cosine_similarity({"a": 1.0}, {"b": 1.0}), 0.0)
def test_distance(self):
self.assertAlmostEqual(cosine_distance({"a": 1.0}, {"a": 1.0}), 0.0)
class TestDbscan(unittest.TestCase):
def test_empty(self):
result = dbscan([])
self.assertEqual(result.labels, [])
self.assertEqual(result.cluster_ids, {})
def test_single_noise(self):
result = dbscan([{"a": 1.0}], eps=0.75, min_samples=2)
self.assertEqual(result.labels, [-1])
def test_two_similar_cluster(self):
result = dbscan([{"a": 1.0}, {"a": 1.0}], eps=0.75, min_samples=1)
self.assertEqual(result.labels[0], result.labels[1])
self.assertNotEqual(result.labels[0], -1)
def test_two_dissimilar_noise(self):
result = dbscan([{"a": 1.0}, {"b": 1.0}], eps=0.75, min_samples=2)
self.assertEqual(result.labels, [-1, -1])
class TestClusterEngine(unittest.TestCase):
def test_cluster_batch_empty(self):
ce = ClusterEngine()
self.assertEqual(ce.cluster_batch([]), {})
def test_cluster_batch_groups_identical(self):
ce = ClusterEngine(eps=0.75, min_samples=1)
logs = [make_log("fl1", "No space left on device"), make_log("fl2", "No space left on device")]
assignment = ce.cluster_batch(logs)
self.assertEqual(assignment["fl1"], assignment["fl2"])
def test_incremental_match_existing_cluster(self):
ce = ClusterEngine(eps=0.75, min_samples=1)
first = ce.cluster_batch([make_log("fl1", "No space left on device")])
second = ce.cluster_batch([make_log("fl2", "No space left on device")])
self.assertEqual(second["fl2"], first["fl1"])
if __name__ == "__main__":
unittest.main()