/* M3.4 gate: unit-test the tool-call block parser (qxmx_tool_call.h). * * ./tool_call_test (no args, no GPU, no model) * * Covers: string/number/bool/object parameter values, multi-line values, * quoted strings, multiple blocks in one TC pair, parameterless * calls, and the malformed structures that must fall back to plain content. * Exits non-zero on the first failing check. */ #include "qxmx_tool_call.h" #include #include #include using namespace qx; static int failures = 0; #define CHECK(cond, msg) \ do { \ if (!(cond)) { \ std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, \ msg); \ failures++; \ } \ } while (0) /* Parse body, requiring success; returns the calls (fatal-checks count). */ static std::vector must_parse(const std::string& body, size_t expect_n) { std::vector calls; bool ok = parse_tool_call_block(body, &calls); CHECK(ok, "parse_tool_call_block returned false"); CHECK(calls.size() == expect_n, "wrong number of parsed calls"); return calls; } /* Re-parse an arguments string and fetch one key (nullptr if absent). */ static const jval* arg_of(const std::string& args_json, const char* key, jval& storage) { if (!jparse(args_json, storage) || storage.t != jval::OBJ) return nullptr; return storage.find(key); } int main() { /* 1. simple call: string + number + bool + object values */ { std::string body = "\n\n" "\nParis\n\n" "\n3\n\n" "\ntrue\n\n" "\n{\"wind\": true, \"hours\": [1, 2]}\n\n" "\n"; auto calls = must_parse(body, 1); if (!calls.empty()) { CHECK(calls[0].name == "get_weather", "name mismatch"); jval a; const jval* city = arg_of(calls[0].arguments_json, "city", a); CHECK(city && city->t == jval::STR && city->s == "Paris", "city not a string 'Paris'"); const jval* days = arg_of(calls[0].arguments_json, "days", a); CHECK(days && days->t == jval::NUM && days->s == "3", "days not number 3"); const jval* metric = arg_of(calls[0].arguments_json, "metric", a); CHECK(metric && metric->t == jval::BOOL && metric->b, "metric not bool true"); const jval* opts = arg_of(calls[0].arguments_json, "options", a); CHECK(opts && opts->t == jval::OBJ && opts->find("wind") && opts->find("hours"), "options not an object"); } } /* 2. multi-line value keeps interior newlines */ { std::string body = "\n\n" "\nline one\nline two\nline three\n\n" "\n"; auto calls = must_parse(body, 1); if (!calls.empty()) { jval a; const jval* t = arg_of(calls[0].arguments_json, "text", a); CHECK(t && t->t == jval::STR && t->s == "line one\nline two\nline three", "multi-line value mangled"); } } /* 3. quoted string stays a string; bare word stays a string */ { std::string body = "\n\n" "\n\"quoted\"\n\n" "\nParis, Texas\n\n" "\n"; auto calls = must_parse(body, 1); if (!calls.empty()) { jval a; const jval* qa = arg_of(calls[0].arguments_json, "a", a); CHECK(qa && qa->t == jval::STR && qa->s == "quoted", "quoted string mismatch"); const jval* qb = arg_of(calls[0].arguments_json, "b", a); CHECK(qb && qb->t == jval::STR && qb->s == "Paris, Texas", "bare string mismatch"); } } /* 4. two blocks in one TC pair (parallel calls) */ { std::string body = "\n\n\n1\n\n\n" "\n\n2\n\n\n"; auto calls = must_parse(body, 2); if (calls.size() == 2) { CHECK(calls[0].name == "f1" && calls[1].name == "f2", "parallel call names mismatch"); } } /* 5. parameterless call -> "{}" */ { std::string body = "\n\n\n"; auto calls = must_parse(body, 1); if (!calls.empty()) CHECK(calls[0].arguments_json == "{}", "empty args not {}"); } /* 6. malformed blocks must return false and leave out unchanged */ { const char* bad[] = { "", /* empty */ "\nno function here\n", /* no \nx\n\n", /* empty name */ "\n\n\nv\n\n", /* unclosed param */ "\n\n\nv\n\n\n", /* empty key */ "\n\n", /* no */ }; for (const char* b : bad) { std::vector calls; CHECK(!parse_tool_call_block(b, &calls) && calls.empty(), "malformed block parsed"); } } /* 7. empty-parameter edge: \n\n -> "" */ { std::string body = "\n\n\n\n\n\n"; auto calls = must_parse(body, 1); if (!calls.empty()) { jval a; const jval* k = arg_of(calls[0].arguments_json, "k", a); CHECK(k && k->t == jval::STR && k->s.empty(), "empty param not empty string"); } } if (failures) { std::fprintf(stderr, "tool_call_test: %d FAILURE(S)\n", failures); return 1; } std::printf("tool_call_test: all checks passed\n"); return 0; }