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

Binary file not shown.

View File

@@ -0,0 +1,6 @@
{"Instrument": {
"Variant": ["AVI64","FVI16"],
"Option": ["activeTransition", "diffVoltmeter", "differential", "highCurrent", "highResolution", "highSpeed", "highVoltage", "highVoltageH", "highVoltageL", "precisionGangRange", "siteInterlacingOn"],
"PinType": ["single","ganged","stacked"],
"PinNumber": ["1","128"]
}}

View File

@@ -0,0 +1,5 @@
{
"Variants": [
{ "Variant": "AVI64","Directory": "hw_ext_variants\\Avi64\\Driver\\TestSetup","FacadeName": "AVI64" }
]
}

View File

@@ -0,0 +1,66 @@
{
"Options": [
"", "highCurrent"
],
"FileNotes" : [
"This is a simple test setup with 1 instrument. It powers up the instrument, and disconnects after measurement.",
"Firsst setup will trigger a problem message, which will be fixed in the second setup.",
"Clamps are set to the minimum clamp distance of largest range, to support proper ganging.",
"Range values are smaller than the actual used ranges, so we can verify proper ranging in states."
],
"Variables": [
{"$forceValue": "1.0"},
{"$clampCurrent": "0.03*gangCount"},
{"$irange": "0.033*gangCount"}
],
"TestSetups" :
[
{
"Setup" :
{
"Name": "Simple_Typ",
"Notes" : [
"This setup will do the main setup, but trigger a problem message.",
"The force value is larger than the actual used ranges, so we receive a problem message."
],
"InstrumentSetup" : {
"Name": "pin1",
"Properties": [
{ "level.vforce": "$forceValue" },
{ "level.iclampSource": "$clampCurrent" },
{ "level.iclampSink": "$clampCurrent" },
{ "level.vrange": "0.0" },
{ "level.irange": "$irange" },
{ "connect": "true" },
{ "disconnect": "true" },
{ "disconnectMode": "hiz" }
],
"Expects": [
{ "type" : "problem", "checks" :
[ {"proplemMsg" : "The $forceValue exceeds the level.vrange*"}]
}
]
}
}
}
,
{
"Setup" :
{
"Name": "Simple2_Typ",
"Notes" : [
"This setup will fix the problem frome above setup."
],
"InstrumentSetup" : {
"Name": "pin1",
"Properties": [
{ "level.vrange": "$forceValue" }
],
"Expects": [
{ "type" : "state", "checks" :[ {"property" : "forceValue" , "value" : "forceValue" }] }
]
}
}
}
]
}

View File

@@ -0,0 +1,31 @@
{
"GroupId": 1000,
"UniqueTemplateId": "auto_generated",
"Description": [
"Simple test level with 1 instrument. Powers up the instrument, and disconnects after measurement.",
"Can check states and logsets after execution."
],
"Features": [
"PowerUpWoPowerSequence",
"PowerDownNoPowerSequence"
],
"TestInput": [
{ "Instruments" : "Instruments" },
{ "SetupFileName" :"Setup_Simple_Typ",
"setups" : [
{"InitialSetup": "Simple_Typ"},
{"FinalSetup": "Simple2_Typ"}
] }
],
"Template": [
{ "call" : "createMeasurement", "args": [ "Instruments"], "return" :"Measurement" },
{ "call" : "applySetup", "args": ["Measurement", "InitialSetup"] },
{ "call" : "buildMeasurement", "args": ["Measurement", "false"] },
{ "call" : "ProblemCheck", "args": ["Measurement", "InitialSetup"] },
{ "call" : "applySetup", "args": ["Measurement", "FinalSetup"] },
{ "call" : "buildMeasurement", "args": ["Measurement", "true"] },
{ "call" : "executeMeasurement", "args": ["Measurement", "true"] },
{ "call" : "StateCheck", "args": ["Measurement", "FinalSetup"] },
{ "call" : "LogSetCheck", "args": ["Measurement", "FinalSetup"] }
]
}

View File

@@ -0,0 +1,25 @@
{
"Tests": [
{
"Name": "Test_Level_Simple",
"Configurations": [
{
"Option": "",
"PinType": "single"
},
{
"Option": "",
"PinType": "ganged"
},
{
"Option": "highCurrent",
"PinType": "single"
},
{
"Option": "highCurrent",
"PinType": "ganged"
}
]
}
]
}

View File

@@ -0,0 +1,32 @@
{
"Instruments": [
{
"InstrumentName": "pin1",
"Variant": "AVI64",
"Option": [],
"PinType": "single",
"PinNumbers": ["1"]
},
{
"InstrumentName": "pin1",
"Variant": "AVI64",
"Option": [],
"PinType": "ganged",
"PinNumbers": ["1","2"]
},
{
"InstrumentName": "pin1",
"Variant": "AVI64",
"Option": ["highCurrent"],
"PinType": "single",
"PinNumbers": ["1"]
},
{
"InstrumentName": "meas1",
"Variant": "AVI64",
"Option": [],
"PinType": "single",
"PinNumbers": ["2"]
}
]
}

View File

@@ -0,0 +1,60 @@
{
"Options": [
"", "highCurrent"
],
"FileNotes" : [""],
"Variables": [
{"$clampCurrent": "0.05*$gangCount"},
{"$irange": "0.06*$gangCount"},
{"$irangeExp": "0.1*$gangCount"}
],
"TestSetups" :
[
{
"Setup" :
{
"Name": "Simple_Typ",
"Notes" : [""],
"InstrumentSetup" : {
"Name": "pin1",
"Properties": [
{ "level.iclampSource": "$clampCurrent" },
{ "level.iclampSink": "$clampCurrent" },
{ "level.irange": "$irange" }
]
}
}
},
{
"Setup" :
{
"Name": "Simple2_Typ",
"Notes" : [""],
"InstrumentSetup" : {
"Name": "pin1",
"Properties": [
{ "level.vrange": "2.0" },
{ "level.iclampSource": "-" }
],
"Expects": [
{ "type" : "state", "checks" :[
{"property" : "iclampSource" , "value" : "$clampCurrent" },
{"property" : "iclampSink" , "value" : "$clampCurrent" },
{"property" : "irange" , "value" : "$irangeExp" },
{"property" : "vrange" , "value" : "3.0" }
]
},
{ "type" : "logset", "checks" :[
{"logset" : "avi64.DcRangeLogicalSet", "properties" : [
{ "property" : "vrange" , "value" : "3.0"},
{ "property" : "irange" , "value" : "$irangeExp"}
]
}
]
}
]
}
}
}
]
}

View File

@@ -0,0 +1,54 @@
#ifndef INSTRUMENTS_H
#define INSTRUMENTS_H
#include <string>
#include <vector>
struct Instruments {
std::string variant;
std::vector<std::string> options;
std::string pinType;
std::vector<int> pinNumbers;
int gangCount;
static Instruments pin1_single() {
Instruments inst;
inst.variant = "AVI64";
inst.pinType = "single";
inst.pinNumbers.push_back(1);
inst.gangCount = 1;
return inst;
}
static Instruments pin1_ganged() {
Instruments inst;
inst.variant = "AVI64";
inst.pinType = "ganged";
inst.pinNumbers.push_back(1);
inst.pinNumbers.push_back(2);
inst.gangCount = 2;
return inst;
}
static Instruments pin1_single_highCurrent() {
Instruments inst;
inst.variant = "AVI64";
inst.options.push_back("highCurrent");
inst.pinType = "single";
inst.pinNumbers.push_back(1);
inst.gangCount = 1;
return inst;
}
static Instruments meas1_single() {
Instruments inst;
inst.variant = "AVI64";
inst.pinType = "single";
inst.pinNumbers.push_back(2);
inst.gangCount = 1;
return inst;
}
};
#endif

View File

@@ -0,0 +1,18 @@
# Makefile for generated tests
CXX = g++
CXXFLAGS = -std=c++11 -Wall -O2
TARGET = test_runner
all: $(TARGET)
$(TARGET): main.cpp Instruments.h Measurement.h Test_Level_Simple.cpp
$(CXX) $(CXXFLAGS) -o $(TARGET) main.cpp
clean:
rm -f $(TARGET) $(TARGET).exe
run: $(TARGET)
./$(TARGET)
.PHONY: all clean run

View File

@@ -0,0 +1,31 @@
#ifndef MEASUREMENT_H
#define MEASUREMENT_H
#include "Instruments.h"
class Measurement {
public:
Measurement(const Instruments& inst) {}
void build(bool flag) {}
void execute(bool flag) {}
void ProblemCheck() {}
void StateCheck() {}
void LogSetCheck() {}
// Property setters
template<typename T>
void level_vforce(const std::string& name, T value) {}
template<typename T>
void level_iclampSource(const std::string& name, T value) {}
template<typename T>
void level_iclampSink(const std::string& name, T value) {}
template<typename T>
void level_vrange(const std::string& name, T value) {}
template<typename T>
void level_irange(const std::string& name, T value) {}
void connect(bool flag) {}
void disconnect(bool flag) {}
void disconnectMode(const std::string& mode) {}
};
#endif

View File

@@ -0,0 +1,44 @@
# Generated Test Files
This directory contains automatically generated C++ test code.
## Files
- `Instruments.h` - Instrument configuration definitions
- `Measurement.h` - Measurement interface (stub implementation)
- `Test_Level_Simple.cpp` - Test class: Test_Level_Simple
- `main.cpp` - Main entry point that executes all tests
- `build.bat` - Windows build script
- `Makefile` - Linux/Mac build script
## Building
**Windows:**
```bash
build.bat
```
**Linux/Mac:**
```bash
make
```
## Running
After building, run the tests:
**Windows:**
```bash
test_runner.exe
```
**Linux/Mac:**
```bash
./test_runner
```
## Note
The `Measurement.h` file contains stub implementations. You should replace
this with your actual measurement implementation or link against your
measurement library when building the tests.

View File

@@ -0,0 +1,57 @@
#include "Instruments.h"
#include "Measurement.h"
// Test class generated from template: auto_generated
class Test_Level_Simple {
public:
void Simple_Typ(Measurement& measurement, int gangCount) {
// Setup: Simple_Typ
double clampCurrent = 0.05*gangCount;
double forceValue = 1.0;
double irange = 0.06*gangCount;
double irangeExp = 0.1*gangCount;
measurement.level_vforce("pin1", 1.0);
measurement.level_iclampSource("pin1", clampCurrent);
measurement.level_iclampSink("pin1", clampCurrent);
measurement.level_vrange("pin1", 0.0);
measurement.level_irange("pin1", irange);
measurement.connect(true);
measurement.disconnect(true);
measurement.disconnectMode("hiz");
}
void Simple2_Typ(Measurement& measurement, int gangCount) {
// Setup: Simple2_Typ
double clampCurrent = 0.05*gangCount;
double forceValue = 1.0;
double irange = 0.06*gangCount;
double irangeExp = 0.1*gangCount;
measurement.level_vrange("pin1", 2.0);
}
void run(const Instruments& instruments) {
int gangCount = instruments.gangCount;
// createMeasurement
Measurement MeasurementObj(instruments);
// applySetup
Simple_Typ(MeasurementObj, gangCount);
// buildMeasurement
MeasurementObj.build(false);
// ProblemCheck
MeasurementObj.ProblemCheck();
// applySetup
Simple2_Typ(MeasurementObj, gangCount);
// buildMeasurement
MeasurementObj.build(true);
// executeMeasurement
MeasurementObj.execute(true);
// StateCheck
MeasurementObj.StateCheck();
// LogSetCheck
MeasurementObj.LogSetCheck();
}
};

View File

@@ -0,0 +1,18 @@
@echo off
REM Build script for generated tests
echo Building generated tests...
echo Compiling main.cpp...
g++ -std=c++11 -Wall -O2 -o test_runner.exe main.cpp
if errorlevel 1 (
echo.
echo Build failed!
exit /b 1
)
echo.
echo Build successful! Executable: test_runner.exe
echo.
echo To run the tests, execute: test_runner.exe

View File

@@ -0,0 +1,34 @@
#include <iostream>
#include "Instruments.h"
#include "Test_Level_Simple.cpp"
int main() {
std::cout << "Running generated tests..." << std::endl;
{
Test_Level_Simple test;
test.run(Instruments::pin1_single());
std::cout << "Completed: Test_Level_Simple with pin1_single" << std::endl;
}
{
Test_Level_Simple test;
test.run(Instruments::pin1_ganged());
std::cout << "Completed: Test_Level_Simple with pin1_ganged" << std::endl;
}
{
Test_Level_Simple test;
test.run(Instruments::pin1_single_highCurrent());
std::cout << "Completed: Test_Level_Simple with pin1_single_highCurrent" << std::endl;
}
{
Test_Level_Simple test;
test.run(Instruments::meas1_single());
std::cout << "Completed: Test_Level_Simple with meas1_single" << std::endl;
}
std::cout << "All tests completed." << std::endl;
return 0;
}

View File

@@ -0,0 +1,45 @@
// Auto-generated instrument definitions - do not edit
#pragma once
#include <vector>
// Instrument definitions
struct InstrumentDef {
std::string name;
std::string variant;
std::vector<std::string> options;
std::string pinType;
std::vector<int> pinNumbers;
};
// All configured instruments
static const std::vector<InstrumentDef> allInstruments = {
{
"pin1", // name
"AVI64", // variant
{}, // options
"single", // pinType
{1} // pinNumbers
},
{
"pin1", // name
"AVI64", // variant
{}, // options
"ganged", // pinType
{1, 2} // pinNumbers
},
{
"pin1", // name
"AVI64", // variant
{"highCurrent"}, // options
"single", // pinType
{1} // pinNumbers
},
{
"meas1", // name
"AVI64", // variant
{}, // options
"single", // pinType
{2} // pinNumbers
}
};

View File

@@ -0,0 +1,66 @@
// Auto-generated test file - do not edit
// Test: Test_Level_Simple
class Test_Level_Simple {
public:
Test_Level_Simple(Instruments instruments)
: instruments_(instruments) {}
// Setup method: Simple_Typ
void setup_Simple_Typ(Measurement measurement) {
// Variables
double clampCurrent = 0.05*gangCount;
double irange = 0.06*gangCount;
double irangeExp = 0.1*gangCount;
double forceValue = 1.0;
// Properties for pin1
setProperty(measurement, "pin1", "disconnectMode", "hiz");
setProperty(measurement, "pin1", "level.iclampSink", clampCurrent);
setProperty(measurement, "pin1", "level.vforce", forceValue);
setProperty(measurement, "pin1", "level.iclampSource", clampCurrent);
setProperty(measurement, "pin1", "level.vrange", 0.0);
setProperty(measurement, "pin1", "level.irange", irange);
setProperty(measurement, "pin1", "connect", true);
setProperty(measurement, "pin1", "disconnect", true);
checkProblem(measurement, "pin1", "The " + std::to_string(forceValue) + " exceeds the level.vrange*");
}
// Setup method: Simple2_Typ
void setup_Simple2_Typ(Measurement measurement) {
// Variables
double clampCurrent = 0.05*gangCount;
double irange = 0.06*gangCount;
double irangeExp = 0.1*gangCount;
double forceValue = 1.0;
// Properties for pin1
setProperty(measurement, "pin1", "level.vrange", 2.0);
checkState(measurement, "pin1", "iclampSource", clampCurrent);
checkState(measurement, "pin1", "iclampSink", clampCurrent);
checkState(measurement, "pin1", "irange", irangeExp);
checkState(measurement, "pin1", "vrange", 3.0);
checkLogset(measurement, "pin1", "avi64.DcRangeLogicalSet", "vrange", 3.0);
checkLogset(measurement, "pin1", "avi64.DcRangeLogicalSet", "irange", irangeExp);
}
// Execute test sequence
void execute() {
auto Measurement = createMeasurement(instruments_);
setup_Simple_Typ(Measurement);
buildMeasurement(Measurement, false);
ProblemCheck(Measurement, "Simple_Typ");
setup_Simple2_Typ(Measurement);
buildMeasurement(Measurement, true);
executeMeasurement(Measurement, true);
StateCheck(Measurement, "Simple2_Typ");
LogSetCheck(Measurement, "Simple2_Typ");
}
private:
Instruments instruments_;
};

View File

@@ -0,0 +1,20 @@
// Auto-generated main file - do not edit
#include "InstrumentDefs.h"
#include "Test_Level_Simple.cpp"
int main(int argc, char* argv[]) {
// Initialize instruments (implementation specific)
Instruments instruments;
// TODO: Initialize instruments from configuration
// Execute test: Test_Level_Simple
{
Test_Level_Simple test(instruments);
// Call setup methods as needed
// test.setup_<method_name>(instruments);
test.execute();
}
return 0;
}

53
TestGenerator/Makefile Normal file
View File

@@ -0,0 +1,53 @@
# Makefile for Test Generator
CXX = g++
CXXFLAGS = -std=c++11 -Wall -O2
TARGET = testgen
SRCDIR = src
OBJDIR = obj
# Source files
SOURCES = $(SRCDIR)/main.cpp \
$(SRCDIR)/json_parser.cpp \
$(SRCDIR)/file_utils.cpp \
$(SRCDIR)/code_generator.cpp
# Object files
OBJECTS = $(OBJDIR)/main.o \
$(OBJDIR)/json_parser.o \
$(OBJDIR)/file_utils.o \
$(OBJDIR)/code_generator.o
# Default target
all: $(OBJDIR) $(TARGET)
# Create object directory
$(OBJDIR):
mkdir -p $(OBJDIR)
# Link executable
$(TARGET): $(OBJECTS)
$(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJECTS)
# Compile source files
$(OBJDIR)/main.o: $(SRCDIR)/main.cpp
$(CXX) $(CXXFLAGS) -c $(SRCDIR)/main.cpp -o $(OBJDIR)/main.o
$(OBJDIR)/json_parser.o: $(SRCDIR)/json_parser.cpp $(SRCDIR)/json_parser.h
$(CXX) $(CXXFLAGS) -c $(SRCDIR)/json_parser.cpp -o $(OBJDIR)/json_parser.o
$(OBJDIR)/file_utils.o: $(SRCDIR)/file_utils.cpp $(SRCDIR)/file_utils.h
$(CXX) $(CXXFLAGS) -c $(SRCDIR)/file_utils.cpp -o $(OBJDIR)/file_utils.o
$(OBJDIR)/code_generator.o: $(SRCDIR)/code_generator.cpp $(SRCDIR)/code_generator.h $(SRCDIR)/data_structures.h
$(CXX) $(CXXFLAGS) -c $(SRCDIR)/code_generator.cpp -o $(OBJDIR)/code_generator.o
# Clean build artifacts
clean:
rm -rf $(OBJDIR) $(TARGET) $(TARGET).exe
# Install (optional)
install: $(TARGET)
cp $(TARGET) /usr/local/bin/
.PHONY: all clean install

115
TestGenerator/README.md Normal file
View File

@@ -0,0 +1,115 @@
# Test Generator
A command-line tool that reads JSON configuration files and generates C++ test code.
## Features
- Parses JSON configuration files without external dependencies
- Generates C++ test classes from templates
- Supports multiple instrument configurations
- Merges common and variant-specific setups
- Variable substitution in configurations
## Building
### Requirements
- GCC (g++) with C++11 support
- Standard C++ library only (no external dependencies)
### On Windows
```bash
build.bat
```
### On Linux/Mac
```bash
make
```
This will create the `testgen` (or `testgen.exe` on Windows) executable.
## Usage
### Generate Test Code
```bash
testgen -commonPath <common_path> -variantPath <variant_path>
```
Example:
```bash
testgen -commonPath ..\SMT_PlayGround\hw_ext_tests -variantPath ..\SMT_PlayGround\hw_ext_variants\Avi64\Driver\TestSetup
```
### Validate JSON File
```bash
testgen -dry <json_file>
```
Example:
```bash
testgen -dry test.json
```
### Display Help
```bash
testgen -help
```
## Generated Files
The tool generates C++ files in `<variantPath>/generatedTests/`:
- `Instruments.h` - Instrument definitions
- `Measurement.h` - Measurement interface (stub)
- `Test_*.cpp` - Generated test classes
- `main.cpp` - Main entry point that runs all tests
- `build.bat` - Windows build script for generated tests
- `Makefile` - Linux/Mac build script for generated tests
## Building Generated Tests
After generation, build and run the tests:
**Windows:**
```bash
cd <variantPath>/generatedTests
build.bat
test_runner.exe
```
**Linux/Mac:**
```bash
cd <variantPath>/generatedTests
make
./test_runner
```
## Project Structure
```
TestGenerator/
├── src/
│ ├── main.cpp # Entry point and command-line parsing
│ ├── json_parser.h/.cpp # JSON parser implementation
│ ├── file_utils.h/.cpp # File I/O utilities
│ ├── code_generator.h/.cpp # Code generation logic
│ └── data_structures.h # Data structure definitions
├── Makefile # Build script for Linux/Mac
├── build.bat # Build script for Windows
└── README.md # This file
```
## Architecture
1. **JSON Parser** - Simple recursive descent parser for JSON
2. **Data Structures** - Type-safe structures for configuration data
3. **File Utils** - Cross-platform file and directory operations
4. **Code Generator** - Template-based C++ code generation
5. **Main** - Command-line interface and orchestration
## Notes
- All code uses only the C++ standard library
- No terminal/console APIs are used in the domain logic
- Generated code requires linking with actual Measurement implementation
- Variable substitution supports expressions like `0.03*gangCount`

37
TestGenerator/build.bat Normal file
View File

@@ -0,0 +1,37 @@
@echo off
REM Build script for Windows using g++ (MinGW or similar)
echo Building Test Generator...
if not exist obj mkdir obj
echo Compiling json_parser.cpp...
g++ -std=c++11 -Wall -O2 -c src\json_parser.cpp -o obj\json_parser.o
if errorlevel 1 goto error
echo Compiling file_utils.cpp...
g++ -std=c++11 -Wall -O2 -c src\file_utils.cpp -o obj\file_utils.o
if errorlevel 1 goto error
echo Compiling code_generator.cpp...
g++ -std=c++11 -Wall -O2 -c src\code_generator.cpp -o obj\code_generator.o
if errorlevel 1 goto error
echo Compiling main.cpp...
g++ -std=c++11 -Wall -O2 -c src\main.cpp -o obj\main.o
if errorlevel 1 goto error
echo Linking testgen.exe...
g++ -std=c++11 -Wall -O2 -o testgen.exe obj\main.o obj\json_parser.o obj\file_utils.o obj\code_generator.o
if errorlevel 1 goto error
echo.
echo Build successful! Executable: testgen.exe
goto end
:error
echo.
echo Build failed!
exit /b 1
:end

View File

@@ -0,0 +1,49 @@
You are an expert programmer.
Create a command-line tool which can read json files and generate c++ code based on data in json files.
CONSTRAINTS
- Use ONLY the language's standard library (no external dependencies).
- Organize code into multiple a sub dirctory having multiple files / modules / packages as appropriate.
- Logic MUST NOT depend on terminal/console APIs.
- Use gcc (c++ language)
ARCHITECTURE REQUIREMENTS
- Data structures for reading and checking json data
- Pure helper functions (file I/O, string manipulation, etc.)
TOOL REQUIREMENTS (MVP)
- Different modes for execution selected via commandline arguments:
- `-commonPath <commonPath>`: specify common location (hw_ext_tests)
- `-variantPath <variantPath>`: specify variant location (hw_ext_variants\Avi64)
- `-dry` : dry run to check if the json file is well-formed and prints "VALID" or "INVALID".
- `-help`: display usage information and exit
- All generated cpp code shall be in variant sub directory, called <variantPath>/generatedTests.
- Before starting a new generation clean the output directories
- Definitions in "Globals" introduce global variables which are USED in the setup.
- Definitions in "Variables" define local variables.
- Section options define variant options for which the setup or test can be applied
- All words in strings which starting with `$` represent a variable.
Separate the string and combine the string with proper syntax with the variable.
- "property", "value" pairs shall become function calls with parameters Measurement, Name, property, value.
- Generate ONE file which includes the instrument definitions as specified in InstrumentConfig
- All members MUST be filled as specified
- Generate ONE cpp file with one class per test
- This class shall be based on data in <commonPath>/templates and shall have the same name as the json (`testFileName`)
- This class MUST have setup methods per instrumentSetup from <commonPath>/setups as base and over write them with setup specified by setups in <variantPath>/TestSetup the method shall be called with `Measurement`.
- The class shall be called with one structure based on `Instruments`
- Call in `Template` named `applySetup` shall call the setup method in class specified with second argument
- Generate ONE file which hosts main function.
- Call every generated test
ENGINE REQUIREMENTS
- Keep the code short but readable.
- Add brief comments only where needed.
DELIVERABLE
- Provide the complete code, separated into at least:
1) a domain module/package (no terminal dependencies)
2) a small entrypoint/main that wires retrieves commandline arguments and calls the domain logic and delivers output
3) build executable

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;
}