Files
deusdata--codebase-memory-mcp/tests/test_yaml.c
T
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

1106 lines
36 KiB
C

/*
* test_yaml.c — Tests for foundation/yaml YAML parser.
*/
#include "test_framework.h"
#include "../src/foundation/yaml.h"
/* ── Parsing: NULL and empty input ─────────────────────────────── */
TEST(yaml_parse_null_input) {
cbm_yaml_node_t *root = cbm_yaml_parse(NULL, 0);
ASSERT_NOT_NULL(root); /* returns empty map on NULL */
ASSERT_FALSE(cbm_yaml_has(root, "anything"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_parse_empty_string) {
cbm_yaml_node_t *root = cbm_yaml_parse("", 0);
ASSERT_NOT_NULL(root);
ASSERT_FALSE(cbm_yaml_has(root, "key"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_parse_negative_len) {
cbm_yaml_node_t *root = cbm_yaml_parse("key: val", -1);
ASSERT_NOT_NULL(root);
/* Negative len treated as empty */
ASSERT_FALSE(cbm_yaml_has(root, "key"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_free_null) {
/* Must not crash */
cbm_yaml_free(NULL);
PASS();
}
/* ── Parsing: single key-value pair ────────────────────────────── */
TEST(yaml_single_kv) {
const char *yaml = "name: hello";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "name"), "hello");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_single_kv_trailing_newline) {
const char *yaml = "name: hello\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "name"), "hello");
cbm_yaml_free(root);
PASS();
}
/* ── Parsing: multiple key-value pairs ─────────────────────────── */
TEST(yaml_multiple_kv) {
const char *yaml =
"name: myproject\n"
"version: 1.2.3\n"
"author: someone\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "name"), "myproject");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "version"), "1.2.3");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "author"), "someone");
cbm_yaml_free(root);
PASS();
}
/* ── Parsing: nested maps ──────────────────────────────────────── */
TEST(yaml_nested_map_2_levels) {
const char *yaml =
"database:\n"
" host: localhost\n"
" port: 5432\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "database.host"), "localhost");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "database.port"), "5432");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_nested_map_3_levels) {
const char *yaml =
"level1:\n"
" level2:\n"
" level3: deep_value\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "level1.level2.level3"), "deep_value");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_nested_siblings) {
const char *yaml =
"server:\n"
" host: 0.0.0.0\n"
" port: 8080\n"
"database:\n"
" host: db.local\n"
" port: 3306\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "server.host"), "0.0.0.0");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "server.port"), "8080");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "database.host"), "db.local");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "database.port"), "3306");
cbm_yaml_free(root);
PASS();
}
/* ── Parsing: string lists ─────────────────────────────────────── */
TEST(yaml_string_list) {
const char *yaml =
"fruits:\n"
" - apple\n"
" - banana\n"
" - cherry\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *items[8];
int count = cbm_yaml_get_str_list(root, "fruits", items, 8);
ASSERT_EQ(count, 3);
ASSERT_STR_EQ(items[0], "apple");
ASSERT_STR_EQ(items[1], "banana");
ASSERT_STR_EQ(items[2], "cherry");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_list_max_out_limit) {
const char *yaml =
"items:\n"
" - a\n"
" - b\n"
" - c\n"
" - d\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *items[2];
int count = cbm_yaml_get_str_list(root, "items", items, 2);
ASSERT_EQ(count, 2);
ASSERT_STR_EQ(items[0], "a");
ASSERT_STR_EQ(items[1], "b");
cbm_yaml_free(root);
PASS();
}
/* ── Parsing: comments ─────────────────────────────────────────── */
TEST(yaml_comment_only) {
const char *yaml =
"# This is a comment\n"
"# Another comment\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FALSE(cbm_yaml_has(root, "#"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_inline_comment) {
const char *yaml = "name: hello # this is a comment\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "name"), "hello");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_comment_between_keys) {
const char *yaml =
"a: 1\n"
"# skip me\n"
"b: 2\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "a"), "1");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "b"), "2");
cbm_yaml_free(root);
PASS();
}
/* ── Parsing: mixed maps and lists ─────────────────────────────── */
TEST(yaml_mixed_maps_and_lists) {
const char *yaml =
"project:\n"
" name: myapp\n"
" tags:\n"
" - web\n"
" - api\n"
" version: 2.0\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "project.name"), "myapp");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "project.version"), "2.0");
const char *tags[4];
int count = cbm_yaml_get_str_list(root, "project.tags", tags, 4);
ASSERT_EQ(count, 2);
ASSERT_STR_EQ(tags[0], "web");
ASSERT_STR_EQ(tags[1], "api");
cbm_yaml_free(root);
PASS();
}
/* ── Parsing: colons in values ─────────────────────────────────── */
TEST(yaml_url_value) {
/* Only the first colon is the separator */
const char *yaml = "url: https://example.com:8080/path\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "url"), "https://example.com:8080/path");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_multiple_colons) {
const char *yaml = "time: 12:30:45\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "time"), "12:30:45");
cbm_yaml_free(root);
PASS();
}
/* ── Parsing: empty values ─────────────────────────────────────── */
TEST(yaml_empty_value_becomes_map) {
/* "key:" with nothing after becomes a map node (if next line isn't "- ...") */
const char *yaml =
"parent:\n"
" child: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_has(root, "parent"));
ASSERT_STR_EQ(cbm_yaml_get_str(root, "parent.child"), "val");
/* parent itself is a map, not a scalar */
ASSERT_NULL(cbm_yaml_get_str(root, "parent"));
cbm_yaml_free(root);
PASS();
}
/* ── Query: get_str ────────────────────────────────────────────── */
TEST(yaml_get_str_scalar) {
const char *yaml = "key: value\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "key"), "value");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_str_missing) {
const char *yaml = "key: value\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_NULL(cbm_yaml_get_str(root, "nonexistent"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_str_nested) {
const char *yaml =
"a:\n"
" b:\n"
" c: found\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "a.b.c"), "found");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_str_on_map_node) {
/* Querying a map node (not scalar) returns NULL */
const char *yaml =
"group:\n"
" key: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_NULL(cbm_yaml_get_str(root, "group"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_str_null_root) {
ASSERT_NULL(cbm_yaml_get_str(NULL, "key"));
PASS();
}
TEST(yaml_get_str_null_path) {
const char *yaml = "key: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_NULL(cbm_yaml_get_str(root, NULL));
cbm_yaml_free(root);
PASS();
}
/* ── Query: get_float ──────────────────────────────────────────── */
TEST(yaml_get_float_valid) {
const char *yaml = "confidence: 0.85\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "confidence", -1.0), 0.85, 0.001);
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_float_integer) {
const char *yaml = "count: 42\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "count", -1.0), 42.0, 0.001);
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_float_negative) {
const char *yaml = "offset: -3.14\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "offset", 0.0), -3.14, 0.001);
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_float_invalid_string) {
const char *yaml = "val: not_a_number\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "val", 99.0), 99.0, 0.001);
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_float_missing_key) {
const char *yaml = "a: 1\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "missing", 77.7), 77.7, 0.001);
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_float_zero) {
const char *yaml = "val: 0.0\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "val", -1.0), 0.0, 0.001);
cbm_yaml_free(root);
PASS();
}
/* ── Query: get_bool ───────────────────────────────────────────── */
TEST(yaml_get_bool_true_false) {
const char *yaml =
"enabled: true\n"
"disabled: false\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_get_bool(root, "enabled", false));
ASSERT_FALSE(cbm_yaml_get_bool(root, "disabled", true));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_bool_yes_no) {
const char *yaml =
"feature_a: yes\n"
"feature_b: no\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_get_bool(root, "feature_a", false));
ASSERT_FALSE(cbm_yaml_get_bool(root, "feature_b", true));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_bool_on_off) {
const char *yaml =
"logging: on\n"
"debug: off\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_get_bool(root, "logging", false));
ASSERT_FALSE(cbm_yaml_get_bool(root, "debug", true));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_bool_1_0) {
const char *yaml =
"flag_on: 1\n"
"flag_off: 0\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_get_bool(root, "flag_on", false));
ASSERT_FALSE(cbm_yaml_get_bool(root, "flag_off", true));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_bool_case_insensitive) {
const char *yaml =
"a: TRUE\n"
"b: False\n"
"c: YES\n"
"d: No\n"
"e: ON\n"
"f: Off\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_get_bool(root, "a", false));
ASSERT_FALSE(cbm_yaml_get_bool(root, "b", true));
ASSERT_TRUE(cbm_yaml_get_bool(root, "c", false));
ASSERT_FALSE(cbm_yaml_get_bool(root, "d", true));
ASSERT_TRUE(cbm_yaml_get_bool(root, "e", false));
ASSERT_FALSE(cbm_yaml_get_bool(root, "f", true));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_bool_missing_returns_default) {
const char *yaml = "a: 1\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_get_bool(root, "missing", true));
ASSERT_FALSE(cbm_yaml_get_bool(root, "missing", false));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_bool_unrecognized_returns_default) {
const char *yaml = "val: maybe\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_get_bool(root, "val", true));
ASSERT_FALSE(cbm_yaml_get_bool(root, "val", false));
cbm_yaml_free(root);
PASS();
}
/* ── Query: get_str_list ───────────────────────────────────────── */
TEST(yaml_get_str_list_non_list_path) {
const char *yaml = "scalar: hello\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *items[4];
int count = cbm_yaml_get_str_list(root, "scalar", items, 4);
ASSERT_EQ(count, 0);
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_str_list_missing_path) {
const char *yaml = "key: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *items[4];
int count = cbm_yaml_get_str_list(root, "nonexistent", items, 4);
ASSERT_EQ(count, 0);
cbm_yaml_free(root);
PASS();
}
TEST(yaml_get_str_list_null_root) {
const char *items[4];
int count = cbm_yaml_get_str_list(NULL, "key", items, 4);
ASSERT_EQ(count, 0);
PASS();
}
/* ── Query: has() ──────────────────────────────────────────────── */
TEST(yaml_has_existing) {
const char *yaml = "key: value\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_has(root, "key"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_has_missing) {
const char *yaml = "key: value\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_FALSE(cbm_yaml_has(root, "nope"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_has_nested) {
const char *yaml =
"a:\n"
" b: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_TRUE(cbm_yaml_has(root, "a"));
ASSERT_TRUE(cbm_yaml_has(root, "a.b"));
ASSERT_FALSE(cbm_yaml_has(root, "a.c"));
ASSERT_FALSE(cbm_yaml_has(root, "a.b.c"));
cbm_yaml_free(root);
PASS();
}
TEST(yaml_has_null_root) {
ASSERT_FALSE(cbm_yaml_has(NULL, "key"));
PASS();
}
/* ── Edge cases: long values ───────────────────────────────────── */
TEST(yaml_long_value) {
char yaml[1200];
char expected[1025];
memset(expected, 'x', 1024);
expected[1024] = '\0';
int n = snprintf(yaml, sizeof(yaml), "longkey: %s\n", expected);
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, n);
ASSERT_NOT_NULL(root);
const char *val = cbm_yaml_get_str(root, "longkey");
ASSERT_NOT_NULL(val);
ASSERT_EQ((int)strlen(val), 1024);
ASSERT_STR_EQ(val, expected);
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: special characters ────────────────────────────── */
TEST(yaml_special_chars_in_value) {
const char *yaml = "pattern: [a-z]+(foo|bar)*\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "pattern"), "[a-z]+(foo|bar)*");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_equals_in_value) {
const char *yaml = "query: SELECT * FROM t WHERE x=1\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "query"), "SELECT * FROM t WHERE x=1");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: deeply nested (4+ levels) ─────────────────────── */
TEST(yaml_deeply_nested) {
const char *yaml =
"l1:\n"
" l2:\n"
" l3:\n"
" l4:\n"
" l5: bottom\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "l1.l2.l3.l4.l5"), "bottom");
/* Intermediate nodes exist but are not scalars */
ASSERT_TRUE(cbm_yaml_has(root, "l1.l2.l3.l4"));
ASSERT_NULL(cbm_yaml_get_str(root, "l1.l2.l3.l4"));
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: non-existent intermediate path ────────────────── */
TEST(yaml_missing_intermediate) {
const char *yaml = "a: 1\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_NULL(cbm_yaml_get_str(root, "x.y.z"));
ASSERT_FALSE(cbm_yaml_has(root, "x.y.z"));
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: tab characters ────────────────────────────────── */
TEST(yaml_tab_not_indentation) {
/* Tabs are not treated as indentation by leading_spaces() */
const char *yaml =
"key1: val1\n"
"\tkey2: val2\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "key1"), "val1");
/* tab-indented line has 0 leading spaces, parsed as top-level */
/* The key will be "key2" after trim_dup strips the tab from the key */
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: carriage return ───────────────────────────────── */
TEST(yaml_crlf_line_endings) {
const char *yaml = "name: hello\r\nversion: 1.0\r\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "name"), "hello");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "version"), "1.0");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: quoted strings with # ─────────────────────────── */
TEST(yaml_quoted_string_with_hash) {
/* Quoted strings: inline comment stripping is skipped for quoted values */
const char *yaml = "color: \"#ff0000\"\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *val = cbm_yaml_get_str(root, "color");
ASSERT_NOT_NULL(val);
/* Parser preserves quotes in value since it doesn't strip them */
ASSERT_STR_EQ(val, "\"#ff0000\"");
cbm_yaml_free(root);
PASS();
}
TEST(yaml_single_quoted_with_hash) {
const char *yaml = "regex: '# not a comment'\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *val = cbm_yaml_get_str(root, "regex");
ASSERT_NOT_NULL(val);
/* Single-quoted values are also preserved with quotes */
ASSERT_STR_EQ(val, "'# not a comment'");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: empty lines between content ───────────────────── */
TEST(yaml_empty_lines_between_keys) {
const char *yaml =
"a: 1\n"
"\n"
"\n"
"b: 2\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "a"), "1");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "b"), "2");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: whitespace-only value ─────────────────────────── */
TEST(yaml_whitespace_after_colon) {
/* "key: " with only spaces after colon -> empty value -> map node */
const char *yaml = "bare: \n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* after_len is 0 after trimming spaces, so this becomes a map/list node */
ASSERT_TRUE(cbm_yaml_has(root, "bare"));
/* Not a scalar, so get_str returns NULL */
ASSERT_NULL(cbm_yaml_get_str(root, "bare"));
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: value is just a number ────────────────────────── */
TEST(yaml_numeric_string_value) {
const char *yaml = "port: 8080\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* Everything is stored as a string */
ASSERT_STR_EQ(cbm_yaml_get_str(root, "port"), "8080");
/* But can be retrieved as float */
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "port", -1.0), 8080.0, 0.001);
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: no trailing newline ───────────────────────────── */
TEST(yaml_no_trailing_newline) {
const char *yaml = "key: value";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "key"), "value");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: line with no colon ────────────────────────────── */
TEST(yaml_line_without_colon_skipped) {
const char *yaml =
"valid: yes\n"
"this line has no colon\n"
"also_valid: sure\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "valid"), "yes");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "also_valid"), "sure");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: hash at start of value (no space before it) ───── */
TEST(yaml_hash_no_preceding_space) {
/* "#" only stripped as inline comment when preceded by space */
const char *yaml = "channel: #general\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "channel"), "#general");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: list after comment lines ──────────────────────── */
TEST(yaml_list_after_comments) {
const char *yaml =
"paths:\n"
" # some paths to exclude\n"
" - /tmp\n"
" - /var/log\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *items[4];
int count = cbm_yaml_get_str_list(root, "paths", items, 4);
ASSERT_EQ(count, 2);
ASSERT_STR_EQ(items[0], "/tmp");
ASSERT_STR_EQ(items[1], "/var/log");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: navigate() path segment > 255 chars ───────────── */
TEST(yaml_path_segment_overflow) {
/* navigate() uses buf[256]; segment >= 256 chars returns NULL */
char long_path[300];
memset(long_path, 'a', 260);
long_path[260] = '\0';
const char *yaml = "key: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_NULL(cbm_yaml_get_str(root, long_path));
ASSERT_FALSE(cbm_yaml_has(root, long_path));
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: empty path string ─────────────────────────────── */
TEST(yaml_empty_path) {
const char *yaml = "key: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* Empty path -> navigate returns root (seg_len 0 -> NULL) */
ASSERT_NULL(cbm_yaml_get_str(root, ""));
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: path with trailing dot ────────────────────────── */
TEST(yaml_path_trailing_dot) {
const char *yaml =
"a:\n"
" b: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* "a." -> segment "a", then empty segment -> seg_len 0 -> NULL */
ASSERT_NULL(cbm_yaml_get_str(root, "a."));
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: duplicate keys ────────────────────────────────── */
TEST(yaml_duplicate_keys) {
/* Parser stores both; find_child returns the first match */
const char *yaml =
"key: first\n"
"key: second\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "key"), "first");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: indentation jump ──────────────────────────────── */
TEST(yaml_indentation_dedent) {
/* After deep nesting, dedent back to top level */
const char *yaml =
"outer:\n"
" inner: deep\n"
"top: level\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "outer.inner"), "deep");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "top"), "level");
cbm_yaml_free(root);
PASS();
}
/* ── Smoke: real-world .cgrconfig format ───────────────────────── */
TEST(yaml_smoke_cgrconfig) {
const char *yaml =
"# codebase-memory config\n"
"mode: fast\n"
"\n"
"http_linker:\n"
" enabled: true\n"
" min_confidence: 0.7\n"
" exclude_paths:\n"
" - /health\n"
" - /metrics\n"
" - /internal/debug\n"
"\n"
"pipeline:\n"
" workers: 4\n"
" verbose: false\n"
"\n"
"# End of config\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* Top-level scalar */
ASSERT_STR_EQ(cbm_yaml_get_str(root, "mode"), "fast");
/* Nested bool */
ASSERT_TRUE(cbm_yaml_get_bool(root, "http_linker.enabled", false));
/* Nested float */
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "http_linker.min_confidence", 0.0), 0.7, 0.001);
/* Nested list */
const char *paths[8];
int count = cbm_yaml_get_str_list(root, "http_linker.exclude_paths", paths, 8);
ASSERT_EQ(count, 3);
ASSERT_STR_EQ(paths[0], "/health");
ASSERT_STR_EQ(paths[1], "/metrics");
ASSERT_STR_EQ(paths[2], "/internal/debug");
/* Another nested section */
ASSERT_STR_EQ(cbm_yaml_get_str(root, "pipeline.workers"), "4");
ASSERT_FALSE(cbm_yaml_get_bool(root, "pipeline.verbose", true));
/* Non-existent */
ASSERT_FALSE(cbm_yaml_has(root, "pipeline.timeout"));
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "pipeline.timeout", 30.0), 30.0, 0.001);
cbm_yaml_free(root);
PASS();
}
/* ── Smoke: parse, query many paths, free ──────────────────────── */
TEST(yaml_smoke_multi_query) {
const char *yaml =
"app:\n"
" name: testapp\n"
" debug: yes\n"
" port: 9090\n"
" features:\n"
" - auth\n"
" - logging\n"
" db:\n"
" host: pghost\n"
" ssl: on\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* Scalars at different depths */
ASSERT_STR_EQ(cbm_yaml_get_str(root, "app.name"), "testapp");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "app.db.host"), "pghost");
/* Bools */
ASSERT_TRUE(cbm_yaml_get_bool(root, "app.debug", false));
ASSERT_TRUE(cbm_yaml_get_bool(root, "app.db.ssl", false));
/* Float */
ASSERT_FLOAT_EQ(cbm_yaml_get_float(root, "app.port", 0.0), 9090.0, 0.001);
/* List */
const char *feats[4];
int count = cbm_yaml_get_str_list(root, "app.features", feats, 4);
ASSERT_EQ(count, 2);
ASSERT_STR_EQ(feats[0], "auth");
ASSERT_STR_EQ(feats[1], "logging");
/* has() checks */
ASSERT_TRUE(cbm_yaml_has(root, "app"));
ASSERT_TRUE(cbm_yaml_has(root, "app.features"));
ASSERT_TRUE(cbm_yaml_has(root, "app.db"));
ASSERT_FALSE(cbm_yaml_has(root, "app.cache"));
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: len shorter than string ───────────────────────── */
TEST(yaml_partial_len) {
/* Only parse first 7 bytes: "key: va" */
const char *yaml = "key: value\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, 7);
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "key"), "va");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: list at top level via map last-child promotion ── */
TEST(yaml_top_level_list_items) {
/* List items after a "key:" become children of that key's list node */
const char *yaml =
"colors:\n"
"- red\n"
"- green\n"
"- blue\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
const char *items[4];
int count = cbm_yaml_get_str_list(root, "colors", items, 4);
ASSERT_EQ(count, 3);
ASSERT_STR_EQ(items[0], "red");
ASSERT_STR_EQ(items[1], "green");
ASSERT_STR_EQ(items[2], "blue");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: mixed indentation levels (2 vs 4 spaces) ──────── */
TEST(yaml_inconsistent_indentation) {
/* Parser tracks indentation levels via stack, not fixed width */
const char *yaml =
"a:\n"
" b:\n"
" c: deep\n"
" d: shallow\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
ASSERT_STR_EQ(cbm_yaml_get_str(root, "a.b.c"), "deep");
ASSERT_STR_EQ(cbm_yaml_get_str(root, "a.d"), "shallow");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: value with leading/trailing whitespace ─────────── */
TEST(yaml_value_whitespace_trimmed) {
const char *yaml = "key: spaced \n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* trim_dup trims leading and trailing whitespace */
ASSERT_STR_EQ(cbm_yaml_get_str(root, "key"), "spaced");
cbm_yaml_free(root);
PASS();
}
/* ── Edge cases: key with leading whitespace at top level ──────── */
TEST(yaml_indented_top_level) {
/* Leading spaces make the parser think it's nested */
const char *yaml = " indented_key: val\n";
cbm_yaml_node_t *root = cbm_yaml_parse(yaml, (int)strlen(yaml));
ASSERT_NOT_NULL(root);
/* With indent=2, stack pops until parent.indent < 2; root.indent=-1 qualifies */
ASSERT_STR_EQ(cbm_yaml_get_str(root, "indented_key"), "val");
cbm_yaml_free(root);
PASS();
}
/* ── Suite registration ────────────────────────────────────────── */
SUITE(yaml) {
/* Parsing: NULL / empty */
RUN_TEST(yaml_parse_null_input);
RUN_TEST(yaml_parse_empty_string);
RUN_TEST(yaml_parse_negative_len);
RUN_TEST(yaml_free_null);
/* Parsing: key-value */
RUN_TEST(yaml_single_kv);
RUN_TEST(yaml_single_kv_trailing_newline);
RUN_TEST(yaml_multiple_kv);
/* Parsing: nested maps */
RUN_TEST(yaml_nested_map_2_levels);
RUN_TEST(yaml_nested_map_3_levels);
RUN_TEST(yaml_nested_siblings);
/* Parsing: lists */
RUN_TEST(yaml_string_list);
RUN_TEST(yaml_list_max_out_limit);
/* Parsing: comments */
RUN_TEST(yaml_comment_only);
RUN_TEST(yaml_inline_comment);
RUN_TEST(yaml_comment_between_keys);
/* Parsing: mixed */
RUN_TEST(yaml_mixed_maps_and_lists);
/* Parsing: colons */
RUN_TEST(yaml_url_value);
RUN_TEST(yaml_multiple_colons);
/* Parsing: empty values */
RUN_TEST(yaml_empty_value_becomes_map);
/* Query: get_str */
RUN_TEST(yaml_get_str_scalar);
RUN_TEST(yaml_get_str_missing);
RUN_TEST(yaml_get_str_nested);
RUN_TEST(yaml_get_str_on_map_node);
RUN_TEST(yaml_get_str_null_root);
RUN_TEST(yaml_get_str_null_path);
/* Query: get_float */
RUN_TEST(yaml_get_float_valid);
RUN_TEST(yaml_get_float_integer);
RUN_TEST(yaml_get_float_negative);
RUN_TEST(yaml_get_float_invalid_string);
RUN_TEST(yaml_get_float_missing_key);
RUN_TEST(yaml_get_float_zero);
/* Query: get_bool */
RUN_TEST(yaml_get_bool_true_false);
RUN_TEST(yaml_get_bool_yes_no);
RUN_TEST(yaml_get_bool_on_off);
RUN_TEST(yaml_get_bool_1_0);
RUN_TEST(yaml_get_bool_case_insensitive);
RUN_TEST(yaml_get_bool_missing_returns_default);
RUN_TEST(yaml_get_bool_unrecognized_returns_default);
/* Query: get_str_list */
RUN_TEST(yaml_get_str_list_non_list_path);
RUN_TEST(yaml_get_str_list_missing_path);
RUN_TEST(yaml_get_str_list_null_root);
/* Query: has() */
RUN_TEST(yaml_has_existing);
RUN_TEST(yaml_has_missing);
RUN_TEST(yaml_has_nested);
RUN_TEST(yaml_has_null_root);
/* Edge cases */
RUN_TEST(yaml_long_value);
RUN_TEST(yaml_special_chars_in_value);
RUN_TEST(yaml_equals_in_value);
RUN_TEST(yaml_deeply_nested);
RUN_TEST(yaml_missing_intermediate);
RUN_TEST(yaml_tab_not_indentation);
RUN_TEST(yaml_crlf_line_endings);
RUN_TEST(yaml_quoted_string_with_hash);
RUN_TEST(yaml_single_quoted_with_hash);
RUN_TEST(yaml_empty_lines_between_keys);
RUN_TEST(yaml_whitespace_after_colon);
RUN_TEST(yaml_numeric_string_value);
RUN_TEST(yaml_no_trailing_newline);
RUN_TEST(yaml_line_without_colon_skipped);
RUN_TEST(yaml_hash_no_preceding_space);
RUN_TEST(yaml_list_after_comments);
RUN_TEST(yaml_path_segment_overflow);
RUN_TEST(yaml_empty_path);
RUN_TEST(yaml_path_trailing_dot);
RUN_TEST(yaml_duplicate_keys);
RUN_TEST(yaml_indentation_dedent);
RUN_TEST(yaml_partial_len);
RUN_TEST(yaml_top_level_list_items);
RUN_TEST(yaml_inconsistent_indentation);
RUN_TEST(yaml_value_whitespace_trimmed);
RUN_TEST(yaml_indented_top_level);
/* Smoke tests */
RUN_TEST(yaml_smoke_cgrconfig);
RUN_TEST(yaml_smoke_multi_query);
}