Files
codebase-memory-mcp/tests/test_discover.c
T
Ivan Porto Carrero 9e2bb92895 fix(arena): NULL-arena guard in cbm_arena_alloc; skip vendored/ dirs
- cbm_arena_alloc returns NULL on a NULL arena (both arena.c copies)
  instead of dereferencing it — defense-in-depth against the NULL-arena
  type-allocation path that could crash the LSP type layer.
- discover: add "vendored" to the always-skip directory list.

Distilled from #374, taking the parts not already on main. The PR's
ts_lsp tuple-arena allocation, the C/C++ template formal-count clamp,
and the type_rep unbound-param preservation it also carried all landed
independently in v0.7.0 / via #322 / #360, so only these two
defensive improvements remain. Relates to #390.
2026-05-30 18:26:44 +02:00

770 lines
21 KiB
C

/*
* test_discover.c — Tests for directory skip logic, suffix filters, and file walk.
*
* RED phase: Tests define expected filtering behavior for the discover module.
*/
#include "test_framework.h"
#include "test_helpers.h"
#include "discover/discover.h"
/* ── Directory skip (always skipped) ───────────────────────────── */
TEST(skip_git) {
ASSERT_TRUE(cbm_should_skip_dir(".git", CBM_MODE_FULL));
PASS();
}
TEST(skip_node_modules) {
ASSERT_TRUE(cbm_should_skip_dir("node_modules", CBM_MODE_FULL));
PASS();
}
TEST(skip_pycache) {
ASSERT_TRUE(cbm_should_skip_dir("__pycache__", CBM_MODE_FULL));
PASS();
}
TEST(skip_venv) {
ASSERT_TRUE(cbm_should_skip_dir("venv", CBM_MODE_FULL));
PASS();
}
TEST(skip_dist) {
ASSERT_TRUE(cbm_should_skip_dir("dist", CBM_MODE_FULL));
PASS();
}
TEST(skip_target) {
ASSERT_TRUE(cbm_should_skip_dir("target", CBM_MODE_FULL));
PASS();
}
TEST(skip_vendor) {
ASSERT_TRUE(cbm_should_skip_dir("vendor", CBM_MODE_FULL));
PASS();
}
TEST(skip_vendored) {
ASSERT_TRUE(cbm_should_skip_dir("vendored", CBM_MODE_FULL));
PASS();
}
TEST(skip_terraform) {
ASSERT_TRUE(cbm_should_skip_dir(".terraform", CBM_MODE_FULL));
PASS();
}
TEST(skip_coverage) {
ASSERT_TRUE(cbm_should_skip_dir("coverage", CBM_MODE_FULL));
PASS();
}
TEST(skip_idea) {
ASSERT_TRUE(cbm_should_skip_dir(".idea", CBM_MODE_FULL));
PASS();
}
TEST(skip_claude) {
ASSERT_TRUE(cbm_should_skip_dir(".claude", CBM_MODE_FULL));
PASS();
}
/* Not skipped in full mode */
TEST(no_skip_src) {
ASSERT_FALSE(cbm_should_skip_dir("src", CBM_MODE_FULL));
PASS();
}
TEST(no_skip_lib) {
ASSERT_FALSE(cbm_should_skip_dir("lib", CBM_MODE_FULL));
PASS();
}
TEST(no_skip_docs_full) {
ASSERT_FALSE(cbm_should_skip_dir("docs", CBM_MODE_FULL));
PASS();
}
TEST(no_skip_test_full) {
ASSERT_FALSE(cbm_should_skip_dir("__tests__", CBM_MODE_FULL));
PASS();
}
/* Fast mode additional skips */
TEST(skip_fast_docs) {
ASSERT_TRUE(cbm_should_skip_dir("docs", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_examples) {
ASSERT_TRUE(cbm_should_skip_dir("examples", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_tests) {
ASSERT_TRUE(cbm_should_skip_dir("__tests__", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_fixtures) {
ASSERT_TRUE(cbm_should_skip_dir("fixtures", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_testdata) {
ASSERT_TRUE(cbm_should_skip_dir("testdata", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_generated) {
ASSERT_TRUE(cbm_should_skip_dir("generated", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_assets) {
ASSERT_TRUE(cbm_should_skip_dir("assets", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_3rdparty) {
ASSERT_TRUE(cbm_should_skip_dir("third_party", CBM_MODE_FAST));
PASS();
}
TEST(skip_fast_e2e) {
ASSERT_TRUE(cbm_should_skip_dir("e2e", CBM_MODE_FAST));
PASS();
}
/* ── Suffix filters ────────────────────────────────────────────── */
TEST(suffix_pyc) {
ASSERT_TRUE(cbm_has_ignored_suffix("module.pyc", CBM_MODE_FULL));
PASS();
}
TEST(suffix_o) {
ASSERT_TRUE(cbm_has_ignored_suffix("main.o", CBM_MODE_FULL));
PASS();
}
TEST(suffix_so) {
ASSERT_TRUE(cbm_has_ignored_suffix("lib.so", CBM_MODE_FULL));
PASS();
}
TEST(suffix_png) {
ASSERT_TRUE(cbm_has_ignored_suffix("icon.png", CBM_MODE_FULL));
PASS();
}
TEST(suffix_jpg) {
ASSERT_TRUE(cbm_has_ignored_suffix("photo.jpg", CBM_MODE_FULL));
PASS();
}
TEST(suffix_wasm) {
ASSERT_TRUE(cbm_has_ignored_suffix("app.wasm", CBM_MODE_FULL));
PASS();
}
TEST(suffix_db) {
ASSERT_TRUE(cbm_has_ignored_suffix("data.db", CBM_MODE_FULL));
PASS();
}
TEST(suffix_sqlite) {
ASSERT_TRUE(cbm_has_ignored_suffix("store.sqlite3", CBM_MODE_FULL));
PASS();
}
TEST(suffix_tmp) {
ASSERT_TRUE(cbm_has_ignored_suffix("file.tmp", CBM_MODE_FULL));
PASS();
}
TEST(suffix_tilde) {
ASSERT_TRUE(cbm_has_ignored_suffix("file~", CBM_MODE_FULL));
PASS();
}
/* Not ignored */
TEST(suffix_go) {
ASSERT_FALSE(cbm_has_ignored_suffix("main.go", CBM_MODE_FULL));
PASS();
}
TEST(suffix_py) {
ASSERT_FALSE(cbm_has_ignored_suffix("app.py", CBM_MODE_FULL));
PASS();
}
TEST(suffix_c) {
ASSERT_FALSE(cbm_has_ignored_suffix("lib.c", CBM_MODE_FULL));
PASS();
}
/* Fast mode additional suffixes */
TEST(suffix_fast_zip) {
ASSERT_TRUE(cbm_has_ignored_suffix("archive.zip", CBM_MODE_FAST));
PASS();
}
TEST(suffix_fast_pdf) {
ASSERT_TRUE(cbm_has_ignored_suffix("manual.pdf", CBM_MODE_FAST));
PASS();
}
TEST(suffix_fast_mp3) {
ASSERT_TRUE(cbm_has_ignored_suffix("sound.mp3", CBM_MODE_FAST));
PASS();
}
TEST(suffix_fast_pem) {
ASSERT_TRUE(cbm_has_ignored_suffix("cert.pem", CBM_MODE_FAST));
PASS();
}
/* ── Filename skip (fast mode) ─────────────────────────────────── */
TEST(fn_skip_license) {
ASSERT_TRUE(cbm_should_skip_filename("LICENSE", CBM_MODE_FAST));
PASS();
}
TEST(fn_skip_changelog) {
ASSERT_TRUE(cbm_should_skip_filename("CHANGELOG.md", CBM_MODE_FAST));
PASS();
}
TEST(fn_skip_gosum) {
ASSERT_TRUE(cbm_should_skip_filename("go.sum", CBM_MODE_FAST));
PASS();
}
TEST(fn_skip_yarnlock) {
ASSERT_TRUE(cbm_should_skip_filename("yarn.lock", CBM_MODE_FAST));
PASS();
}
TEST(fn_skip_pkglock) {
ASSERT_TRUE(cbm_should_skip_filename("package-lock.json", CBM_MODE_FAST));
PASS();
}
/* Not skipped in full mode */
TEST(fn_no_skip_license_full) {
ASSERT_FALSE(cbm_should_skip_filename("LICENSE", CBM_MODE_FULL));
PASS();
}
/* ── Fast mode patterns ────────────────────────────────────────── */
TEST(pattern_dts) {
ASSERT_TRUE(cbm_matches_fast_pattern("types.d.ts", CBM_MODE_FAST));
PASS();
}
TEST(pattern_pbgo) {
ASSERT_TRUE(cbm_matches_fast_pattern("service.pb.go", CBM_MODE_FAST));
PASS();
}
TEST(pattern_pb2py) {
ASSERT_TRUE(cbm_matches_fast_pattern("api_pb2.py", CBM_MODE_FAST));
PASS();
}
TEST(pattern_mock) {
ASSERT_TRUE(cbm_matches_fast_pattern("mock_service.go", CBM_MODE_FAST));
PASS();
}
TEST(pattern_test_dot) {
ASSERT_TRUE(cbm_matches_fast_pattern("App.test.js", CBM_MODE_FAST));
PASS();
}
TEST(pattern_spec) {
ASSERT_TRUE(cbm_matches_fast_pattern("App.spec.ts", CBM_MODE_FAST));
PASS();
}
TEST(pattern_stories) {
ASSERT_TRUE(cbm_matches_fast_pattern("Button.stories.tsx", CBM_MODE_FAST));
PASS();
}
/* Not matched in full mode */
TEST(pattern_dts_full) {
ASSERT_FALSE(cbm_matches_fast_pattern("types.d.ts", CBM_MODE_FULL));
PASS();
}
/* ── File discovery (integration) — cross-platform via test_helpers.h ── */
TEST(discover_simple) {
char *base = th_mktempdir("cbm_disc_simple");
ASSERT(base != NULL);
th_write_file(TH_PATH(base, "src/main.go"), "package main\n");
th_write_file(TH_PATH(base, "src/app.py"), "print(1)\n");
th_write_file(TH_PATH(base, "src/icon.png"), "binary\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 2); /* main.go + app.py, not icon.png */
bool found_go = false, found_py = false;
for (int i = 0; i < count; i++) {
if (files[i].language == CBM_LANG_GO)
found_go = true;
if (files[i].language == CBM_LANG_PYTHON)
found_py = true;
}
ASSERT_TRUE(found_go);
ASSERT_TRUE(found_py);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_skips_git_dir) {
char *base = th_mktempdir("cbm_disc_git");
ASSERT(base != NULL);
th_mkdir_p(TH_PATH(base, ".git"));
th_write_file(TH_PATH(base, ".git/config"), "x\n");
th_write_file(TH_PATH(base, "src/main.go"), "package main\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_with_gitignore) {
char *base = th_mktempdir("cbm_disc_gi");
ASSERT(base != NULL);
th_mkdir_p(TH_PATH(base, ".git"));
th_write_file(TH_PATH(base, ".gitignore"), "*.log\n");
th_write_file(TH_PATH(base, "src/main.go"), "package main\n");
th_write_file(TH_PATH(base, "src/debug.log"), "error\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
ASSERT_EQ(files[0].language, CBM_LANG_GO);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_max_file_size) {
char *base = th_mktempdir("cbm_disc_size");
ASSERT(base != NULL);
th_write_file(TH_PATH(base, "small.go"), "small\n");
/* Create a large file (> 1KB) */
char bigpath[512];
snprintf(bigpath, sizeof(bigpath), "%s/big.go", base);
FILE *f = fopen(bigpath, "w");
ASSERT(f != NULL);
for (int i = 0; i < 200; i++) {
fprintf(f, "// padding line %d to exceed 1KB\n", i);
}
fclose(f);
cbm_discover_opts_t opts = {0};
opts.max_file_size = 1024;
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_null_path) {
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(NULL, NULL, &files, &count);
ASSERT_EQ(rc, -1);
PASS();
}
TEST(discover_nonexistent_path) {
char *base = th_mktempdir("cbm_disc_noexist");
char fake[512];
snprintf(fake, sizeof(fake), "%s/nonexistent_12345", base ? base : "/tmp");
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(fake, NULL, &files, &count);
ASSERT_EQ(rc, -1);
th_cleanup(base);
PASS();
}
TEST(discover_free_null) {
cbm_discover_free(NULL, 0);
PASS();
}
TEST(discover_skips_worktrees) {
char *base = th_mktempdir("cbm_disc_wt");
ASSERT(base != NULL);
th_write_file(TH_PATH(base, "src/main.go"), "package main\n");
th_write_file(TH_PATH(base, ".worktrees/feature/src/app.go"), "package app\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
bool found_main = false;
for (int i = 0; i < count; i++) {
if (strstr(files[i].rel_path, "main.go"))
found_main = true;
ASSERT_NULL(strstr(files[i].rel_path, ".worktrees"));
}
ASSERT_TRUE(found_main);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_cbmignore) {
char *base = th_mktempdir("cbm_disc_cbmi");
ASSERT(base != NULL);
th_mkdir_p(TH_PATH(base, ".git"));
th_write_file(TH_PATH(base, ".cbmignore"), "generated/\n*.pb.go\n");
th_write_file(TH_PATH(base, "main.go"), "package main\n");
th_write_file(TH_PATH(base, "generated/types.go"), "package gen\n");
th_write_file(TH_PATH(base, "api.pb.go"), "package api\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
ASSERT_TRUE(strstr(files[0].rel_path, "main.go") != NULL);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_cbmignore_stacks) {
char *base = th_mktempdir("cbm_disc_stack");
ASSERT(base != NULL);
th_mkdir_p(TH_PATH(base, ".git"));
th_write_file(TH_PATH(base, ".gitignore"), "*.log\n");
th_write_file(TH_PATH(base, ".cbmignore"), "docs/\n");
th_write_file(TH_PATH(base, "main.go"), "package main\n");
th_write_file(TH_PATH(base, "docs/api.go"), "package docs\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
bool found_docs = false;
for (int i = 0; i < count; i++) {
if (strstr(files[i].rel_path, "docs/"))
found_docs = true;
}
ASSERT_FALSE(found_docs);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_symlink_skipped) {
#ifdef _WIN32
/* Symlinks require elevated privileges on Windows — skip.
* Guard the entire body: symlink() doesn't exist on Windows. */
SKIP("symlinks need admin on Windows");
#else
char *base = th_mktempdir("cbm_disc_sym");
ASSERT(base != NULL);
th_write_file(TH_PATH(base, "real.go"), "package main\n");
char real_path[512], link_path[512];
snprintf(real_path, sizeof(real_path), "%s/real.go", base);
snprintf(link_path, sizeof(link_path), "%s/link.go", base);
symlink(real_path, link_path);
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
bool found_real = false, found_link = false;
for (int i = 0; i < count; i++) {
if (strstr(files[i].rel_path, "real.go"))
found_real = true;
if (strstr(files[i].rel_path, "link.go"))
found_link = true;
}
ASSERT_TRUE(found_real);
ASSERT_FALSE(found_link);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
#endif
}
TEST(discover_new_ignore_patterns) {
char *base = th_mktempdir("cbm_disc_newign");
ASSERT(base != NULL);
const char *dirs[] = {".next", ".terraform", "zig-cache", ".cargo", "elm-stuff", "bazel-out"};
for (int i = 0; i < 6; i++) {
char path[512];
snprintf(path, sizeof(path), "%s/%s/file.go", base, dirs[i]);
th_write_file(path, "package x\n");
}
th_write_file(TH_PATH(base, "main.go"), "package main\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
ASSERT_TRUE(strstr(files[0].rel_path, "main.go") != NULL);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_generic_dirs_full_mode) {
char *base = th_mktempdir("cbm_disc_genfull");
ASSERT(base != NULL);
th_write_file(TH_PATH(base, "bin/main.go"), "package bin\n");
th_write_file(TH_PATH(base, "build/main.go"), "package build\n");
th_write_file(TH_PATH(base, "out/main.go"), "package out\n");
cbm_discover_opts_t opts = {.mode = CBM_MODE_FULL};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 3);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_generic_dirs_fast_mode) {
char *base = th_mktempdir("cbm_disc_genfast");
ASSERT(base != NULL);
th_write_file(TH_PATH(base, "bin/main.go"), "package bin\n");
th_write_file(TH_PATH(base, "build/main.go"), "package build\n");
th_write_file(TH_PATH(base, "out/main.go"), "package out\n");
cbm_discover_opts_t opts = {.mode = CBM_MODE_FAST};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 0);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_cbmignore_no_git) {
char *base = th_mktempdir("cbm_disc_nogit");
ASSERT(base != NULL);
th_write_file(TH_PATH(base, ".cbmignore"), "scratch/\n");
th_write_file(TH_PATH(base, "main.go"), "package main\n");
th_write_file(TH_PATH(base, "scratch/tmp.go"), "package scratch\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
ASSERT_EQ(count, 1);
ASSERT_TRUE(strstr(files[0].rel_path, "main.go") != NULL);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
/* ── Nested .gitignore tests (issue #178) ──────────────────────── */
TEST(discover_nested_gitignore) {
char *base = th_mktempdir("cbm_disc_ngi");
ASSERT(base != NULL);
th_mkdir_p(TH_PATH(base, ".git"));
th_write_file(TH_PATH(base, "main.go"), "package main\n");
th_write_file(TH_PATH(base, "webapp/.gitignore"), "generated/\n");
th_write_file(TH_PATH(base, "webapp/src/routes.js"), "export default []\n");
th_write_file(TH_PATH(base, "webapp/generated/types.js"), "export {}\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
bool found_generated = false;
bool found_routes = false;
for (int i = 0; i < count; i++) {
if (strstr(files[i].rel_path, "generated"))
found_generated = true;
if (strstr(files[i].rel_path, "routes.js"))
found_routes = true;
}
ASSERT_FALSE(found_generated);
ASSERT_TRUE(found_routes);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
TEST(discover_nested_gitignore_stacks_with_root) {
char *base = th_mktempdir("cbm_disc_ngi_stack");
ASSERT(base != NULL);
th_mkdir_p(TH_PATH(base, ".git"));
th_write_file(TH_PATH(base, ".gitignore"), "*.log\n");
th_write_file(TH_PATH(base, "webapp/.gitignore"), ".output/\n");
th_write_file(TH_PATH(base, "main.go"), "package main\n");
th_write_file(TH_PATH(base, "error.log"), "error log\n");
th_write_file(TH_PATH(base, "webapp/src/app.js"), "const x = 1\n");
th_write_file(TH_PATH(base, "webapp/.output/data.js"), "output data\n");
cbm_discover_opts_t opts = {0};
cbm_file_info_t *files = NULL;
int count = 0;
int rc = cbm_discover(base, &opts, &files, &count);
ASSERT_EQ(rc, 0);
bool found_log = false;
bool found_output = false;
bool found_main = false;
bool found_app = false;
for (int i = 0; i < count; i++) {
if (strstr(files[i].rel_path, ".log"))
found_log = true;
if (strstr(files[i].rel_path, ".output"))
found_output = true;
if (strstr(files[i].rel_path, "main.go"))
found_main = true;
if (strstr(files[i].rel_path, "app.js"))
found_app = true;
}
ASSERT_FALSE(found_log);
ASSERT_FALSE(found_output);
ASSERT_TRUE(found_main);
ASSERT_TRUE(found_app);
cbm_discover_free(files, count);
th_cleanup(base);
PASS();
}
/* ── Suite ─────────────────────────────────────────────────────── */
SUITE(discover) {
/* Directory skip — always */
RUN_TEST(skip_git);
RUN_TEST(skip_node_modules);
RUN_TEST(skip_pycache);
RUN_TEST(skip_venv);
RUN_TEST(skip_dist);
RUN_TEST(skip_target);
RUN_TEST(skip_vendor);
RUN_TEST(skip_vendored);
RUN_TEST(skip_terraform);
RUN_TEST(skip_coverage);
RUN_TEST(skip_idea);
RUN_TEST(skip_claude);
/* Not skipped */
RUN_TEST(no_skip_src);
RUN_TEST(no_skip_lib);
RUN_TEST(no_skip_docs_full);
RUN_TEST(no_skip_test_full);
/* Fast mode directory skips */
RUN_TEST(skip_fast_docs);
RUN_TEST(skip_fast_examples);
RUN_TEST(skip_fast_tests);
RUN_TEST(skip_fast_fixtures);
RUN_TEST(skip_fast_testdata);
RUN_TEST(skip_fast_generated);
RUN_TEST(skip_fast_assets);
RUN_TEST(skip_fast_3rdparty);
RUN_TEST(skip_fast_e2e);
/* Suffix filters */
RUN_TEST(suffix_pyc);
RUN_TEST(suffix_o);
RUN_TEST(suffix_so);
RUN_TEST(suffix_png);
RUN_TEST(suffix_jpg);
RUN_TEST(suffix_wasm);
RUN_TEST(suffix_db);
RUN_TEST(suffix_sqlite);
RUN_TEST(suffix_tmp);
RUN_TEST(suffix_tilde);
RUN_TEST(suffix_go);
RUN_TEST(suffix_py);
RUN_TEST(suffix_c);
RUN_TEST(suffix_fast_zip);
RUN_TEST(suffix_fast_pdf);
RUN_TEST(suffix_fast_mp3);
RUN_TEST(suffix_fast_pem);
/* Filename skip */
RUN_TEST(fn_skip_license);
RUN_TEST(fn_skip_changelog);
RUN_TEST(fn_skip_gosum);
RUN_TEST(fn_skip_yarnlock);
RUN_TEST(fn_skip_pkglock);
RUN_TEST(fn_no_skip_license_full);
/* Fast mode patterns */
RUN_TEST(pattern_dts);
RUN_TEST(pattern_pbgo);
RUN_TEST(pattern_pb2py);
RUN_TEST(pattern_mock);
RUN_TEST(pattern_test_dot);
RUN_TEST(pattern_spec);
RUN_TEST(pattern_stories);
RUN_TEST(pattern_dts_full);
/* Integration tests (cross-platform) */
RUN_TEST(discover_simple);
RUN_TEST(discover_skips_git_dir);
RUN_TEST(discover_with_gitignore);
RUN_TEST(discover_max_file_size);
RUN_TEST(discover_null_path);
RUN_TEST(discover_nonexistent_path);
RUN_TEST(discover_free_null);
/* Go test ports (cross-platform) */
RUN_TEST(discover_skips_worktrees);
RUN_TEST(discover_cbmignore);
RUN_TEST(discover_cbmignore_stacks);
RUN_TEST(discover_symlink_skipped);
RUN_TEST(discover_new_ignore_patterns);
RUN_TEST(discover_generic_dirs_full_mode);
RUN_TEST(discover_generic_dirs_fast_mode);
RUN_TEST(discover_cbmignore_no_git);
/* Nested .gitignore tests (issue #178) */
RUN_TEST(discover_nested_gitignore);
RUN_TEST(discover_nested_gitignore_stacks_with_root);
}