diff --git a/build.gradle b/build.gradle index c9f5a8d654fdd6a213e364528ce70ebe1173e63c..03170b2193172398fcaf465fdf0d94037ae28a1d 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,7 @@ dependencies { testFixturesApi group: 'org.assertj', name: 'assertj-core', version: '3.18.0' testFixturesApi group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.0-rc1' testFixturesApi group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.12.0-rc1' + testFixturesApi group: 'commons-io', name: 'commons-io', version: '2.8.0' } test { diff --git a/src/main/jastadd/backend/AbstractGrammar.jadd b/src/main/jastadd/backend/AbstractGrammar.jadd index da803c5448c6129d4123bc55ad80cc1abedcc6e9..3683fe27c2fba45459a5e7eb325f62ab1ad8e102 100644 --- a/src/main/jastadd/backend/AbstractGrammar.jadd +++ b/src/main/jastadd/backend/AbstractGrammar.jadd @@ -43,14 +43,16 @@ aspect BackendAbstractGrammar { if (getAbstract()) { b.append("abstract "); } - b.append(getName()).append(" "); + b.append(getName()); if (hasSuperType()) { - b.append(": ").append(getSuperType().getName()).append(" "); + b.append(" : ").append(getSuperType().getName()); } - b.append("::="); - for (Component component : getComponentList()) { - b.append(" "); - component.generateAbstractGrammar(b); + if (getNumComponent() > 0) { + b.append(" ::="); + for (Component component : getComponentList()) { + b.append(" "); + component.generateAbstractGrammar(b); + } } b.append(";"); super.generateAbstractGrammar(b); @@ -62,8 +64,7 @@ aspect BackendAbstractGrammar { if (getNTA()) { b.append("/"); } - - if (!getName().equals("")) { + if (!getName().equals("") && !getName().equals(getTypeDecl().getName())) { b.append(getName()).append(":"); } b.append(getTypeDecl().getName()); @@ -76,8 +77,7 @@ aspect BackendAbstractGrammar { if (getNTA()) { b.append("/"); } - - if (!getName().equals("")) { + if (!getName().equals("") && !getName().equals(getTypeDecl().getName())) { b.append(getName()).append(":"); } b.append(getTypeDecl().getName()).append("*"); @@ -91,7 +91,7 @@ aspect BackendAbstractGrammar { b.append("/"); } b.append("["); - if (!getName().equals("")) { + if (!getName().equals("") && !getName().equals(getTypeDecl().getName())) { b.append(getName()).append(":"); } b.append(getTypeDecl().getName()).append("]"); @@ -187,7 +187,7 @@ aspect BackendAbstractGrammar { } public void SingleLineComment.generateAbstractGrammar(StringBuilder b) { - b.append("//").append(getText()).append("\n"); + b.append("//").append(getText()); } public void MultiLineComment.generateAbstractGrammar(StringBuilder b) { diff --git a/src/main/java/org/jastadd/relast/compiler/Mustache.java b/src/main/java/org/jastadd/relast/compiler/Mustache.java index 3b428abf1f60b87ff004b7cb92aaefcbf99b03df..24d368abf9d13cbca1882caef1a49084943499a9 100644 --- a/src/main/java/org/jastadd/relast/compiler/Mustache.java +++ b/src/main/java/org/jastadd/relast/compiler/Mustache.java @@ -6,16 +6,34 @@ import com.github.jknack.handlebars.io.ClassPathTemplateLoader; import com.github.jknack.handlebars.io.TemplateLoader; import org.yaml.snakeyaml.Yaml; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; +import java.io.*; +import java.nio.file.Paths; public class Mustache { - public static void javaMustache(String templateFileName, String yamlFileName, String outputFileName) throws IOException { + private Mustache() { + // hide public constructor + } + + public static void javaMustache(String templateFileName, File yamlFile, String outputFileName) throws IOException { + + //noinspection ResultOfMethodCallIgnored + Paths.get(outputFileName).getParent().toFile().mkdirs(); // create directory structure if necessary + + Object context = new Yaml().load(new FileReader(yamlFile)); + applyTemplate(templateFileName, outputFileName, context); + } + + public static void javaMustache(String templateFileName, String yaml, String outputFileName) throws IOException { + + //noinspection ResultOfMethodCallIgnored + Paths.get(outputFileName).getParent().toFile().mkdirs(); // create directory structure if necessary + + Object context = new Yaml().load(new StringReader(yaml)); + applyTemplate(templateFileName, outputFileName, context); + } - Object context = new Yaml().load(new FileReader(yamlFileName)); + private static void applyTemplate(String templateFileName, String outputFileName, Object context) throws IOException { TemplateLoader loader = new ClassPathTemplateLoader(); loader.setSuffix(".mustache"); // the default is ".hbs" diff --git a/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java b/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java index b5624f2aee6e22179800de5e09c471df8b3e2071..4e3c97d120b8a1906b77b3d334fea536bdfa40bc 100644 --- a/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java +++ b/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java @@ -21,7 +21,7 @@ public abstract class RelAstProcessor extends AbstractCompiler { protected ValueOption optionOutputBaseDir; protected ValueOption optionInputBaseDir; - public RelAstProcessor(String name, boolean jastAddCompliant) { + protected RelAstProcessor(String name, boolean jastAddCompliant) { super(name, jastAddCompliant); } diff --git a/src/test/java/org/jastadd/relast/tests/PreprocessorTest.java b/src/test/java/org/jastadd/relast/tests/PreprocessorTest.java index c6fde6f5331ebc9e94680dee87b54ce31e58bad6..b58349632e19b3caf3848ae3fff836673c441221 100644 --- a/src/test/java/org/jastadd/relast/tests/PreprocessorTest.java +++ b/src/test/java/org/jastadd/relast/tests/PreprocessorTest.java @@ -1,15 +1,13 @@ package org.jastadd.relast.tests; import org.jastadd.relast.compiler.RelastSourceToSourceCompiler; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Paths; +import org.junit.jupiter.api.BeforeAll; public class PreprocessorTest extends RelAstProcessorTestBase { - @Test - void testMinimalGrammar() throws IOException, InterruptedException { - directoryTest(RelastSourceToSourceCompiler.class, Paths.get("src/test/resources/MinimalGrammar")); + @BeforeAll + static void init() { + mainClass = RelastSourceToSourceCompiler.class; } + } diff --git a/src/test/resources/Comments/config.yaml b/src/test/resources/Comments/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6095c8da742b0eaa2f636aeee218d77d37221859 --- /dev/null +++ b/src/test/resources/Comments/config.yaml @@ -0,0 +1,8 @@ +- name: "Parse and reprint tests" + args: + - "--inputBaseDir=in" + - "--outputBaseDir=out" + - "CommentsA.relast" + out: "out" + expected: "in" + compare: true diff --git a/src/test/resources/Comments/in/CommentsA.relast b/src/test/resources/Comments/in/CommentsA.relast new file mode 100644 index 0000000000000000000000000000000000000000..37e963fdf8a7ca9b1613deca9bc46923f29f00d2 --- /dev/null +++ b/src/test/resources/Comments/in/CommentsA.relast @@ -0,0 +1,15 @@ +// this file only contains comments +// + +// + +// like this one +/* or this one */ +/* or this + one */ +/**/ +//test +/*test*/ + +// + diff --git a/src/test/resources/MinimalGrammar/config.yaml b/src/test/resources/MinimalGrammar/config.yaml index c22d8d533545dff1d7139fa62a5464506d703c13..135f6bd74b057a00cc5582b7ca78e83995167e48 100644 --- a/src/test/resources/MinimalGrammar/config.yaml +++ b/src/test/resources/MinimalGrammar/config.yaml @@ -1,10 +1,9 @@ -- name: "MinimalGrammar" +- name: "Parse and reprint tests" args: - "--inputBaseDir=in" - "--outputBaseDir=out" - "Example.relast" -- name: "Comments in front" - args: - - "--inputBaseDir=in" - - "--outputBaseDir=out" - "CommentInFront.relast" + out: "out" + expected: "in" + compare: true diff --git a/src/test/resources/MinimalGrammar/in/CommentInFront.relast b/src/test/resources/MinimalGrammar/in/CommentInFront.relast index 99b677ef4d8481b99e4d9f2f366764e1de6a568b..96c5eecaae626aa6903b7d0b9641916688c7a997 100644 --- a/src/test/resources/MinimalGrammar/in/CommentInFront.relast +++ b/src/test/resources/MinimalGrammar/in/CommentInFront.relast @@ -1,2 +1,2 @@ // comment -CommentInFront ; +CommentInFront; diff --git a/src/test/resources/MinimalGrammar/in/Example.relast b/src/test/resources/MinimalGrammar/in/Example.relast index 0d9c9069419cd5b38fd1f861e9152cce7ffeb750..7c28b7bcd84c826aedd622460eb93da08ee80c3d 100644 --- a/src/test/resources/MinimalGrammar/in/Example.relast +++ b/src/test/resources/MinimalGrammar/in/Example.relast @@ -4,7 +4,7 @@ ZoneModel ::= <Size:IntPosition> SafetyZone:Zone*; Zone ::= Coordinate*; -RobotArm ::= Joint* EndEffector <_AttributeTestSource:int> /<_AppropriateSpeed:double>/ ; // normally this would be: <AttributeTestSource:int> ; +RobotArm ::= Joint* EndEffector <_AttributeTestSource:int> /<_AppropriateSpeed:double>/; // normally this would be: <AttributeTestSource:int> ; Joint ::= <Name> <CurrentPosition:IntPosition>; // normally this would be: <CurrentPosition:IntPosition> diff --git a/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java b/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java index bdd620c19de7ae3d9891960c6191c51cfb6ef37d..e83746c04a4a85327eabf5db0a1de580e6a42e3c 100644 --- a/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java +++ b/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java @@ -3,25 +3,25 @@ package org.jastadd.relast.tests; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.filefilter.TrueFileFilter; import org.assertj.core.util.Files; import org.jastadd.relast.tests.config.Configuration; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.TestFactory; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; +import java.io.*; +import java.nio.charset.Charset; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; - -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.*; +import java.util.stream.Stream; public class RelAstProcessorTestBase { + protected static Class<?> mainClass; + protected static int runProcess(File workingDirectory, List<String> command, StringBuilder outStringBuider, StringBuilder errStringBuilder) throws IOException, InterruptedException { File outFile = Files.newTemporaryFile(); @@ -72,7 +72,7 @@ public class RelAstProcessorTestBase { command.addAll(args); } - System.out.println("Running java -jar -cp [...] " + className + " " + args.stream().reduce((s1, s2) -> s1 + " " + s2).orElse("")); + System.out.println("Running java -jar -cp [...] " + className + " " + (args != null ? args.stream().reduce((s1, s2) -> s1 + " " + s2).orElse("") : "")); return runProcess(workingDirectory, command, outStringBuider, errStringBuilder); } @@ -86,6 +86,9 @@ public class RelAstProcessorTestBase { for (Configuration config : configs) { + FileUtils.forceMkdir(dir.resolve(config.getOut()).toFile()); + FileUtils.cleanDirectory(dir.resolve(config.getOut()).toFile()); + StringBuilder outBuilder = new StringBuilder(); StringBuilder errBuilder = new StringBuilder(); int returnValue = runJavaProcess(mainClass, dir.toFile(), Arrays.asList(config.getArgs()), outBuilder, errBuilder); @@ -96,46 +99,78 @@ public class RelAstProcessorTestBase { System.err.println(err); if (config.shouldFail()) { - Assertions.assertNotEquals(0, returnValue, "Zero return value of preprocessor for negative test."); + Assertions.assertNotEquals(0, returnValue, config.getName() + ": Zero return value of preprocessor for negative test."); } else { - Assertions.assertEquals(0, returnValue, "Non-Zero return value of preprocessor for positive test."); + Assertions.assertEquals(0, returnValue, config.getName() + ": Non-Zero return value of preprocessor for positive test."); } - for (String errMatchString : config.getErrMatches()) { - if (!err.matches(errMatchString)) { - Assertions.fail("Error stream does not match '" + errMatchString + "'"); - } + checkOutput(config, out, err); + + if (config.shouldCompare()) { + Path outPath = dir.resolve(config.getOut()); + Path expectedPath = dir.resolve(config.getExpected()); + comparePaths(outPath, expectedPath); } + } + } - for (String errContainsString : config.getErrContains()) { - if (!err.contains(errContainsString)) { - Assertions.fail("Error stream does not contain '" + errContainsString + "'"); - } + private void checkOutput(Configuration config, String out, String err) { + for (String errMatchString : config.getErrMatches()) { + if (!err.matches(errMatchString)) { + Assertions.fail("Error stream does not match '" + errMatchString + "'"); } + } - for (String outMatchString : config.getOutMatches()) { - if (!out.matches(outMatchString)) { - Assertions.fail("Error stream does not match '" + outMatchString + "'"); - } + for (String errContainsString : config.getErrContains()) { + if (!err.contains(errContainsString)) { + Assertions.fail("Error stream does not contain '" + errContainsString + "'"); } + } - for (String outContainsString : config.getOutContains()) { - if (!out.contains(outContainsString)) { - Assertions.fail("Error stream does not contain '" + outContainsString + "'"); - } + for (String outMatchString : config.getOutMatches()) { + if (!out.matches(outMatchString)) { + Assertions.fail("Output stream does not match '" + outMatchString + "'"); } } - } - protected void ensureOutputDir(String outputDir) { - File outputDirFile = Paths.get(outputDir).toFile(); - if (outputDirFile.exists()) { - assertTrue(outputDirFile.isDirectory()); - if (Objects.requireNonNull(outputDirFile.list(), "Could not read output directory").length != 0) { - System.out.println("output directory is not empty!"); + for (String outContainsString : config.getOutContains()) { + if (!out.contains(outContainsString)) { + Assertions.fail("Output stream does not contain '" + outContainsString + "'"); } - } else { - assertTrue(outputDirFile.mkdirs()); } } + + private void comparePaths(Path outPath, Path expectedPath) { + final Collection<File> files = FileUtils.listFiles(expectedPath.toFile(), TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE); + files.forEach(f -> { + final Path relative = expectedPath.relativize(f.toPath()); + final String relativePath = relative.toFile().getPath(); + final File bfile = new File(outPath.toFile(), relativePath); + if (bfile.exists()) { + try { + final Charset charset = Charset.defaultCharset(); + final String expected = FileUtils.readFileToString(f, charset); + final String result = FileUtils.readFileToString(bfile, charset); + Assertions.assertEquals(expected, result); + } catch (IOException e) { + Assertions.fail("Unable to compare input files '" + f + "' and '" + bfile + "'", e); + } + } else { + Assertions.fail(relativePath + " expected to exist"); + } + }); + } + + @TestFactory + Stream<DynamicTest> testAll() { + File baseDir = new File("src/test/resources/"); + + Assertions.assertTrue(baseDir.exists()); + Assertions.assertTrue(baseDir.isDirectory()); + File[] files = baseDir.listFiles((FileFilter) FileFilterUtils.directoryFileFilter()); + Assertions.assertNotNull(files); + return Arrays.stream(files).map(File::toPath).map(f -> DynamicTest.dynamicTest(f.getFileName().toString(), + () -> directoryTest(mainClass, f))); + } + } diff --git a/src/testFixtures/java/org/jastadd/relast/tests/config/Configuration.java b/src/testFixtures/java/org/jastadd/relast/tests/config/Configuration.java index 7a1ec9419bc5bb39463941ff70ceb097ab33c5d5..faeb91a1065ecf7530e299189444ce4d094086c5 100644 --- a/src/testFixtures/java/org/jastadd/relast/tests/config/Configuration.java +++ b/src/testFixtures/java/org/jastadd/relast/tests/config/Configuration.java @@ -9,6 +9,35 @@ public class Configuration { private String[] errContains = new String[]{}; private String[] outMatches = new String[]{}; private String[] outContains = new String[]{}; + private String in = "in"; + private String out = "out"; + private String expected = "expected"; + + private boolean compare = false; + + public String getIn() { + return in; + } + + public void setIn(String in) { + this.in = in; + } + + public String getOut() { + return out; + } + + public void setOut(String out) { + this.out = out; + } + + public String getExpected() { + return expected; + } + + public void setExpected(String expected) { + this.expected = expected; + } @com.fasterxml.jackson.annotation.JsonGetter("out-matches") public String[] getOutMatches() { @@ -46,7 +75,7 @@ public class Configuration { } @com.fasterxml.jackson.annotation.JsonSetter("err-contains") - public void setErrContains(String[] errMatches) { + public void setErrContains(String[] errContains) { this.errContains = errContains; } @@ -75,4 +104,14 @@ public class Configuration { public void shouldFail(boolean fail) { this.fail = fail; } + + @com.fasterxml.jackson.annotation.JsonGetter("compare") + public boolean shouldCompare() { + return compare; + } + + @com.fasterxml.jackson.annotation.JsonSetter("compare") + public void shouldCompare(boolean compare) { + this.compare = compare; + } }