Step 66 TDD test: Cross-language memory projection
Tests Lossless Projection Logic: Python @Reclaim(Tracing)->C++ shared_ptr, C++ @Deallocate(Explicit)->Python wrapper, @Owner(Single) preserved in Python metadata, round-trip annotation preservation, @Lifetime(RAII)-> unique_ptr. Defines CrossLanguageProjector interface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -187,6 +187,9 @@ target_include_directories(step64_test PRIVATE src)
|
||||
add_executable(step65_test tests/step65_test.cpp)
|
||||
target_include_directories(step65_test PRIVATE src)
|
||||
|
||||
add_executable(step66_test tests/step66_test.cpp)
|
||||
target_include_directories(step66_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
|
||||
|
||||
204
editor/tests/step66_test.cpp
Normal file
204
editor/tests/step66_test.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
// Step 66 TDD Test: Cross-language memory projection
|
||||
//
|
||||
// Tests the "Lossless Projection Logic" from Memory strategy.md Section 3:
|
||||
// 1. Python → C++: @Reclaim(Tracing) → inject ref-counting shim (shared_ptr)
|
||||
// 2. C++ → Python: @Deallocate(Explicit) → wrap in try-with-resources pattern
|
||||
// 3. Rust-like → Python: @Owner(Single) → preserve in metadata, don't enforce
|
||||
// 4. Round-trip: annotation survives language round-trip
|
||||
// 5. @Lifetime(RAII) → inject destructor calls at scope end
|
||||
//
|
||||
// Will fail until cross-language projection is implemented.
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
#include "ast/Variable.h"
|
||||
#include "ast/Annotation.h"
|
||||
#include "ast/Generator.h"
|
||||
|
||||
static bool contains(const std::string& haystack, const std::string& needle) {
|
||||
return haystack.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
// Forward declaration — CrossLanguageProjector
|
||||
class CrossLanguageProjector {
|
||||
public:
|
||||
// Project an AST from source language to target language
|
||||
// Returns a new AST with annotations adapted for the target
|
||||
std::unique_ptr<Module> project(const Module* source, const std::string& targetLanguage) const;
|
||||
|
||||
// Check if all annotations survived the projection
|
||||
bool annotationsPreserved(const Module* original, const Module* projected) const;
|
||||
};
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
CrossLanguageProjector projector;
|
||||
|
||||
// --- Test 1: Python @Reclaim(Tracing) → C++ shared_ptr ---
|
||||
{
|
||||
Module pyMod("m1", "PyModule", "python");
|
||||
Function* fn = new Function("f1", "process");
|
||||
|
||||
ReclaimAnnotation* anno = new ReclaimAnnotation();
|
||||
anno->id = "a1";
|
||||
anno->strategy = "Tracing";
|
||||
fn->addChild("annotations", anno);
|
||||
|
||||
Variable* var = new Variable("v1", "data");
|
||||
fn->addChild("body", var);
|
||||
pyMod.addChild("functions", fn);
|
||||
|
||||
auto cppMod = projector.project(&pyMod, "cpp");
|
||||
assert(cppMod != nullptr && "Projection should produce a module");
|
||||
assert(cppMod->targetLanguage == "cpp" && "Target should be cpp");
|
||||
|
||||
// Generate C++ from the projected module
|
||||
CppGenerator gen;
|
||||
std::string output = gen.generate(cppMod.get());
|
||||
|
||||
// @Reclaim(Tracing) in C++ → should use shared_ptr for GC-like semantics
|
||||
assert(contains(output, "shared_ptr") &&
|
||||
"Python @Reclaim(Tracing) → C++ should use shared_ptr");
|
||||
|
||||
std::cout << "Test 1 PASS: Python @Reclaim(Tracing) → C++ shared_ptr" << std::endl;
|
||||
++passed;
|
||||
|
||||
delete var;
|
||||
delete anno;
|
||||
delete fn;
|
||||
}
|
||||
|
||||
// --- Test 2: C++ @Deallocate(Explicit) → Python wrapper ---
|
||||
{
|
||||
Module cppMod("m1", "CppModule", "cpp");
|
||||
Function* fn = new Function("f1", "allocate");
|
||||
|
||||
DeallocateAnnotation* anno = new DeallocateAnnotation();
|
||||
anno->id = "a1";
|
||||
anno->strategy = "Explicit";
|
||||
fn->addChild("annotations", anno);
|
||||
|
||||
Variable* var = new Variable("v1", "ptr");
|
||||
fn->addChild("body", var);
|
||||
cppMod.addChild("functions", fn);
|
||||
|
||||
auto pyMod = projector.project(&cppMod, "python");
|
||||
assert(pyMod != nullptr && "Projection should produce a module");
|
||||
assert(pyMod->targetLanguage == "python" && "Target should be python");
|
||||
|
||||
// The annotation should still be present in metadata
|
||||
auto projectedFns = pyMod->getChildren("functions");
|
||||
assert(!projectedFns.empty() && "Should have functions");
|
||||
|
||||
std::cout << "Test 2 PASS: C++ @Deallocate(Explicit) → Python projection" << std::endl;
|
||||
++passed;
|
||||
|
||||
delete var;
|
||||
delete anno;
|
||||
delete fn;
|
||||
}
|
||||
|
||||
// --- Test 3: @Owner(Single) → Python preserves in metadata ---
|
||||
{
|
||||
Module rustLike("m1", "RustLike", "cpp");
|
||||
Function* fn = new Function("f1", "ownership");
|
||||
|
||||
OwnerAnnotation* anno = new OwnerAnnotation();
|
||||
anno->id = "a1";
|
||||
anno->strategy = "Single";
|
||||
fn->addChild("annotations", anno);
|
||||
rustLike.addChild("functions", fn);
|
||||
|
||||
auto pyMod = projector.project(&rustLike, "python");
|
||||
|
||||
// @Owner(Single) should be preserved in metadata even though Python doesn't enforce it
|
||||
auto projectedFns = pyMod->getChildren("functions");
|
||||
auto* projectedFn = static_cast<Function*>(projectedFns[0]);
|
||||
auto annos = projectedFn->getChildren("annotations");
|
||||
|
||||
bool hasOwner = false;
|
||||
for (auto* a : annos) {
|
||||
if (a->conceptType == "OwnerAnnotation") {
|
||||
hasOwner = true;
|
||||
}
|
||||
}
|
||||
assert(hasOwner && "@Owner(Single) should be preserved in Python projection metadata");
|
||||
|
||||
std::cout << "Test 3 PASS: @Owner(Single) preserved in Python metadata" << std::endl;
|
||||
++passed;
|
||||
|
||||
delete anno;
|
||||
delete fn;
|
||||
}
|
||||
|
||||
// --- Test 4: Round-trip preserves annotations ---
|
||||
{
|
||||
Module pyMod("m1", "RoundTrip", "python");
|
||||
Function* fn = new Function("f1", "process");
|
||||
|
||||
ReclaimAnnotation* anno = new ReclaimAnnotation();
|
||||
anno->id = "a1";
|
||||
anno->strategy = "Tracing";
|
||||
fn->addChild("annotations", anno);
|
||||
pyMod.addChild("functions", fn);
|
||||
|
||||
// Python → C++
|
||||
auto cppMod = projector.project(&pyMod, "cpp");
|
||||
|
||||
// C++ → Python
|
||||
auto pyModBack = projector.project(cppMod.get(), "python");
|
||||
|
||||
// Annotation should survive round-trip
|
||||
assert(projector.annotationsPreserved(&pyMod, pyModBack.get()) &&
|
||||
"Annotations should survive Python → C++ → Python round-trip");
|
||||
|
||||
std::cout << "Test 4 PASS: Round-trip preserves annotations" << std::endl;
|
||||
++passed;
|
||||
|
||||
delete anno;
|
||||
delete fn;
|
||||
}
|
||||
|
||||
// --- Test 5: @Lifetime(RAII) in C++ generation ---
|
||||
{
|
||||
Module cppMod("m1", "RAIIModule", "cpp");
|
||||
Function* fn = new Function("f1", "scoped");
|
||||
|
||||
LifetimeAnnotation* anno = new LifetimeAnnotation();
|
||||
anno->id = "a1";
|
||||
anno->strategy = "RAII";
|
||||
fn->addChild("annotations", anno);
|
||||
|
||||
Variable* var = new Variable("v1", "resource");
|
||||
fn->addChild("body", var);
|
||||
|
||||
PrimitiveType* retType = new PrimitiveType("t1", "void");
|
||||
fn->setChild("returnType", retType);
|
||||
cppMod.addChild("functions", fn);
|
||||
|
||||
CppGenerator gen;
|
||||
std::string output = gen.generate(&cppMod);
|
||||
|
||||
// @Lifetime(RAII) → unique_ptr with RAII semantics
|
||||
assert(contains(output, "unique_ptr") &&
|
||||
"@Lifetime(RAII) should produce unique_ptr in C++");
|
||||
|
||||
std::cout << "Test 5 PASS: @Lifetime(RAII) generates unique_ptr" << std::endl;
|
||||
++passed;
|
||||
|
||||
delete retType;
|
||||
delete var;
|
||||
delete anno;
|
||||
delete fn;
|
||||
}
|
||||
|
||||
// --- Summary ---
|
||||
std::cout << "\n=== Step 66 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user