Files
whetstone_DSL/editor/tests/step478_test.cpp

225 lines
9.4 KiB
C++
Raw Normal View History

// Step 478: Scaffold File Generation Tests (12 tests)
#include "ArchitectScaffoldGenerator.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
namespace fs = std::filesystem;
static bool endsWith(const std::string& v, const std::string& suffix) {
return v.size() >= suffix.size() &&
v.compare(v.size() - suffix.size(), suffix.size(), suffix) == 0;
}
static const ScaffoldFileSpec* findBySuffix(const ScaffoldPlan& plan,
const std::string& suffix) {
for (const auto& f : plan.files) {
if (endsWith(f.relativePath, suffix)) return &f;
}
return nullptr;
}
static SkeletonFunctionSpec fn(const std::string& name) {
SkeletonFunctionSpec f;
f.name = name;
f.signature = "void " + name + "()";
f.annotations = {
{"@Intent", "intent " + name},
{"@Complexity", "medium"},
{"@ContextWidth", "module"},
{"@Automatability", "llm"},
{"@Contract", "pre: x; post: y"}
};
return f;
}
static SkeletonModuleSpec mod(const std::string& name, const std::string& lang,
std::initializer_list<SkeletonFunctionSpec> fns = {}) {
SkeletonModuleSpec m;
m.moduleName = name;
m.language = lang;
m.functions.assign(fns.begin(), fns.end());
return m;
}
static SkeletonProjectSpec mixedSkeleton() {
SkeletonProjectSpec sk;
sk.modules.push_back(mod("api", "typescript", {fn("handleRequest")}));
sk.modules.push_back(mod("workers", "go", {fn("dispatch")}));
sk.modules.push_back(mod("schema", "sql", {fn("migrate")}));
return sk;
}
void test_generates_source_files_with_semanno_annotations() {
TEST(generates_source_files_with_semanno_annotations);
ScaffoldInput in{"/tmp", "demo", mixedSkeleton(), true};
auto plan = ArchitectScaffoldGenerator::buildPlan(in);
auto* src = findBySuffix(plan, "demo/src/api/api.ts");
CHECK(src != nullptr, "missing api scaffold file");
CHECK(src->content.find("@Intent") != std::string::npos, "missing @Intent annotation");
CHECK(src->content.find("handleRequest") != std::string::npos, "missing function stub");
PASS();
}
void test_generates_package_json_for_node_stack() {
TEST(generates_package_json_for_node_stack);
SkeletonProjectSpec sk;
sk.modules.push_back(mod("api", "typescript", {fn("main")}));
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", sk, true});
CHECK(plan.hasFile("demo/package.json"), "missing package.json");
PASS();
}
void test_generates_pyproject_for_python_stack() {
TEST(generates_pyproject_for_python_stack);
SkeletonProjectSpec sk;
sk.modules.push_back(mod("jobs", "python", {fn("runJobs")}));
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", sk, true});
CHECK(plan.hasFile("demo/pyproject.toml"), "missing pyproject.toml");
PASS();
}
void test_generates_cargo_for_rust_stack() {
TEST(generates_cargo_for_rust_stack);
SkeletonProjectSpec sk;
sk.modules.push_back(mod("core", "rust", {fn("compute")}));
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", sk, true});
CHECK(plan.hasFile("demo/Cargo.toml"), "missing Cargo.toml");
PASS();
}
void test_generates_cmake_for_cpp_stack() {
TEST(generates_cmake_for_cpp_stack);
SkeletonProjectSpec sk;
sk.modules.push_back(mod("engine", "cpp", {fn("solve")}));
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", sk, true});
CHECK(plan.hasFile("demo/CMakeLists.txt"), "missing CMakeLists.txt");
PASS();
}
void test_directory_structure_follows_language_conventions() {
TEST(directory_structure_follows_language_conventions);
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", mixedSkeleton(), true});
CHECK(findBySuffix(plan, "demo/src/api/api.ts") != nullptr, "missing ts convention path");
CHECK(findBySuffix(plan, "demo/internal/workers/workers.go") != nullptr,
"missing go convention path");
CHECK(findBySuffix(plan, "demo/db/schema.sql") != nullptr, "missing sql convention path");
PASS();
}
void test_gitignore_matches_selected_stack() {
TEST(gitignore_matches_selected_stack);
SkeletonProjectSpec sk;
sk.modules.push_back(mod("api", "typescript", {fn("a")}));
sk.modules.push_back(mod("jobs", "python", {fn("b")}));
sk.modules.push_back(mod("core", "rust", {fn("c")}));
sk.modules.push_back(mod("engine", "cpp", {fn("d")}));
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", sk, true});
auto* gi = findBySuffix(plan, "demo/.gitignore");
CHECK(gi != nullptr, "missing .gitignore");
CHECK(gi->content.find("node_modules/") != std::string::npos, "missing node ignore");
CHECK(gi->content.find("__pycache__/") != std::string::npos, "missing python ignore");
CHECK(gi->content.find("target/") != std::string::npos, "missing rust ignore");
CHECK(gi->content.find("build/") != std::string::npos, "missing cpp ignore");
PASS();
}
void test_whetstone_state_and_sidecars_are_generated() {
TEST(whetstone_state_and_sidecars_are_generated);
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", mixedSkeleton(), true});
CHECK(plan.hasFile("demo/.whetstone/workflow_state.json"), "missing workflow state file");
CHECK(plan.hasFile("demo/.whetstone/sidecars/api.ast.json"), "missing api sidecar");
CHECK(plan.hasFile("demo/.whetstone/sidecars/workers.ast.json"), "missing workers sidecar");
PASS();
}
void test_operations_are_filecreate_and_filewrite_pairs() {
TEST(operations_are_filecreate_and_filewrite_pairs);
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", mixedSkeleton(), true});
CHECK(plan.operations.size() == plan.files.size() * 2, "unexpected operation count");
int creates = 0, writes = 0;
for (const auto& op : plan.operations) {
if (op.method == "fileCreate") ++creates;
if (op.method == "fileWrite") ++writes;
}
CHECK(creates == (int)plan.files.size(), "fileCreate count mismatch");
CHECK(writes == (int)plan.files.size(), "fileWrite count mismatch");
PASS();
}
void test_apply_plan_writes_files_to_disk() {
TEST(apply_plan_writes_files_to_disk);
fs::path root = fs::temp_directory_path() / "whetstone_step478_apply";
fs::remove_all(root);
fs::create_directories(root);
auto plan = ArchitectScaffoldGenerator::buildPlan({root.string(), "demo", mixedSkeleton(), true});
std::string err;
bool ok = ArchitectScaffoldGenerator::applyPlan(plan, &err);
CHECK(ok, ("apply failed: " + err).c_str());
CHECK(fs::exists(root / "demo/src/api/api.ts"), "missing written source file");
CHECK(fs::exists(root / "demo/.whetstone/workflow_state.json"), "missing written workflow state");
std::ifstream in(root / "demo/src/api/api.ts");
std::string body((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
CHECK(body.find("@Intent") != std::string::npos, "written file missing annotation");
fs::remove_all(root);
PASS();
}
void test_apply_plan_is_idempotent_when_files_exist() {
TEST(apply_plan_is_idempotent_when_files_exist);
fs::path root = fs::temp_directory_path() / "whetstone_step478_idempotent";
fs::remove_all(root);
fs::create_directories(root);
auto plan = ArchitectScaffoldGenerator::buildPlan({root.string(), "demo", mixedSkeleton(), true});
std::string err;
CHECK(ArchitectScaffoldGenerator::applyPlan(plan, &err), ("first apply failed: " + err).c_str());
err.clear();
CHECK(ArchitectScaffoldGenerator::applyPlan(plan, &err), ("second apply failed: " + err).c_str());
fs::remove_all(root);
PASS();
}
void test_empty_skeleton_still_generates_basics() {
TEST(empty_skeleton_still_generates_basics);
SkeletonProjectSpec sk;
auto plan = ArchitectScaffoldGenerator::buildPlan({"/tmp", "demo", sk, true});
CHECK(plan.hasFile("demo/.gitignore"), "missing .gitignore");
CHECK(plan.hasFile("demo/.whetstone/workflow_state.json"), "missing workflow state");
CHECK(!plan.operations.empty(), "expected non-empty file operations");
PASS();
}
int main() {
std::cout << "Step 478: Scaffold File Generation Tests\n";
test_generates_source_files_with_semanno_annotations(); // 1
test_generates_package_json_for_node_stack(); // 2
test_generates_pyproject_for_python_stack(); // 3
test_generates_cargo_for_rust_stack(); // 4
test_generates_cmake_for_cpp_stack(); // 5
test_directory_structure_follows_language_conventions(); // 6
test_gitignore_matches_selected_stack(); // 7
test_whetstone_state_and_sidecars_are_generated(); // 8
test_operations_are_filecreate_and_filewrite_pairs(); // 9
test_apply_plan_writes_files_to_disk(); // 10
test_apply_plan_is_idempotent_when_files_exist(); // 11
test_empty_skeleton_still_generates_basics(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed)
<< " passed\n";
return failed == 0 ? 0 : 1;
}