Commit 91c2b0b4 authored by Alexander Ryndin's avatar Alexander Ryndin
Browse files

impl. catch2-based tests generation

parent efd036c0
......@@ -183,6 +183,18 @@
<version>4.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
<!-- .. Test Libraries ............................................... -->
<dependency>
<groupId>junit</groupId>
......
......@@ -20,12 +20,18 @@ import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Sascha Schneiders
*/
public class GeneratorCPP implements Generator {
private Path modelsDirPath;
private boolean isGenerateTests = false;
private final List<BluePrintCPP> bluePrints = new ArrayList<>();
protected String generationTargetPath = "./target/generated-sources-cpp/";
protected boolean algebraicOptimizations = false;
......@@ -53,6 +59,7 @@ public class GeneratorCPP implements Generator {
GeneratorCPP generatorCPP = new GeneratorCPP();
generatorCPP.setGenerationTargetPath(outputPath);
generatorCPP.setModelsDirPath(resolvingPath);
generatorCPP.generateFiles(componentSymbol, symtab);
}
......@@ -88,6 +95,10 @@ public class GeneratorCPP implements Generator {
}
}
if (bluePrintCPP != null) {
bluePrints.add(bluePrintCPP);
}
String result = languageUnitCPP.getGeneratedHeader(bluePrintCPP);
return result;
}
......@@ -134,6 +145,10 @@ public class GeneratorCPP implements Generator {
public List<File> generateFiles(ExpandedComponentInstanceSymbol componentSymbol, Scope symtab) throws IOException {
List<FileContent> fileContents = generateStrings(componentSymbol, symtab);
if (isGenerateTests()) {
TestsGeneratorCPP g = new TestsGeneratorCPP(this);
fileContents.addAll(g.generateStreamTests(symtab));
}
//System.out.println(fileContents);
if (getGenerationTargetPath().charAt(getGenerationTargetPath().length() - 1) != '/') {
setGenerationTargetPath(getGenerationTargetPath() + "/");
......@@ -231,4 +246,24 @@ public class GeneratorCPP implements Generator {
public void setUseMPIDefinitionFix(boolean useFix) {
this.MPIDefinitionFix = useFix;
}
public Path getModelsDirPath() {
return modelsDirPath;
}
public void setModelsDirPath(Path modelsDirPath) {
this.modelsDirPath = modelsDirPath;
}
public boolean isGenerateTests() {
return isGenerateTests;
}
public void setGenerateTests(boolean generateTests) {
isGenerateTests = generateTests;
}
public List<BluePrintCPP> getBluePrints() {
return Collections.unmodifiableList(bluePrints);
}
}
package de.monticore.lang.monticar.generator.cpp;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import de.monticore.lang.monticar.generator.cpp.resolver.Resolver;
import de.monticore.lang.monticar.generator.cpp.resolver.SymTabCreator;
import de.monticore.symboltable.Scope;
import de.se_rwth.commons.logging.Log;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class GeneratorCppCli {
private static final Option OPTION_MODELS_PATH = Option.builder("m")
.longOpt("models-dir")
.desc("full path to directory with EMAM models e.g. C:\\Users\\vpupkin\\proj\\MyAwesomeAutopilot\\src\\main\\emam")
.hasArg(true)
.required(true)
.build();
private static final Option OPTION_ROOT_MODEL = Option.builder("r")
.longOpt("root-model")
.desc("fully qualified name of the root model e.g. de.rwth.vpupkin.modeling.mySuperAwesomeAutopilotComponent")
.hasArg(true)
.required(true)
.build();
private static final Option OPTION_OUTPUT_PATH = Option.builder("o")
.longOpt("output-dir")
.desc("full path to output directory for tests e.g. C:\\Users\\vpupkin\\proj\\MyAwesomeAutopilot\\target\\gen-cpp")
.hasArg(true)
.required(true)
.build();
private static final Option OPTION_FLAG_TESTS = Option.builder("t")
.longOpt("flag-generate-tests")
.desc("optional flag indicating if tests generation is needed")
.hasArg(false)
.required(false)
.build();
private GeneratorCppCli() {
}
public static void main(String[] args) {
Options options = new Options();
options.addOption(OPTION_MODELS_PATH);
options.addOption(OPTION_ROOT_MODEL);
options.addOption(OPTION_OUTPUT_PATH);
options.addOption(OPTION_FLAG_TESTS);
CommandLineParser parser = new DefaultParser();
CommandLine cliArgs;
try {
cliArgs = parser.parse(options, args);
} catch (ParseException e) {
System.err.println("argument parsing exception: " + e.getMessage());
System.exit(1);
return;
}
Path modelsDirPath = Paths.get(cliArgs.getOptionValue(OPTION_MODELS_PATH.getOpt()));
String rootModelName = cliArgs.getOptionValue(OPTION_ROOT_MODEL.getOpt());
String outputPath = cliArgs.getOptionValue(OPTION_OUTPUT_PATH.getOpt());
SymTabCreator symTabCreator = new SymTabCreator(modelsDirPath);
Scope symtab = symTabCreator.createSymTab();
Resolver resolver = new Resolver(symtab);
ExpandedComponentInstanceSymbol componentSymbol = resolver.getExpandedComponentInstanceSymbol(rootModelName).orElse(null);
if (componentSymbol == null) {
Log.error("could not resolve component " + rootModelName);
System.exit(1);
return;
}
GeneratorCPP g = new GeneratorCPP();
g.setModelsDirPath(modelsDirPath);
g.setGenerationTargetPath(outputPath);
g.setGenerateTests(cliArgs.hasOption(OPTION_FLAG_TESTS.getOpt()));
try {
g.generateFiles(componentSymbol, symtab);
} catch (IOException e) {
Log.error("error during generation", e);
System.exit(1);
}
}
}
package de.monticore.lang.monticar.generator.cpp;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc.StreamScanner;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ComponentSymbol;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.ExpandedComponentInstanceSymbol;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.PortSymbol;
import de.monticore.lang.monticar.generator.FileContent;
import de.monticore.lang.monticar.generator.cpp.template.AllTemplates;
import de.monticore.lang.monticar.generator.cpp.viewmodel.ComponentStreamTestViewModel;
import de.monticore.lang.monticar.generator.cpp.viewmodel.StreamViewModel;
import de.monticore.lang.monticar.generator.cpp.viewmodel.TestsMainEntryViewModel;
import de.monticore.lang.monticar.generator.cpp.viewmodel.check.BooleanOutputPortCheck;
import de.monticore.lang.monticar.generator.cpp.viewmodel.check.ComponentCheckViewModel;
import de.monticore.lang.monticar.generator.cpp.viewmodel.check.IOutputPortCheck;
import de.monticore.lang.monticar.generator.cpp.viewmodel.check.RangeOutputPortCheck;
import de.monticore.lang.monticar.literals2._ast.ASTBooleanLiteral;
import de.monticore.lang.monticar.streamunits._ast.ASTNamedStreamUnits;
import de.monticore.lang.monticar.streamunits._ast.ASTPrecisionNumber;
import de.monticore.lang.monticar.streamunits._ast.ASTStream;
import de.monticore.lang.monticar.streamunits._ast.ASTStreamInstruction;
import de.monticore.lang.monticar.streamunits._ast.ASTStreamValue;
import de.monticore.lang.monticar.streamunits._symboltable.ComponentStreamUnitsSymbol;
import de.monticore.lang.monticar.streamunits._symboltable.NamedStreamUnitsSymbol;
import de.monticore.lang.monticar.streamunits._visitor.StreamUnitsVisitor;
import de.monticore.symboltable.Scope;
import de.se_rwth.commons.logging.Log;
import siunit.monticoresiunit.si._ast.ASTUnitNumber;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
public final class TestsGeneratorCPP {
public static final String TESTS_DIRECTORY_NAME = "test";
private final GeneratorCPP generator;
TestsGeneratorCPP(GeneratorCPP generator) {
this.generator = Log.errorIfNull(generator);
}
public List<FileContent> generateStreamTests(Scope symTab) {
Set<String> testedComponents = new HashSet<>();
List<BluePrintCPP> bluePrints = generator.getBluePrints();
if (bluePrints == null || bluePrints.isEmpty()) {
Log.warn("no blue prints were generated");
return Collections.emptyList();
}
StreamScanner scanner = new StreamScanner(generator.getModelsDirPath(), symTab);
Map<ComponentSymbol, Set<ComponentStreamUnitsSymbol>> availableStreams = scanner.scan();
List<FileContent> files = new ArrayList<>();
TestsMainEntryViewModel viewModelForMain = new TestsMainEntryViewModel();
viewModelForMain.setIncludes(new ArrayList<>());
for (BluePrintCPP b : bluePrints) {
ExpandedComponentInstanceSymbol s = b.getOriginalSymbol();
if (s != null) {
ComponentSymbol cs = s.getComponentType().getReferencedSymbol();
if (testedComponents.add(cs.getFullName())) {
Set<ComponentStreamUnitsSymbol> streamsForComponent = availableStreams.get(cs);
if (streamsForComponent != null && !streamsForComponent.isEmpty()) {
ComponentStreamTestViewModel viewModel = getStreamViewModel(b, cs, streamsForComponent);
String genTestCode = AllTemplates.generateComponentStreamTest(viewModel);
files.add(new FileContent(genTestCode, getFileName(viewModel)));
viewModelForMain.getIncludes().add(viewModel.getFileNameWithExtension());
}
}
} else {
Log.warn("no symbol info for blue print " + b.getName() + " (package: " + b.getPackageName() + ")");
}
}
files.add(new FileContent(AllTemplates.generateMainEntry(viewModelForMain), TESTS_DIRECTORY_NAME + "/tests_main.cpp"));
files.add(getCatchLib());
return files;
}
private static ComponentStreamTestViewModel getStreamViewModel(BluePrintCPP b, ComponentSymbol cs, Set<ComponentStreamUnitsSymbol> streamsForComponent) {
ComponentStreamTestViewModel viewModel = new ComponentStreamTestViewModel();
viewModel.setComponentName(b.getName());
viewModel.setFileNameWithoutExtension(b.getName() + "_test");
viewModel.setStreams(new ArrayList<>());
for (ComponentStreamUnitsSymbol stream : streamsForComponent) {
StreamViewModel svm = new StreamViewModel();
viewModel.getStreams().add(svm);
svm.setName(stream.getFullName());
svm.setChecks(getComponentPortChecks(cs, stream));
}
return viewModel;
}
private static List<ComponentCheckViewModel> getComponentPortChecks(ComponentSymbol cs, ComponentStreamUnitsSymbol stream) {
List<ComponentCheckViewModel> result = new ArrayList<>();
Map<PortSymbol, ASTStream> port2NamedStream = new HashMap<>();
for (PortSymbol port : cs.getPorts()) {
NamedStreamUnitsSymbol namedStreamForPort = stream.getNamedStream(port.getName()).orElse(null);
if (namedStreamForPort != null && namedStreamForPort.getAstNode().isPresent()) {
ASTNamedStreamUnits node = (ASTNamedStreamUnits) namedStreamForPort.getAstNode().get();
port2NamedStream.put(port, node.getStream());
}
}
int streamLength = -1;
for (ASTStream ns : port2NamedStream.values()) {
int l = ns.getStreamInstructions().size();
if (streamLength == -1) {
streamLength = l;
} else if (streamLength != l) {
String msg = String.format("streams have different lengths: %s and %s (stream %s)", streamLength, l, stream.getFullName());
Log.error(msg);
throw new RuntimeException(msg);
}
}
if (streamLength <= 0) {
String msg = String.format("invalid stream data in %s", stream.getFullName());
Log.error(msg);
throw new RuntimeException(msg);
}
for (int i = 0; i < streamLength; i++) {
ComponentCheckViewModel vm = new ComponentCheckViewModel();
vm.setInputPortName2Value(new HashMap<>());
vm.setOutputPortName2Check(new HashMap<>());
for (Map.Entry<PortSymbol, ASTStream> kv : port2NamedStream.entrySet()) {
ASTStreamInstruction nextInstruction = kv.getValue().getStreamInstructions().get(i);
if (nextInstruction.getStreamValue().isPresent()) {
ASTStreamValue sv = nextInstruction.getStreamValue().get();
PortSymbol port = kv.getKey();
if (port.isIncoming()) {
ASTStreamValue2InputPortValue converter = new ASTStreamValue2InputPortValue();
sv.accept(converter);
if (converter.getResult() != null) {
vm.getInputPortName2Value().put(port.getName(), converter.getResult());
}
} else {
ASTStreamValue2OutputPortCheck converter = new ASTStreamValue2OutputPortCheck();
sv.accept(converter);
if (converter.getResult() != null) {
vm.getOutputPortName2Check().put(port.getName(), converter.getResult());
}
}
}
}
result.add(vm);
}
return result;
}
private static FileContent getCatchLib() {
InputStream resource = TestsGeneratorCPP.class.getResourceAsStream("/vendor/catch.hpp");
String body = new Scanner(resource, "UTF-8").useDelimiter("\\A").next();
return new FileContent(body, TESTS_DIRECTORY_NAME + "/catch.hpp");
}
private static String getFileName(ComponentStreamTestViewModel viewModel) {
return TESTS_DIRECTORY_NAME + "/" + viewModel.getFileNameWithExtension();
}
private static final class ASTStreamValue2OutputPortCheck implements StreamUnitsVisitor {
private IOutputPortCheck result = null;
public IOutputPortCheck getResult() {
return result;
}
@Override
public void visit(ASTBooleanLiteral node) {
if (node.getValue()) {
result = BooleanOutputPortCheck.TRUE_EXPECTED;
} else {
result = BooleanOutputPortCheck.FALSE_EXPECTED;
}
}
@Override
public void visit(ASTPrecisionNumber node) {
ASTUnitNumber unitNumber = node.getUnitNumber();
if (!unitNumber.getNumber().isPresent()) {
return;
}
double baseValue = unitNumber.getNumber().get().doubleValue();
if (node.getPrecision().isPresent()
&& node.getPrecision().get().getUnitNumber().getNumber().isPresent()) {
double delta = node.getPrecision().get().getUnitNumber().getNumber().get().doubleValue();
result = RangeOutputPortCheck.from(baseValue - delta, baseValue + delta);
} else {
result = RangeOutputPortCheck.from(baseValue, baseValue);
}
}
}
private static final class ASTStreamValue2InputPortValue implements StreamUnitsVisitor {
private String result = null;
public String getResult() {
return result;
}
@Override
public void visit(ASTBooleanLiteral node) {
result = node.getValue() ? "true" : "false";
}
@Override
public void visit(ASTPrecisionNumber node) {
ASTUnitNumber unitNumber = node.getUnitNumber();
if (!unitNumber.getNumber().isPresent()) {
return;
}
result = Double.toString(unitNumber.getNumber().get().doubleValue());
}
}
}
package de.monticore.lang.monticar.generator.cpp.template;
import de.monticore.lang.monticar.generator.cpp.viewmodel.ComponentStreamTestViewModel;
import de.monticore.lang.monticar.generator.cpp.viewmodel.TestsMainEntryViewModel;
import de.monticore.lang.monticar.generator.cpp.viewmodel.ViewModelBase;
import de.se_rwth.commons.logging.Log;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import java.io.IOException;
import java.io.StringWriter;
public final class AllTemplates {
private static final Template COMPONENT_STREAM_TEST;
private static final Template TESTS_MAIN_ENTRY;
static {
Configuration conf = new Configuration(Configuration.VERSION_2_3_23);
conf.setDefaultEncoding("UTF-8");
conf.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
conf.setLogTemplateExceptions(false);
conf.setClassForTemplateLoading(AllTemplates.class, "/template");
try {
COMPONENT_STREAM_TEST = conf.getTemplate("/test/ComponentStreamTest.ftl");
TESTS_MAIN_ENTRY = conf.getTemplate("/test/TestsMainEntry.ftl");
} catch (IOException e) {
String msg = "could not load templates";
Log.error(msg, e);
throw new RuntimeException(msg, e);
}
}
private AllTemplates() {
}
public static String generateComponentStreamTest(ComponentStreamTestViewModel viewModel) {
return generate(COMPONENT_STREAM_TEST, viewModel);
}
public static String generateMainEntry(TestsMainEntryViewModel viewModel) {
return generate(TESTS_MAIN_ENTRY, viewModel);
}
private static String generate(Template template, ViewModelBase viewModelBase) {
return generate(template, TemplateHelper.getDataForTemplate(viewModelBase));
}
private static String generate(Template template, Object dataForTemplate) {
Log.errorIfNull(template);
Log.errorIfNull(dataForTemplate);
StringWriter sw = new StringWriter();
try {
template.process(dataForTemplate, sw);
} catch (TemplateException | IOException e) {
Log.error("template generation failed, template: " + template.getName(), e);
}
return sw.toString();
}
}
package de.monticore.lang.monticar.generator.cpp.template;
import de.monticore.lang.monticar.generator.cpp.viewmodel.ViewModelBase;
import de.monticore.lang.monticar.generator.cpp.viewmodel.check.BooleanOutputPortCheck;
import de.monticore.lang.monticar.generator.cpp.viewmodel.check.RangeOutputPortCheck;
import de.se_rwth.commons.logging.Log;
import java.util.HashMap;
import java.util.Map;
public final class TemplateHelper {
public static final TemplateHelper INSTANCE = new TemplateHelper();
private TemplateHelper() {
}
public boolean isBooleanOutputPortCheck(Object check) {
return check instanceof BooleanOutputPortCheck;
}
public boolean isRangeOutputPortCheck(Object check) {
return check instanceof RangeOutputPortCheck;
}
public boolean isTrueExpectedCheck(Object check) {
return BooleanOutputPortCheck.TRUE_EXPECTED.equals(check);
}
public boolean isFalseExpectedCheck(Object check) {
return BooleanOutputPortCheck.FALSE_EXPECTED.equals(check);
}
public static Map<String, Object> getDataForTemplate(ViewModelBase viewModel) {
HashMap<String, Object> data = new HashMap<>();
data.put("viewModel", Log.errorIfNull(viewModel));
data.put("helper", INSTANCE);
return data;
}
}
package de.monticore.lang.monticar.generator.cpp.viewmodel;
import java.util.Collections;
import java.util.List;
public final class ComponentStreamTestViewModel extends ViewModelBase {
private String fileNameWithoutExtension;
private String componentName;
private List<StreamViewModel> streams = Collections.emptyList();
public String getFileNameWithExtension() {
return getFileNameWithoutExtension() + ".hpp";
}
public String getFileNameWithoutExtension() {
return fileNameWithoutExtension;
}
public void setFileNameWithoutExtension(String fileNameWithoutExtension) {
this.fileNameWithoutExtension = fileNameWithoutExtension;
}
public String getComponentName() {
return componentName;
}
public void setComponentName(String componentName) {
this.componentName = componentName;
}
public List<StreamViewModel> getStreams() {
return streams;
}
public void setStreams(List<StreamViewModel> streams) {
this.streams = streams;
}
}
package de.monticore.lang.monticar.generator.cpp.viewmodel;
import de.monticore.lang.monticar.generator.cpp.viewmodel.check.ComponentCheckViewModel;
import java.util.Collections;
import java.util.List;
public final class StreamViewModel extends ViewModelBase {
private String name;
private List<ComponentCheckViewModel> checks = Collections.emptyList();
public String getName() {
return name;