Skip to content

Commit 7f579f9

Browse files
committed
Add 480+ tests, fix 3 bugs discovered by new test coverage
New tests (6481 lines across 23 files): - Arena: reset invariants, growth, sized init, destroy safety - Dynamic array: capacity, remove, clear, grow patterns - FQN: module/package/class name computation (592 lines) - Graph buffer: node/edge CRUD, dedup, merge, concurrent access - Hash table: collision, resize, iteration, edge cases - MCP: tool dispatch, argument parsing, error handling - Memory: allocation tracking, collection, limits - Pipeline: test detection (path + func name patterns) - Store: node/edge CRUD, search, batch ops, persistence - String: intern dedup, util edge cases, YAML parsing (1105 lines) - Traces: ingestion, dedup, query - Watcher: baseline, polling, change detection - Worker pool: task distribution, completion Bugs found and fixed: 1. Arena reset overflow: block_size not reset to block_sizes[0] after growth — subsequent allocations could overflow blocks[0] 2. Test path detection: missing __tests__/, tests/, spec/ directory patterns and _spec.rb suffix 3. Test func detection: "Testable" falsely matched as Go test — now requires uppercase or end-of-string after "Test" prefix
1 parent cbac7be commit 7f579f9

23 files changed

Lines changed: 6481 additions & 5 deletions

Makefile.cbm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ TEST_DISCOVER_SRCS = \
268268

269269
TEST_GRAPH_BUFFER_SRCS = tests/test_graph_buffer.c
270270

271-
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c
271+
TEST_PIPELINE_SRCS = tests/test_registry.c tests/test_pipeline.c tests/test_fqn.c tests/test_configlink.c tests/test_infrascan.c tests/test_worker_pool.c tests/test_parallel.c
272272

273273
TEST_WATCHER_SRCS = tests/test_watcher.c
274274

@@ -294,7 +294,9 @@ TEST_UI_SRCS = tests/test_ui.c
294294

295295
TEST_SECURITY_SRCS = tests/test_security.c
296296

297-
ALL_TEST_SRCS = $(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_HTTPLINK_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_SECURITY_SRCS) $(TEST_INTEGRATION_SRCS)
297+
TEST_YAML_SRCS = tests/test_yaml.c
298+
299+
ALL_TEST_SRCS = $(TEST_FOUNDATION_SRCS) $(TEST_EXTRACTION_SRCS) $(TEST_STORE_SRCS) $(TEST_CYPHER_SRCS) $(TEST_MCP_SRCS) $(TEST_DISCOVER_SRCS) $(TEST_GRAPH_BUFFER_SRCS) $(TEST_PIPELINE_SRCS) $(TEST_WATCHER_SRCS) $(TEST_LZ4_SRCS) $(TEST_SQLITE_WRITER_SRCS) $(TEST_GO_LSP_SRCS) $(TEST_C_LSP_SRCS) $(TEST_TRACES_SRCS) $(TEST_HTTPLINK_SRCS) $(TEST_CLI_SRCS) $(TEST_MEM_SRCS) $(TEST_UI_SRCS) $(TEST_SECURITY_SRCS) $(TEST_YAML_SRCS) $(TEST_INTEGRATION_SRCS)
298300

299301
# ── Build directories ────────────────────────────────────────────
300302

src/foundation/arena.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ void cbm_arena_reset(CBMArena *a) {
136136
}
137137
a->used = 0;
138138
a->total_alloc = 0;
139+
/* Reset block_size to match surviving block — prevents overflow if
140+
* block_size grew during previous allocations (e.g., 128 → 256). */
141+
if (a->nblocks == 1) {
142+
a->block_size = a->block_sizes[0];
143+
}
139144
}
140145

141146
void cbm_arena_destroy(CBMArena *a) {

src/pipeline/pass_tests.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,22 @@ bool cbm_is_test_path(const char *path) {
7676
return true;
7777
}
7878

79+
/* Directory-based: __tests__/, tests/, test/, spec/ */
80+
if (strstr(path, "__tests__/") || strstr(path, "/tests/") || strstr(path, "/test/") ||
81+
strstr(path, "/spec/")) {
82+
return true;
83+
}
84+
/* Also match if path STARTS with these directories */
85+
if (strncmp(path, "tests/", 6) == 0 || strncmp(path, "test/", 5) == 0 ||
86+
strncmp(path, "spec/", 5) == 0 || strncmp(path, "__tests__/", 10) == 0) {
87+
return true;
88+
}
89+
90+
/* Ruby: _spec.rb suffix */
91+
if (str_ends_with(path, len, "_spec.rb")) {
92+
return true;
93+
}
94+
7995
return false;
8096
}
8197

@@ -84,9 +100,17 @@ bool cbm_is_test_func_name(const char *name) {
84100
if (!name) {
85101
return false;
86102
}
87-
/* Go: Test*, Benchmark*, Example* */
88-
if (strncmp(name, "Test", 4) == 0 || strncmp(name, "Benchmark", 9) == 0 ||
89-
strncmp(name, "Example", 7) == 0) {
103+
/* Go: Test/Benchmark/Example + uppercase letter or end-of-string.
104+
* "TestFoo" = test, "Testable" = not test (lowercase after prefix). */
105+
if (strncmp(name, "Test", 4) == 0 && (name[4] == '\0' || (name[4] >= 'A' && name[4] <= 'Z'))) {
106+
return true;
107+
}
108+
if (strncmp(name, "Benchmark", 9) == 0 &&
109+
(name[9] == '\0' || (name[9] >= 'A' && name[9] <= 'Z'))) {
110+
return true;
111+
}
112+
if (strncmp(name, "Example", 7) == 0 &&
113+
(name[7] == '\0' || (name[7] >= 'A' && name[7] <= 'Z'))) {
90114
return true;
91115
}
92116
/* Python/Rust/C++/Lua/Java: test_ or test prefix (lowercase) */

src/ui/embedded_assets.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* embedded_assets.c — Generated file mapping URL paths to embedded bytes.
3+
* DO NOT EDIT — regenerated by scripts/embed-frontend.sh
4+
*/
5+
#include "ui/embedded_assets.h"
6+
#include <string.h>
7+
8+
extern const unsigned char _binary_assets_index_D1_3bntm_css_data[];
9+
extern const unsigned int _binary_assets_index_D1_3bntm_css_size;
10+
extern const unsigned char _binary_assets_index_urRrcEH6_js_data[];
11+
extern const unsigned int _binary_assets_index_urRrcEH6_js_size;
12+
extern const unsigned char _binary_index_html_data[];
13+
extern const unsigned int _binary_index_html_size;
14+
15+
cbm_embedded_file_t CBM_EMBEDDED_FILES[] = {
16+
{"/assets/index-D1-3bntm.css", _binary_assets_index_D1_3bntm_css_data, 0, "text/css"},
17+
{"/assets/index-urRrcEH6.js", _binary_assets_index_urRrcEH6_js_data, 0, "application/javascript"},
18+
{"/index.html", _binary_index_html_data, 0, "text/html"},
19+
};
20+
const int CBM_EMBEDDED_FILE_COUNT = 3;
21+
22+
static void __attribute__((constructor)) init_embedded_sizes(void) {
23+
cbm_embedded_file_t *files = CBM_EMBEDDED_FILES;
24+
files[0].size = _binary_assets_index_D1_3bntm_css_size;
25+
files[1].size = _binary_assets_index_urRrcEH6_js_size;
26+
files[2].size = _binary_index_html_size;
27+
}
28+
29+
const cbm_embedded_file_t *cbm_embedded_lookup(const char *path) {
30+
for (int i = 0; i < CBM_EMBEDDED_FILE_COUNT; i++) {
31+
if (strcmp(CBM_EMBEDDED_FILES[i].path, path) == 0) {
32+
return &CBM_EMBEDDED_FILES[i];
33+
}
34+
}
35+
return NULL;
36+
}

tests/test_arena.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,37 @@ TEST(arena_total_through_reset) {
377377
PASS();
378378
}
379379

380+
TEST(arena_reset_block_size_invariant) {
381+
/* BUG DETECTOR: after reset, block_size should match blocks[0]'s actual size.
382+
* arena_grow() updates block_size to the new (larger) block's size.
383+
* arena_reset() frees the grown blocks but does NOT restore block_size.
384+
* Result: arena thinks blocks[0] has more capacity than it actually does.
385+
* This is a heap-buffer-overflow waiting to happen. */
386+
CBMArena a;
387+
cbm_arena_init_sized(&a, 128);
388+
size_t original_block_size = a.block_size;
389+
ASSERT_EQ(original_block_size, 128);
390+
ASSERT_EQ(a.block_sizes[0], 128);
391+
392+
/* Force growth: block_size will be updated to new (larger) value */
393+
cbm_arena_alloc(&a, 100);
394+
cbm_arena_alloc(&a, 100); /* triggers grow */
395+
ASSERT_GTE(a.nblocks, 2);
396+
ASSERT_GT(a.block_size, 128); /* block_size grew */
397+
398+
/* Reset: frees grown blocks, keeps blocks[0] (which is 128 bytes) */
399+
cbm_arena_reset(&a);
400+
ASSERT_EQ(a.nblocks, 1);
401+
402+
/* CRITICAL CHECK: block_size must match blocks[0]'s actual capacity.
403+
* If block_size > block_sizes[0], the arena will allow allocations
404+
* that overflow blocks[0]. */
405+
ASSERT_EQ(a.block_size, a.block_sizes[0]);
406+
407+
cbm_arena_destroy(&a);
408+
PASS();
409+
}
410+
380411
TEST(arena_strndup_zero_len) {
381412
/* strndup with len=0 — should return empty string */
382413
CBMArena a;
@@ -419,5 +450,6 @@ SUITE(arena) {
419450
RUN_TEST(arena_calloc_zero);
420451
RUN_TEST(arena_many_large_allocs_block_growth);
421452
RUN_TEST(arena_total_through_reset);
453+
RUN_TEST(arena_reset_block_size_invariant);
422454
RUN_TEST(arena_strndup_zero_len);
423455
}

0 commit comments

Comments
 (0)