-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsplinter_test.c
More file actions
403 lines (327 loc) · 15.1 KB
/
splinter_test.c
File metadata and controls
403 lines (327 loc) · 15.1 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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
* Splinter unit tests, inspired by TAP (just made lightweight and dynamic)
* There are many backwards-compatibility hacks for older loggers that have
* on-board clock failure issues and extremely sparse FAT16 implementations.
*
* I don't generally number them, I just kind of herd them into groups that
* make the most sense. What matters is they get written :)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>
#include <linux/limits.h>
#include <time.h>
#include <stddef.h>
#include <stdint.h>
#include <stdatomic.h>
#include <stdalign.h>
#include "splinter.h"
#include "config.h"
#include <fcntl.h>
#include <stdalign.h>
#ifdef HAVE_VALGRIND_H
#include <valgrind/valgrind.h>
#endif
#ifndef TIME_T_MAX
#define TIME_T_MAX 0x00
#endif
/* Tracker for enumeration tests */
struct enum_tracker {
int count;
char last_key[SPLINTER_KEY_MAX];
};
static void test_enum_callback(const char *key, uint64_t epoch, void *data) {
struct enum_tracker *t = (struct enum_tracker *)data;
t->count++;
strncpy(t->last_key, key, SPLINTER_KEY_MAX - 1);
(void)epoch; // unused in this specific check
}
/*
* Returns true on success, false if the value cannot be represented.
* Older data loggers can wake up pre-1970 on battery changes, so we
* need to not overflow signedness.
*/
static bool time_to_unsigned_long(time_t src, unsigned long *dst)
{
/* Reject negative timestamps. */
if (src < 0) {
return false;
}
/* Ensure the destination type is large enough.
This works at compile time for the common cases,
and falls back to a run‑time check when necessary. */
#if ULONG_MAX >= TIME_T_MAX
/* On platforms where unsigned long can already hold any time_t value,
we can just cast. */
(void)TIME_T_MAX; // silence unused‑macro warning
#else
/* Otherwise we need a run‑time guard. */
if ((unsigned long)src > ULONG_MAX) {
return false; // overflow would occur
}
#endif
*dst = (unsigned long)src;
return true;
}
/* test statistics */
static int total = 0;
static int passed = 0;
/* this is used to make temporary stores / keys */
static pid_t pid = 0;
#define TEST(name, expr) do { \
total++; \
if (expr) { \
passed++; \
printf("ok %d - %s\n", total, name); \
} else { \
printf("not ok %d - %s\n", total, name); \
} \
} while (0)
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
int main(void) {
char bus[16] = { 0 };
char buspath[PATH_MAX] = { 0 };
pid = getpid();
snprintf(bus, 16, "%d-tap-test", pid);
TEST("splinter slot 64 byte alignment check", (alignof(struct splinter_slot) == 64));
TEST("create splinter store", splinter_create_or_open(bus, 1000, 4096) == 0);
const char *test_key = "test_key";
const char *test_value = "hello world";
TEST("set key-value pair", splinter_set(test_key, test_value, strlen(test_value)) == 0);
char buf[256];
size_t out_sz;
TEST("get key-value pair", splinter_get(test_key, buf, sizeof(buf), &out_sz) == 0);
buf[out_sz] = '\0';
TEST("retrieved value matches", strcmp(buf, test_value) == 0);
TEST("retrieved size is correct", out_sz == strlen(test_value));
size_t query_sz;
TEST("query size with NULL buffer", splinter_get(test_key, NULL, 0, &query_sz) == 0);
TEST("queried size matches", query_sz == strlen(test_value));
const char *new_value = "updated value";
TEST("update existing key", splinter_set(test_key, new_value, strlen(new_value)) == 0);
TEST("get updated value", splinter_get(test_key, buf, sizeof(buf), &out_sz) == 0);
buf[out_sz] = '\0';
TEST("updated value is correct", strcmp(buf, new_value) == 0);
TEST("set second key", splinter_set("key2", "value2", 6) == 0);
TEST("set third key", splinter_set("key3", "value3", 6) == 0);
char *key_list[10];
size_t key_count;
TEST("list keys", splinter_list(key_list, 10, &key_count) == 0);
TEST("correct number of keys", key_count == 3);
TEST("unset key", splinter_unset("key2") >= 0);
int original_av = splinter_get_av();
TEST("set auto scrubbing mode", splinter_set_av(0) == 0);
TEST("get auto scrubbing mode", splinter_get_av() == 0);
splinter_set_av((uint32_t) original_av);
splinter_header_snapshot_t snap = { 0 };
TEST("get header snapshot", splinter_get_header_snapshot(&snap) == 0);
TEST("magic number greater than zero", snap.magic > 0);
TEST("epoch greater than zero", snap.epoch > 0);
TEST("auto_scrubbing is really off", (snap.core_flags & SPL_SYS_AUTO_SCRUB) == 0 ? 1 : 0);
TEST("slots are non-zero", snap.slots > 0);
splinter_slot_snapshot_t snap1 = { 0 };
TEST("create header snapshot key", splinter_set("header_snap", "hello", 5) == 0);
TEST("take snapshot of header_snap slot metadata", splinter_get_slot_snapshot("header_snap", &snap1) == 0);
TEST("snap1 epoch is nonzero", snap1.epoch > 0);
TEST("length of header_snap is 5: h e l l o", snap1.val_len == 5);
splinter_slot_snapshot_t snap2 = { 0 };
TEST("name slot as text", splinter_set_named_type("header_snap", SPL_SLOT_TYPE_VARTEXT) == 0);
TEST("re-acquire snapshot to test named type", splinter_get_slot_snapshot("header_snap", &snap2) ==0);
TEST("ensure header_snap is SPL_SLOT_TYPE_VARTEXT", (snap2.type_flag & SPL_SLOT_TYPE_VARTEXT) != 0);
TEST("ensure header_snap is not also SPL_SLOT_TYPE_JSON", (snap2.type_flag & SPL_SLOT_TYPE_JSON) == 0);
time_t curtime = time(NULL);
unsigned long longtime = 0;
TEST("host can convert time_t to unsigned long and temporal tests can continue",
time_to_unsigned_long(curtime, &longtime) == true);
splinter_slot_snapshot_t snap3 = { 0 };
TEST("set key creation time", splinter_set_slot_time("header_snap", SPL_TIME_CTIME, curtime, 0) == 0);
TEST("set key last access time", splinter_set_slot_time("header_snap", SPL_TIME_ATIME, curtime, 0) == 0);
TEST("rea-acquire snapshot to test timestamps", splinter_get_slot_snapshot("header_snap", &snap3) == 0);
TEST("snapshot ctime = snapshot curtime", (snap3.ctime == longtime));
TEST("snapshot atime = snapshot curtime", (snap3.atime == longtime));
splinter_unset("header_snap");
#ifdef SPLINTER_EMBEDDINGS
float mock_vec[SPLINTER_EMBED_DIM] = { 0 };
for (int i = 0; i < SPLINTER_EMBED_DIM; i++) {
mock_vec[i] = (float)i * 0.1f; // Linear mock values
}
TEST("set 768-dim embedding", splinter_set_embedding(test_key, mock_vec) == 0);
float read_vec[SPLINTER_EMBED_DIM] = { 0 };
TEST("get 768-dim embedding", splinter_get_embedding(test_key, read_vec) == 0);
int vec_match = 1;
for (int i = 0; i < SPLINTER_EMBED_DIM; i++) {
if (read_vec[i] != mock_vec[i]) {
vec_match = 0;
break;
}
}
TEST("embedding vector data matches exactly", vec_match == 1);
splinter_slot_snapshot_t embed_snap = { 0 };
TEST("get slot snapshot with embedding", splinter_get_slot_snapshot(test_key, &embed_snap) == 0);
TEST("snapshot embedding encapsulation check",
embed_snap.embedding[0] == mock_vec[0] &&
embed_snap.embedding[SPLINTER_EMBED_DIM-1] == mock_vec[SPLINTER_EMBED_DIM-1]);
#endif // SPLINTER_EMBEDDINGS
const char *int_key = "atomic_int";
uint64_t initial_val = 0xF0F0F0F0F0F0F0F0ULL; // Alternating nibbles
uint64_t op_val, result;
TEST("set initial uint64 value", splinter_set(int_key, &initial_val, sizeof(uint64_t)) == 0);
TEST("name slot as BIGUINT", splinter_set_named_type(int_key, SPL_SLOT_TYPE_BIGUINT) == 0);
op_val = 0x0F0F0F0F0F0F0F0FUL;
TEST("op: OR (0xF0.. | 0x0F..)", splinter_integer_op(int_key, SPL_OP_OR, &op_val) == 0);
splinter_get(int_key, &result, sizeof(uint64_t), &out_sz);
TEST("result is all Fs", result == 0xFFFFFFFFFFFFFFFFUL);
op_val = 0xAAAAAAAAAAAAAAAAUL;
TEST("op: AND (0xFF.. & 0xAA..)", splinter_integer_op(int_key, SPL_OP_AND, &op_val) == 0);
splinter_get(int_key, &result, sizeof(uint64_t), &out_sz);
TEST("result is 0xAA..", result == 0xAAAAAAAAAAAAAAAAUL);
op_val = 0xAAAAAAAAAAAAAAAAUL;
TEST("op: XOR (0xAA.. ^ 0xAA..)", splinter_integer_op(int_key, SPL_OP_XOR, &op_val) == 0);
splinter_get(int_key, &result, sizeof(uint64_t), &out_sz);
TEST("result is 0x00 (Identity)", result == 0x00UL);
// Set to max of first byte to test carry-over to second byte
initial_val = 0xFFUL;
splinter_set(int_key, &initial_val, sizeof(uint64_t));
op_val = 1;
TEST("op: INC (0xFF + 1 carry check)", splinter_integer_op(int_key, SPL_OP_INC, &op_val) == 0);
splinter_get(int_key, &result, sizeof(uint64_t), &out_sz);
TEST("carry successful (0x100)", result == 0x100UL);
op_val = 1;
TEST("op: DEC (0x100 - 1 borrow check)", splinter_integer_op(int_key, SPL_OP_DEC, &op_val) == 0);
splinter_get(int_key, &result, sizeof(uint64_t), &out_sz);
TEST("borrow successful (0xFF)", result == 0xFFUL);
// mask is ignored for NOT, but we pass it to satisfy signature
TEST("op: NOT (~0x00...0xFF)", splinter_integer_op(int_key, SPL_OP_NOT, &op_val) == 0);
splinter_get(int_key, &result, sizeof(uint64_t), &out_sz);
TEST("result is inverted (~0xFF)", result == 0xFFFFFFFFFFFFFF00UL);
// our only real "opinion" is you can't bit-twiddle text
const char *text_key = "text_only";
splinter_set(text_key, "data", 4);
splinter_set_named_type(text_key, SPL_SLOT_TYPE_VARTEXT);
TEST("enforce EPROTOTYPE on non-BIGUINT slot", splinter_integer_op(text_key, SPL_OP_INC, &op_val) == -1 && errno == EPROTOTYPE);
/* --- Tandem / Multi-Order Key Tests --- */
const char *base_key = "multi_part_sensor";
const char *val0 = "part_zero";
const char *val1 = "part_one";
const char *val2 = "part_two";
const void *vals[] = { val0, val1, val2 };
size_t lens[] = { strlen(val0), strlen(val1), strlen(val2) };
uint8_t orders = 3;
TEST("client_set_tandem (3 orders)",
splinter_client_set_tandem(base_key, vals, lens, orders) == 0);
char buf_verify[64];
size_t out_verify;
TEST("verify base key exists", splinter_get(base_key, buf_verify, 64, &out_verify) == 0);
TEST("verify order .1 exists", splinter_get("multi_part_sensor.1", buf_verify, 64, &out_verify) == 0);
TEST("verify order .2 exists", splinter_get("multi_part_sensor.2", buf_verify, 64, &out_verify) == 0);
splinter_client_unset_tandem(base_key, orders);
TEST("verify base key was unset", splinter_get(base_key, buf_verify, 64, &out_verify) != 0);
TEST("verify order .1 was unset", splinter_get("multi_part_sensor.1", buf_verify, 64, &out_verify) != 0);
TEST("verify order .2 was unset", splinter_get("multi_part_sensor.2", buf_verify, 64, &out_verify) != 0);
// --- Signal Arena Verification via Snapshots ---
const char *sig_key = "signal_test";
splinter_set(sig_key, "data", 4);
TEST("register watch group 5", splinter_watch_register(sig_key, 5) == 0);
splinter_header_snapshot_t snap_before = { 0 };
splinter_get_header_snapshot(&snap_before);
// Pulse the watcher via a set operation.
// This should increment the slot epoch, the signal counter, AND the global epoch.
splinter_set(sig_key, "updated", 7);
splinter_header_snapshot_t snap_after = { 0 };
splinter_get_header_snapshot(&snap_after);
// We verify the pulse reached the header by checking the global epoch delta
TEST("global epoch incremented after signal pulse", snap_after.epoch > snap_before.epoch);
// Test 2: Unregister logic
splinter_watch_unregister(sig_key, 5);
splinter_get_header_snapshot(&snap_before);
splinter_set(sig_key, "no_watch", 8);
splinter_get_header_snapshot(&snap_after);
// The epoch still increments because of the set, but we've verified the path is clean
TEST("epoch still advances on unmapped set", snap_after.epoch > snap_before.epoch);
// --- Bloom Label Tests ---
const uint64_t TEST_LABEL = (1ULL << 3);
const uint8_t TEST_GROUP = 10;
TEST("register label watch (bit 3 -> group 10)",
splinter_watch_label_register(TEST_LABEL, TEST_GROUP) == 0);
splinter_header_snapshot_t b_before = { 0 };
splinter_get_header_snapshot(&b_before);
// 1. Tag a key with the label
splinter_set("sensor_01", "val", 3);
splinter_set_label("sensor_01", TEST_LABEL);
// 2. This set triggers pulse_watchers, which sees the bloom match
splinter_set("sensor_01", "pulse", 5);
splinter_header_snapshot_t b_after = { 0 };
splinter_get_header_snapshot(&b_after);
TEST("label watch triggered pulse (global epoch check)", b_after.epoch > b_before.epoch);
/* --- Enumerator Tests --- */
struct enum_tracker tracker = { 0, "" };
const uint64_t ENUM_LABEL = (1ULL << 5);
// Setup: two keys with the same label, one without
splinter_set("enum_01", "val", 3);
splinter_set_label("enum_01", ENUM_LABEL);
splinter_set("enum_02", "val", 3);
splinter_set_label("enum_02", ENUM_LABEL);
splinter_set("enum_skip", "val", 3); // No label
// Execute enumeration
splinter_enumerate_matches(ENUM_LABEL, test_enum_callback, &tracker);
TEST("enumerate matches found correct number of keys (2)", tracker.count == 2);
TEST("enumerate matches found expected key names",
strcmp(tracker.last_key, "enum_02") == 0 || strcmp(tracker.last_key, "enum_01") == 0);
/* --- Hybrid Auto-Scrub & Hygiene Tests --- */
// Test the "One Fell Swoop" transition
TEST("set hybrid av mode (combined gate + mop)", splinter_set_hybrid_av() == 0);
TEST("get hybrid av mode returns 1", splinter_get_hybrid_av() == 1);
TEST("hybrid av automatically enabled regular av gate", splinter_get_av() == 1);
splinter_header_snapshot_t hybrid_snap = { 0 };
splinter_get_header_snapshot(&hybrid_snap);
TEST("header snapshot confirms both bits (AUTO | HYBRID) are set",
(hybrid_snap.core_flags & SPL_SYS_AUTO_SCRUB) &&
(hybrid_snap.core_flags & SPL_SYS_HYBRID_SCRUB));
// Verify the reset: one motion to clear the path
TEST("set_av(0) clears both bits in one motion", splinter_set_av(0) == 0);
TEST("get hybrid av returns 0 after full reset", splinter_get_hybrid_av() == 0);
TEST("get regular av returns 0 after full reset", splinter_get_av() == 0);
/* --- Purge / Centerline Sweep Tests --- */
// Setup the bus for a sweep: one active key, one empty slot
const char *active_key = "survivor_key";
const char *purge_key = "ghost_key";
splinter_set(active_key, "data_to_keep", 12);
splinter_set(purge_key, "temporary_data", 14);
// Unset the ghost key so the purge has an empty slot to boil
splinter_unset(purge_key);
splinter_purge();
// if we get here without access violations, it worked.
TEST("splinter_purge execution completed", 1);
// Verify that the 'passive substrate' still holds valid data for active keys
char verify_buf[64];
size_t verify_sz;
TEST("active data survives hygiene sweep",
splinter_get(active_key, verify_buf, sizeof(verify_buf), &verify_sz) == 0);
verify_buf[verify_sz] = '\0';
TEST("verified content integrity after purge", strcmp(verify_buf, "data_to_keep") == 0);
splinter_close();
splinter_header_snapshot_t closed = { 0 };
TEST("store actually closed", splinter_get_header_snapshot(&closed) != 0);
#ifndef SPLINTER_PERSISTENT
snprintf(buspath, sizeof(buspath) -1, "/dev/shm/%s", bus);
#else
snprintf(buspath, sizeof(buspath) -1, "./%s", bus);
#endif /* SPLINTER_PERSISTENT */
unlink(buspath);
#ifdef HAVE_VALGRIND_H
if (RUNNING_ON_VALGRIND) {
printf("\n** Valgrind Detected. Thank you for your diligence! **\n\n");
if (VALGRIND_COUNT_ERRORS) {
fprintf(stderr,"\nValgrind detected errors in this run. Exiting abnormally.\n");
fprintf(stderr, "(sad trombone sound)\n");
return 1;
}
}
#endif // HAVE_VALGRIND_H
return (passed == total) ? 0 : 1;
}