Step 364: Add C integration tests and projection coverage
This commit is contained in:
218
editor/tests/step364_test.cpp
Normal file
218
editor/tests/step364_test.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// Step 364: C Integration + Self-Hosting Prep (8 tests)
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "Pipeline.h"
|
||||
#include "AnnotationInference.h"
|
||||
#include "MemoryStrategyInference.h"
|
||||
#include "CrossLanguageProjector.h"
|
||||
#include "MCPServer.h"
|
||||
#include "ast/CParser.h"
|
||||
#include "ast/CGenerator.h"
|
||||
#include "ast/CppGenerator.h"
|
||||
#include "ast/PythonGenerator.h"
|
||||
#include "ast/RustGenerator.h"
|
||||
|
||||
static bool contains(const std::string& text, const std::string& token) {
|
||||
return text.find(token) != std::string::npos;
|
||||
}
|
||||
|
||||
static bool hasSuggestion(const std::vector<MemoryStrategyInference::Suggestion>& suggestions,
|
||||
const std::string& annotationType,
|
||||
const std::string& strategy) {
|
||||
for (const auto& s : suggestions) {
|
||||
if (s.annotationType == annotationType && s.strategy == strategy) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool hasInference(const std::vector<AnnotationInference::InferredAnnotation>& inferred,
|
||||
const std::string& annotationType,
|
||||
const std::string& value) {
|
||||
for (const auto& a : inferred) {
|
||||
if (a.annotationType == annotationType && a.value == value) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
|
||||
// Test 1: Parse C -> generate C -> reparse keeps core declarations
|
||||
{
|
||||
std::string src = "typedef struct Point { int x; int y; } Point;\\n"
|
||||
"int add(int a, int b) { return a + b; }\\n";
|
||||
auto mod = CParser::parseC(src);
|
||||
CGenerator gen;
|
||||
std::string out = gen.generate(mod.get());
|
||||
auto roundtrip = CParser::parseC(out);
|
||||
assert(!roundtrip->getChildren("classes").empty());
|
||||
assert(contains(out, "typedef struct Point"));
|
||||
std::cout << "Test 1 PASSED: C parse/generate roundtrip keeps declarations\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 2: C struct projects as Python class shape
|
||||
{
|
||||
auto* cls = new ClassDeclaration("c1", "Point");
|
||||
auto* x = new Variable("v1", "x");
|
||||
x->setChild("type", new PrimitiveType("t1", "int"));
|
||||
cls->addChild("fields", x);
|
||||
|
||||
PythonGenerator py;
|
||||
std::string out = py.generate(cls);
|
||||
assert(contains(out, "class Point"));
|
||||
assert(contains(out, "x"));
|
||||
std::cout << "Test 2 PASSED: C struct emits Python class form\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 3: Python-style class emits C struct + constructor-style function
|
||||
{
|
||||
auto* cls = new ClassDeclaration("c1", "Widget");
|
||||
auto* field = new Variable("v1", "value");
|
||||
field->setChild("type", new PrimitiveType("t1", "int"));
|
||||
cls->addChild("fields", field);
|
||||
auto* init = new MethodDeclaration("m1", "init");
|
||||
init->className = "Widget";
|
||||
init->setChild("returnType", new PrimitiveType("rt", "void"));
|
||||
cls->addChild("methods", init);
|
||||
CGenerator cgen;
|
||||
std::string outStruct = cgen.generate(cls);
|
||||
std::string outCtor = cgen.generate(init);
|
||||
assert(contains(outStruct, "typedef struct Widget"));
|
||||
assert(contains(outCtor, "Widget_init("));
|
||||
std::cout << "Test 3 PASSED: Python class shape + constructor-style method generation\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 4: C manual ownership projects to Rust ownership semantics
|
||||
{
|
||||
Module cmod("m1", "cmod", "c");
|
||||
auto* fn = new Function("f1", "make");
|
||||
fn->addChild("annotations", new OwnerAnnotation("a1", "Manual"));
|
||||
cmod.addChild("functions", fn);
|
||||
|
||||
CrossLanguageProjector projector;
|
||||
auto rustMod = projector.project(&cmod, "rust");
|
||||
auto* rustFn = static_cast<Function*>(rustMod->getChildren("functions")[0]);
|
||||
|
||||
bool hasBox = false;
|
||||
for (auto* a : rustFn->getChildren("annotations")) {
|
||||
if (a->conceptType == "OwnerAnnotation") {
|
||||
auto* oa = static_cast<OwnerAnnotation*>(a);
|
||||
hasBox = hasBox || (oa->strategy == "Box");
|
||||
}
|
||||
}
|
||||
assert(hasBox);
|
||||
|
||||
RustGenerator rust;
|
||||
std::string out = rust.generate(rustMod.get());
|
||||
assert(contains(out, "@owner(Box)"));
|
||||
std::cout << "Test 4 PASSED: C manual owner adapts to Rust Box\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 5: Pipeline.run C->C++ succeeds and emits C++ translation unit shell
|
||||
{
|
||||
Pipeline p;
|
||||
std::string src = "struct Point { int x; int y; };\\n";
|
||||
auto result = p.run(src, "c", "cpp");
|
||||
assert(result.success);
|
||||
assert(!result.generatedCode.empty());
|
||||
assert(contains(result.generatedCode, "namespace"));
|
||||
std::cout << "Test 5 PASSED: pipeline C->C++ run succeeds with generated output\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 6: C inference captures malloc + array access + pointer parameter
|
||||
{
|
||||
Module mod("m1", "infer", "c");
|
||||
auto* fn = new Function("f1", "process");
|
||||
auto* param = new Parameter("p1", "cb");
|
||||
param->setChild("type", new PrimitiveType("pt", "void (*)(int)"));
|
||||
fn->addChild("parameters", param);
|
||||
|
||||
auto* allocCall = new FunctionCall();
|
||||
allocCall->id = "fc1";
|
||||
allocCall->functionName = "malloc";
|
||||
fn->addChild("body", allocCall);
|
||||
|
||||
auto* idx = new IndexAccess();
|
||||
idx->id = "idx1";
|
||||
idx->setChild("target", new VariableReference("vr1", "arr"));
|
||||
idx->setChild("index", new VariableReference("vr2", "i"));
|
||||
auto* stmt = new ExpressionStatement();
|
||||
stmt->id = "s1";
|
||||
stmt->setChild("expression", idx);
|
||||
fn->addChild("body", stmt);
|
||||
mod.addChild("functions", fn);
|
||||
|
||||
MemoryStrategyInference mem;
|
||||
AnnotationInference anno;
|
||||
auto suggestions = mem.inferAnnotations(&mod);
|
||||
auto inferred = anno.inferAll(&mod);
|
||||
|
||||
assert(hasSuggestion(suggestions, "OwnerAnnotation", "Manual"));
|
||||
assert(hasSuggestion(suggestions, "OwnerAnnotation", "Borrowed"));
|
||||
assert(hasInference(inferred, "BoundsCheckAnnotation", "unchecked"));
|
||||
std::cout << "Test 6 PASSED: C inference covers malloc/array/pointer patterns\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 7: Parse simplified C header from dependency-style source
|
||||
{
|
||||
std::string header =
|
||||
"#ifndef TINY_HDR_H\\n"
|
||||
"#define TINY_HDR_H\\n"
|
||||
"#include <stddef.h>\\n"
|
||||
"typedef struct Tiny { int size; } Tiny;\\n"
|
||||
"enum TinyMode { TinyFast = 1, TinySafe = 2 };\\n"
|
||||
"#endif\\n";
|
||||
|
||||
auto mod = CParser::parseC(header);
|
||||
assert(!mod->getChildren("classes").empty());
|
||||
assert(!mod->getChildren("statements").empty());
|
||||
std::cout << "Test 7 PASSED: simplified C header parses into expected AST\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
// Test 8: MCP tools list stable and pipeline tool accepts language parameter
|
||||
{
|
||||
MCPServer server;
|
||||
auto tools = server.getTools();
|
||||
const size_t expectedCount = tools.size();
|
||||
|
||||
json req = {
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", 1},
|
||||
{"method", "tools/list"},
|
||||
{"params", json::object()}
|
||||
};
|
||||
auto resp = server.handleRequest(req);
|
||||
assert(resp.contains("result"));
|
||||
assert(resp["result"].contains("tools"));
|
||||
auto listed = resp["result"]["tools"];
|
||||
assert(listed.is_array());
|
||||
assert(listed.size() == expectedCount);
|
||||
|
||||
bool hasRunPipeline = false;
|
||||
for (const auto& t : listed) {
|
||||
if (t.value("name", "") == "whetstone_run_pipeline") {
|
||||
hasRunPipeline = true;
|
||||
auto props = t["inputSchema"]["properties"];
|
||||
assert(props.contains("sourceLanguage"));
|
||||
assert(props["sourceLanguage"]["type"] == "string");
|
||||
}
|
||||
}
|
||||
assert(hasRunPipeline);
|
||||
std::cout << "Test 8 PASSED: MCP tools stable; run_pipeline accepts language string\\n";
|
||||
passed++;
|
||||
}
|
||||
|
||||
std::cout << "\\nResults: " << passed << "/8\\n";
|
||||
assert(passed == 8);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user