From 3945b687d47a37cb9eb0c051a5ab4cb0e583cc82 Mon Sep 17 00:00:00 2001 From: Detlef Jenett Date: Thu, 26 Feb 2026 17:41:05 +0100 Subject: [PATCH] Initial commit --- Documentation/Test Streamlining .pptx | Bin 0 -> 66383 bytes .../hw_ext_tests/InstrumentConfig.json | 6 + .../hw_ext_tests/VariantConfig.json | 5 + .../hw_ext_tests/setups/Setup_Simple_Typ.json | 66 ++ .../templates/Test_Level_Simple.json | 31 + .../Driver/TestSetup/ExecutionConfig.json | 25 + .../Driver/TestSetup/InstrumentConfig.json | 32 + .../Driver/TestSetup/Setup_Simple_Typ.json | 60 ++ .../TestSetup/generatedTests/Instruments.h | 54 ++ .../Driver/TestSetup/generatedTests/Makefile | 18 + .../TestSetup/generatedTests/Measurement.h | 31 + .../Driver/TestSetup/generatedTests/README.md | 44 + .../generatedTests/Test_Level_Simple.cpp | 57 ++ .../Driver/TestSetup/generatedTests/build.bat | 18 + .../Driver/TestSetup/generatedTests/main.cpp | 34 + .../Avi64/generatedTests/InstrumentDefs.h | 45 + .../generatedTests/Test_Level_Simple.cpp | 66 ++ .../Avi64/generatedTests/main.cpp | 20 + TestGenerator/Makefile | 53 ++ TestGenerator/README.md | 115 +++ TestGenerator/build.bat | 37 + TestGenerator/copilot_instrucations | 49 ++ TestGenerator/src/code_generator.cpp | 820 ++++++++++++++++++ TestGenerator/src/code_generator.h | 46 + TestGenerator/src/data_structures.h | 117 +++ TestGenerator/src/file_utils.cpp | 200 +++++ TestGenerator/src/file_utils.h | 41 + TestGenerator/src/json_parser.cpp | 194 +++++ TestGenerator/src/json_parser.h | 65 ++ TestGenerator/src/main.cpp | 149 ++++ 30 files changed, 2498 insertions(+) create mode 100644 Documentation/Test Streamlining .pptx create mode 100644 SMT_PlayGround/hw_ext_tests/InstrumentConfig.json create mode 100644 SMT_PlayGround/hw_ext_tests/VariantConfig.json create mode 100644 SMT_PlayGround/hw_ext_tests/setups/Setup_Simple_Typ.json create mode 100644 SMT_PlayGround/hw_ext_tests/templates/Test_Level_Simple.json create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/ExecutionConfig.json create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/InstrumentConfig.json create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/Setup_Simple_Typ.json create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Instruments.h create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Makefile create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Measurement.h create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/README.md create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Test_Level_Simple.cpp create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/build.bat create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/main.cpp create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/InstrumentDefs.h create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/Test_Level_Simple.cpp create mode 100644 SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/main.cpp create mode 100644 TestGenerator/Makefile create mode 100644 TestGenerator/README.md create mode 100644 TestGenerator/build.bat create mode 100644 TestGenerator/copilot_instrucations create mode 100644 TestGenerator/src/code_generator.cpp create mode 100644 TestGenerator/src/code_generator.h create mode 100644 TestGenerator/src/data_structures.h create mode 100644 TestGenerator/src/file_utils.cpp create mode 100644 TestGenerator/src/file_utils.h create mode 100644 TestGenerator/src/json_parser.cpp create mode 100644 TestGenerator/src/json_parser.h create mode 100644 TestGenerator/src/main.cpp diff --git a/Documentation/Test Streamlining .pptx b/Documentation/Test Streamlining .pptx new file mode 100644 index 0000000000000000000000000000000000000000..72ad5440ac9cdb8a28b32bd02d7b41305946a766 GIT binary patch literal 66383 zcmeFYQ*&m~w=W#qwr$(CZQD-Aw$riGv28m~Y}@SE$=iGHbMc-!|8H>4x>!}K#=4ke z*8E|NQBz446buar0tgBS2#6R+Or!Gi5Eux^1O*5P6$lDMSJctL&D_DwP|e%P+*O~! z%ifNt2n>X(00`vY|Nrm#zjz0RGbR*e9ER(5wT_!ZdKz?K(I%#LydN|rE4%QNJELqzd|n$w)rTZgMP9sGt5y7B0W zXjWPXa>Lj3N%X%3fv}^~b9sxe(@n;N7fsG-3v#c``CG|h#d9VS2W%W*m9Y|xIb|%E zJAMzTZ7K1)s4=R6wDuYSf^I_Tg-(%aaeM!itvmE658D$!6I-OiTM4hV#NKdjQwCmc z!?XvUT8X7eC4D+gb61n85et|VrVD8RG2CIg{JT2K+4w_|{qT*x7)t>+Mgi6}5B*$( zdL>E86{+BamAgm9GvMqNoN@-H%_$)Asm~nKM{nW$Jfh?3nA%D_b2I9NKM*>kixkXw z=njT($!nGWt=iC_ot?xF>*b^h{3CF|eQ7A)XUm~(%9$N%PdfF>uIFbG%_8WXIZV63 zU(VPDSFn_6Ii658<9#fFT2B0E46Rh+-V{ca{f!yCvY4;XX}-?+i*)vXZpA2%HQgFfs( zZZLE)w{vA=_^t=1Qzut5 zG9uUK&T0(%cb&l#PQ{PB)thJGoV&)N-yE-G-f4Gflw%o|_NZ6~TEe+1r<+bAa|H%2EHbJu^8YV2n1=KI zu)%~jc1wAUPqM4P#V4%88y`*X)V z*0&Zvd*xxsY$+K=XPZaj%PN6z?5&^r$u2&txK1S;uNI0Kx|~ybCPdzu8}P+qTN+o8 z>>`c^=iU#Wu9{pFr!u|dZr)pnp2X=c%58P2qb4S-+1(jNbi3DUQp-so=^!o>-+6by zdU*NNId$%r>qrZ=%Y&Sn$#+z0cQmj2fbIB9$Do>QCaJlJDXp&IN*;ueHPlQ_>yKJd z{;JL=<^D8E9fRi06&W&|0AfU0`VLQp)$x$|k-wjQy&{dJUCht_t`bkbehcupDqvKk zd^V^Y?mz-d%(Z9hEH!D=*Cid4S8 zVP1={8}pmtp8AO#NHEVQE&$hMhF4V#6uy125AR};d~_z~VnEh!aSav=o?m72w{l#( zAoEMT`Y)Oq6qUsY@N$9?s>iw`l5*CvEG0)W1%J?8yvdL5V-J-NofP?a*mkhxkw$s; zgA#%k?(SxN6e(A^XIC)$=dH`A<5C{}%*Y z?X1nrT^avJLH_%Lnfbq<@ZVw*JKF;@$bSN|{m;5Egvuvk`SBoSAXK!1|nrcrjN z9@@vVMf2XCmtmivCgIK@kwNMjc%3mlEz*&E{fsBhP1~XkWH9Tm7}39fj7h>poAj16 zahTFcZ}iQS(PnYpa}EutIr!toS;X$m7sA<07|bu!#2)(Nmr5&JY5Z{KrsSMkaTeBV zg||wje%1=@a|=zGW|MrskqfeIvUH|*>;?W~?mK^( z+wA-vS(n}kI$=zGF1*_dGjE4FxH~sGDmKrdQ{`^htA6o9&sCa#7>6RX|)CDl*S{8ce&H)od`X!cD8m8b1ctqv<;YzI%0Ds%i@iTE!hCX{S0Tcey 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 +#include + +struct Instruments { + std::string variant; + std::vector options; + std::string pinType; + std::vector 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 diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Makefile b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Makefile new file mode 100644 index 0000000..5241a04 --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Makefile @@ -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 diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Measurement.h b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Measurement.h new file mode 100644 index 0000000..3778970 --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Measurement.h @@ -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 + void level_vforce(const std::string& name, T value) {} + template + void level_iclampSource(const std::string& name, T value) {} + template + void level_iclampSink(const std::string& name, T value) {} + template + void level_vrange(const std::string& name, T value) {} + template + void level_irange(const std::string& name, T value) {} + void connect(bool flag) {} + void disconnect(bool flag) {} + void disconnectMode(const std::string& mode) {} +}; + +#endif diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/README.md b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/README.md new file mode 100644 index 0000000..19ffd29 --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/README.md @@ -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. diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Test_Level_Simple.cpp b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Test_Level_Simple.cpp new file mode 100644 index 0000000..9dc51ea --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/Test_Level_Simple.cpp @@ -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(); + } +}; + diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/build.bat b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/build.bat new file mode 100644 index 0000000..a2a2a3f --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/build.bat @@ -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 diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/main.cpp b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/main.cpp new file mode 100644 index 0000000..b9fdeda --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/Driver/TestSetup/generatedTests/main.cpp @@ -0,0 +1,34 @@ +#include +#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; +} diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/InstrumentDefs.h b/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/InstrumentDefs.h new file mode 100644 index 0000000..ee55c84 --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/InstrumentDefs.h @@ -0,0 +1,45 @@ +// Auto-generated instrument definitions - do not edit + +#pragma once +#include + +// Instrument definitions +struct InstrumentDef { + std::string name; + std::string variant; + std::vector options; + std::string pinType; + std::vector pinNumbers; +}; + +// All configured instruments +static const std::vector 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 + } +}; diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/Test_Level_Simple.cpp b/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/Test_Level_Simple.cpp new file mode 100644 index 0000000..8b95ffc --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/Test_Level_Simple.cpp @@ -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_; +}; diff --git a/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/main.cpp b/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/main.cpp new file mode 100644 index 0000000..06a3f29 --- /dev/null +++ b/SMT_PlayGround/hw_ext_variants/Avi64/generatedTests/main.cpp @@ -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_(instruments); + test.execute(); + } + + return 0; +} diff --git a/TestGenerator/Makefile b/TestGenerator/Makefile new file mode 100644 index 0000000..7082415 --- /dev/null +++ b/TestGenerator/Makefile @@ -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 diff --git a/TestGenerator/README.md b/TestGenerator/README.md new file mode 100644 index 0000000..2006796 --- /dev/null +++ b/TestGenerator/README.md @@ -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 -variantPath +``` + +Example: +```bash +testgen -commonPath ..\SMT_PlayGround\hw_ext_tests -variantPath ..\SMT_PlayGround\hw_ext_variants\Avi64\Driver\TestSetup +``` + +### Validate JSON File +```bash +testgen -dry +``` + +Example: +```bash +testgen -dry test.json +``` + +### Display Help +```bash +testgen -help +``` + +## Generated Files + +The tool generates C++ files in `/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 /generatedTests +build.bat +test_runner.exe +``` + +**Linux/Mac:** +```bash +cd /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` diff --git a/TestGenerator/build.bat b/TestGenerator/build.bat new file mode 100644 index 0000000..0cdafdb --- /dev/null +++ b/TestGenerator/build.bat @@ -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 diff --git a/TestGenerator/copilot_instrucations b/TestGenerator/copilot_instrucations new file mode 100644 index 0000000..a37242b --- /dev/null +++ b/TestGenerator/copilot_instrucations @@ -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 `: specify common location (hw_ext_tests) + - `-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 /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 /templates and shall have the same name as the json (`testFileName`) + - This class MUST have setup methods per instrumentSetup from /setups as base and over write them with setup specified by setups in /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 + \ No newline at end of file diff --git a/TestGenerator/src/code_generator.cpp b/TestGenerator/src/code_generator.cpp new file mode 100644 index 0000000..bec25e8 --- /dev/null +++ b/TestGenerator/src/code_generator.cpp @@ -0,0 +1,820 @@ +#include "code_generator.h" +#include "file_utils.h" +#include +#include +#include + +CodeGenerator::CodeGenerator(const Config& cfg) : config(cfg) {} + +Instrument CodeGenerator::parseInstrument(std::shared_ptr 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 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 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 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 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 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 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 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& 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 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 \n"; + ss << "#include \n\n"; + + ss << "struct Instruments {\n"; + ss << " std::string variant;\n"; + ss << " std::vector options;\n"; + ss << " std::string pinType;\n"; + ss << " std::vector 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 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& testNames) { + std::stringstream ss; + + ss << "#include \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\n"; + measurementHeader << " void level_vforce(const std::string& name, T value) {}\n"; + measurementHeader << " template\n"; + measurementHeader << " void level_iclampSource(const std::string& name, T value) {}\n"; + measurementHeader << " template\n"; + measurementHeader << " void level_iclampSink(const std::string& name, T value) {}\n"; + measurementHeader << " template\n"; + measurementHeader << " void level_vrange(const std::string& name, T value) {}\n"; + measurementHeader << " template\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 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; +} diff --git a/TestGenerator/src/code_generator.h b/TestGenerator/src/code_generator.h new file mode 100644 index 0000000..9a391d8 --- /dev/null +++ b/TestGenerator/src/code_generator.h @@ -0,0 +1,46 @@ +#ifndef CODE_GENERATOR_H +#define CODE_GENERATOR_H + +#include "data_structures.h" +#include "json_parser.h" +#include +#include +#include + +class CodeGenerator { +private: + Config config; + std::map commonSetups; + std::map variantSetups; + std::map globalVariables; + + // Parse JSON helpers + Instrument parseInstrument(std::shared_ptr json); + Property parseProperty(std::shared_ptr json); + Variable parseVariable(std::shared_ptr json); + Setup parseSetup(std::shared_ptr json); + SetupFile parseSetupFile(std::shared_ptr json); + Template parseTemplate(std::shared_ptr json); + TestExecution parseTestExecution(std::shared_ptr 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& testNames); + + // Variable substitution + std::string substituteVariables(const std::string& expr, const std::map& 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 diff --git a/TestGenerator/src/data_structures.h b/TestGenerator/src/data_structures.h new file mode 100644 index 0000000..b5b7ed6 --- /dev/null +++ b/TestGenerator/src/data_structures.h @@ -0,0 +1,117 @@ +#ifndef DATA_STRUCTURES_H +#define DATA_STRUCTURES_H + +#include +#include +#include + +// Instrument definition +struct Instrument { + std::string instrumentName; + std::string variant; + std::vector options; + std::string pinType; + std::vector 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 properties; +}; + +// Expects definition +struct Expect { + std::string type; // "problem", "state", "logset" + std::vector checks; + std::vector logsetChecks; +}; + +// Instrument setup definition +struct InstrumentSetup { + std::string name; + std::vector properties; + std::vector expects; +}; + +// Setup definition +struct Setup { + std::string name; + std::vector notes; + InstrumentSetup instrumentSetup; +}; + +// Variable definition +struct Variable { + std::string name; + std::string expression; +}; + +// Setup file data +struct SetupFile { + std::vector options; + std::vector fileNotes; + std::vector variables; + std::vector setups; +}; + +// Template call +struct TemplateCall { + std::string call; + std::vector args; + std::string returnVar; +}; + +// Template input +struct TemplateInput { + std::string key; + std::string value; + std::vector> setups; +}; + +// Template definition +struct Template { + int groupId; + std::string uniqueTemplateId; + std::vector description; + std::vector features; + std::vector testInput; + std::vector templateCalls; +}; + +// Test configuration for execution +struct TestConfiguration { + std::string option; + std::string pinType; +}; + +// Test execution configuration +struct TestExecution { + std::string name; + std::vector configurations; +}; + +// Configuration data +struct Config { + std::string commonPath; + std::string variantPath; + bool dryRun; + std::vector instruments; + std::vector setupFiles; + std::vector