"""TF-IDF 特征 + DBSCAN 聚类测试。""" import _bootstrap # noqa: F401 import unittest from fault_log_analyzer.cluster import ( Clusterer, cosine_distance, cosine_similarity, dbscan, tfidf_vectors, ) class TestTfidf(unittest.TestCase): def test_empty(self): self.assertEqual(tfidf_vectors([]), []) def test_vectors_shape(self): vecs = tfidf_vectors(["no space left on device", "out of memory error"]) self.assertEqual(len(vecs), 2) def test_identical_docs_similar(self): vecs = tfidf_vectors(["disk full", "disk full"]) self.assertAlmostEqual(cosine_similarity(vecs[0], vecs[1]), 1.0) def test_distinct_docs_less_similar(self): vecs = tfidf_vectors(["disk full error", "memory exhausted"]) self.assertLess(cosine_similarity(vecs[0], vecs[1]), 1.0) class TestCosine(unittest.TestCase): def test_similarity_zero_for_empty(self): self.assertEqual(cosine_similarity({}, {}), 0.0) def test_distance_identical_is_zero(self): self.assertAlmostEqual(cosine_distance({"a": 1.0}, {"a": 1.0}), 0.0) def test_distance_is_one_for_orthogonal(self): self.assertAlmostEqual(cosine_distance({"a": 1.0}, {"b": 1.0}), 1.0) class TestDbscan(unittest.TestCase): def test_empty(self): self.assertEqual(dbscan([], 0.75, 5), []) def test_single_sample_is_noise(self): self.assertEqual(dbscan([{"a": 1.0}], 0.75, 5), [-1]) def test_two_identical_with_min2(self): labels = dbscan([{"a": 1.0}, {"a": 1.0}], 0.75, 2) self.assertEqual(labels, [0, 0]) def test_two_distinct_with_min2_are_noise(self): labels = dbscan([{"a": 1.0}, {"b": 1.0}], 0.75, 2) self.assertEqual(labels, [-1, -1]) class TestClusterer(unittest.TestCase): def test_fit_predict_small_returns_noise(self): c = Clusterer(eps=0.75, min_samples=5) self.assertEqual(c.fit_predict(["only one message"]), [-1]) def test_assign_empty_centroids(self): self.assertEqual(Clusterer().assign("anything"), -1) def test_fit_predict_group(self): messages = [ "no space left on device", "no space left on device /var", "no space left on device /tmp", "out of memory error", "out of memory killed process", ] c = Clusterer(eps=0.75, min_samples=2) labels = c.fit_predict(messages) self.assertEqual(len(labels), 5) self.assertGreaterEqual(c.n_clusters, 1) def test_assign_to_nearest_cluster(self): messages = [ "no space left on device", "no space left on device /var", "no space left on device /tmp", ] c = Clusterer(eps=0.75, min_samples=2) c.fit_predict(messages) self.assertGreaterEqual(c.n_clusters, 1) self.assertGreaterEqual(c.assign("no space left on device /opt"), 0) if __name__ == "__main__": unittest.main()