-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_harmonic_libs.omc
More file actions
238 lines (207 loc) · 7.9 KB
/
test_harmonic_libs.omc
File metadata and controls
238 lines (207 loc) · 7.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# =============================================================================
# Tests for harmonic libraries — validates harmonic_anomaly,
# harmonic_clustering, harmonic_recommend using OMC's --test mode.
# =============================================================================
# Run with:
# ./target/release/omnimcode-standalone --test examples/tests/test_harmonic_libs.omc
#
# Each `fn test_*()` is auto-discovered. Failures use error("msg")
# to signal — the harness reports per-test pass/fail and a summary.
# Exit code = number of failures (clamped to 1) for CI use.
# =============================================================================
import "examples/lib/harmonic_anomaly.omc" as ha;
import "examples/lib/harmonic_clustering.omc" as hc;
import "examples/lib/harmonic_recommend.omc" as hr;
# ---- assertion helpers ---------------------------------------------------
fn assert_eq(actual, expected, msg) {
if actual != expected {
error(concat_many("FAIL ", msg, ": expected ", expected, " got ", actual));
}
}
fn assert_true(cond, msg) {
if cond != 1 { error(concat_many("FAIL ", msg, ": expected truthy")); }
}
fn assert_in(needle, haystack, msg) {
h k = 0;
while k < arr_len(haystack) {
if arr_get(haystack, k) == needle { return 1; }
k += 1;
}
error(concat_many("FAIL ", msg, ": ", needle, " not in ", haystack));
}
# =============================================================================
# harmonic_anomaly tests
# =============================================================================
fn test_anomaly_detect_credential_stuffing() {
# 100 normal requests + 5 credential-stuffing rows.
h rows = [];
h i = 0;
while i < 100 {
arr_push(rows, [40, 200, 0, 14]); # latency, status, endpoint, hour
i += 1;
}
h attack_indices = [];
h j = 0;
while j < 5 {
arr_push(attack_indices, arr_len(rows));
arr_push(rows, [15, 401, 8, 3]); # the structural attack
j += 1;
}
h det = ha.new(["latency", "status", "endpoint", "hour"]);
ha.set_strategy(det, 1, "discrete");
ha.set_strategy(det, 2, "discrete");
ha.set_strategy(det, 3, "modulo");
ha.fit(det, rows);
h top = ha.top_k(det, rows, 5);
# All top 5 must be in the attack set.
h k = 0;
while k < arr_len(top) {
assert_in(arr_get(top, k), attack_indices,
concat_many("attack at top ", k));
k += 1;
}
}
fn test_anomaly_detect_returns_correct_arity() {
h rows = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
h det = ha.new(["a", "b", "c"]);
ha.fit(det, rows);
h top = ha.top_k(det, rows, 2);
assert_eq(arr_len(top), 2, "top_k respects k");
}
fn test_anomaly_score_is_deterministic() {
h rows = [[1, 2], [10, 20], [100, 200], [1000, 2000]];
h det = ha.new(["x", "y"]);
ha.fit(det, rows);
h s1 = ha.score(det, [1, 2]);
h s2 = ha.score(det, [1, 2]);
assert_eq(s1, s2, "score is deterministic for same input");
}
fn test_anomaly_one_shot_api() {
# ha.detect should equal ha.new + fit + top_k.
h rows = [[1, 1], [2, 2], [3, 3], [99, 99]];
h via_one_shot = ha.detect(["x", "y"], rows, 1);
h det = ha.new(["x", "y"]);
ha.fit(det, rows);
h via_steps = ha.top_k(det, rows, 1);
assert_eq(arr_get(via_one_shot, 0), arr_get(via_steps, 0),
"one-shot detect matches new+fit+top_k");
}
# =============================================================================
# harmonic_clustering tests
# =============================================================================
fn test_clustering_three_decades() {
# Three obvious clusters by magnitude.
h rows = [
[3, 5], [7, 2], [4, 8], [6, 1], [2, 9], # tiny
[12, 35], [45, 18], [88, 27], [33, 76], [55, 14], # mid
[144, 233], [200, 377], [300, 144], [500, 233] # big
];
h cl = hc.new(["x", "y"]);
hc.fit(cl, rows);
assert_eq(hc.n_clusters(cl), 3, "discovered 3 clusters by decade");
}
fn test_clustering_predict_assigns_existing_rows() {
h rows = [[5, 5], [50, 50], [500, 500]];
h cl = hc.new(["a", "b"]);
hc.fit(cl, rows);
h labels = hc.predict(cl, rows);
# Each row must get a non-negative label (it's in the training set).
h k = 0;
while k < arr_len(labels) {
assert_true(arr_get(labels, k) >= 0,
concat_many("row ", k, " gets a cluster"));
k += 1;
}
}
fn test_clustering_predict_unseen_returns_negative() {
h rows = [[5, 5], [50, 50]];
h cl = hc.new(["a", "b"]);
hc.fit(cl, rows);
# 5000 falls in an attractor bucket (decade 3) we never saw.
h label = hc.predict_one(cl, [5000, 5000]);
assert_eq(label, 0 - 1, "unseen attractor returns -1");
}
fn test_clustering_centroid_count_matches_cluster_count() {
h rows = [[1, 1], [10, 10], [100, 100]];
h cl = hc.new(["x", "y"]);
hc.fit(cl, rows);
assert_eq(arr_len(hc.centroids(cl)), hc.n_clusters(cl),
"one centroid per cluster");
}
# =============================================================================
# harmonic_recommend tests
# =============================================================================
fn test_recommend_basic_suggestion() {
h rec = hr.new();
hr.add_rating(rec, "alice", "movie_1", 5);
hr.add_rating(rec, "alice", "movie_2", 4);
hr.add_rating(rec, "bob", "movie_1", 4);
hr.add_rating(rec, "bob", "movie_3", 5);
hr.add_rating(rec, "carol", "movie_2", 5);
hr.add_rating(rec, "carol", "movie_3", 4);
hr.fit(rec);
h suggestions = hr.suggest_for(rec, "alice", 5);
# Alice rated movie_1 (5) and movie_2 (4) highly. movie_3 should
# be suggested (rated highly by bob+carol). Alice's already-rated
# items should NOT appear.
assert_in("movie_3", suggestions, "movie_3 in alice's suggestions");
h k = 0;
while k < arr_len(suggestions) {
h s = arr_get(suggestions, k);
assert_true(s != "movie_1", "no already-rated movie_1");
assert_true(s != "movie_2", "no already-rated movie_2");
k += 1;
}
}
fn test_recommend_state_persists_across_add_ratings() {
# Regression test for the equality bug: adding multiple ratings
# for the same user used to clobber the user's previous items
# because dict_value == null erroneously returned true.
h rec = hr.new();
hr.add_rating(rec, "alice", "movie_1", 5);
hr.add_rating(rec, "alice", "movie_2", 4);
hr.add_rating(rec, "alice", "movie_3", 3);
h ur = dict_get(rec, "user_ratings");
h alice_items = dict_get(ur, "alice");
assert_eq(dict_len(alice_items), 3, "alice has 3 ratings, not 1");
}
fn test_recommend_n_users_n_items_correct() {
h rec = hr.new();
hr.add_rating(rec, "u1", "i1", 5);
hr.add_rating(rec, "u2", "i2", 5);
hr.add_rating(rec, "u3", "i3", 5);
hr.fit(rec);
assert_eq(hr.n_users(rec), 3, "3 users tracked");
assert_eq(hr.n_items(rec), 3, "3 items tracked");
}
# =============================================================================
# Cross-language regression tests for the equality bug fix
# =============================================================================
fn test_dict_not_equal_to_null() {
h d = {"a": 1};
assert_true(d != null, "non-null dict != null");
}
fn test_empty_dict_not_equal_to_null() {
h d = {};
assert_true(d != null, "empty dict != null");
}
fn test_array_not_equal_to_null() {
h a = [1, 2, 3];
assert_true(a != null, "array != null");
}
fn test_function_not_equal_to_null() {
h f = fn(x) { return x * 2; };
assert_true(f != null, "function != null");
}
fn test_null_equal_to_null() {
assert_eq(null, null, "null == null");
}
fn test_zero_int_not_equal_to_null() {
# 0 == null was harder to catch but worth pinning down — both
# coerce to to_int=0 in the old code path.
assert_true(0 != null, "0 != null");
}
fn test_empty_string_not_equal_to_null() {
h s = "";
assert_true(s != null, "empty string != null");
}