From d6f0cbe837e89f45440db864d4390ecc4e5afd5c Mon Sep 17 00:00:00 2001 From: jonfreedman Date: Tue, 31 Jul 2018 10:44:18 +0100 Subject: [PATCH] new tests to verify the output of the codegenerator can be compiled --- quickfixj-core/pom.xml | 13 ++ .../org/quickfixj/codegenerator/Compiler.java | 73 +++++++++ .../MessageCodeGeneratorTest.java | 148 ++++++++++++++++++ .../test/resources/codegenerator/basic.xml | 120 ++++++++++++++ .../resources/codegenerator/nested-group.xml | 131 ++++++++++++++++ 5 files changed, 485 insertions(+) create mode 100644 quickfixj-core/src/test/java/org/quickfixj/codegenerator/Compiler.java create mode 100644 quickfixj-core/src/test/java/org/quickfixj/codegenerator/MessageCodeGeneratorTest.java create mode 100644 quickfixj-core/src/test/resources/codegenerator/basic.xml create mode 100644 quickfixj-core/src/test/resources/codegenerator/nested-group.xml diff --git a/quickfixj-core/pom.xml b/quickfixj-core/pom.xml index 23dc556da7..8fbe442aa8 100644 --- a/quickfixj-core/pom.xml +++ b/quickfixj-core/pom.xml @@ -56,6 +56,19 @@ test + + org.quickfixj + quickfixj-codegenerator + ${project.version} + test + + + org.jooq + joor-java-8 + 0.9.9 + test + + org.apache.mina mina-core diff --git a/quickfixj-core/src/test/java/org/quickfixj/codegenerator/Compiler.java b/quickfixj-core/src/test/java/org/quickfixj/codegenerator/Compiler.java new file mode 100644 index 0000000000..2fd2c14643 --- /dev/null +++ b/quickfixj-core/src/test/java/org/quickfixj/codegenerator/Compiler.java @@ -0,0 +1,73 @@ +package org.quickfixj.codegenerator; +import org.joor.Reflect; +import javax.tools.*; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.StringWriter; +import java.lang.invoke.MethodHandles; +import java.net.URI; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +class Compiler { + private Compiler() { + } + static Map compile(final Map classNameToSourceMap) { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + final ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null)); + final List files = new ArrayList<>(); + for (final Map.Entry entry : classNameToSourceMap.entrySet()) { + files.add(new CharSequenceJavaFileObject(entry.getKey(), entry.getValue())); + } + final StringWriter out = new StringWriter(); + compiler.getTask(out, fileManager, null, null, null, files).call(); + if (!fileManager.output.keySet().containsAll(classNameToSourceMap.keySet())) { + throw new RuntimeException("Compilation error:\n" + out.toString()); + } + final ClassLoader cl = lookup.lookupClass().getClassLoader(); + final Map instances = new LinkedHashMap<>(); + for (final Map.Entry output : fileManager.output.entrySet()) { + final String className = output.getKey(); + final byte[] b = output.getValue().getBytes(); + final Class clazz = Reflect.on(cl).call("defineClass", className, b, 0, b.length).get(); + instances.put(className, Reflect.on(clazz)); + } + return instances; + } + private static final class ClassFileManager extends ForwardingJavaFileManager { + private final Map output = new LinkedHashMap<>(); + ClassFileManager(final StandardJavaFileManager standardManager) { + super(standardManager); + } + @Override + public JavaFileObject getJavaFileForOutput(final JavaFileManager.Location location, final String className, final JavaFileObject.Kind kind, final FileObject sibling) { + return output.computeIfAbsent(className, (cn) -> new JavaFileObject(cn, kind)); + } + } + private static final class JavaFileObject extends SimpleJavaFileObject { + private final ByteArrayOutputStream os = new ByteArrayOutputStream(); + JavaFileObject(final String name, final JavaFileObject.Kind kind) { + super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); + } + byte[] getBytes() { + return os.toByteArray(); + } + @Override + public OutputStream openOutputStream() { + return os; + } + } + private static final class CharSequenceJavaFileObject extends SimpleJavaFileObject { + private final CharSequence content; + CharSequenceJavaFileObject(final String className, final CharSequence content) { + super(URI.create("string:///" + className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE); + this.content = content; + } + @Override + public CharSequence getCharContent(final boolean ignoreEncodingErrors) { + return content; + } + } +} \ No newline at end of file diff --git a/quickfixj-core/src/test/java/org/quickfixj/codegenerator/MessageCodeGeneratorTest.java b/quickfixj-core/src/test/java/org/quickfixj/codegenerator/MessageCodeGeneratorTest.java new file mode 100644 index 0000000000..f613a0ce37 --- /dev/null +++ b/quickfixj-core/src/test/java/org/quickfixj/codegenerator/MessageCodeGeneratorTest.java @@ -0,0 +1,148 @@ +package org.quickfixj.codegenerator; +import org.joor.Reflect; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import quickfix.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.LinkedHashMap; +import java.util.Map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +public class MessageCodeGeneratorTest { + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + @Test + public void generateFromBasicFixDictionary() throws Exception { + final File schema = new File(MessageCodeGeneratorTest.class.getResource("/org/quickfixj/codegenerator/MessageFactory.xsl").getFile()); + final File spec = new File(MessageCodeGeneratorTest.class.getResource("/codegenerator/basic.xml").getFile()); + final MessageCodeGenerator.Task task = new MessageCodeGenerator.Task(); + task.setName("basic"); + task.setSpecification(spec); + task.setTransformDirectory(schema.getParentFile()); + task.setMessagePackage("basic"); + task.setOutputBaseDirectory(folder.getRoot()); + task.setFieldPackage("field"); + task.setOverwrite(true); + task.setOrderedFields(true); + task.setDecimalGenerated(false); + final MessageCodeGenerator generator = new MessageCodeGenerator(); + generator.generate(task); + final File fieldDir = new File(folder.getRoot(), "field"); + final File messageDir = new File(folder.getRoot(), "basic"); + final Map classNameToSourceMap = new LinkedHashMap<>(); + classNameToSourceMap.put("field.BeginString", getSource(new File(fieldDir, "BeginString.java"))); + classNameToSourceMap.put("field.BodyLength", getSource(new File(fieldDir, "BodyLength.java"))); + classNameToSourceMap.put("field.CheckSum", getSource(new File(fieldDir, "CheckSum.java"))); + classNameToSourceMap.put("field.MsgType", getSource(new File(fieldDir, "MsgType.java"))); + classNameToSourceMap.put("field.Signature", getSource(new File(fieldDir, "Signature.java"))); + classNameToSourceMap.put("field.SignatureLength", getSource(new File(fieldDir, "SignatureLength.java"))); + classNameToSourceMap.put("field.TestReqID", getSource(new File(fieldDir, "TestReqID.java"))); + classNameToSourceMap.put("basic.Message", getSource(new File(messageDir, "Message.java"))); + classNameToSourceMap.put("basic.TestRequest", getSource(new File(messageDir, "TestRequest.java"))); + classNameToSourceMap.put("basic.MessageCracker", getSource(new File(messageDir, "MessageCracker.java"))); + classNameToSourceMap.put("basic.MessageFactory", getSource(new File(messageDir, "MessageFactory.java"))); + final Map classes = Compiler.compile(classNameToSourceMap); + final Map fieldDefs = new LinkedHashMap<>(); + fieldDefs.put("BeginString", new FieldDef(8, StringField.class)); + fieldDefs.put("BodyLength", new FieldDef(9, IntField.class)); + fieldDefs.put("CheckSum", new FieldDef(10, StringField.class)); + fieldDefs.put("MsgType", new FieldDef(35, StringField.class)); + fieldDefs.put("Signature", new FieldDef(89, StringField.class)); + fieldDefs.put("SignatureLength", new FieldDef(93, IntField.class)); + fieldDefs.put("TestReqID", new FieldDef(112, StringField.class)); + validateFields(classes, fieldDefs); + final Map messageDefs = new LinkedHashMap<>(); + messageDefs.put("TestRequest", new MessageDef("1")); + validateMessages(classes, messageDefs); + } + /** + * This test is based on the FXAll FIX spec post MiFID II which has the same group in different locations within a + * message based on the context of the message. At present this generates Java code which does not compile due to + * duplicate case labels. + */ + @Test(expected = RuntimeException.class) + public void generateFromFixDictionaryWithNestedGroups() throws Exception { + final File schema = new File(MessageCodeGeneratorTest.class.getResource("/org/quickfixj/codegenerator/MessageFactory.xsl").getFile()); + final File spec = new File(MessageCodeGeneratorTest.class.getResource("/codegenerator/nested-group.xml").getFile()); + final MessageCodeGenerator.Task task = new MessageCodeGenerator.Task(); + task.setName("nested"); + task.setSpecification(spec); + task.setTransformDirectory(schema.getParentFile()); + task.setMessagePackage("nested"); + task.setOutputBaseDirectory(folder.getRoot()); + task.setFieldPackage("field"); + task.setOverwrite(true); + task.setOrderedFields(true); + task.setDecimalGenerated(false); + final MessageCodeGenerator generator = new MessageCodeGenerator(); + generator.generate(task); + final File fieldDir = new File(folder.getRoot(), "field"); + final File messageDir = new File(folder.getRoot(), "nested"); + final Map classNameToSourceMap = new LinkedHashMap<>(); + classNameToSourceMap.put("field.BeginString", getSource(new File(fieldDir, "BeginString.java"))); + classNameToSourceMap.put("field.BodyLength", getSource(new File(fieldDir, "BodyLength.java"))); + classNameToSourceMap.put("field.CheckSum", getSource(new File(fieldDir, "CheckSum.java"))); + classNameToSourceMap.put("field.MsgType", getSource(new File(fieldDir, "MsgType.java"))); + classNameToSourceMap.put("field.Signature", getSource(new File(fieldDir, "Signature.java"))); + classNameToSourceMap.put("field.SignatureLength", getSource(new File(fieldDir, "SignatureLength.java"))); + classNameToSourceMap.put("field.TestReqID", getSource(new File(fieldDir, "TestReqID.java"))); + classNameToSourceMap.put("field.NoFoos", getSource(new File(fieldDir, "NoFoos.java"))); + classNameToSourceMap.put("field.NoBars", getSource(new File(fieldDir, "NoBars.java"))); + classNameToSourceMap.put("field.Foo", getSource(new File(fieldDir, "Foo.java"))); + classNameToSourceMap.put("basic.Message", getSource(new File(messageDir, "Message.java"))); + classNameToSourceMap.put("basic.TestRequest", getSource(new File(messageDir, "TestRequest.java"))); + classNameToSourceMap.put("basic.MessageCracker", getSource(new File(messageDir, "MessageCracker.java"))); + classNameToSourceMap.put("basic.MessageFactory", getSource(new File(messageDir, "MessageFactory.java"))); + final Map classes = Compiler.compile(classNameToSourceMap); + final Map fieldDefs = new LinkedHashMap<>(); + fieldDefs.put("BeginString", new FieldDef(8, StringField.class)); + fieldDefs.put("BodyLength", new FieldDef(9, IntField.class)); + fieldDefs.put("CheckSum", new FieldDef(10, StringField.class)); + fieldDefs.put("MsgType", new FieldDef(35, StringField.class)); + fieldDefs.put("Signature", new FieldDef(89, StringField.class)); + fieldDefs.put("SignatureLength", new FieldDef(93, IntField.class)); + fieldDefs.put("TestReqID", new FieldDef(112, StringField.class)); + fieldDefs.put("NoFoos", new FieldDef(112, IntField.class)); + fieldDefs.put("NoBars", new FieldDef(112, IntField.class)); + fieldDefs.put("Foo", new FieldDef(112, StringField.class)); + validateFields(classes, fieldDefs); + final Map messageDefs = new LinkedHashMap<>(); + messageDefs.put("TestRequest", new MessageDef("1")); + validateMessages(classes, messageDefs); + } + private String getSource(final File file) throws IOException { + return new String(Files.readAllBytes(file.toPath())); + } + private void validateFields(final Map classes, final Map fieldDefs) { + for (final Map.Entry fieldDef : fieldDefs.entrySet()) { + final String fieldName = fieldDef.getKey(); + final Field fieldInstance = classes.get("field." + fieldName).create().get(); + assertEquals(String.format("Mismatch on field number for %s", fieldName), fieldDef.getValue().fieldNumber, fieldInstance.getField()); + assertTrue(String.format("Expected %s to be an instance of %s", fieldName, fieldDef.getValue().clazz.getSimpleName()), fieldDef.getValue().clazz.isAssignableFrom(fieldInstance.getClass())); + } + } + private void validateMessages(final Map classes, final Map messageDefs) throws FieldNotFound { + for (final Map.Entry messageDef : messageDefs.entrySet()) { + final String messageName = messageDef.getKey(); + final Message messageInstance = classes.get("basic." + messageName).create().get(); + assertEquals(String.format("Mismatch on message type for %s", messageName), messageDef.getValue().messageType, messageInstance.getHeader().getString(35)); + } + } + private final class FieldDef { + private final int fieldNumber; + private final Class> clazz; + FieldDef(final int fieldNumber, final Class> clazz) { + this.fieldNumber = fieldNumber; + this.clazz = clazz; + } + } + private final class MessageDef { + private final String messageType; + MessageDef(final String messageType) { + this.messageType = messageType; + } + } +} \ No newline at end of file diff --git a/quickfixj-core/src/test/resources/codegenerator/basic.xml b/quickfixj-core/src/test/resources/codegenerator/basic.xml new file mode 100644 index 0000000000..13fb5e51ea --- /dev/null +++ b/quickfixj-core/src/test/resources/codegenerator/basic.xml @@ -0,0 +1,120 @@ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/quickfixj-core/src/test/resources/codegenerator/nested-group.xml b/quickfixj-core/src/test/resources/codegenerator/nested-group.xml new file mode 100644 index 0000000000..c4cbbd6be1 --- /dev/null +++ b/quickfixj-core/src/test/resources/codegenerator/nested-group.xml @@ -0,0 +1,131 @@ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file