2026-02-07 08:26:57 -07:00
|
|
|
// Step 62 TDD Test: Context API
|
|
|
|
|
//
|
|
|
|
|
// Tests the agent context API:
|
|
|
|
|
// 1. getInScopeSymbols returns variables/functions/parameters visible at a position
|
|
|
|
|
// 2. getCallHierarchy returns callers and callees
|
|
|
|
|
// 3. getDependencyGraph returns data flow dependencies
|
|
|
|
|
// 4. Scope respects nesting (inner scope sees outer, not vice versa)
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <cassert>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include "ast/ASTNode.h"
|
|
|
|
|
#include "ast/Module.h"
|
|
|
|
|
#include "ast/Function.h"
|
|
|
|
|
#include "ast/Variable.h"
|
|
|
|
|
#include "ast/Parameter.h"
|
|
|
|
|
#include "ast/Statement.h"
|
|
|
|
|
#include "ast/Expression.h"
|
Phase 3e complete: Agent API Extend (Steps 59, 61-63)
Step 59: WebSocketAgentServer with session management, JSON-RPC routing, MockWebSocketTransport (10/10 tests)
Step 61: ASTMutationAPI with setProperty, updateNode, deleteNode, insertNode, journal, OptimizationLock warning (6/6 tests)
Step 62: ContextAPI with getInScopeSymbols, getCallHierarchy, getDependencyGraph (6/6 tests)
Step 63: BatchMutationAPI with atomic applySequence and reverse-order rollback (5/5 tests)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:57:39 -07:00
|
|
|
#include "ContextAPI.h"
|
2026-02-07 08:26:57 -07:00
|
|
|
|
|
|
|
|
static bool hasSymbol(const std::vector<ContextAPI::Symbol>& symbols, const std::string& name) {
|
|
|
|
|
return std::any_of(symbols.begin(), symbols.end(),
|
|
|
|
|
[&](const ContextAPI::Symbol& s) { return s.name == name; });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
int passed = 0;
|
|
|
|
|
int failed = 0;
|
|
|
|
|
|
|
|
|
|
// Build test AST:
|
|
|
|
|
// Module TestModule:
|
|
|
|
|
// Variable: globalVar
|
|
|
|
|
// Function add(x, y):
|
|
|
|
|
// Variable: localSum
|
|
|
|
|
// Return: localSum
|
|
|
|
|
// Function main():
|
|
|
|
|
// Variable: result
|
|
|
|
|
// ExpressionStatement: add(1, 2) [FunctionCall]
|
|
|
|
|
Module mod("m1", "TestModule", "python");
|
|
|
|
|
|
|
|
|
|
Variable* globalVar = new Variable("gv1", "globalVar");
|
|
|
|
|
mod.addChild("variables", globalVar);
|
|
|
|
|
|
|
|
|
|
Function* addFn = new Function("f1", "add");
|
|
|
|
|
Parameter* px = new Parameter("p1", "x");
|
|
|
|
|
Parameter* py = new Parameter("p2", "y");
|
|
|
|
|
addFn->addChild("parameters", px);
|
|
|
|
|
addFn->addChild("parameters", py);
|
|
|
|
|
|
|
|
|
|
Variable* localSum = new Variable("v1", "localSum");
|
|
|
|
|
addFn->addChild("body", localSum);
|
|
|
|
|
|
|
|
|
|
Return* ret = new Return();
|
|
|
|
|
ret->id = "r1";
|
|
|
|
|
VariableReference* retRef = new VariableReference("vr1", "localSum");
|
|
|
|
|
ret->setChild("value", retRef);
|
|
|
|
|
addFn->addChild("body", ret);
|
|
|
|
|
|
|
|
|
|
mod.addChild("functions", addFn);
|
|
|
|
|
|
|
|
|
|
Function* mainFn = new Function("f2", "main");
|
|
|
|
|
Variable* result = new Variable("v2", "result");
|
|
|
|
|
mainFn->addChild("body", result);
|
|
|
|
|
|
|
|
|
|
ExpressionStatement* callStmt = new ExpressionStatement();
|
|
|
|
|
callStmt->id = "es1";
|
|
|
|
|
FunctionCall* call = new FunctionCall();
|
|
|
|
|
call->id = "fc1";
|
|
|
|
|
call->functionName = "add";
|
|
|
|
|
IntegerLiteral* arg1 = new IntegerLiteral("i1", 1);
|
|
|
|
|
IntegerLiteral* arg2 = new IntegerLiteral("i2", 2);
|
|
|
|
|
call->addChild("arguments", arg1);
|
|
|
|
|
call->addChild("arguments", arg2);
|
|
|
|
|
callStmt->setChild("expression", call);
|
|
|
|
|
mainFn->addChild("body", callStmt);
|
|
|
|
|
|
|
|
|
|
mod.addChild("functions", mainFn);
|
|
|
|
|
|
|
|
|
|
ContextAPI api;
|
|
|
|
|
api.setRoot(&mod);
|
|
|
|
|
|
|
|
|
|
// --- Test 1: Symbols in scope inside add function body ---
|
|
|
|
|
{
|
|
|
|
|
auto symbols = api.getInScopeSymbols("v1"); // localSum inside add()
|
|
|
|
|
assert(hasSymbol(symbols, "x") && "Should see parameter x");
|
|
|
|
|
assert(hasSymbol(symbols, "y") && "Should see parameter y");
|
|
|
|
|
assert(hasSymbol(symbols, "localSum") && "Should see local variable");
|
|
|
|
|
assert(hasSymbol(symbols, "globalVar") && "Should see module-level variable");
|
|
|
|
|
assert(hasSymbol(symbols, "add") && "Should see sibling function");
|
|
|
|
|
|
|
|
|
|
std::cout << "Test 1 PASS: Inner scope sees params, locals, globals, and functions" << std::endl;
|
|
|
|
|
++passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Test 2: Module scope doesn't see function locals ---
|
|
|
|
|
{
|
|
|
|
|
auto symbols = api.getInScopeSymbols("m1"); // Module level
|
|
|
|
|
assert(hasSymbol(symbols, "globalVar") && "Module should see its own variables");
|
|
|
|
|
assert(hasSymbol(symbols, "add") && "Module should see its functions");
|
|
|
|
|
assert(hasSymbol(symbols, "main") && "Module should see its functions");
|
|
|
|
|
assert(!hasSymbol(symbols, "localSum") && "Module should NOT see function locals");
|
|
|
|
|
assert(!hasSymbol(symbols, "x") && "Module should NOT see function parameters");
|
|
|
|
|
|
|
|
|
|
std::cout << "Test 2 PASS: Module scope doesn't see function internals" << std::endl;
|
|
|
|
|
++passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Test 3: main() scope sees its locals but not add() locals ---
|
|
|
|
|
{
|
|
|
|
|
auto symbols = api.getInScopeSymbols("v2"); // result inside main()
|
|
|
|
|
assert(hasSymbol(symbols, "result") && "Should see own local");
|
|
|
|
|
assert(hasSymbol(symbols, "globalVar") && "Should see module variable");
|
|
|
|
|
assert(hasSymbol(symbols, "add") && "Should see sibling function");
|
|
|
|
|
assert(!hasSymbol(symbols, "localSum") && "Should NOT see add()'s locals");
|
|
|
|
|
assert(!hasSymbol(symbols, "x") && "Should NOT see add()'s parameters");
|
|
|
|
|
|
|
|
|
|
std::cout << "Test 3 PASS: main() doesn't see add()'s locals/params" << std::endl;
|
|
|
|
|
++passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Test 4: Call hierarchy for add() ---
|
|
|
|
|
{
|
|
|
|
|
auto callInfo = api.getCallHierarchy("f1"); // add function
|
|
|
|
|
assert(callInfo.functionName == "add" && "Should be about 'add'");
|
|
|
|
|
// main() calls add(), so add should have main as a caller
|
|
|
|
|
assert(!callInfo.callerIds.empty() && "add() should have callers");
|
|
|
|
|
bool mainCalls = std::any_of(callInfo.callerIds.begin(), callInfo.callerIds.end(),
|
|
|
|
|
[](const std::string& id) { return id == "f2"; });
|
|
|
|
|
assert(mainCalls && "main (f2) should be a caller of add");
|
|
|
|
|
|
|
|
|
|
std::cout << "Test 4 PASS: Call hierarchy shows main() calls add()" << std::endl;
|
|
|
|
|
++passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Test 5: Call hierarchy for main() ---
|
|
|
|
|
{
|
|
|
|
|
auto callInfo = api.getCallHierarchy("f2"); // main function
|
|
|
|
|
assert(callInfo.functionName == "main" && "Should be about 'main'");
|
|
|
|
|
// main() calls add()
|
|
|
|
|
bool callsAdd = std::any_of(callInfo.calleeIds.begin(), callInfo.calleeIds.end(),
|
|
|
|
|
[](const std::string& id) { return id == "f1"; });
|
|
|
|
|
assert(callsAdd && "main should call add (f1)");
|
|
|
|
|
|
|
|
|
|
std::cout << "Test 5 PASS: Call hierarchy shows main() calls add()" << std::endl;
|
|
|
|
|
++passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Test 6: Dependency graph for return value ---
|
|
|
|
|
{
|
|
|
|
|
auto deps = api.getDependencyGraph("r1"); // Return node
|
|
|
|
|
// The return depends on localSum (vr1 references it)
|
|
|
|
|
assert(!deps.empty() && "Return should have dependencies");
|
|
|
|
|
|
|
|
|
|
std::cout << "Test 6 PASS: Dependency graph returns dependencies" << std::endl;
|
|
|
|
|
++passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
|
delete arg2;
|
|
|
|
|
delete arg1;
|
|
|
|
|
delete call;
|
|
|
|
|
delete callStmt;
|
|
|
|
|
delete result;
|
|
|
|
|
delete mainFn;
|
|
|
|
|
delete retRef;
|
|
|
|
|
delete ret;
|
|
|
|
|
delete localSum;
|
|
|
|
|
delete py;
|
|
|
|
|
delete px;
|
|
|
|
|
delete addFn;
|
|
|
|
|
delete globalVar;
|
|
|
|
|
|
|
|
|
|
// --- Summary ---
|
|
|
|
|
std::cout << "\n=== Step 62 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
|
|
|
|
return failed > 0 ? 1 : 0;
|
|
|
|
|
}
|