Step 61 TDD test: AST mutation API (extend)
Tests setProperty, updateNode bulk update, journal recording, OptimizationLock warning on mutation (not rejection), deleteNode, and insertNode. Defines ASTMutationAPI interface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -172,6 +172,9 @@ target_include_directories(step57_test PRIVATE src)
|
||||
add_executable(step60_test tests/step60_test.cpp)
|
||||
target_include_directories(step60_test PRIVATE src)
|
||||
|
||||
add_executable(step61_test tests/step61_test.cpp)
|
||||
target_include_directories(step61_test PRIVATE src)
|
||||
|
||||
add_executable(whetstone_editor src/main.cpp)
|
||||
target_include_directories(whetstone_editor PRIVATE src)
|
||||
# find_package(SDL2 REQUIRED) # Commented out for now to avoid build issues
|
||||
|
||||
192
editor/tests/step61_test.cpp
Normal file
192
editor/tests/step61_test.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
// Step 61 TDD Test: AST mutation API (extend)
|
||||
//
|
||||
// Tests extended mutation API with validation and lock checking:
|
||||
// 1. updateNode bulk property update
|
||||
// 2. Mutation recorded in operation journal
|
||||
// 3. Mutation on OptimizationLock node produces warning (not rejection)
|
||||
// 4. deleteNode removes a node from its parent
|
||||
// 5. insertNode adds a node at the correct position
|
||||
//
|
||||
// Will fail until the extended mutation API is implemented.
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
#include "ast/Variable.h"
|
||||
#include "ast/Statement.h"
|
||||
#include "ast/Expression.h"
|
||||
#include "ast/Annotation.h"
|
||||
|
||||
// Forward declaration — ASTMutationAPI
|
||||
class ASTMutationAPI {
|
||||
public:
|
||||
struct MutationResult {
|
||||
bool success;
|
||||
std::string warning; // Non-empty if a warning was produced
|
||||
std::string error; // Non-empty if mutation failed
|
||||
};
|
||||
|
||||
// Set the root AST to mutate
|
||||
void setRoot(ASTNode* root);
|
||||
|
||||
// Update multiple properties on a node at once
|
||||
MutationResult updateNode(const std::string& nodeId,
|
||||
const std::map<std::string, std::string>& properties);
|
||||
|
||||
// Delete a node by ID (removes from parent)
|
||||
MutationResult deleteNode(const std::string& nodeId);
|
||||
|
||||
// Insert a node as child of parentId in the given role
|
||||
MutationResult insertNode(const std::string& parentId,
|
||||
const std::string& role,
|
||||
ASTNode* node);
|
||||
|
||||
// Set a property on a node
|
||||
MutationResult setProperty(const std::string& nodeId,
|
||||
const std::string& property,
|
||||
const std::string& value);
|
||||
|
||||
// Get operation journal entries since last call
|
||||
std::vector<std::string> getJournalEntries() const;
|
||||
|
||||
// Clear journal
|
||||
void clearJournal();
|
||||
};
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
// Build test AST
|
||||
Module mod("m1", "TestModule", "python");
|
||||
|
||||
Function* fn = new Function("f1", "compute");
|
||||
|
||||
// Add an OptimizationLock annotation
|
||||
OptimizationLock* lock = new OptimizationLock();
|
||||
lock->id = "lock1";
|
||||
lock->lockedBy = "agent-1";
|
||||
lock->lockReason = "Performance critical";
|
||||
lock->lockLevel = "warning";
|
||||
fn->addChild("annotations", lock);
|
||||
|
||||
Variable* var = new Variable("v1", "result");
|
||||
fn->addChild("body", var);
|
||||
|
||||
Return* ret = new Return();
|
||||
ret->id = "r1";
|
||||
IntegerLiteral* val = new IntegerLiteral("i1", 42);
|
||||
ret->setChild("value", val);
|
||||
fn->addChild("body", ret);
|
||||
|
||||
mod.addChild("functions", fn);
|
||||
|
||||
ASTMutationAPI api;
|
||||
api.setRoot(&mod);
|
||||
|
||||
// --- Test 1: setProperty updates a node ---
|
||||
{
|
||||
auto result = api.setProperty("f1", "name", "calculate");
|
||||
assert(result.success && "setProperty should succeed");
|
||||
assert(fn->name == "calculate" && "Function name should be updated");
|
||||
|
||||
// Restore for later tests
|
||||
fn->name = "compute";
|
||||
|
||||
std::cout << "Test 1 PASS: setProperty updates node property" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 2: updateNode bulk property update ---
|
||||
{
|
||||
std::map<std::string, std::string> props;
|
||||
props["name"] = "process";
|
||||
auto result = api.updateNode("f1", props);
|
||||
assert(result.success && "updateNode should succeed");
|
||||
assert(fn->name == "process" && "Function name should be bulk-updated");
|
||||
|
||||
// Restore
|
||||
fn->name = "compute";
|
||||
|
||||
std::cout << "Test 2 PASS: updateNode bulk update works" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 3: Mutation is recorded in journal ---
|
||||
{
|
||||
api.clearJournal();
|
||||
api.setProperty("v1", "name", "output");
|
||||
|
||||
auto journal = api.getJournalEntries();
|
||||
assert(!journal.empty() && "Journal should have entries after mutation");
|
||||
|
||||
std::cout << "Test 3 PASS: Mutation recorded in journal" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 4: Mutation on locked node produces warning ---
|
||||
{
|
||||
// The function has an OptimizationLock annotation
|
||||
auto result = api.setProperty("f1", "name", "optimized_compute");
|
||||
assert(result.success && "Mutation should succeed even on locked nodes");
|
||||
assert(!result.warning.empty() &&
|
||||
"Mutation on locked node should produce a warning");
|
||||
|
||||
// Restore
|
||||
fn->name = "compute";
|
||||
|
||||
std::cout << "Test 4 PASS: Locked node mutation produces warning (not rejection)" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 5: deleteNode removes node from parent ---
|
||||
{
|
||||
// Delete the variable from the function body
|
||||
auto result = api.deleteNode("v1");
|
||||
assert(result.success && "deleteNode should succeed");
|
||||
|
||||
auto body = fn->getChildren("body");
|
||||
bool hasVar = false;
|
||||
for (auto* node : body) {
|
||||
if (node->id == "v1") hasVar = true;
|
||||
}
|
||||
assert(!hasVar && "Variable should be removed from body");
|
||||
|
||||
std::cout << "Test 5 PASS: deleteNode removes node from parent" << std::endl;
|
||||
++passed;
|
||||
}
|
||||
|
||||
// --- Test 6: insertNode adds node at correct position ---
|
||||
{
|
||||
Variable* newVar = new Variable("v2", "newResult");
|
||||
auto result = api.insertNode("f1", "body", newVar);
|
||||
assert(result.success && "insertNode should succeed");
|
||||
|
||||
auto body = fn->getChildren("body");
|
||||
bool hasNewVar = false;
|
||||
for (auto* node : body) {
|
||||
if (node->id == "v2") hasNewVar = true;
|
||||
}
|
||||
assert(hasNewVar && "New variable should be in function body");
|
||||
|
||||
std::cout << "Test 6 PASS: insertNode adds node to parent" << std::endl;
|
||||
++passed;
|
||||
|
||||
delete newVar;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
delete val;
|
||||
delete ret;
|
||||
delete var;
|
||||
delete lock;
|
||||
delete fn;
|
||||
|
||||
// --- Summary ---
|
||||
std::cout << "\n=== Step 61 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user