package de.gaalop; import java.io.File; import java.io.IOException; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Scanner; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import de.gaalop.CluCalcCppTest.*; import de.gaalop.testbench.CoeffReader; import de.gaalop.testbench.TestbenchGenerator; import de.gaalop.testbench.TestbenchLexer; import de.gaalop.testbench.TestbenchParser; import static org.junit.Assert.*; /** * Executes a suite of test comparing the original CLUCalc output with optimized CLUCalc and C++ outputs. <br /> * <br /> * <b>Note:</b> No not forget to clean and rebuild source files when editing CLUCalc files externally. Otherwise, cached * binary files could contain obsolete code. * * @author Christian Schwinn * */ @RunWith(Suite.class) @SuiteClasses(value = { FileTests.class, ErrorTests.class }) public class CluCalcCppTest { public static class OutputSet { private double[] cppValues; private double[] cluOriginalValues; private double[] cluOptimizedValues; public void setCPP(String result) { cppValues = parseString(result); } public void setCLUOriginal(String result) { cluOriginalValues = parseString(result); } public void setCLUOptimized(String result) { cluOptimizedValues = parseString(result); } public double[] getCppValues() { return cppValues; } public double[] getCluOriginalValues() { return cluOriginalValues; } public double[] getCluOptimizedValues() { return cluOptimizedValues; } private double[] parseString(String string) { ANTLRStringStream input = new ANTLRStringStream(string); TestbenchLexer lexer = new TestbenchLexer(input); CommonTokenStream tokenStream = new CommonTokenStream(lexer); TestbenchParser parser = new TestbenchParser(tokenStream); try { CoeffReader reader = parser.line(); return reader.getCoeffs(); } catch (RecognitionException e) { e.printStackTrace(); } throw new IllegalStateException("Line " + string + " could not be parsed."); } } public static class FileTests { private Random r = new Random(System.currentTimeMillis()); private void generateInputValue(String variable) { float nextFloat = r.nextFloat(); if (DEBUG) { System.out.printf("Using %f for input variable %s\n", nextFloat, variable); } inputValues.put(variable, nextFloat); } @Before public void init() { r.setSeed(System.currentTimeMillis()); CluCalcCppTest.init(); } /** * Tests the Horizon.clu example. * * @throws IOException */ @Test public void horizon() throws Exception { String fileName = getClass().getResource("Horizon.clu").getFile(); generateInputValue("mx"); generateInputValue("my"); generateInputValue("mz"); generateInputValue("px"); generateInputValue("py"); generateInputValue("pz"); generateInputValue("r"); int outputMVs = 1; // C outputNames.add("C"); compare(fileName, outputMVs); } /** * Tests the inverse kinematics algorithm.<br> * <br> * Note: this test case does not always succeed because of the use of random variables without auxiliary * conditions. * * @throws IOException */ @Test public void inverseKinematics() throws Exception { String fileName = getClass().getResource("IK_Gaalop-2.0_input.clu").getFile(); generateInputValue("pwx"); generateInputValue("pwy"); generateInputValue("pwz"); inputValues.put("d1", 1.50f); inputValues.put("d2", 1.10f); generateInputValue("phi"); int outputMVs = 6; // SwivelPlane, p_e, q_e, q12, q3, q_s outputNames.add("SwivelPlane"); outputNames.add("p_e"); outputNames.add("q_e"); outputNames.add("q12"); outputNames.add("q3"); outputNames.add("q_s"); compare(fileName, outputMVs); } /** * Tests all-control-flow.clu example. * * @throws IOException */ @Test public void allControlFlow() throws Exception { String fileName = getClass().getResource("all-control-flow.clu").getFile(); // no input variables for this test int outputMVs = 1; // x outputNames.add("x"); compare(fileName, outputMVs); } /** * Tests the loops.clu example. * * @throws Exception */ @Test public void loops() throws Exception { String fileName = getClass().getResource("loops.clu").getFile(); // no input variables for this test int outputMVs = 1; // x outputNames.add("x"); compare(fileName, outputMVs); } /** * Tests the loop_counter.clu example. * * @throws Exception */ @Test public void loopCounter() throws Exception { String fileName = getClass().getResource("loop_counter.clu").getFile(); // no input variables for this test int outputMVs = 1; // x outputNames.add("x"); compare(fileName, outputMVs); } /** * Tests the loop_in_branch.clu example. * * @throws Exception */ @Test public void loopInBranch() throws Exception { String fileName = getClass().getResource("loop_in_branch.clu").getFile(); // no input variables for this test int outputMVs = 1; // x outputNames.add("x"); compare(fileName, outputMVs); } /** * Tests the loop_no_unrolling.clu example. * * @throws Exception */ @Test public void loopNoUnrolling() throws Exception { String fileName = getClass().getResource("loop_no_unrolling.clu").getFile(); generateInputValue("x"); generateInputValue("y"); generateInputValue("z"); generateInputValue("a"); generateInputValue("b"); generateInputValue("c"); inputValues.put("unknown", 1.0f); int outputMVs = 2; // val, var outputNames.add("val"); outputNames.add("var"); compare(fileName, outputMVs); } /** * Tests the loop_unroll_inner.clu example. * * @throws Exception */ @Test public void loopUnrollInner() throws Exception { String fileName = getClass().getResource("loop_unroll_inner.clu").getFile(); generateInputValue("x"); generateInputValue("y"); generateInputValue("z"); generateInputValue("a"); generateInputValue("b"); generateInputValue("c"); inputValues.put("unknown", 1.0f); int outputMVs = 2; // val, var outputNames.add("val"); outputNames.add("var"); compare(fileName, outputMVs); } /** * Tests the loop_unroll_outer.clu example. * * @throws Exception */ @Test public void loopUnrollOuter() throws Exception { String fileName = getClass().getResource("loop_unroll_outer.clu").getFile(); generateInputValue("x"); generateInputValue("y"); generateInputValue("z"); generateInputValue("a"); generateInputValue("b"); generateInputValue("c"); inputValues.put("unknown", 1.0f); int outputMVs = 2; // val, var outputNames.add("val"); outputNames.add("var"); compare(fileName, outputMVs); } /** * Tests the loop_unroll_both.clu example. * * @throws Exception */ @Test public void loopUnrollBoth() throws Exception { String fileName = getClass().getResource("loop_unroll_both.clu").getFile(); generateInputValue("x"); generateInputValue("y"); generateInputValue("z"); generateInputValue("a"); generateInputValue("b"); generateInputValue("c"); inputValues.put("unknown", 1.0f); int outputMVs = 2; // val, var outputNames.add("val"); outputNames.add("var"); compare(fileName, outputMVs); } /** * Tests the Nested_If.clu example. * * @throws IOException */ @Test public void nestedIf() throws Exception { String fileName = getClass().getResource("Nested_If.clu").getFile(); generateInputValue("s1"); generateInputValue("s2"); generateInputValue("s3"); generateInputValue("r"); generateInputValue("z"); generateInputValue("p1"); generateInputValue("p2"); generateInputValue("p3"); inputValues.put("z", 5.0f); int outputMVs = 1; // rslt outputNames.add("rslt"); compare(fileName, outputMVs); } /** * Tests the mixed_macros.clu example. * * @throws IOException */ @Test public void mixedMacros() throws Exception { String fileName = getClass().getResource("mixed_macros.clu").getFile(); // no input values for this test int outputMVs = 1; // rslt outputNames.add("rslt"); compare(fileName, outputMVs); } /** * Tests the equality_condition.clu example. * * @throws Exception */ @Test public void equalityCondition() throws Exception { String fileName = getClass().getResource("equality_condition.clu").getFile(); generateInputValue("a"); generateInputValue("b"); generateInputValue("c"); generateInputValue("j"); generateInputValue("k"); int outputMVs = 1; // x outputNames.add("x"); compare(fileName, outputMVs); } /** * Tests the unknown_if.clu example for +1 and -1. * * @throws Exception */ @Test public void unknownIf() throws Exception { String fileName = getClass().getResource("unknown_if.clu").getFile(); int outputMVs = 1; inputValues.put("unknown", 1.0f); // r outputNames.add("r"); compare(fileName, outputMVs); inputValues.put("unknown", -1.0f); // r compare(fileName, outputMVs); } /** * Tests the unknown_condition.clu example. * * @throws Exception */ @Test public void unknownCondition() throws Exception { String fileName = getClass().getResource("unknown_condition.clu").getFile(); int outputMVs = 1; outputNames.add("a"); compare(fileName, outputMVs); } /** * Tests the if_reuse_vars.clu example. * * @throws Exception */ @Test public void ifReuseVars() throws Exception { String fileName = getClass().getResource("if_reuse_vars.clu").getFile(); generateInputValue("a"); generateInputValue("b"); generateInputValue("c"); generateInputValue("p1"); generateInputValue("p2"); generateInputValue("p3"); generateInputValue("m1"); generateInputValue("m2"); generateInputValue("m3"); generateInputValue("phi"); inputValues.put("cond1", 1.0f); inputValues.put("cond2", 1.0f); inputValues.put("cond3", 1.0f); inputValues.put("cond4", 1.0f); inputValues.put("cond5", 1.0f); int outputMVs = 2; outputNames.add("testX"); outputNames.add("testFoo4"); compare(fileName, outputMVs); } /** * Tests the if_reuse_opt.clu example. * * @throws Exception */ @Test public void ifReuseOpt() throws Exception { String fileName = getClass().getResource("if_reuse_opt.clu").getFile(); generateInputValue("a"); generateInputValue("b"); generateInputValue("c"); generateInputValue("m1"); generateInputValue("m2"); generateInputValue("m3"); generateInputValue("phi"); inputValues.put("cond1", 1.0f); inputValues.put("cond2", 1.0f); int outputMVs = 1; outputNames.add("testX"); compare(fileName, outputMVs); } } public static class ErrorTests { private static final String RECURSIVE_X = "The Variable x cannot be a local variable and an input variable at the same time."; private static final String NO_OPT = "There are no lines marked for optimization ('?')"; @Before public void init() { CluCalcCppTest.init(); } /** * Tests if a code parser exception is thrown for missing question marks as expected. * * @throws Exception */ @Test(expected = CodeParserException.class) public void missingQuestionMark() throws Exception { String content = "x = VecN3(1,2,3);"; try { compare("no-question-mark.clu", content, 0); } catch (CodeParserException e) { assertEquals(NO_OPT, e.getMessage()); throw e; } } /** * Tests if a code parser exception is thrown for recursive assignments as expected. * * @throws Exception */ @Test(expected = CodeParserException.class) public void recursiveInputVariable() throws Exception { String content = "?x = VecN3(1,2,x);"; try { compare("recursive-assignment.clu", content, 0); } catch (CodeParserException e) { assertEquals(RECURSIVE_X, e.getMessage()); throw e; } } /** * Tests if a code parser exception is thrown for uninitialized variables in recursive assignments as expected. * * @throws Exception */ @Test(expected = CodeParserException.class) public void uninitializedVariable() throws Exception { String fileName = getClass().getResource("uninitializedVariable.clu").getFile(); // no input variables for this test try { compare(fileName, 0); } catch (CodeParserException e) { assertEquals(RECURSIVE_X, e.getMessage()); throw e; } } } final static String PATH = "C:/Users/Christian/Downloads/Testbench/"; final static String INCLUDE = "C:/Program Files (x86)/CLUViz/v6_1/SDK/include"; final static String LIBPATH = "C:/Program Files (x86)/CLUViz/v6_1/SDK/lib"; public static boolean DEBUG = true; static TestbenchGenerator generator; static Map<String, Float> inputValues = new HashMap<String, Float>(); static List<String> outputNames = new ArrayList<String>(); static void init() { inputValues.clear(); outputNames.clear(); } private static File compile() throws Exception { generator.run(); generator.createCompileScript(INCLUDE, LIBPATH); return generator.compile(); } private static Scanner run(File executable) throws IOException { ProcessBuilder pb = new ProcessBuilder(executable.getAbsolutePath()); pb.directory(executable.getParentFile()); Process p = pb.start(); return new Scanner(p.getInputStream()); } private static List<OutputSet> parseResult(Scanner scanner, int numElements) { List<OutputSet> results = new ArrayList<OutputSet>(); for (int i = 0; i < numElements; i++) { results.add(new OutputSet()); } // read CPP header printNextLine(scanner); // read CPP outputs for (int i = 0; i < numElements; i++) { String vector = printNextLine(scanner); results.get(i).setCPP(vector); } // read CLU original header printNextLine(scanner); // read CLU original outputs for (int i = 0; i < numElements; i++) { String vector = printNextLine(scanner); results.get(i).setCLUOriginal(vector); } // read CLU opt header printNextLine(scanner); // read CLU opt outputs for (int i = 0; i < numElements; i++) { String vector = printNextLine(scanner); results.get(i).setCLUOptimized(vector); } return results; } private static String printNextLine(Scanner scanner) { String line = scanner.nextLine(); if (DEBUG) { System.out.println(line); } return line; } static void compare(String fileName, int numVectors) throws Exception { generator = new TestbenchGenerator(fileName, PATH, inputValues); doCompare(numVectors); } static void compare(String fileName, String contents, int numVectors) throws Exception { generator = new TestbenchGenerator(fileName, contents, PATH, inputValues); doCompare(numVectors); } private static void doCompare(int numVectors) throws Exception { assertEquals("Numbers of output multivectors and output names do not match. Forgot to add output names?", numVectors, outputNames.size()); File exe = compile(); Scanner scanner = run(exe); List<OutputSet> results = parseResult(scanner, numVectors); compareMultivectors(results); } private static void compareMultivectors(List<OutputSet> actualList) { for (int i = 0; i < actualList.size(); i++) { OutputSet actual = actualList.get(i); String outputName = outputNames.get(i); for (int element = 0; element < 32; element++) { double cluOriginal = actual.getCluOriginalValues()[element]; double cluOptimized = actual.getCluOptimizedValues()[element]; double cpp = actual.getCppValues()[element]; double epsilon = getEpsilon(cluOriginal); try { boolean allZero = (cluOriginal + cluOptimized + cpp) == 0; if (DEBUG && !allZero) { System.out.printf("comparing %s[%d]: %f\t%f\t%f\tepsilon=%f\n", outputName, element, cluOriginal, cluOptimized, cpp, epsilon); } assertEquals(cluOriginal, cluOptimized, epsilon); assertEquals(cluOriginal, cpp, epsilon); } catch (AssertionError e) { System.err.printf("Unequal coefficient at index %d for variable %s\n", element, outputName); throw e; } } } } private static double getEpsilon(double d) { Locale.setDefault(Locale.US); DecimalFormatSymbols format = DecimalFormatSymbols.getInstance(); String string = Double.toString(d); int pointIndex = string.indexOf(format.getDecimalSeparator()); int indexE = string.lastIndexOf(format.getExponentSeparator()); int exponent = 1; if (indexE != -1) { exponent = Integer.parseInt(string.substring(indexE + 1)); } int end = indexE == -1 ? string.length() : indexE; String decimalPlaces = string.substring(pointIndex + 1, end); int decimals = decimalPlaces.length(); return Math.pow(10, exponent - decimals); } }