// Step 219: Trace generation and export tests // // Verifies: // 1. Traces conform to schema (valid JSON, correct roles) // 2. Each scenario template produces traces with expected structure // 3. Different seeds produce distinct traces // 4. Anthropic Messages export is well-formed // 5. OpenAI Chat export is well-formed // 6. JSONL export is well-formed // 7. Markdown export is well-formed // 8. Batch generation produces correct count with expected distribution // 9. Filtering works correctly // 10. Statistics are computed correctly // 11. Code corpus is populated // 12. Trace toJson roundtrip #include #include #include #include #include #include #include "TraceGenerator.h" #include "TraceExporter.h" using json = nlohmann::json; static int passed = 0; static int failed = 0; static void expect(bool cond, const char* msg) { if (cond) { ++passed; } else { ++failed; printf(" FAIL: %s\n", msg); } } // --- Test 1: Trace schema validation --- static void test_trace_schema() { printf("Test 1: Trace schema validation...\n"); TraceGenerator gen; gen.setSeed(100); auto trace = gen.generate(ScenarioType::ReadAndUnderstand); json j = trace.toJson(); expect(j.contains("id"), "trace has id"); expect(j.contains("scenario"), "trace has scenario"); expect(j.contains("difficulty"), "trace has difficulty"); expect(j.contains("language"), "trace has language"); expect(j.contains("steps"), "trace has steps"); expect(j.contains("toolsUsed"), "trace has toolsUsed"); expect(j.contains("toolCallCount"), "trace has toolCallCount"); expect(j.contains("success"), "trace has success"); expect(j["steps"].is_array(), "steps is array"); expect(!j["steps"].empty(), "steps not empty"); // Validate step roles std::set validRoles = {"user", "assistant", "tool_call", "tool_result"}; for (const auto& step : j["steps"]) { expect(step.contains("role"), "step has role"); std::string role = step["role"]; expect(validRoles.count(role) > 0, ("valid role: " + role).c_str()); } } // --- Test 2: All scenario templates produce traces --- static void test_all_scenarios() { printf("Test 2: All scenario templates...\n"); TraceGenerator gen; gen.setSeed(200); std::vector> scenarios = { {ScenarioType::ReadAndUnderstand, "read_and_understand"}, {ScenarioType::AddAnnotations, "add_annotations"}, {ScenarioType::CrossLanguage, "cross_language"}, {ScenarioType::Refactor, "refactor"}, {ScenarioType::SecurityAudit, "security_audit"}, {ScenarioType::MultiStepDebug, "multi_step_debug"} }; for (const auto& [type, name] : scenarios) { ScenarioParams params; params.language = "python"; auto trace = gen.generate(type, params); expect(!trace.id.empty(), ("trace id not empty for " + name).c_str()); expect(trace.scenario == name, ("scenario matches for " + name).c_str()); expect(!trace.steps.empty(), ("steps not empty for " + name).c_str()); expect(trace.toolCallCount > 0, ("has tool calls for " + name).c_str()); expect(!trace.toolsUsed.empty(), ("has tools used for " + name).c_str()); // First step should be user expect(trace.steps[0].role == "user", ("first step is user for " + name).c_str()); // Tool calls should be paired with tool results for (size_t i = 0; i < trace.steps.size(); ++i) { if (trace.steps[i].role == "tool_call") { expect(i + 1 < trace.steps.size(), "tool_call has following step"); if (i + 1 < trace.steps.size()) { expect(trace.steps[i + 1].role == "tool_result", ("tool_result follows tool_call for " + name).c_str()); expect(trace.steps[i].toolName == trace.steps[i + 1].toolName, ("tool names match for " + name).c_str()); } } } } } // --- Test 3: Different seeds produce distinct traces --- static void test_seed_determinism() { printf("Test 3: Seed determinism and distinction...\n"); TraceGenerator gen; ScenarioParams p1; p1.seed = 42; ScenarioParams p2; p2.seed = 43; ScenarioParams p3; p3.seed = 42; auto trace1 = gen.generate(ScenarioType::ReadAndUnderstand, p1); auto trace2 = gen.generate(ScenarioType::ReadAndUnderstand, p2); auto trace3 = gen.generate(ScenarioType::ReadAndUnderstand, p3); // Sequential traces from same generator have different IDs expect(trace1.id != trace2.id, "sequential traces different ids"); // Traces should have the same scenario expect(trace1.scenario == trace2.scenario, "same scenario type"); expect(trace1.scenario == trace3.scenario, "same scenario type (seed 42 again)"); } // --- Test 4: Anthropic Messages export --- static void test_anthropic_export() { printf("Test 4: Anthropic Messages export...\n"); TraceGenerator gen; gen.setSeed(300); auto trace = gen.generate(ScenarioType::AddAnnotations); json exported = TraceExporter::toAnthropicMessages(trace); expect(exported.contains("id"), "exported has id"); expect(exported.contains("messages"), "exported has messages"); expect(exported.contains("metadata"), "exported has metadata"); expect(exported["messages"].is_array(), "messages is array"); expect(!exported["messages"].empty(), "messages not empty"); // Check message roles bool hasUser = false, hasAssistant = false; for (const auto& msg : exported["messages"]) { expect(msg.contains("role"), "message has role"); std::string role = msg["role"]; expect(role == "user" || role == "assistant", "valid Anthropic role"); if (role == "user") hasUser = true; if (role == "assistant") hasAssistant = true; } expect(hasUser, "has user messages"); expect(hasAssistant, "has assistant messages"); // Check tool_use blocks exist in assistant messages bool hasToolUse = false; for (const auto& msg : exported["messages"]) { if (msg["role"] == "assistant" && msg["content"].is_array()) { for (const auto& block : msg["content"]) { if (block.contains("type") && block["type"] == "tool_use") { hasToolUse = true; expect(block.contains("id"), "tool_use has id"); expect(block.contains("name"), "tool_use has name"); expect(block.contains("input"), "tool_use has input"); } } } } expect(hasToolUse, "has tool_use blocks"); // Check tool_result blocks exist bool hasToolResult = false; for (const auto& msg : exported["messages"]) { if (msg["role"] == "user" && msg["content"].is_array()) { for (const auto& block : msg["content"]) { if (block.contains("type") && block["type"] == "tool_result") { hasToolResult = true; expect(block.contains("tool_use_id"), "tool_result has tool_use_id"); expect(block.contains("content"), "tool_result has content"); } } } } expect(hasToolResult, "has tool_result blocks"); // Metadata expect(exported["metadata"]["scenario"] == "add_annotations", "metadata scenario"); expect(exported["metadata"]["language"] == "python", "metadata language"); } // --- Test 5: OpenAI Chat export --- static void test_openai_export() { printf("Test 5: OpenAI Chat export...\n"); TraceGenerator gen; gen.setSeed(400); auto trace = gen.generate(ScenarioType::Refactor); json exported = TraceExporter::toOpenAIChat(trace); expect(exported.contains("id"), "exported has id"); expect(exported.contains("messages"), "exported has messages"); expect(exported["messages"].is_array(), "messages is array"); // Check valid OpenAI roles std::set validRoles = {"user", "assistant", "tool"}; bool hasToolCalls = false; bool hasToolRole = false; for (const auto& msg : exported["messages"]) { expect(msg.contains("role"), "message has role"); std::string role = msg["role"]; expect(validRoles.count(role) > 0, ("valid OpenAI role: " + role).c_str()); if (role == "assistant" && msg.contains("tool_calls")) { hasToolCalls = true; for (const auto& tc : msg["tool_calls"]) { expect(tc.contains("id"), "tool_call has id"); expect(tc.contains("type"), "tool_call has type"); expect(tc["type"] == "function", "tool_call type is function"); expect(tc.contains("function"), "tool_call has function"); expect(tc["function"].contains("name"), "function has name"); expect(tc["function"].contains("arguments"), "function has arguments"); } } if (role == "tool") { hasToolRole = true; expect(msg.contains("tool_call_id"), "tool msg has tool_call_id"); expect(msg.contains("content"), "tool msg has content"); } } expect(hasToolCalls, "has tool_calls in assistant messages"); expect(hasToolRole, "has tool role messages"); } // --- Test 6: JSONL export --- static void test_jsonl_export() { printf("Test 6: JSONL export...\n"); TraceGenerator gen; gen.setSeed(500); auto traces = gen.generateBatch(6, 500); std::string jsonl = TraceExporter::toJSONL(traces); // Count lines int lineCount = 0; std::istringstream iss(jsonl); std::string line; while (std::getline(iss, line)) { if (!line.empty()) { ++lineCount; // Each line should be valid JSON try { json parsed = json::parse(line); expect(parsed.contains("id"), "JSONL line has id"); expect(parsed.contains("scenario"), "JSONL line has scenario"); } catch (...) { expect(false, "JSONL line is valid JSON"); } } } expect(lineCount == 6, "JSONL has correct number of lines"); // Single trace export std::string single = TraceExporter::toJSONLSingle(traces[0]); expect(!single.empty(), "single JSONL not empty"); expect(single.back() == '\n', "single JSONL ends with newline"); } // --- Test 7: Markdown export --- static void test_markdown_export() { printf("Test 7: Markdown export...\n"); TraceGenerator gen; gen.setSeed(600); auto trace = gen.generate(ScenarioType::CrossLanguage); std::string md = TraceExporter::toMarkdown(trace); expect(!md.empty(), "markdown not empty"); expect(md.find("# Trace:") != std::string::npos, "has trace header"); expect(md.find("**Scenario:**") != std::string::npos, "has scenario field"); expect(md.find("**Difficulty:**") != std::string::npos, "has difficulty field"); expect(md.find("**Language:**") != std::string::npos, "has language field"); expect(md.find("**Tool calls:**") != std::string::npos, "has tool calls field"); expect(md.find("### Step") != std::string::npos, "has step headers"); expect(md.find("```json") != std::string::npos, "has JSON code blocks"); expect(md.find("cross_language") != std::string::npos, "scenario name in markdown"); } // --- Test 8: Batch generation --- static void test_batch_generation() { printf("Test 8: Batch generation...\n"); TraceGenerator gen; auto traces = gen.generateBatch(12, 42); expect((int)traces.size() == 12, "batch produces correct count"); // Check scenario distribution (6 types, 12 traces = 2 of each) std::map scenarioCounts; for (const auto& t : traces) { scenarioCounts[t.scenario]++; } expect(scenarioCounts.size() == 6, "all 6 scenario types present"); for (const auto& [scenario, count] : scenarioCounts) { expect(count == 2, ("2 traces for " + scenario).c_str()); } // Check language alternation for (int i = 0; i < 12; ++i) { std::string expected = (i % 2 == 0) ? "python" : "cpp"; expect(traces[i].language == expected, ("language alternation at index " + std::to_string(i)).c_str()); } // All traces should have unique IDs std::set ids; for (const auto& t : traces) ids.insert(t.id); expect((int)ids.size() == 12, "all traces have unique IDs"); } // --- Test 9: Filtering --- static void test_filtering() { printf("Test 9: Filtering...\n"); TraceGenerator gen; auto traces = gen.generateBatch(12, 42); // Filter by scenario TraceFilter f; f.scenario = "read_and_understand"; auto filtered = TraceExporter::filter(traces, f); for (const auto& t : filtered) { expect(t.scenario == "read_and_understand", "filtered by scenario"); } expect(!filtered.empty(), "scenario filter has results"); // Filter by language TraceFilter f2; f2.language = "python"; auto filtered2 = TraceExporter::filter(traces, f2); for (const auto& t : filtered2) { expect(t.language == "python", "filtered by language"); } expect((int)filtered2.size() == 6, "6 python traces"); // Filter by difficulty TraceFilter f3; f3.difficulty = "advanced"; auto filtered3 = TraceExporter::filter(traces, f3); for (const auto& t : filtered3) { expect(t.difficulty == "advanced", "filtered by difficulty"); } // Filter by min tool calls TraceFilter f4; f4.minToolCalls = 2; auto filtered4 = TraceExporter::filter(traces, f4); for (const auto& t : filtered4) { expect(t.toolCallCount >= 2, "filtered by min tool calls"); } } // --- Test 10: Statistics --- static void test_statistics() { printf("Test 10: Statistics...\n"); TraceGenerator gen; auto traces = gen.generateBatch(12, 42); auto stats = TraceExporter::computeStats(traces); expect(stats.totalTraces == 12, "total traces count"); expect(stats.successCount + stats.failureCount == 12, "success + failure = total"); expect(stats.totalToolCalls > 0, "total tool calls > 0"); expect(stats.avgToolCalls > 0.0, "avg tool calls > 0"); expect(stats.avgSteps > 0.0, "avg steps > 0"); expect(stats.scenarioDistribution.size() == 6, "6 scenarios in distribution"); expect(stats.languageDistribution.size() == 2, "2 languages in distribution"); expect(!stats.toolUsageCount.empty(), "tool usage tracked"); expect(!stats.difficultyDistribution.empty(), "difficulty distribution tracked"); // Stats to JSON json statsJson = stats.toJson(); expect(statsJson.contains("totalTraces"), "stats json has totalTraces"); expect(statsJson["totalTraces"] == 12, "stats json totalTraces value"); } // --- Test 11: Code corpus --- static void test_corpus() { printf("Test 11: Code corpus...\n"); TraceGenerator gen; const auto& corpus = gen.corpus(); expect(!corpus.empty(), "corpus not empty"); expect(corpus.size() >= 9, "at least 9 samples in corpus"); // Check corpus has multiple languages std::set languages; for (const auto& s : corpus) { languages.insert(s.language); expect(!s.source.empty(), "sample has source"); expect(!s.description.empty(), "sample has description"); expect(s.complexity >= 1 && s.complexity <= 3, "valid complexity"); } expect(languages.size() >= 4, "at least 4 languages in corpus"); expect(languages.count("python") > 0, "has python samples"); expect(languages.count("cpp") > 0, "has cpp samples"); } // --- Test 12: Trace toJson roundtrip --- static void test_toJson_roundtrip() { printf("Test 12: Trace toJson roundtrip...\n"); TraceGenerator gen; gen.setSeed(700); auto trace = gen.generate(ScenarioType::AddAnnotations); json j = trace.toJson(); // Verify all fields round-trip expect(j["id"] == trace.id, "id roundtrip"); expect(j["scenario"] == trace.scenario, "scenario roundtrip"); expect(j["difficulty"] == trace.difficulty, "difficulty roundtrip"); expect(j["language"] == trace.language, "language roundtrip"); expect(j["toolCallCount"] == trace.toolCallCount, "toolCallCount roundtrip"); expect(j["success"] == trace.success, "success roundtrip"); expect(j["steps"].size() == trace.steps.size(), "steps count roundtrip"); expect(j["toolsUsed"].size() == trace.toolsUsed.size(), "toolsUsed count roundtrip"); // Verify JSON is parseable after dump std::string dumped = j.dump(); json reparsed = json::parse(dumped); expect(reparsed == j, "JSON dump/parse roundtrip"); } // --- Test 13: Batch export formats --- static void test_batch_exports() { printf("Test 13: Batch export formats...\n"); TraceGenerator gen; auto traces = gen.generateBatch(6, 42); json anthropicBatch = TraceExporter::toAnthropicBatch(traces); expect(anthropicBatch.is_array(), "Anthropic batch is array"); expect(anthropicBatch.size() == 6, "Anthropic batch has 6 items"); json openaiBatch = TraceExporter::toOpenAIBatch(traces); expect(openaiBatch.is_array(), "OpenAI batch is array"); expect(openaiBatch.size() == 6, "OpenAI batch has 6 items"); } int main() { printf("=== Step 219: Trace Generation & Export Tests ===\n\n"); test_trace_schema(); test_all_scenarios(); test_seed_determinism(); test_anthropic_export(); test_openai_export(); test_jsonl_export(); test_markdown_export(); test_batch_generation(); test_filtering(); test_statistics(); test_corpus(); test_toJson_roundtrip(); test_batch_exports(); printf("\n=== Results: %d passed, %d failed ===\n", passed, failed); return failed ? 1 : 0; }