Files
codebase-memory-mcp/tests/test_str_util.c
T
Andrew Hundt 59def6aaeb merge(upstream): integrate main through Laravel route fixes
Merge upstream/main at 2469ecc3 into integrate-upstream-split-search-tools while retaining branch-specific incremental indexing, MCP API consolidation, and graph extraction behavior.

The staged tree adds upstream's Mojo and Perl grammar support, issue reproduction suites, Graph UI changes, Windows smoke coverage, TOON graph output, subprocess supervision, and Laravel facade route handling. Conflict resolutions in src/pipeline/httplink.c and pass_httplinks.c retain dynamic growth and IMPORTS edge identity; scripts/smoke-test.sh and scripts/soak-test.sh retain hermetic cache isolation.

Validation against the merge plus the follow-up working-tree reconciliation: ASan/UBSan MCP suite 216/216 passed. Follow-up semantic and concurrency fixes remain intentionally unstaged for the next focused commit.
2026-07-14 01:47:43 -04:00

578 lines
14 KiB
C

/*
* test_str_util.c — RED phase tests for foundation/str_util.
*/
#include "test_framework.h"
#include "../src/foundation/str_util.h"
static CBMArena a;
static void setup(void) {
cbm_arena_init(&a);
}
static void teardown(void) {
cbm_arena_destroy(&a);
}
TEST(path_join_basic) {
setup();
ASSERT_STR_EQ(cbm_path_join(&a, "src", "main.c"), "src/main.c");
teardown();
PASS();
}
TEST(path_join_trailing_slash) {
setup();
ASSERT_STR_EQ(cbm_path_join(&a, "src/", "main.c"), "src/main.c");
teardown();
PASS();
}
TEST(path_join_leading_slash) {
setup();
ASSERT_STR_EQ(cbm_path_join(&a, "src", "/main.c"), "src/main.c");
teardown();
PASS();
}
TEST(path_join_empty_base) {
setup();
ASSERT_STR_EQ(cbm_path_join(&a, "", "main.c"), "main.c");
teardown();
PASS();
}
TEST(path_join_empty_name) {
setup();
ASSERT_STR_EQ(cbm_path_join(&a, "src", ""), "src");
teardown();
PASS();
}
TEST(path_join_n) {
setup();
const char *parts[] = {"a", "b", "c", "d.txt"};
ASSERT_STR_EQ(cbm_path_join_n(&a, parts, 4), "a/b/c/d.txt");
teardown();
PASS();
}
TEST(path_ext) {
ASSERT_STR_EQ(cbm_path_ext("foo.go"), "go");
ASSERT_STR_EQ(cbm_path_ext("foo.tar.gz"), "gz");
ASSERT_STR_EQ(cbm_path_ext("Makefile"), "");
ASSERT_STR_EQ(cbm_path_ext(".gitignore"), "gitignore");
PASS();
}
TEST(path_base) {
ASSERT_STR_EQ(cbm_path_base("src/main.c"), "main.c");
ASSERT_STR_EQ(cbm_path_base("main.c"), "main.c");
ASSERT_STR_EQ(cbm_path_base("/a/b/c"), "c");
PASS();
}
TEST(path_dir) {
setup();
ASSERT_STR_EQ(cbm_path_dir(&a, "src/main.c"), "src");
ASSERT_STR_EQ(cbm_path_dir(&a, "main.c"), ".");
ASSERT_STR_EQ(cbm_path_dir(&a, "/a/b/c"), "/a/b");
teardown();
PASS();
}
TEST(str_starts_with) {
ASSERT_TRUE(cbm_str_starts_with("hello world", "hello"));
ASSERT_FALSE(cbm_str_starts_with("hello", "hello world"));
ASSERT_TRUE(cbm_str_starts_with("hello", ""));
ASSERT_TRUE(cbm_str_starts_with("hello", "hello"));
PASS();
}
TEST(str_ends_with) {
ASSERT_TRUE(cbm_str_ends_with("hello world", "world"));
ASSERT_FALSE(cbm_str_ends_with("hello", "hello world"));
ASSERT_TRUE(cbm_str_ends_with("hello", ""));
ASSERT_TRUE(cbm_str_ends_with("hello", "hello"));
PASS();
}
TEST(str_contains) {
ASSERT_TRUE(cbm_str_contains("hello world", "lo wo"));
ASSERT_FALSE(cbm_str_contains("hello", "xyz"));
ASSERT_TRUE(cbm_str_contains("hello", ""));
PASS();
}
TEST(str_copy_fixed_buffer) {
char buf[6] = {'x', 'x', 'x', 'x', 'x', 'x'};
ASSERT_TRUE(cbm_str_copy(buf, sizeof(buf), "hello"));
ASSERT_STR_EQ(buf, "hello");
ASSERT_FALSE(cbm_str_copy(buf, sizeof(buf), "hello world"));
ASSERT_STR_EQ(buf, "hello");
ASSERT_TRUE(cbm_str_copy(buf, sizeof(buf), NULL));
ASSERT_STR_EQ(buf, "");
ASSERT_FALSE(cbm_str_copy(NULL, sizeof(buf), "x"));
ASSERT_FALSE(cbm_str_copy(buf, 0, "x"));
PASS();
}
TEST(str_tolower) {
setup();
ASSERT_STR_EQ(cbm_str_tolower(&a, "Hello World"), "hello world");
ASSERT_STR_EQ(cbm_str_tolower(&a, "already"), "already");
ASSERT_STR_EQ(cbm_str_tolower(&a, ""), "");
teardown();
PASS();
}
TEST(str_replace_char) {
setup();
ASSERT_STR_EQ(cbm_str_replace_char(&a, "a/b/c", '/', '.'), "a.b.c");
ASSERT_STR_EQ(cbm_str_replace_char(&a, "no-change", '/', '.'), "no-change");
teardown();
PASS();
}
TEST(str_strip_ext) {
setup();
ASSERT_STR_EQ(cbm_str_strip_ext(&a, "foo.go"), "foo");
ASSERT_STR_EQ(cbm_str_strip_ext(&a, "foo.tar.gz"), "foo.tar");
ASSERT_STR_EQ(cbm_str_strip_ext(&a, "Makefile"), "Makefile");
teardown();
PASS();
}
TEST(str_split) {
setup();
int count = 0;
char **parts = cbm_str_split(&a, "a/b/c/d", '/', &count);
ASSERT_EQ(count, 4);
ASSERT_STR_EQ(parts[0], "a");
ASSERT_STR_EQ(parts[1], "b");
ASSERT_STR_EQ(parts[2], "c");
ASSERT_STR_EQ(parts[3], "d");
teardown();
PASS();
}
TEST(str_split_empty) {
setup();
int count = 0;
char **parts = cbm_str_split(&a, "", '/', &count);
ASSERT_EQ(count, 1);
ASSERT_STR_EQ(parts[0], "");
teardown();
PASS();
}
TEST(str_split_no_delim) {
setup();
int count = 0;
char **parts = cbm_str_split(&a, "hello", '/', &count);
ASSERT_EQ(count, 1);
ASSERT_STR_EQ(parts[0], "hello");
teardown();
PASS();
}
/* ── NULL safety tests ─────────────────────────────────────────── */
TEST(path_join_null_base) {
setup();
ASSERT_NULL(cbm_path_join(&a, NULL, "main.c"));
teardown();
PASS();
}
TEST(path_join_null_name) {
setup();
ASSERT_NULL(cbm_path_join(&a, "src", NULL));
teardown();
PASS();
}
TEST(path_join_both_null) {
setup();
ASSERT_NULL(cbm_path_join(&a, NULL, NULL));
teardown();
PASS();
}
TEST(path_dir_null) {
setup();
ASSERT_STR_EQ(cbm_path_dir(&a, NULL), ".");
teardown();
PASS();
}
TEST(str_starts_with_null_s) {
ASSERT_FALSE(cbm_str_starts_with(NULL, "hello"));
PASS();
}
TEST(str_starts_with_null_prefix) {
ASSERT_FALSE(cbm_str_starts_with("hello", NULL));
PASS();
}
TEST(str_ends_with_null) {
ASSERT_FALSE(cbm_str_ends_with(NULL, "world"));
ASSERT_FALSE(cbm_str_ends_with("world", NULL));
PASS();
}
TEST(str_contains_null) {
ASSERT_FALSE(cbm_str_contains(NULL, "sub"));
ASSERT_FALSE(cbm_str_contains("hello", NULL));
PASS();
}
TEST(str_tolower_null) {
setup();
ASSERT_NULL(cbm_str_tolower(&a, NULL));
teardown();
PASS();
}
TEST(str_replace_char_null) {
setup();
ASSERT_NULL(cbm_str_replace_char(&a, NULL, 'a', 'b'));
teardown();
PASS();
}
TEST(str_strip_ext_null) {
setup();
ASSERT_NULL(cbm_str_strip_ext(&a, NULL));
teardown();
PASS();
}
TEST(str_split_null_string) {
setup();
int count = 0;
ASSERT_NULL(cbm_str_split(&a, NULL, '/', &count));
teardown();
PASS();
}
TEST(str_split_null_out_count) {
setup();
ASSERT_NULL(cbm_str_split(&a, "a/b", '/', NULL));
teardown();
PASS();
}
/* ── path_join edge cases ─────────────────────────────────────── */
TEST(path_join_multi_slashes) {
setup();
ASSERT_STR_EQ(cbm_path_join(&a, "src///", "///main.c"), "src/main.c");
teardown();
PASS();
}
TEST(path_join_only_slash_base) {
setup();
ASSERT_STR_EQ(cbm_path_join(&a, "/", "main.c"), "main.c");
teardown();
PASS();
}
TEST(path_join_long_paths) {
setup();
char long_base[512];
char long_name[512];
memset(long_base, 'a', 511);
long_base[511] = '\0';
memset(long_name, 'b', 511);
long_name[511] = '\0';
char *result = cbm_path_join(&a, long_base, long_name);
ASSERT_NOT_NULL(result);
ASSERT_TRUE(cbm_str_starts_with(result, "aaa"));
ASSERT_TRUE(cbm_str_ends_with(result, "bbb"));
ASSERT_TRUE(cbm_str_contains(result, "/"));
ASSERT_EQ(strlen(result), 511 + 1 + 511);
teardown();
PASS();
}
TEST(path_join_n_zero) {
setup();
ASSERT_STR_EQ(cbm_path_join_n(&a, NULL, 0), "");
teardown();
PASS();
}
TEST(path_join_n_null_parts) {
setup();
ASSERT_STR_EQ(cbm_path_join_n(&a, NULL, 3), "");
teardown();
PASS();
}
/* ── path_ext edge cases ──────────────────────────────────────── */
TEST(path_ext_null) {
ASSERT_STR_EQ(cbm_path_ext(NULL), "");
PASS();
}
TEST(path_ext_dot_directory) {
ASSERT_STR_EQ(cbm_path_ext("dir.name/file"), "");
PASS();
}
TEST(path_ext_hidden_file) {
ASSERT_STR_EQ(cbm_path_ext(".gitignore"), "gitignore");
PASS();
}
TEST(path_ext_no_extension) {
ASSERT_STR_EQ(cbm_path_ext("Makefile"), "");
PASS();
}
TEST(path_ext_multiple_dots) {
ASSERT_STR_EQ(cbm_path_ext("file.test.spec.ts"), "ts");
PASS();
}
TEST(path_ext_trailing_dot) {
ASSERT_STR_EQ(cbm_path_ext("file."), "");
PASS();
}
/* ── path_base edge cases ─────────────────────────────────────── */
TEST(path_base_null) {
ASSERT_STR_EQ(cbm_path_base(NULL), "");
PASS();
}
TEST(path_base_empty) {
ASSERT_STR_EQ(cbm_path_base(""), "");
PASS();
}
TEST(path_base_just_filename) {
ASSERT_STR_EQ(cbm_path_base("hello.txt"), "hello.txt");
PASS();
}
TEST(path_base_trailing_slash) {
ASSERT_STR_EQ(cbm_path_base("dir/"), "");
PASS();
}
TEST(path_base_backslash_separator) {
ASSERT_STR_EQ(cbm_path_base("dir\\package.json"), "package.json");
PASS();
}
/* ── validate_shell_arg tests ─────────────────────────────────── */
TEST(validate_shell_arg_null) {
ASSERT_FALSE(cbm_validate_shell_arg(NULL));
PASS();
}
TEST(validate_shell_arg_safe) {
ASSERT_TRUE(cbm_validate_shell_arg("hello-world_123"));
PASS();
}
TEST(validate_shell_arg_single_quote) {
ASSERT_FALSE(cbm_validate_shell_arg("it's bad"));
PASS();
}
TEST(validate_shell_arg_semicolon) {
ASSERT_FALSE(cbm_validate_shell_arg("cmd; rm -rf"));
PASS();
}
TEST(validate_shell_arg_pipe) {
ASSERT_FALSE(cbm_validate_shell_arg("cmd | evil"));
PASS();
}
TEST(validate_shell_arg_ampersand) {
ASSERT_FALSE(cbm_validate_shell_arg("cmd & bg"));
PASS();
}
TEST(validate_shell_arg_dollar) {
ASSERT_FALSE(cbm_validate_shell_arg("$HOME"));
PASS();
}
TEST(validate_shell_arg_backtick) {
ASSERT_FALSE(cbm_validate_shell_arg("`whoami`"));
PASS();
}
TEST(validate_shell_arg_newline) {
ASSERT_FALSE(cbm_validate_shell_arg("line1\nline2"));
PASS();
}
TEST(validate_shell_arg_carriage_return) {
ASSERT_FALSE(cbm_validate_shell_arg("line1\rline2"));
PASS();
}
TEST(validate_shell_arg_backslash) {
#ifdef _WIN32
/* Backslash is allowed on Windows (path separator) */
ASSERT_TRUE(cbm_validate_shell_arg("path\\to\\file"));
#else
ASSERT_FALSE(cbm_validate_shell_arg("path\\to\\file"));
#endif
PASS();
}
TEST(validate_shell_arg_empty) {
ASSERT_TRUE(cbm_validate_shell_arg(""));
PASS();
}
TEST(validate_shell_arg_spaces) {
ASSERT_TRUE(cbm_validate_shell_arg("hello world with spaces"));
PASS();
}
TEST(json_escape_control_chars) {
char buf[64];
const char input[] = {'A', 0x01, 'B', '\n', 0x1f, '\0'};
int len = cbm_json_escape(buf, sizeof(buf), input);
ASSERT_STR_EQ(buf, "A\\u0001B\\n\\u001f");
ASSERT_EQ(len, 16);
PASS();
}
/* ── SNPRINTF_APPEND tests ────────────────────────────────────── */
TEST(snprintf_append_basic) {
char buf[64];
int off = 0;
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, "hello");
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, " world");
ASSERT_STR_EQ(buf, "hello world");
ASSERT_EQ(off, 11);
PASS();
}
TEST(snprintf_append_fills_exactly) {
char buf[6];
int off = 0;
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, "hello");
ASSERT_STR_EQ(buf, "hello");
ASSERT_EQ(off, 5);
PASS();
}
TEST(snprintf_append_overflow) {
char buf[8];
int off = 0;
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, "hello world this is way too long");
/* offset clamped to sizeof(buf) - 1 */
ASSERT_EQ(off, 7);
/* buffer is null-terminated and truncated */
ASSERT_EQ(strlen(buf), 7);
PASS();
}
TEST(snprintf_append_multiple_sequential) {
char buf[32];
int off = 0;
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, "a=%d", 1);
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, " b=%d", 2);
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, " c=%d", 3);
CBM_SNPRINTF_APPEND(buf, sizeof(buf), off, " d=%d", 4);
ASSERT_STR_EQ(buf, "a=1 b=2 c=3 d=4");
ASSERT_EQ(off, 15);
PASS();
}
SUITE(str_util) {
RUN_TEST(path_join_basic);
RUN_TEST(path_join_trailing_slash);
RUN_TEST(path_join_leading_slash);
RUN_TEST(path_join_empty_base);
RUN_TEST(path_join_empty_name);
RUN_TEST(path_join_n);
RUN_TEST(path_ext);
RUN_TEST(path_base);
RUN_TEST(path_dir);
RUN_TEST(str_starts_with);
RUN_TEST(str_ends_with);
RUN_TEST(str_contains);
RUN_TEST(str_copy_fixed_buffer);
RUN_TEST(str_tolower);
RUN_TEST(str_replace_char);
RUN_TEST(str_strip_ext);
RUN_TEST(str_split);
RUN_TEST(str_split_empty);
RUN_TEST(str_split_no_delim);
/* NULL safety */
RUN_TEST(path_join_null_base);
RUN_TEST(path_join_null_name);
RUN_TEST(path_join_both_null);
RUN_TEST(path_dir_null);
RUN_TEST(str_starts_with_null_s);
RUN_TEST(str_starts_with_null_prefix);
RUN_TEST(str_ends_with_null);
RUN_TEST(str_contains_null);
RUN_TEST(str_tolower_null);
RUN_TEST(str_replace_char_null);
RUN_TEST(str_strip_ext_null);
RUN_TEST(str_split_null_string);
RUN_TEST(str_split_null_out_count);
/* path_join edge cases */
RUN_TEST(path_join_multi_slashes);
RUN_TEST(path_join_only_slash_base);
RUN_TEST(path_join_long_paths);
RUN_TEST(path_join_n_zero);
RUN_TEST(path_join_n_null_parts);
/* path_ext edge cases */
RUN_TEST(path_ext_null);
RUN_TEST(path_ext_dot_directory);
RUN_TEST(path_ext_hidden_file);
RUN_TEST(path_ext_no_extension);
RUN_TEST(path_ext_multiple_dots);
RUN_TEST(path_ext_trailing_dot);
/* path_base edge cases */
RUN_TEST(path_base_null);
RUN_TEST(path_base_empty);
RUN_TEST(path_base_just_filename);
RUN_TEST(path_base_trailing_slash);
RUN_TEST(path_base_backslash_separator);
/* validate_shell_arg */
RUN_TEST(validate_shell_arg_null);
RUN_TEST(validate_shell_arg_safe);
RUN_TEST(validate_shell_arg_single_quote);
RUN_TEST(validate_shell_arg_semicolon);
RUN_TEST(validate_shell_arg_pipe);
RUN_TEST(validate_shell_arg_ampersand);
RUN_TEST(validate_shell_arg_dollar);
RUN_TEST(validate_shell_arg_backtick);
RUN_TEST(validate_shell_arg_newline);
RUN_TEST(validate_shell_arg_carriage_return);
RUN_TEST(validate_shell_arg_backslash);
RUN_TEST(validate_shell_arg_empty);
RUN_TEST(validate_shell_arg_spaces);
/* JSON Escaping */
RUN_TEST(json_escape_control_chars);
/* SNPRINTF_APPEND */
RUN_TEST(snprintf_append_basic);
RUN_TEST(snprintf_append_fills_exactly);
RUN_TEST(snprintf_append_overflow);
RUN_TEST(snprintf_append_multiple_sequential);
}