90 lines
2.8 KiB
Python
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()
|