Initial commit

This commit is contained in:
2026-02-26 17:41:05 +01:00
commit 3945b687d4
30 changed files with 2498 additions and 0 deletions

View File

@@ -0,0 +1,820 @@
#include "code_generator.h"
#include "file_utils.h"
#include <sstream>
#include <iostream>
#include <algorithm>
CodeGenerator::CodeGenerator(const Config& cfg) : config(cfg) {}
Instrument CodeGenerator::parseInstrument(std::shared_ptr<JsonValue> json) {
Instrument inst;
inst.instrumentName = json->get("InstrumentName")->getString();
inst.variant = json->get("Variant")->getString();
inst.pinType = json->get("PinType")->getString();
auto options = json->get("Option");
if (options->isArray()) {
for (const auto& opt : options->getArray()) {
inst.options.push_back(opt->getString());
}
}
auto pins = json->get("PinNumbers");
if (pins->isArray()) {
for (const auto& pin : pins->getArray()) {
inst.pinNumbers.push_back(pin->getString());
}
}
return inst;
}
Property CodeGenerator::parseProperty(std::shared_ptr<JsonValue> json) {
Property prop;
if (json->isObject()) {
for (const auto& pair : json->getObject()) {
prop.key = pair.first;
prop.value = pair.second->getString();
break;
}
}
return prop;
}
Variable CodeGenerator::parseVariable(std::shared_ptr<JsonValue> json) {
Variable var;
if (json->isObject()) {
for (const auto& pair : json->getObject()) {
var.name = pair.first;
var.expression = pair.second->getString();
break;
}
}
return var;
}
Setup CodeGenerator::parseSetup(std::shared_ptr<JsonValue> json) {
Setup setup;
auto setupObj = json->get("Setup");
setup.name = setupObj->get("Name")->getString();
auto notes = setupObj->get("Notes");
if (notes->isArray()) {
for (const auto& note : notes->getArray()) {
setup.notes.push_back(note->getString());
}
}
auto instSetup = setupObj->get("InstrumentSetup");
setup.instrumentSetup.name = instSetup->get("Name")->getString();
auto props = instSetup->get("Properties");
if (props->isArray()) {
for (const auto& prop : props->getArray()) {
setup.instrumentSetup.properties.push_back(parseProperty(prop));
}
}
auto expects = instSetup->get("Expects");
if (expects->isArray()) {
for (const auto& expectJson : expects->getArray()) {
Expect expect;
expect.type = expectJson->get("type")->getString();
auto checks = expectJson->get("checks");
if (checks->isArray()) {
for (const auto& check : checks->getArray()) {
if (check->isObject()) {
for (const auto& pair : check->getObject()) {
Check c;
c.key = pair.first;
c.value = pair.second->getString();
expect.checks.push_back(c);
}
}
}
}
setup.instrumentSetup.expects.push_back(expect);
}
}
return setup;
}
SetupFile CodeGenerator::parseSetupFile(std::shared_ptr<JsonValue> json) {
SetupFile setupFile;
auto options = json->get("Options");
if (options->isArray()) {
for (const auto& opt : options->getArray()) {
setupFile.options.push_back(opt->getString());
}
}
auto notes = json->get("FileNotes");
if (notes->isArray()) {
for (const auto& note : notes->getArray()) {
setupFile.fileNotes.push_back(note->getString());
}
}
auto vars = json->get("Variables");
if (vars->isArray()) {
for (const auto& var : vars->getArray()) {
setupFile.variables.push_back(parseVariable(var));
}
}
auto setups = json->get("TestSetups");
if (setups->isArray()) {
for (const auto& setup : setups->getArray()) {
setupFile.setups.push_back(parseSetup(setup));
}
}
return setupFile;
}
Template CodeGenerator::parseTemplate(std::shared_ptr<JsonValue> json) {
Template tmpl;
tmpl.groupId = (int)json->get("GroupId")->getNumber();
tmpl.uniqueTemplateId = json->get("UniqueTemplateId")->getString();
auto desc = json->get("Description");
if (desc->isArray()) {
for (const auto& d : desc->getArray()) {
tmpl.description.push_back(d->getString());
}
}
auto features = json->get("Features");
if (features->isArray()) {
for (const auto& f : features->getArray()) {
tmpl.features.push_back(f->getString());
}
}
auto testInput = json->get("TestInput");
if (testInput->isArray()) {
for (const auto& input : testInput->getArray()) {
TemplateInput ti;
// Process all keys in the object
for (const auto& pair : input->getObject()) {
if (pair.second->isString()) {
// Store string values in key and value
if (ti.key.empty()) {
ti.key = pair.first;
ti.value = pair.second->getString();
}
} else if (pair.second->isArray()) {
// If it's the setups array, store it
if (pair.first == "setups") {
for (const auto& setupItem : pair.second->getArray()) {
std::map<std::string, std::string> setupMap;
for (const auto& setupPair : setupItem->getObject()) {
setupMap[setupPair.first] = setupPair.second->getString();
}
ti.setups.push_back(setupMap);
}
}
}
// Also store SetupFileName separately if needed
if (pair.first == "SetupFileName" && pair.second->isString()) {
ti.value = pair.second->getString();
if (ti.key.empty() || ti.key == "Instruments") {
ti.key = pair.first;
}
}
}
tmpl.testInput.push_back(ti);
}
}
auto templateCalls = json->get("Template");
if (templateCalls->isArray()) {
for (const auto& call : templateCalls->getArray()) {
TemplateCall tc;
tc.call = call->get("call")->getString();
auto args = call->get("args");
if (args->isArray()) {
for (const auto& arg : args->getArray()) {
tc.args.push_back(arg->getString());
}
}
auto ret = call->get("return");
if (ret->isString()) {
tc.returnVar = ret->getString();
}
tmpl.templateCalls.push_back(tc);
}
}
return tmpl;
}
TestExecution CodeGenerator::parseTestExecution(std::shared_ptr<JsonValue> json) {
TestExecution testExec;
testExec.name = json->get("Name")->getString();
auto configs = json->get("Configurations");
if (configs->isArray()) {
for (const auto& config : configs->getArray()) {
TestConfiguration tc;
tc.option = config->get("Option")->getString();
tc.pinType = config->get("PinType")->getString();
testExec.configurations.push_back(tc);
}
}
return testExec;
}
bool CodeGenerator::loadConfigurations() {
JsonParser parser;
// Load InstrumentConfig from variant
std::string instConfigPath = FileUtils::joinPath(config.variantPath, "InstrumentConfig.json");
std::string instConfigContent = FileUtils::readFile(instConfigPath);
if (instConfigContent.empty()) {
std::cerr << "Failed to read: " << instConfigPath << std::endl;
return false;
}
auto instConfigJson = parser.parse(instConfigContent);
auto instruments = instConfigJson->get("Instruments");
if (instruments->isArray()) {
for (const auto& inst : instruments->getArray()) {
config.instruments.push_back(parseInstrument(inst));
}
}
// Load setups from common path
std::string commonSetupsPath = FileUtils::joinPath(config.commonPath, "setups");
auto setupFiles = FileUtils::listFiles(commonSetupsPath, ".json");
for (const auto& filename : setupFiles) {
std::string filepath = FileUtils::joinPath(commonSetupsPath, filename);
std::string content = FileUtils::readFile(filepath);
if (!content.empty()) {
auto setupJson = parser.parse(content);
std::string baseName = filename.substr(0, filename.rfind('.'));
commonSetups[baseName] = parseSetupFile(setupJson);
}
}
// Load setups from variant path
std::string variantSetupsPath = config.variantPath;
setupFiles = FileUtils::listFiles(variantSetupsPath, ".json");
for (const auto& filename : setupFiles) {
if (filename.find("Setup_") == 0) {
std::string filepath = FileUtils::joinPath(variantSetupsPath, filename);
std::string content = FileUtils::readFile(filepath);
if (!content.empty()) {
auto setupJson = parser.parse(content);
std::string baseName = filename.substr(0, filename.rfind('.'));
variantSetups[baseName] = parseSetupFile(setupJson);
}
}
}
// Load templates from common path
std::string templatesPath = FileUtils::joinPath(config.commonPath, "templates");
auto templateFiles = FileUtils::listFiles(templatesPath, ".json");
for (const auto& filename : templateFiles) {
std::string filepath = FileUtils::joinPath(templatesPath, filename);
std::string content = FileUtils::readFile(filepath);
if (!content.empty()) {
auto tmplJson = parser.parse(content);
config.templates.push_back(parseTemplate(tmplJson));
}
}
// Load execution config from variant path
std::string execConfigPath = FileUtils::joinPath(config.variantPath, "ExecutionConfig.json");
std::string execConfigContent = FileUtils::readFile(execConfigPath);
if (!execConfigContent.empty()) {
auto execJson = parser.parse(execConfigContent);
auto tests = execJson->get("Tests");
if (tests->isArray()) {
for (const auto& test : tests->getArray()) {
config.testExecutions.push_back(parseTestExecution(test));
}
}
}
return true;
}
std::string CodeGenerator::substituteVariables(const std::string& expr, const std::map<std::string, std::string>& localVars) {
std::string result = expr;
// Find all variables (starting with $)
size_t pos = 0;
while ((pos = result.find('$', pos)) != std::string::npos) {
size_t end = pos + 1;
while (end < result.length() && (std::isalnum(result[end]) || result[end] == '_')) {
end++;
}
std::string varName = result.substr(pos, end - pos);
std::string replacement = varName;
// Look up variable value
auto it = localVars.find(varName);
if (it != localVars.end()) {
replacement = it->second;
} else if (globalVariables.find(varName) != globalVariables.end()) {
replacement = globalVariables[varName].expression;
}
// If expression contains multiply operator, convert to code
if (replacement.find('*') != std::string::npos) {
// Keep as expression for now, will be evaluated in generated code
replacement = varName;
}
result.replace(pos, end - pos, replacement);
pos += replacement.length();
}
return result;
}
std::string CodeGenerator::processVariableExpression(const std::string& expr) {
// Convert expressions like "0.03*gangCount" to C++ code
std::string result = expr;
// Replace variables with their C++ equivalents
result = FileUtils::replaceAll(result, "$gangCount", "gangCount");
result = FileUtils::replaceAll(result, "$", "");
return result;
}
Setup CodeGenerator::mergeSetup(const std::string& setupName, const std::string& instrumentName) {
Setup merged;
merged.name = setupName;
// Search through all common setup files for a setup with matching name
for (const auto& filePair : commonSetups) {
const auto& commonFile = filePair.second;
for (const auto& setup : commonFile.setups) {
if (setup.name == setupName) {
merged = setup;
break;
}
}
if (!merged.instrumentSetup.name.empty()) break;
}
// Search through all variant setup files and merge/override
for (const auto& filePair : variantSetups) {
const auto& variantFile = filePair.second;
for (const auto& setup : variantFile.setups) {
if (setup.name == setupName) {
// Merge properties
for (const auto& prop : setup.instrumentSetup.properties) {
bool found = false;
for (auto& existingProp : merged.instrumentSetup.properties) {
if (existingProp.key == prop.key) {
existingProp.value = prop.value;
found = true;
break;
}
}
if (!found) {
merged.instrumentSetup.properties.push_back(prop);
}
}
// Merge expects
if (!setup.instrumentSetup.expects.empty()) {
merged.instrumentSetup.expects = setup.instrumentSetup.expects;
}
// Merge variables
for (const auto& var : variantFile.variables) {
globalVariables[var.name] = var;
}
break;
}
}
}
return merged;
}
std::string CodeGenerator::generateSetupMethod(const std::string& setupName, const std::string& instrumentName) {
Setup setup = mergeSetup(setupName, instrumentName);
std::stringstream ss;
ss << " void " << setupName << "(Measurement& measurement, int gangCount) {\n";
ss << " // Setup: " << setupName << "\n";
// Collect variables from all setup files that contain this setup
std::map<std::string, std::string> variables;
for (const auto& filePair : commonSetups) {
for (const auto& fileSetup : filePair.second.setups) {
if (fileSetup.name == setupName) {
// Add variables from this file
for (const auto& var : filePair.second.variables) {
variables[var.name] = var.expression;
}
break;
}
}
}
for (const auto& filePair : variantSetups) {
for (const auto& fileSetup : filePair.second.setups) {
if (fileSetup.name == setupName) {
// Add/override variables from this file
for (const auto& var : filePair.second.variables) {
variables[var.name] = var.expression;
}
break;
}
}
}
// Generate variable declarations
for (const auto& var : variables) {
std::string expr = processVariableExpression(var.second);
std::string varNameClean = var.first.substr(1); // Remove $
ss << " double " << varNameClean << " = " << expr << ";\n";
}
if (!variables.empty()) {
ss << "\n";
}
// Generate property calls
for (const auto& prop : setup.instrumentSetup.properties) {
std::string value = substituteVariables(prop.value, variables);
value = processVariableExpression(value);
// Convert property name: level.vforce -> level_vforce
std::string methodName = FileUtils::replaceAll(prop.key, ".", "_");
if (value == "true" || value == "false") {
ss << " measurement." << methodName << "(" << value << ");\n";
} else if (value == "\"-\"" || value == "-" || value.empty()) {
// Skip removals or empty values
} else {
// Check if value is a number or variable
bool isNumeric = !value.empty() && (std::isdigit(value[0]) || value[0] == '-' || value[0] == '.');
bool isVariable = false;
for (const auto& var : variables) {
if (var.first.substr(1) == value) {
isVariable = true;
break;
}
}
// Special handling for methods that don't need instrument name
if (methodName == "connect" || methodName == "disconnect" || methodName == "disconnectMode") {
if (isNumeric || isVariable) {
ss << " measurement." << methodName << "(" << value << ");\n";
} else {
ss << " measurement." << methodName << "(\"" << value << "\");\n";
}
} else {
// Regular property setters with instrument name
if (isNumeric || isVariable) {
ss << " measurement." << methodName << "(\"" << instrumentName << "\", " << value << ");\n";
} else {
ss << " measurement." << methodName << "(\"" << instrumentName << "\", \"" << value << "\");\n";
}
}
}
}
ss << " }\n\n";
return ss.str();
}
std::string CodeGenerator::generateInstrumentDefinitions() {
std::stringstream ss;
ss << "#ifndef INSTRUMENTS_H\n";
ss << "#define INSTRUMENTS_H\n\n";
ss << "#include <string>\n";
ss << "#include <vector>\n\n";
ss << "struct Instruments {\n";
ss << " std::string variant;\n";
ss << " std::vector<std::string> options;\n";
ss << " std::string pinType;\n";
ss << " std::vector<int> pinNumbers;\n";
ss << " int gangCount;\n\n";
for (const auto& inst : config.instruments) {
std::string varName = inst.instrumentName + "_" + inst.pinType;
if (!inst.options.empty() && inst.options[0] != "") {
for (const auto& opt : inst.options) {
varName += "_" + opt;
}
}
ss << " static Instruments " << varName << "() {\n";
ss << " Instruments inst;\n";
ss << " inst.variant = \"" << inst.variant << "\";\n";
for (const auto& opt : inst.options) {
if (!opt.empty()) {
ss << " inst.options.push_back(\"" << opt << "\");\n";
}
}
ss << " inst.pinType = \"" << inst.pinType << "\";\n";
for (const auto& pin : inst.pinNumbers) {
ss << " inst.pinNumbers.push_back(" << pin << ");\n";
}
ss << " inst.gangCount = " << inst.pinNumbers.size() << ";\n";
ss << " return inst;\n";
ss << " }\n\n";
}
ss << "};\n\n";
ss << "#endif\n";
return ss.str();
}
std::string CodeGenerator::generateTestClass(const Template& tmpl, const TestExecution& testExec) {
std::stringstream ss;
ss << "#include \"Instruments.h\"\n";
ss << "#include \"Measurement.h\"\n\n";
ss << "// Test class generated from template: " << tmpl.uniqueTemplateId << "\n";
ss << "class " << testExec.name << " {\n";
ss << "public:\n";
// Generate setup methods
std::map<std::string, std::string> setupMapping;
for (const auto& input : tmpl.testInput) {
if (input.key == "SetupFileName") {
for (const auto& setupMap : input.setups) {
for (const auto& pair : setupMap) {
setupMapping[pair.first] = pair.second;
ss << generateSetupMethod(pair.second, "pin1");
}
}
}
}
// Generate run method
ss << " void run(const Instruments& instruments) {\n";
ss << " int gangCount = instruments.gangCount;\n\n";
for (const auto& call : tmpl.templateCalls) {
ss << " // " << call.call << "\n";
if (call.call == "createMeasurement") {
if (!call.returnVar.empty()) {
ss << " Measurement " << call.returnVar << "Obj(instruments);\n";
}
} else if (call.call == "applySetup") {
if (call.args.size() >= 2) {
std::string setupName = call.args[1];
auto it = setupMapping.find(setupName);
if (it != setupMapping.end()) {
ss << " " << it->second << "(" << call.args[0] << "Obj, gangCount);\n";
}
}
} else if (call.call == "buildMeasurement") {
ss << " " << call.args[0] << "Obj.build(" << call.args[1] << ");\n";
} else if (call.call == "executeMeasurement") {
ss << " " << call.args[0] << "Obj.execute(" << call.args[1] << ");\n";
} else if (call.call == "ProblemCheck" || call.call == "StateCheck" || call.call == "LogSetCheck") {
ss << " " << call.args[0] << "Obj." << call.call << "();\n";
}
}
ss << " }\n";
ss << "};\n\n";
return ss.str();
}
std::string CodeGenerator::generateMainFile(const std::vector<std::string>& testNames) {
std::stringstream ss;
ss << "#include <iostream>\n";
ss << "#include \"Instruments.h\"\n";
for (const auto& testName : testNames) {
ss << "#include \"" << testName << ".cpp\"\n";
}
ss << "\nint main() {\n";
ss << " std::cout << \"Running generated tests...\" << std::endl;\n\n";
// Generate test runs for each configuration
for (size_t i = 0; i < config.instruments.size(); i++) {
const auto& inst = config.instruments[i];
std::string varName = inst.instrumentName + "_" + inst.pinType;
if (!inst.options.empty() && inst.options[0] != "") {
for (const auto& opt : inst.options) {
varName += "_" + opt;
}
}
for (const auto& testName : testNames) {
ss << " {\n";
ss << " " << testName << " test;\n";
ss << " test.run(Instruments::" << varName << "());\n";
ss << " std::cout << \"Completed: " << testName << " with " << varName << "\" << std::endl;\n";
ss << " }\n\n";
}
}
ss << " std::cout << \"All tests completed.\" << std::endl;\n";
ss << " return 0;\n";
ss << "}\n";
return ss.str();
}
bool CodeGenerator::generateCode() {
// Create output directory
std::string outputPath = FileUtils::joinPath(config.variantPath, "generatedTests");
FileUtils::createDirectory(outputPath);
FileUtils::cleanDirectory(outputPath);
// Generate Instruments.h
std::string instrumentsHeader = generateInstrumentDefinitions();
std::string instrumentsPath = FileUtils::joinPath(outputPath, "Instruments.h");
if (!FileUtils::writeFile(instrumentsPath, instrumentsHeader)) {
std::cerr << "Failed to write Instruments.h" << std::endl;
return false;
}
// Generate Measurement stub header
std::stringstream measurementHeader;
measurementHeader << "#ifndef MEASUREMENT_H\n";
measurementHeader << "#define MEASUREMENT_H\n\n";
measurementHeader << "#include \"Instruments.h\"\n\n";
measurementHeader << "class Measurement {\n";
measurementHeader << "public:\n";
measurementHeader << " Measurement(const Instruments& inst) {}\n";
measurementHeader << " void build(bool flag) {}\n";
measurementHeader << " void execute(bool flag) {}\n";
measurementHeader << " void ProblemCheck() {}\n";
measurementHeader << " void StateCheck() {}\n";
measurementHeader << " void LogSetCheck() {}\n";
measurementHeader << " \n";
measurementHeader << " // Property setters\n";
measurementHeader << " template<typename T>\n";
measurementHeader << " void level_vforce(const std::string& name, T value) {}\n";
measurementHeader << " template<typename T>\n";
measurementHeader << " void level_iclampSource(const std::string& name, T value) {}\n";
measurementHeader << " template<typename T>\n";
measurementHeader << " void level_iclampSink(const std::string& name, T value) {}\n";
measurementHeader << " template<typename T>\n";
measurementHeader << " void level_vrange(const std::string& name, T value) {}\n";
measurementHeader << " template<typename T>\n";
measurementHeader << " void level_irange(const std::string& name, T value) {}\n";
measurementHeader << " void connect(bool flag) {}\n";
measurementHeader << " void disconnect(bool flag) {}\n";
measurementHeader << " void disconnectMode(const std::string& mode) {}\n";
measurementHeader << "};\n\n";
measurementHeader << "#endif\n";
std::string measurementPath = FileUtils::joinPath(outputPath, "Measurement.h");
if (!FileUtils::writeFile(measurementPath, measurementHeader.str())) {
std::cerr << "Failed to write Measurement.h" << std::endl;
return false;
}
// Generate test classes
std::vector<std::string> testNames;
for (const auto& testExec : config.testExecutions) {
// Find matching template
for (const auto& tmpl : config.templates) {
std::string testFileName = testExec.name + ".cpp";
std::string testContent = generateTestClass(tmpl, testExec);
std::string testPath = FileUtils::joinPath(outputPath, testFileName);
if (!FileUtils::writeFile(testPath, testContent)) {
std::cerr << "Failed to write " << testFileName << std::endl;
return false;
}
testNames.push_back(testExec.name);
}
}
// Generate main.cpp
std::string mainContent = generateMainFile(testNames);
std::string mainPath = FileUtils::joinPath(outputPath, "main.cpp");
if (!FileUtils::writeFile(mainPath, mainContent)) {
std::cerr << "Failed to write main.cpp" << std::endl;
return false;
}
// Generate build script for Windows
std::stringstream buildBat;
buildBat << "@echo off\n";
buildBat << "REM Build script for generated tests\n\n";
buildBat << "echo Building generated tests...\n\n";
buildBat << "echo Compiling main.cpp...\n";
buildBat << "g++ -std=c++11 -Wall -O2 -o test_runner.exe main.cpp\n\n";
buildBat << "if errorlevel 1 (\n";
buildBat << " echo.\n";
buildBat << " echo Build failed!\n";
buildBat << " exit /b 1\n";
buildBat << ")\n\n";
buildBat << "echo.\n";
buildBat << "echo Build successful! Executable: test_runner.exe\n";
buildBat << "echo.\n";
buildBat << "echo To run the tests, execute: test_runner.exe\n";
std::string buildBatPath = FileUtils::joinPath(outputPath, "build.bat");
if (!FileUtils::writeFile(buildBatPath, buildBat.str())) {
std::cerr << "Failed to write build.bat" << std::endl;
return false;
}
// Generate Makefile for Linux/Mac
std::stringstream makefile;
makefile << "# Makefile for generated tests\n\n";
makefile << "CXX = g++\n";
makefile << "CXXFLAGS = -std=c++11 -Wall -O2\n";
makefile << "TARGET = test_runner\n\n";
makefile << "all: $(TARGET)\n\n";
makefile << "$(TARGET): main.cpp Instruments.h Measurement.h";
for (const auto& testName : testNames) {
makefile << " " << testName << ".cpp";
}
makefile << "\n";
makefile << "\t$(CXX) $(CXXFLAGS) -o $(TARGET) main.cpp\n\n";
makefile << "clean:\n";
makefile << "\trm -f $(TARGET) $(TARGET).exe\n\n";
makefile << "run: $(TARGET)\n";
makefile << "\t./$(TARGET)\n\n";
makefile << ".PHONY: all clean run\n";
std::string makefilePath = FileUtils::joinPath(outputPath, "Makefile");
if (!FileUtils::writeFile(makefilePath, makefile.str())) {
std::cerr << "Failed to write Makefile" << std::endl;
return false;
}
// Generate README for the generated tests
std::stringstream readme;
readme << "# Generated Test Files\n\n";
readme << "This directory contains automatically generated C++ test code.\n\n";
readme << "## Files\n\n";
readme << "- `Instruments.h` - Instrument configuration definitions\n";
readme << "- `Measurement.h` - Measurement interface (stub implementation)\n";
for (const auto& testName : testNames) {
readme << "- `" << testName << ".cpp` - Test class: " << testName << "\n";
}
readme << "- `main.cpp` - Main entry point that executes all tests\n";
readme << "- `build.bat` - Windows build script\n";
readme << "- `Makefile` - Linux/Mac build script\n\n";
readme << "## Building\n\n";
readme << "**Windows:**\n";
readme << "```bash\n";
readme << "build.bat\n";
readme << "```\n\n";
readme << "**Linux/Mac:**\n";
readme << "```bash\n";
readme << "make\n";
readme << "```\n\n";
readme << "## Running\n\n";
readme << "After building, run the tests:\n\n";
readme << "**Windows:**\n";
readme << "```bash\n";
readme << "test_runner.exe\n";
readme << "```\n\n";
readme << "**Linux/Mac:**\n";
readme << "```bash\n";
readme << "./test_runner\n";
readme << "```\n\n";
readme << "## Note\n\n";
readme << "The `Measurement.h` file contains stub implementations. You should replace\n";
readme << "this with your actual measurement implementation or link against your\n";
readme << "measurement library when building the tests.\n";
std::string readmePath = FileUtils::joinPath(outputPath, "README.md");
if (!FileUtils::writeFile(readmePath, readme.str())) {
std::cerr << "Failed to write README.md" << std::endl;
return false;
}
std::cout << "Code generation completed successfully!" << std::endl;
std::cout << "Generated files in: " << outputPath << std::endl;
std::cout << "\nTo build the tests:" << std::endl;
std::cout << " Windows: cd " << outputPath << " && build.bat" << std::endl;
std::cout << " Linux/Mac: cd " << outputPath << " && make" << std::endl;
return true;
}

View File

@@ -0,0 +1,46 @@
#ifndef CODE_GENERATOR_H
#define CODE_GENERATOR_H
#include "data_structures.h"
#include "json_parser.h"
#include <string>
#include <map>
#include <memory>
class CodeGenerator {
private:
Config config;
std::map<std::string, SetupFile> commonSetups;
std::map<std::string, SetupFile> variantSetups;
std::map<std::string, Variable> globalVariables;
// Parse JSON helpers
Instrument parseInstrument(std::shared_ptr<JsonValue> json);
Property parseProperty(std::shared_ptr<JsonValue> json);
Variable parseVariable(std::shared_ptr<JsonValue> json);
Setup parseSetup(std::shared_ptr<JsonValue> json);
SetupFile parseSetupFile(std::shared_ptr<JsonValue> json);
Template parseTemplate(std::shared_ptr<JsonValue> json);
TestExecution parseTestExecution(std::shared_ptr<JsonValue> json);
// Code generation helpers
std::string generateInstrumentDefinitions();
std::string generateTestClass(const Template& tmpl, const TestExecution& testExec);
std::string generateSetupMethod(const std::string& setupName, const std::string& instrumentName);
std::string generateMainFile(const std::vector<std::string>& testNames);
// Variable substitution
std::string substituteVariables(const std::string& expr, const std::map<std::string, std::string>& localVars);
std::string processVariableExpression(const std::string& expr);
// Setup merging
Setup mergeSetup(const std::string& setupName, const std::string& instrumentName);
public:
CodeGenerator(const Config& cfg);
bool loadConfigurations();
bool generateCode();
};
#endif

View File

@@ -0,0 +1,117 @@
#ifndef DATA_STRUCTURES_H
#define DATA_STRUCTURES_H
#include <string>
#include <vector>
#include <map>
// Instrument definition
struct Instrument {
std::string instrumentName;
std::string variant;
std::vector<std::string> options;
std::string pinType;
std::vector<std::string> pinNumbers;
};
// Property pair for instrument setup
struct Property {
std::string key;
std::string value;
};
// Check definition for expects
struct Check {
std::string key;
std::string value;
};
// LogSet check
struct LogSetCheck {
std::string logset;
std::vector<Property> properties;
};
// Expects definition
struct Expect {
std::string type; // "problem", "state", "logset"
std::vector<Check> checks;
std::vector<LogSetCheck> logsetChecks;
};
// Instrument setup definition
struct InstrumentSetup {
std::string name;
std::vector<Property> properties;
std::vector<Expect> expects;
};
// Setup definition
struct Setup {
std::string name;
std::vector<std::string> notes;
InstrumentSetup instrumentSetup;
};
// Variable definition
struct Variable {
std::string name;
std::string expression;
};
// Setup file data
struct SetupFile {
std::vector<std::string> options;
std::vector<std::string> fileNotes;
std::vector<Variable> variables;
std::vector<Setup> setups;
};
// Template call
struct TemplateCall {
std::string call;
std::vector<std::string> args;
std::string returnVar;
};
// Template input
struct TemplateInput {
std::string key;
std::string value;
std::vector<std::map<std::string, std::string>> setups;
};
// Template definition
struct Template {
int groupId;
std::string uniqueTemplateId;
std::vector<std::string> description;
std::vector<std::string> features;
std::vector<TemplateInput> testInput;
std::vector<TemplateCall> templateCalls;
};
// Test configuration for execution
struct TestConfiguration {
std::string option;
std::string pinType;
};
// Test execution configuration
struct TestExecution {
std::string name;
std::vector<TestConfiguration> configurations;
};
// Configuration data
struct Config {
std::string commonPath;
std::string variantPath;
bool dryRun;
std::vector<Instrument> instruments;
std::vector<SetupFile> setupFiles;
std::vector<Template> templates;
std::vector<TestExecution> testExecutions;
};
#endif

View File

@@ -0,0 +1,200 @@
#include "file_utils.h"
#include <fstream>
#include <sstream>
#include <sys/stat.h>
#include <algorithm>
#include <cctype>
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#define PATH_SEPARATOR "\\"
#define mkdir(path, mode) _mkdir(path)
#else
#include <dirent.h>
#include <unistd.h>
#define PATH_SEPARATOR "/"
#endif
std::string FileUtils::readFile(const std::string& filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
return "";
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
bool FileUtils::writeFile(const std::string& filepath, const std::string& content) {
std::ofstream file(filepath);
if (!file.is_open()) {
return false;
}
file << content;
file.close();
return true;
}
bool FileUtils::fileExists(const std::string& filepath) {
struct stat buffer;
return (stat(filepath.c_str(), &buffer) == 0);
}
bool FileUtils::createDirectory(const std::string& dirpath) {
if (dirpath.empty()) return false;
if (fileExists(dirpath)) return true;
// Create parent directories recursively
size_t pos = 0;
std::string path = dirpath;
// Normalize path separators
std::replace(path.begin(), path.end(), '/', PATH_SEPARATOR[0]);
std::replace(path.begin(), path.end(), '\\', PATH_SEPARATOR[0]);
// Skip drive letter on Windows
if (path.length() > 1 && path[1] == ':') {
pos = 2;
}
while (pos != std::string::npos) {
pos = path.find(PATH_SEPARATOR, pos + 1);
std::string subpath = path.substr(0, pos);
if (!subpath.empty() && !fileExists(subpath)) {
if (mkdir(subpath.c_str(), 0755) != 0 && !fileExists(subpath)) {
return false;
}
}
}
return true;
}
bool FileUtils::cleanDirectory(const std::string& dirpath) {
if (!fileExists(dirpath)) return true;
#ifdef _WIN32
WIN32_FIND_DATA findData;
std::string pattern = dirpath + "\\*";
HANDLE hFind = FindFirstFile(pattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
std::string filename = findData.cFileName;
if (filename != "." && filename != "..") {
std::string filepath = dirpath + "\\" + filename;
DeleteFile(filepath.c_str());
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
}
#else
DIR* dir = opendir(dirpath.c_str());
if (dir) {
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
std::string filename = entry->d_name;
if (filename != "." && filename != "..") {
std::string filepath = dirpath + "/" + filename;
unlink(filepath.c_str());
}
}
closedir(dir);
}
#endif
return true;
}
std::string FileUtils::joinPath(const std::string& path1, const std::string& path2) {
if (path1.empty()) return path2;
if (path2.empty()) return path1;
std::string result = path1;
if (result.back() != '/' && result.back() != '\\') {
result += PATH_SEPARATOR;
}
// Skip leading separator in path2
size_t start = 0;
while (start < path2.length() && (path2[start] == '/' || path2[start] == '\\')) {
start++;
}
result += path2.substr(start);
return result;
}
std::vector<std::string> FileUtils::listFiles(const std::string& dirpath, const std::string& extension) {
std::vector<std::string> files;
#ifdef _WIN32
WIN32_FIND_DATA findData;
std::string pattern = dirpath + "\\*";
HANDLE hFind = FindFirstFile(pattern.c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
std::string filename = findData.cFileName;
if (filename != "." && filename != "..") {
if (extension.empty() || filename.length() >= extension.length() &&
filename.substr(filename.length() - extension.length()) == extension) {
files.push_back(filename);
}
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
}
#else
DIR* dir = opendir(dirpath.c_str());
if (dir) {
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
std::string filename = entry->d_name;
if (filename != "." && filename != "..") {
if (extension.empty() || filename.length() >= extension.length() &&
filename.substr(filename.length() - extension.length()) == extension) {
files.push_back(filename);
}
}
}
closedir(dir);
}
#endif
return files;
}
std::string FileUtils::replaceAll(const std::string& str, const std::string& from, const std::string& to) {
std::string result = str;
size_t pos = 0;
while ((pos = result.find(from, pos)) != std::string::npos) {
result.replace(pos, from.length(), to);
pos += to.length();
}
return result;
}
std::string FileUtils::trim(const std::string& str) {
size_t first = str.find_first_not_of(" \t\n\r");
if (first == std::string::npos) return "";
size_t last = str.find_last_not_of(" \t\n\r");
return str.substr(first, last - first + 1);
}
std::vector<std::string> FileUtils::split(const std::string& str, char delimiter) {
std::vector<std::string> tokens;
std::stringstream ss(str);
std::string token;
while (std::getline(ss, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}

View File

@@ -0,0 +1,41 @@
#ifndef FILE_UTILS_H
#define FILE_UTILS_H
#include <string>
#include <vector>
// File utility functions
class FileUtils {
public:
// Read entire file content
static std::string readFile(const std::string& filepath);
// Write content to file
static bool writeFile(const std::string& filepath, const std::string& content);
// Check if file exists
static bool fileExists(const std::string& filepath);
// Create directory if it doesn't exist (recursive)
static bool createDirectory(const std::string& dirpath);
// Delete all files in a directory
static bool cleanDirectory(const std::string& dirpath);
// Join paths with proper separators
static std::string joinPath(const std::string& path1, const std::string& path2);
// Get files in directory matching pattern
static std::vector<std::string> listFiles(const std::string& dirpath, const std::string& extension = "");
// Replace all occurrences in string
static std::string replaceAll(const std::string& str, const std::string& from, const std::string& to);
// Trim whitespace from string
static std::string trim(const std::string& str);
// Split string by delimiter
static std::vector<std::string> split(const std::string& str, char delimiter);
};
#endif

View File

@@ -0,0 +1,194 @@
#include "json_parser.h"
#include <cctype>
#include <stdexcept>
void JsonParser::skipWhitespace() {
while (pos < json.length() && std::isspace(json[pos])) {
pos++;
}
}
char JsonParser::peek() {
skipWhitespace();
if (pos >= json.length()) return '\0';
return json[pos];
}
char JsonParser::next() {
skipWhitespace();
if (pos >= json.length()) return '\0';
return json[pos++];
}
bool JsonParser::match(char c) {
skipWhitespace();
if (pos < json.length() && json[pos] == c) {
pos++;
return true;
}
return false;
}
std::string JsonParser::parseString() {
if (!match('"')) throw std::runtime_error("Expected '\"'");
std::string result;
while (pos < json.length() && json[pos] != '"') {
if (json[pos] == '\\') {
pos++;
if (pos >= json.length()) throw std::runtime_error("Incomplete escape sequence");
char c = json[pos++];
switch (c) {
case '"': result += '"'; break;
case '\\': result += '\\'; break;
case '/': result += '/'; break;
case 'b': result += '\b'; break;
case 'f': result += '\f'; break;
case 'n': result += '\n'; break;
case 'r': result += '\r'; break;
case 't': result += '\t'; break;
default: result += c; break;
}
} else {
result += json[pos++];
}
}
if (!match('"')) throw std::runtime_error("Expected '\"'");
return result;
}
double JsonParser::parseNumber() {
size_t start = pos;
if (json[pos] == '-') pos++;
if (!std::isdigit(json[pos])) throw std::runtime_error("Invalid number");
while (pos < json.length() && std::isdigit(json[pos])) pos++;
if (pos < json.length() && json[pos] == '.') {
pos++;
while (pos < json.length() && std::isdigit(json[pos])) pos++;
}
if (pos < json.length() && (json[pos] == 'e' || json[pos] == 'E')) {
pos++;
if (pos < json.length() && (json[pos] == '+' || json[pos] == '-')) pos++;
while (pos < json.length() && std::isdigit(json[pos])) pos++;
}
return std::stod(json.substr(start, pos - start));
}
bool JsonParser::parseKeyword(const std::string& keyword) {
skipWhitespace();
if (json.substr(pos, keyword.length()) == keyword) {
pos += keyword.length();
return true;
}
return false;
}
std::shared_ptr<JsonValue> JsonParser::parseValue() {
char c = peek();
auto value = std::make_shared<JsonValue>();
if (c == '"') {
value->type = JsonValue::STRING;
value->stringValue = parseString();
} else if (c == '{') {
return parseObject();
} else if (c == '[') {
return parseArray();
} else if (c == 't' || c == 'f') {
value->type = JsonValue::BOOL;
if (parseKeyword("true")) {
value->boolValue = true;
} else if (parseKeyword("false")) {
value->boolValue = false;
} else {
throw std::runtime_error("Invalid boolean");
}
} else if (c == 'n') {
if (parseKeyword("null")) {
value->type = JsonValue::NULL_TYPE;
} else {
throw std::runtime_error("Invalid null");
}
} else if (c == '-' || std::isdigit(c)) {
value->type = JsonValue::NUMBER;
value->numberValue = parseNumber();
} else {
throw std::runtime_error("Invalid value");
}
return value;
}
std::shared_ptr<JsonValue> JsonParser::parseObject() {
if (!match('{')) throw std::runtime_error("Expected '{'");
auto obj = std::make_shared<JsonValue>();
obj->type = JsonValue::OBJECT;
if (peek() == '}') {
match('}');
return obj;
}
while (true) {
std::string key = parseString();
if (!match(':')) throw std::runtime_error("Expected ':'");
obj->objectValue[key] = parseValue();
if (!match(',')) break;
if (peek() == '}') break;
}
if (!match('}')) throw std::runtime_error("Expected '}'");
return obj;
}
std::shared_ptr<JsonValue> JsonParser::parseArray() {
if (!match('[')) throw std::runtime_error("Expected '['");
auto arr = std::make_shared<JsonValue>();
arr->type = JsonValue::ARRAY;
if (peek() == ']') {
match(']');
return arr;
}
while (true) {
arr->arrayValue.push_back(parseValue());
if (!match(',')) break;
if (peek() == ']') break;
}
if (!match(']')) throw std::runtime_error("Expected ']'");
return arr;
}
std::shared_ptr<JsonValue> JsonParser::parse(const std::string& jsonString) {
json = jsonString;
pos = 0;
try {
return parseValue();
} catch (const std::exception& e) {
return std::make_shared<JsonValue>();
}
}
bool JsonParser::validate(const std::string& jsonString) {
json = jsonString;
pos = 0;
try {
parseValue();
skipWhitespace();
return pos >= json.length();
} catch (const std::exception& e) {
return false;
}
}

View File

@@ -0,0 +1,65 @@
#ifndef JSON_PARSER_H
#define JSON_PARSER_H
#include <string>
#include <map>
#include <vector>
#include <memory>
// Simple JSON parser for basic JSON structures
class JsonValue {
public:
enum Type { NULL_TYPE, BOOL, NUMBER, STRING, ARRAY, OBJECT };
Type type;
bool boolValue;
double numberValue;
std::string stringValue;
std::vector<std::shared_ptr<JsonValue>> arrayValue;
std::map<std::string, std::shared_ptr<JsonValue>> objectValue;
JsonValue() : type(NULL_TYPE), boolValue(false), numberValue(0.0) {}
bool isNull() const { return type == NULL_TYPE; }
bool isBool() const { return type == BOOL; }
bool isNumber() const { return type == NUMBER; }
bool isString() const { return type == STRING; }
bool isArray() const { return type == ARRAY; }
bool isObject() const { return type == OBJECT; }
bool getBool() const { return boolValue; }
double getNumber() const { return numberValue; }
const std::string& getString() const { return stringValue; }
const std::vector<std::shared_ptr<JsonValue>>& getArray() const { return arrayValue; }
const std::map<std::string, std::shared_ptr<JsonValue>>& getObject() const { return objectValue; }
std::shared_ptr<JsonValue> get(const std::string& key) const {
auto it = objectValue.find(key);
if (it != objectValue.end()) return it->second;
return std::make_shared<JsonValue>();
}
};
class JsonParser {
private:
std::string json;
size_t pos;
void skipWhitespace();
char peek();
char next();
bool match(char c);
std::string parseString();
double parseNumber();
bool parseKeyword(const std::string& keyword);
std::shared_ptr<JsonValue> parseValue();
std::shared_ptr<JsonValue> parseObject();
std::shared_ptr<JsonValue> parseArray();
public:
JsonParser() : pos(0) {}
std::shared_ptr<JsonValue> parse(const std::string& jsonString);
bool validate(const std::string& jsonString);
};
#endif

149
TestGenerator/src/main.cpp Normal file
View File

@@ -0,0 +1,149 @@
#include "json_parser.h"
#include "file_utils.h"
#include "code_generator.h"
#include <iostream>
#include <string>
#include <vector>
void printUsage() {
std::cout << "Test Generator - Generate C++ test code from JSON configuration\n\n";
std::cout << "Usage:\n";
std::cout << " testgen -commonPath <path> -variantPath <path> Generate test code\n";
std::cout << " testgen -dry <jsonfile> Validate JSON file\n";
std::cout << " testgen -help Display this help\n\n";
std::cout << "Options:\n";
std::cout << " -commonPath <path> Path to common test directory (hw_ext_tests)\n";
std::cout << " -variantPath <path> Path to variant directory (hw_ext_variants\\Avi64\\Driver\\TestSetup)\n";
std::cout << " -dry <jsonfile> Dry run to validate JSON file format\n";
std::cout << " -help Display this help message\n\n";
std::cout << "Examples:\n";
std::cout << " testgen -commonPath hw_ext_tests -variantPath hw_ext_variants\\Avi64\\Driver\\TestSetup\n";
std::cout << " testgen -dry test.json\n";
}
bool dryRun(const std::string& jsonFile) {
std::string content = FileUtils::readFile(jsonFile);
if (content.empty()) {
std::cout << "INVALID - Cannot read file: " << jsonFile << std::endl;
return false;
}
JsonParser parser;
bool valid = parser.validate(content);
if (valid) {
std::cout << "VALID" << std::endl;
return true;
} else {
std::cout << "INVALID" << std::endl;
return false;
}
}
bool parseArguments(int argc, char* argv[], Config& config) {
if (argc < 2) {
return false;
}
std::string arg1 = argv[1];
if (arg1 == "-help") {
printUsage();
return false;
}
if (arg1 == "-dry") {
if (argc < 3) {
std::cerr << "Error: -dry requires a JSON file path\n\n";
printUsage();
return false;
}
config.dryRun = true;
config.commonPath = argv[2];
return true;
}
// Parse -commonPath and -variantPath
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-commonPath") {
if (i + 1 < argc) {
config.commonPath = argv[++i];
} else {
std::cerr << "Error: -commonPath requires a path argument\n\n";
printUsage();
return false;
}
} else if (arg == "-variantPath") {
if (i + 1 < argc) {
config.variantPath = argv[++i];
} else {
std::cerr << "Error: -variantPath requires a path argument\n\n";
printUsage();
return false;
}
}
}
if (config.commonPath.empty() || config.variantPath.empty()) {
std::cerr << "Error: Both -commonPath and -variantPath are required\n\n";
printUsage();
return false;
}
return true;
}
int main(int argc, char* argv[]) {
Config config;
config.dryRun = false;
if (!parseArguments(argc, argv, config)) {
if (argc > 1 && std::string(argv[1]) != "-help") {
return 1;
}
return 0;
}
// Handle dry run mode
if (config.dryRun) {
return dryRun(config.commonPath) ? 0 : 1;
}
// Verify paths exist
if (!FileUtils::fileExists(config.commonPath)) {
std::cerr << "Error: commonPath does not exist: " << config.commonPath << std::endl;
return 1;
}
if (!FileUtils::fileExists(config.variantPath)) {
std::cerr << "Error: variantPath does not exist: " << config.variantPath << std::endl;
return 1;
}
std::cout << "Test Generator" << std::endl;
std::cout << "Common path: " << config.commonPath << std::endl;
std::cout << "Variant path: " << config.variantPath << std::endl;
std::cout << std::endl;
// Create code generator and load configurations
CodeGenerator generator(config);
std::cout << "Loading configurations..." << std::endl;
if (!generator.loadConfigurations()) {
std::cerr << "Error: Failed to load configurations" << std::endl;
return 1;
}
std::cout << "Generating code..." << std::endl;
if (!generator.generateCode()) {
std::cerr << "Error: Failed to generate code" << std::endl;
return 1;
}
std::cout << "\nCode generation completed successfully!" << std::endl;
return 0;
}