Completes Sprint 10. Adds EnvironmentSpec AST node, capability validation (E0501), annotation compatibility checks (E0502-E0505), env-aware lowering hints, 3 host boundary AST nodes (HostCall, ScheduleTask, ModuleLoad), and 4 MCP environment tools. 38 MCP tools total. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
237 lines
7.9 KiB
C++
237 lines
7.9 KiB
C++
// Step 288: Host Boundary AST Nodes (12 tests)
|
|
//
|
|
// Tests HostCall, ScheduleTask, ModuleLoad AST node construction,
|
|
// JSON roundtrip serialization, capability validation, and compact
|
|
// AST integration.
|
|
#include <cassert>
|
|
#include <iostream>
|
|
#include <string>
|
|
#include "ast/ASTNode.h"
|
|
#include "ast/Module.h"
|
|
#include "ast/Function.h"
|
|
#include "ast/HostBoundary.h"
|
|
#include "ast/Serialization.h"
|
|
#include "ASTUtils.h"
|
|
#include "CompactAST.h"
|
|
#include "EnvironmentSpec.h"
|
|
#include <nlohmann/json.hpp>
|
|
|
|
using json = nlohmann::json;
|
|
|
|
static int passed = 0, failed = 0;
|
|
static void check(bool c, const std::string& n) {
|
|
if (c) { std::cout << " PASS: " << n << "\n"; ++passed; }
|
|
else { std::cout << " FAIL: " << n << "\n"; ++failed; }
|
|
}
|
|
|
|
// Test 1: HostCall default construction
|
|
static void test_hostcall_default() {
|
|
HostCall hc;
|
|
check(hc.conceptType == "HostCall", "conceptType is HostCall");
|
|
check(hc.capability.empty(), "default capability empty");
|
|
check(hc.name.empty(), "default name empty");
|
|
}
|
|
|
|
// Test 2: HostCall parameterized construction
|
|
static void test_hostcall_params() {
|
|
HostCall hc("io.fs", "readFile");
|
|
check(hc.conceptType == "HostCall", "conceptType is HostCall");
|
|
check(hc.capability == "io.fs", "capability set to io.fs");
|
|
check(hc.name == "readFile", "name set to readFile");
|
|
}
|
|
|
|
// Test 3: HostCall JSON roundtrip
|
|
static void test_hostcall_roundtrip() {
|
|
auto* hc = new HostCall("io.net", "listen");
|
|
hc->id = "hc1";
|
|
|
|
json j = toJson(hc);
|
|
check(j["concept"] == "HostCall", "JSON concept");
|
|
check(j["properties"]["capability"] == "io.net", "JSON capability");
|
|
check(j["properties"]["name"] == "listen", "JSON name");
|
|
|
|
ASTNode* restored = fromJson(j);
|
|
check(restored != nullptr, "fromJson non-null");
|
|
check(restored->conceptType == "HostCall", "restored type");
|
|
auto* rhc = static_cast<HostCall*>(restored);
|
|
check(rhc->capability == "io.net", "roundtrip capability");
|
|
check(rhc->name == "listen", "roundtrip name");
|
|
delete hc;
|
|
delete restored;
|
|
}
|
|
|
|
// Test 4: ScheduleTask default construction
|
|
static void test_schedule_default() {
|
|
ScheduleTask st;
|
|
check(st.conceptType == "ScheduleTask", "conceptType is ScheduleTask");
|
|
check(st.queue.empty(), "default queue empty");
|
|
}
|
|
|
|
// Test 5: ScheduleTask JSON roundtrip
|
|
static void test_schedule_roundtrip() {
|
|
auto* st = new ScheduleTask();
|
|
st->id = "st1";
|
|
st->queue = "microtask";
|
|
|
|
json j = toJson(st);
|
|
check(j["concept"] == "ScheduleTask", "JSON concept");
|
|
check(j["properties"]["queue"] == "microtask", "JSON queue");
|
|
|
|
ASTNode* restored = fromJson(j);
|
|
check(restored != nullptr, "fromJson non-null");
|
|
auto* rst = static_cast<ScheduleTask*>(restored);
|
|
check(rst->queue == "microtask", "roundtrip queue");
|
|
delete st;
|
|
delete restored;
|
|
}
|
|
|
|
// Test 6: ModuleLoad default construction
|
|
static void test_moduleload_default() {
|
|
ModuleLoad ml;
|
|
check(ml.conceptType == "ModuleLoad", "conceptType is ModuleLoad");
|
|
check(ml.moduleName.empty(), "default moduleName empty");
|
|
check(ml.mode.empty(), "default mode empty");
|
|
}
|
|
|
|
// Test 7: ModuleLoad JSON roundtrip
|
|
static void test_moduleload_roundtrip() {
|
|
auto* ml = new ModuleLoad();
|
|
ml->id = "ml1";
|
|
ml->moduleName = "math";
|
|
ml->mode = "static";
|
|
|
|
json j = toJson(ml);
|
|
check(j["concept"] == "ModuleLoad", "JSON concept");
|
|
check(j["properties"]["moduleName"] == "math", "JSON moduleName");
|
|
check(j["properties"]["mode"] == "static", "JSON mode");
|
|
|
|
ASTNode* restored = fromJson(j);
|
|
check(restored != nullptr, "fromJson non-null");
|
|
auto* rml = static_cast<ModuleLoad*>(restored);
|
|
check(rml->moduleName == "math", "roundtrip moduleName");
|
|
check(rml->mode == "static", "roundtrip mode");
|
|
delete ml;
|
|
delete restored;
|
|
}
|
|
|
|
// Test 8: createNode for all host boundary types
|
|
static void test_create_nodes() {
|
|
ASTNode* hc = createNode("HostCall");
|
|
check(hc != nullptr && hc->conceptType == "HostCall",
|
|
"createNode HostCall");
|
|
delete hc;
|
|
|
|
ASTNode* st = createNode("ScheduleTask");
|
|
check(st != nullptr && st->conceptType == "ScheduleTask",
|
|
"createNode ScheduleTask");
|
|
delete st;
|
|
|
|
ASTNode* ml = createNode("ModuleLoad");
|
|
check(ml != nullptr && ml->conceptType == "ModuleLoad",
|
|
"createNode ModuleLoad");
|
|
delete ml;
|
|
}
|
|
|
|
// Test 9: HostCall in function body AST
|
|
static void test_hostcall_in_function() {
|
|
auto* mod = new Module(); mod->id = "mod1"; mod->name = "test";
|
|
auto* fn = new Function(); fn->id = "f1"; fn->name = "readConfig";
|
|
auto* hc = new HostCall("io.fs", "readFile");
|
|
hc->id = "hc1";
|
|
fn->addChild("body", hc);
|
|
mod->addChild("functions", fn);
|
|
|
|
json j = toJson(mod);
|
|
ASTNode* restored = fromJson(j);
|
|
auto fns = restored->getChildren("functions");
|
|
check(fns.size() == 1, "restored has 1 function");
|
|
auto body = fns[0]->getChildren("body");
|
|
check(body.size() == 1, "function has 1 body node");
|
|
check(body[0]->conceptType == "HostCall", "body node is HostCall");
|
|
auto* rhc = static_cast<HostCall*>(body[0]);
|
|
check(rhc->capability == "io.fs", "nested HostCall capability");
|
|
check(rhc->name == "readFile", "nested HostCall name");
|
|
delete mod;
|
|
delete restored;
|
|
}
|
|
|
|
// Test 10: Compact AST getNodeName for host boundary nodes
|
|
static void test_compact_node_names() {
|
|
auto* hc = new HostCall("io.fs", "readFile");
|
|
hc->id = "hc1";
|
|
std::string hcName = getNodeName(hc);
|
|
check(hcName == "readFile", "HostCall node name is readFile");
|
|
|
|
auto* st = new ScheduleTask();
|
|
st->id = "st1"; st->queue = "microtask";
|
|
std::string stName = getNodeName(st);
|
|
// ScheduleTask may return queue as name or empty — just check no crash
|
|
check(!stName.empty() || stName.empty(), "ScheduleTask getNodeName no crash");
|
|
|
|
auto* ml = new ModuleLoad();
|
|
ml->id = "ml1"; ml->moduleName = "math";
|
|
std::string mlName = getNodeName(ml);
|
|
check(mlName == "math", "ModuleLoad node name is math");
|
|
|
|
delete hc; delete st; delete ml;
|
|
}
|
|
|
|
// Test 11: HostCall capability validation against env
|
|
static void test_hostcall_cap_validation() {
|
|
auto* mod = new Module(); mod->id = "mod1"; mod->name = "test";
|
|
auto* env = new EnvironmentSpec();
|
|
env->envId = "restricted";
|
|
env->capabilities = {"io.fs"};
|
|
mod->addChild("environment", env);
|
|
|
|
auto* fn = new Function(); fn->id = "f1"; fn->name = "serve";
|
|
auto* cr = new CapabilityRequirement();
|
|
cr->capability = "io.net"; cr->required = true;
|
|
fn->addChild("annotations", cr);
|
|
mod->addChild("functions", fn);
|
|
|
|
auto diags = validateCapabilities(mod, env);
|
|
check(diags.size() == 1, "1 diag for missing io.net");
|
|
check(diags[0].message.find("E0501") != std::string::npos,
|
|
"E0501 for missing capability");
|
|
delete mod;
|
|
}
|
|
|
|
// Test 12: ScheduleTask with body children
|
|
static void test_schedule_with_body() {
|
|
auto* st = new ScheduleTask();
|
|
st->id = "st1";
|
|
st->queue = "thread";
|
|
|
|
auto* hc = new HostCall("io.fs", "writeFile");
|
|
hc->id = "hc1";
|
|
st->addChild("body", hc);
|
|
|
|
json j = toJson(st);
|
|
ASTNode* restored = fromJson(j);
|
|
check(restored != nullptr, "roundtrip non-null");
|
|
auto body = restored->getChildren("body");
|
|
check(body.size() == 1, "ScheduleTask has 1 body child");
|
|
check(body[0]->conceptType == "HostCall", "body child is HostCall");
|
|
delete st;
|
|
delete restored;
|
|
}
|
|
|
|
int main() {
|
|
std::cout << "=== Step 288: Host Boundary AST Nodes ===\n";
|
|
test_hostcall_default();
|
|
test_hostcall_params();
|
|
test_hostcall_roundtrip();
|
|
test_schedule_default();
|
|
test_schedule_roundtrip();
|
|
test_moduleload_default();
|
|
test_moduleload_roundtrip();
|
|
test_create_nodes();
|
|
test_hostcall_in_function();
|
|
test_compact_node_names();
|
|
test_hostcall_cap_validation();
|
|
test_schedule_with_body();
|
|
std::cout << "\nResults: " << passed << "/" << (passed+failed) << "\n";
|
|
return failed > 0 ? 1 : 0;
|
|
}
|