# HG changeset patch # User David Piorkowski # Date 1407281648 25200 # Node ID 2a5ec181dad4c15ad30cd259a0cf1ef12d846782 # Parent bd6b44b04143f69cbc14f2f0bcd8bc52bd6a8541 SL: Added instrumentation testing diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/WrapperTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/instrument/WrapperTest.java Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.api.test.instrument; + +import java.util.*; + +import org.junit.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; + +/** + * This test does the following: + *
    + *
  1. Creates a simple add AST
  2. + *
  3. Verifies its structure
  4. + *
  5. Instruments the add node
  6. + *
  7. Attaches a simple probe to the instrumented node
  8. + *
  9. Verifies the structure of the instrumented AST
  10. + *
  11. Verifies the execution of the instrumented AST
  12. + *
+ * To do these tests, several required classes have been implemented in their most basic form, only + * implementing the methods necessary for the tests to pass, with stubs elsewhere. + */ +public class WrapperTest { + + @Test + public void test() { + // Build a tree + TruffleRuntime runtime = Truffle.getRuntime(); + TestChildNode leftChild = new TestChildNode(); + TestChildNode rightChild = new TestChildNode(); + TestSourceSection sourceSection = new TestSourceSection(); + TestAddNode addNode = new TestAddNode(leftChild, rightChild, sourceSection); + TestRootNode rootNode = new TestRootNode(addNode); + + // Have to create a call target before checking parent/child relationships + CallTarget target = runtime.createCallTarget(rootNode); + + // Check tree structure + Assert.assertEquals(addNode, leftChild.getParent()); + Assert.assertEquals(addNode, rightChild.getParent()); + Iterator iterator = addNode.getChildren().iterator(); + Assert.assertEquals(leftChild, iterator.next()); + Assert.assertEquals(rightChild, iterator.next()); + Assert.assertFalse(iterator.hasNext()); + Assert.assertEquals(rootNode, addNode.getParent()); + iterator = rootNode.getChildren().iterator(); + Assert.assertEquals(addNode, iterator.next()); + Assert.assertFalse(iterator.hasNext()); + Object result = target.call(); + Assert.assertEquals(42, result); + + // Add a wrapper + TestExecutionContext context = new TestExecutionContext(); + TestWrapper wrapper = new TestWrapper(addNode, context); + rootNode = new TestRootNode(wrapper); + target = runtime.createCallTarget(rootNode); + + // Check the new tree structure + Assert.assertEquals(addNode, leftChild.getParent()); + Assert.assertEquals(addNode, rightChild.getParent()); + iterator = addNode.getChildren().iterator(); + Assert.assertEquals(leftChild, iterator.next()); + Assert.assertEquals(rightChild, iterator.next()); + Assert.assertFalse(iterator.hasNext()); + Assert.assertEquals(wrapper, addNode.getParent()); + iterator = wrapper.getChildren().iterator(); + Assert.assertEquals(addNode, iterator.next()); + Assert.assertFalse(iterator.hasNext()); + Assert.assertEquals(rootNode, wrapper.getParent()); + iterator = rootNode.getChildren().iterator(); + Assert.assertEquals(wrapper, iterator.next()); + Assert.assertFalse(iterator.hasNext()); + result = target.call(); + Assert.assertEquals(42, result); + + // Add an instrument + wrapper.getProbe().addInstrument(new TestInstrument()); + + // Check instrument and result + result = target.call(); + Assert.assertEquals(Counter.numInstrumentEnter, 1); + Assert.assertEquals(Counter.numInstrumentLeave, 1); + Assert.assertEquals(42, result); + + } + + private class TestRootNode extends RootNode { + @Child private RootNode child; + + public TestRootNode(RootNode child) { + super(null); + this.child = child; + } + + @Override + public Object execute(VirtualFrame frame) { + return child.execute(frame); + } + } + + private class TestAddNode extends RootNode { + + @Child private TestChildNode left; + @Child private TestChildNode right; + + public TestAddNode(TestChildNode left, TestChildNode right, TestSourceSection sourceSection) { + super(sourceSection); + this.left = left; + this.right = right; + } + + @Override + public Object execute(VirtualFrame frame) { + return left.execute() + right.execute(); + } + } + + private class TestChildNode extends Node { + + public TestChildNode() { + super(null); + } + + public int execute() { + return 21; + } + } + + private class TestWrapper extends RootNode implements Wrapper { + @Child private RootNode child; + private Probe probe; + + public TestWrapper(RootNode child, ExecutionContext context) { + this.child = insert(child); + this.probe = context.getProbe(child.getSourceSection()); + } + + public boolean isTaggedAs(SyntaxTag tag) { + return false; + } + + public Iterable getSyntaxTags() { + return null; + } + + public Node getChild() { + return child; + } + + public Probe getProbe() { + return probe; + } + + @Override + public Object execute(VirtualFrame frame) { + probe.enter(child, frame); + Object result; + + try { + result = child.execute(frame); + probe.leave(child, frame, result); + } catch (Exception e) { + probe.leaveExceptional(child, frame, e); + throw (e); + } + return result; + } + } + + private class TestSourceSection implements SourceSection { + + public Source getSource() { + return null; + } + + public int getStartLine() { + return 0; + } + + public LineLocation getLineLocation() { + return null; + } + + public int getStartColumn() { + return 0; + } + + public int getCharIndex() { + return 0; + } + + public int getCharLength() { + return 0; + } + + public int getCharEndIndex() { + return 0; + } + + public String getIdentifier() { + return null; + } + + public String getCode() { + return null; + } + + public String getShortDescription() { + return null; + } + + } + + private class TestExecutionContext extends ExecutionContext { + + @Override + public String getLanguageShortName() { + return "test"; + } + + @Override + protected void setSourceCallback(SourceCallback sourceCallback) { + + } + + } + + private class TestInstrument extends Instrument { + @Override + public void enter(Node astNode, VirtualFrame frame) { + Counter.numInstrumentEnter++; + } + + @Override + public void leave(Node astNode, VirtualFrame frame, Object result) { + Counter.numInstrumentLeave++; + } + } + + public static class Counter { + + public static int numInstrumentEnter = 0; + public static int numInstrumentLeave = 0; + } +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLCheckVariableEqualityInstrument.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLCheckVariableEqualityInstrument.java Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.sl.test.instrument; + +import java.io.*; + +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.sl.nodes.controlflow.*; +import com.oracle.truffle.sl.runtime.*; + +/** + * This sample instrument provides an example of a naive way to check if two numbers in SL are + * equivalent using their variable names. This instrument is designed to be attached to an + * {@link SLReturnNode}, but provides no guards against this. + */ +public class SLCheckVariableEqualityInstrument extends Instrument { + + private final String varName1; + private final String varName2; + private final PrintStream output; + + /** + * Constructor + * + * @param varName1 The name of the first variable to compare + * @param varName2 The name of the second variable to compare + * @param output The {@link PrintStream} from the context used to print results. See + * {@link SLContext#getOutput()} for more info. + */ + public SLCheckVariableEqualityInstrument(String varName1, String varName2, PrintStream output) { + this.varName1 = varName1; + this.varName2 = varName2; + this.output = output; + } + + /** + * In the instrumentation test, this instrument is attached to a return statement. Since returns + * are handled via exceptions in Simple, we need to override the leaveExceptional method. This + * method does very limited error checking and simply prints "true" if the passed-in variables + * match or "false" if they do not. + */ + @Override + public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) { + FrameSlot f1 = frame.getFrameDescriptor().findFrameSlot(varName1); + FrameSlot f2 = frame.getFrameDescriptor().findFrameSlot(varName2); + + if (f1 == null || f2 == null) + output.println("false"); + else { + try { + output.println(frame.getLong(f1) == frame.getLong(f2)); + } catch (FrameSlotTypeException e1) { + e1.printStackTrace(); + } + } + + } +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestNodeProber.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestNodeProber.java Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.sl.test.instrument; + +import static com.oracle.truffle.api.instrument.StandardSyntaxTag.*; + +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.sl.nodes.*; +import com.oracle.truffle.sl.nodes.controlflow.*; +import com.oracle.truffle.sl.nodes.instrument.*; +import com.oracle.truffle.sl.nodes.local.*; +import com.oracle.truffle.sl.runtime.*; + +/** + * This sample AST Node Prober for simple is used to instrument the nodes that we are interested in + * testing. This prober wraps return nodes and assignment nodes. For the purposes of this example, + * this is appropriate, but ideally there would be only one node prober responsible for + * instrumenting all the nodes of interest instead of a selective one like this one. + * + */ +public final class SLInstrumentTestNodeProber implements SLNodeProber { + private final SLContext slContext; + + public SLInstrumentTestNodeProber(SLContext slContext) { + this.slContext = slContext; + } + + /** + * Not implemented, only returns the astNode that was passed in. + */ + public Node probeAs(Node astNode, SyntaxTag tag, Object... args) { + // TODO dp: Currently does nothing in the general case + return astNode; + } + + /** + * If the passed in node is a {@link SLStatementWrapper}, then this simply tags it as a + * statement. If the passed in node is a {@link SLReturnNode}, then it is instrumented and + * tagged as a statement for testing. Only SLReturnNodes are wrapped. + */ + public SLStatementNode probeAsStatement(SLStatementNode node) { + assert node != null; + + SLStatementWrapper wrapper = null; + if (node instanceof SLStatementWrapper) { + wrapper = (SLStatementWrapper) node; + tagStatementNode(wrapper); + return wrapper; + } else if (node instanceof SLReturnNode) { + wrapper = new SLStatementWrapper(slContext, node); + tagStatementNode(wrapper); + return wrapper; + } + return node; + } + + /** + * Not implemented. Returns the passed in node. + */ + public SLExpressionNode probeAsCall(SLExpressionNode node, String callName) { + return node; + } + + /** + * If the passed in node is a {@link SLExpressionWrapper}, then this simply tags it as an + * assignment. If the passed in node is a {@link SLWriteLocalVariableNode}, then it is + * instrumented and tagged as a assignment for testing. Only SLWriteLocalVariableNode are + * wrapped. + */ + public SLExpressionNode probeAsLocalAssignment(SLExpressionNode node, String localName) { + assert node != null; + + SLExpressionWrapper wrapper = null; + if (node instanceof SLExpressionWrapper) { + wrapper = (SLExpressionWrapper) node; + tagAssignmentNode(wrapper); + return wrapper; + } else if (node instanceof SLWriteLocalVariableNode) { + wrapper = new SLExpressionWrapper(slContext, node); + tagAssignmentNode(wrapper); + return wrapper; + } + return node; + } + + private static void tagAssignmentNode(SLExpressionWrapper wrapper) { + if (!wrapper.isTaggedAs(ASSIGNMENT)) { + wrapper.tagAs(ASSIGNMENT); + } + } + + private static void tagStatementNode(SLStatementWrapper wrapper) { + if (!wrapper.isTaggedAs(STATEMENT)) { + wrapper.tagAs(STATEMENT); + } + } + +} \ No newline at end of file diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestRunner.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestRunner.java Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.sl.test.instrument; + +import java.io.*; +import java.nio.charset.*; +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.util.*; + +import org.junit.*; +import org.junit.internal.*; +import org.junit.runner.*; +import org.junit.runner.manipulation.*; +import org.junit.runner.notification.*; +import org.junit.runners.*; +import org.junit.runners.model.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.sl.nodes.instrument.*; +import com.oracle.truffle.sl.parser.*; +import com.oracle.truffle.sl.runtime.*; +import com.oracle.truffle.sl.test.*; +import com.oracle.truffle.sl.test.instrument.SLInstrumentTestRunner.InstrumentTestCase; + +/** + * This class builds and executes the tests for instrumenting SL. Although much of this class is + * written with future automation in mind, at the moment the tests that are created are hard-coded + * according to the file name of the test. To be automated, an automatic way of generating both the + * node visitor and the node prober is necessary. + * + * Testing is done via JUnit via comparing execution outputs with expected outputs. + */ +public final class SLInstrumentTestRunner extends ParentRunner { + + private static final String SOURCE_SUFFIX = ".sl"; + private static final String INPUT_SUFFIX = ".input"; + private static final String OUTPUT_SUFFIX = ".output"; + private static final String VISITOR_ASSIGNMENT_COUNT_SUFFIX = "_assnCount"; + private static final String VISITOR_VARIABLE_COMPARE_SUFFIX = "_varCompare"; + + private static final String LF = System.getProperty("line.separator"); + private static SLContext slContext; + + static class InstrumentTestCase { + protected final Description name; + protected final Path path; + protected final String baseName; + protected final String sourceName; + protected final String testInput; + protected final String expectedOutput; + protected String actualOutput; + + protected InstrumentTestCase(Class testClass, String baseName, String sourceName, Path path, String testInput, String expectedOutput) { + this.name = Description.createTestDescription(testClass, baseName); + this.baseName = baseName; + this.sourceName = sourceName; + this.path = path; + this.testInput = testInput; + this.expectedOutput = expectedOutput; + } + } + + private final List testCases; + + public SLInstrumentTestRunner(Class testClass) throws InitializationError { + super(testClass); + try { + testCases = createTests(testClass); + } catch (IOException e) { + throw new InitializationError(e); + } + } + + @Override + protected List getChildren() { + return testCases; + } + + @Override + protected Description describeChild(InstrumentTestCase child) { + return child.name; + } + + /** + * Tests are created based on the files that exist in the directory specified in the passed in + * annotation. Each test must have a source file and an expected output file. Optionally, each + * test can also include an input file. Source files have an ".sl" extension. Expected output + * have a ".output" extension. Input files have an ".input" extension. All these files must + * share the same base name to be correctly grouped. For example: "test1_assnCount.sl", + * "test1_assnCount.output" and "test1_assnCount.input" would all be used to create a single + * test called "test1_assnCount". + * + * This method iterates over the files in the directory and creates a new InstrumentTestCase for + * each group of related files. Each file is also expected to end with an identified at the end + * of the base name to indicate what visitor needs to be attached. Currently, visitors are hard + * coded to work on specific lines, so the code here is not currently generalizable. + * + * @param c The annotation containing the directory with tests + * @return A list of {@link InstrumentTestCase}s to run. + * @throws IOException If the directory is invalid. + * @throws InitializationError If no directory is provided. + * + * @see #runChild(InstrumentTestCase, RunNotifier) + */ + protected static List createTests(final Class c) throws IOException, InitializationError { + SLInstrumentTestSuite suite = c.getAnnotation(SLInstrumentTestSuite.class); + if (suite == null) { + throw new InitializationError(String.format("@%s annotation required on class '%s' to run with '%s'.", SLTestSuite.class.getSimpleName(), c.getName(), SLTestRunner.class.getSimpleName())); + } + + String[] paths = suite.value(); + + Path root = null; + for (String path : paths) { + root = FileSystems.getDefault().getPath(path); + if (Files.exists(root)) { + break; + } + } + if (root == null && paths.length > 0) { + throw new FileNotFoundException(paths[0]); + } + + final Path rootPath = root; + + final List testCases = new ArrayList<>(); + + // Scaffolding in place for future automation + Files.walkFileTree(rootPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException { + String sourceName = sourceFile.getFileName().toString(); + if (sourceName.endsWith(SOURCE_SUFFIX)) { + String baseName = sourceName.substring(0, sourceName.length() - SOURCE_SUFFIX.length()); + + Path inputFile = sourceFile.resolveSibling(baseName + INPUT_SUFFIX); + String testInput = ""; + if (Files.exists(inputFile)) { + testInput = readAllLines(inputFile); + } + + Path outputFile = sourceFile.resolveSibling(baseName + OUTPUT_SUFFIX); + String expectedOutput = ""; + if (Files.exists(outputFile)) { + expectedOutput = readAllLines(outputFile); + } + + testCases.add(new InstrumentTestCase(c, baseName, sourceName, sourceFile, testInput, expectedOutput)); + + } + return FileVisitResult.CONTINUE; + } + }); + + return testCases; + } + + private static String readAllLines(Path file) throws IOException { + // fix line feeds for non unix os + StringBuilder outFile = new StringBuilder(); + for (String line : Files.readAllLines(file, Charset.defaultCharset())) { + outFile.append(line).append(LF); + } + return outFile.toString(); + } + + /** + * Executes the passed in test case. Instrumentation is added according to the name of the file + * as explained in {@link #createTests(Class)}. Note that this code is not generalizable. + */ + @Override + protected void runChild(InstrumentTestCase testCase, RunNotifier notifier) { + // TODO Current tests are hard-coded, automate this eventually + notifier.fireTestStarted(testCase.name); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintStream printer = new PrintStream(out); + try { + // We use the name of the file to determine what visitor to attach to it. + if (testCase.baseName.endsWith(VISITOR_ASSIGNMENT_COUNT_SUFFIX) || testCase.baseName.endsWith(VISITOR_VARIABLE_COMPARE_SUFFIX)) { + NodeVisitor nodeVisitor = null; + slContext = new SLContext(new BufferedReader(new StringReader(testCase.testInput)), printer); + final Source source = Source.fromText(readAllLines(testCase.path), testCase.sourceName); + SLASTProber prober = new SLASTProber(); + + // Note that the visitor looks for an attachment point via line number + if (testCase.baseName.endsWith(VISITOR_ASSIGNMENT_COUNT_SUFFIX)) { + nodeVisitor = new NodeVisitor() { + + public boolean visit(Node node) { + if (node instanceof SLExpressionWrapper) { + SLExpressionWrapper wrapper = (SLExpressionWrapper) node; + int lineNum = wrapper.getSourceSection().getLineLocation().getLineNumber(); + + if (lineNum == 4) { + wrapper.getProbe().addInstrument(new SLPrintAssigmentValueInstrument(slContext.getOutput())); + } + } + return true; + } + }; + + // Note that the visitor looks for an attachment point via line number + } else if (testCase.baseName.endsWith(VISITOR_VARIABLE_COMPARE_SUFFIX)) { + nodeVisitor = new NodeVisitor() { + + public boolean visit(Node node) { + if (node instanceof SLStatementWrapper) { + SLStatementWrapper wrapper = (SLStatementWrapper) node; + int lineNum = wrapper.getSourceSection().getLineLocation().getLineNumber(); + + if (lineNum == 6) { + wrapper.getProbe().addInstrument(new SLCheckVariableEqualityInstrument("i", "count", slContext.getOutput())); + } + } + return true; + } + }; + } + + prober.addNodeProber(new SLInstrumentTestNodeProber(slContext)); + Parser.parseSL(slContext, source, prober); + List functionList = slContext.getFunctionRegistry().getFunctions(); + + // Since only functions can be global in SL, this guarantees that we instrument + // everything of interest. Parsing must occur before accepting the visitors since + // parsing is what creates our instrumentation points. + for (SLFunction function : functionList) { + RootCallTarget rootCallTarget = function.getCallTarget(); + rootCallTarget.getRootNode().accept(nodeVisitor); + } + + SLFunction main = slContext.getFunctionRegistry().lookup("main"); + main.getCallTarget().call(); + } else { + notifier.fireTestFailure(new Failure(testCase.name, new UnsupportedOperationException("No instrumentation found."))); + } + + String actualOutput = new String(out.toByteArray()); + Assert.assertEquals(testCase.expectedOutput, actualOutput); + } catch (Throwable ex) { + notifier.fireTestFailure(new Failure(testCase.name, ex)); + } finally { + notifier.fireTestFinished(testCase.name); + } + + } + + public static void runInMain(Class testClass, String[] args) throws InitializationError, NoTestsRemainException { + JUnitCore core = new JUnitCore(); + core.addListener(new TextListener(System.out)); + SLTestRunner suite = new SLTestRunner(testClass); + if (args.length > 0) { + suite.filter(new NameFilter(args[0])); + } + Result r = core.run(suite); + if (!r.wasSuccessful()) { + System.exit(1); + } + } + + private static final class NameFilter extends Filter { + private final String pattern; + + private NameFilter(String pattern) { + this.pattern = pattern.toLowerCase(); + } + + @Override + public boolean shouldRun(Description description) { + return description.getMethodName().toLowerCase().contains(pattern); + } + + @Override + public String describe() { + return "Filter contains " + pattern; + } + } +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestSuite.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLInstrumentTestSuite.java Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.sl.test.instrument; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SLInstrumentTestSuite { + + /** + * Defines the base path of the test suite. Multiple base paths can be specified. However only + * the first base that exists is used to lookup the test cases. + */ + String[] value(); + +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLPrintAssigmentValueInstrument.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLPrintAssigmentValueInstrument.java Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.sl.test.instrument; + +import java.io.*; + +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.sl.nodes.local.*; + +/** + * This sample instrument provides prints the value of an assignment (after the assignment is + * complete) to the {@link PrintStream} specified in the constructor. This instrument can only be + * attached to a wrapped {@link SLWriteLocalVariableNode}, but provides no guards to protect it from + * being attached elsewhere. + */ +public final class SLPrintAssigmentValueInstrument extends Instrument { + + private PrintStream output; + + public SLPrintAssigmentValueInstrument(PrintStream output) { + this.output = output; + } + + @Override + public void leave(Node astNode, VirtualFrame frame, Object result) { + output.println(result); + } +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLSimpleInstrumentTestSuite.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/src/com/oracle/truffle/sl/test/instrument/SLSimpleInstrumentTestSuite.java Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.sl.test.instrument; + +import org.junit.*; +import org.junit.runner.*; + +@RunWith(SLInstrumentTestRunner.class) +@SLInstrumentTestSuite({"graal/com.oracle.truffle.sl.test/tests_instrumentation", "tests_instrumentation"}) +public class SLSimpleInstrumentTestSuite { + + public static void main(String[] args) throws Exception { + SLInstrumentTestRunner.runInMain(SLSimpleInstrumentTestSuite.class, args); + } + + /* + * Our "mx unittest" command looks for methods that are annotated with @Test. By just defining + * an empty method, this class gets included and the test suite is properly executed. + */ + @Test + public void unittest() { + } +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_assnCount.output --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_assnCount.output Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,101 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +100 diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_assnCount.sl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_assnCount.sl Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,12 @@ +function loop(count) { + i = 0; + while (i < count) { + i = i + 1; + } + return i; +} + +function main() { + count = loop(100); + println(count); +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_varCompare.output --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_varCompare.output Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,2 @@ +true +100 diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_varCompare.sl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.sl.test/tests_instrumentation/Instrumentation_varCompare.sl Tue Aug 05 16:34:08 2014 -0700 @@ -0,0 +1,12 @@ +function loop(count) { + i = 0; + while (i < count) { + i = i + 1; + } + return i; +} + +function main() { + count = loop(100); + println(count); +} diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/parser/SLNodeFactory.java --- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/parser/SLNodeFactory.java Tue Aug 05 22:00:12 2014 +0200 +++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/parser/SLNodeFactory.java Tue Aug 05 16:34:08 2014 -0700 @@ -155,40 +155,127 @@ } } + /** + * Returns a {@link SLBreakNode} for the given token. This node will be instrumented, tagged as + * a statement and wrapped in an {@link SLStatementWrapper} if an {@link SLASTProber} was + * initialized in this class. ({@link #prober} != null) + * + * @param breakToken The token containing the break node's info + * @return either: + *
    + *
  • An un-instrumented SLBreakNode if there is no prober
  • + *
  • An {@link SLStatementWrapper} instrumenting this node.
  • + *
+ */ public SLStatementNode createBreak(Token breakToken) { - return new SLBreakNode(srcFromToken(breakToken)); + final SLBreakNode breakNode = new SLBreakNode(srcFromToken(breakToken)); + if (prober != null) { + return prober.probeAsStatement(breakNode); + } + return breakNode; } + /** + * Returns a {@link SLContinueNode} for the given token. This node will be instrumented, tagged + * as a statement and wrapped in an {@link SLStatementWrapper} if an {@link SLASTProber} was + * initialized in this class. ({@link #prober} != null) + * + * @param continueToken The token containing the continue node's info + * @return either: + *
    + *
  • An un-instrumented SLContinueNode if there is no prober
  • + *
  • An {@link SLStatementWrapper} instrumenting this node.
  • + *
+ */ public SLStatementNode createContinue(Token continueToken) { - return new SLContinueNode(srcFromToken(continueToken)); + final SLContinueNode continueNode = new SLContinueNode(srcFromToken(continueToken)); + if (prober != null) { + return prober.probeAsStatement(continueNode); + } + return continueNode; } + /** + * Returns a {@link SLWhileNode} for the given token. This node will be instrumented, tagged as + * a statement and wrapped in an {@link SLStatementWrapper} if an {@link SLASTProber} was + * initialized in this class. ({@link #prober} != null) + * + * @param whileToken The token containing the while node's info + * @param conditionNode The conditional node for this while loop + * @param bodyNode The body of the while loop + * @return either: + *
    + *
  • An un-instrumented SLWhileNode if there is no prober
  • + *
  • An {@link SLStatementWrapper} instrumenting this node.
  • + *
+ */ public SLStatementNode createWhile(Token whileToken, SLExpressionNode conditionNode, SLStatementNode bodyNode) { final int start = whileToken.charPos; final int end = bodyNode.getSourceSection().getCharEndIndex(); - return new SLWhileNode(source.createSection(whileToken.val, start, end - start), conditionNode, bodyNode); + final SLWhileNode whileNode = new SLWhileNode(source.createSection(whileToken.val, start, end - start), conditionNode, bodyNode); + if (prober != null) { + return prober.probeAsStatement(whileNode); + } + return whileNode; } + /** + * Returns a {@link SLIfNode} for the given token. This node will be instrumented, tagged as a + * statement and wrapped in an {@link SLStatementWrapper} if an {@link SLASTProber} was + * initialized in this class. ({@link #prober} != null) + * + * @param ifToken The token containing the if node's info + * @param conditionNode The condition node of this if statement + * @param thenPartNode The then part of the if + * @param elsePartNode The else part of the if + * @return either: + *
    + *
  • An un-instrumented SLIfNode if there is no prober
  • + *
  • An {@link SLStatementWrapper} instrumenting this node.
  • + *
+ */ public SLStatementNode createIf(Token ifToken, SLExpressionNode conditionNode, SLStatementNode thenPartNode, SLStatementNode elsePartNode) { final int start = ifToken.charPos; final int end = elsePartNode == null ? thenPartNode.getSourceSection().getCharEndIndex() : elsePartNode.getSourceSection().getCharEndIndex(); - - // if (prober != null) { - // SLStatementNode wrappedThenNode = prober.probeAsStatement(thenPartNode); - // // SLStatementNode wrappedElseNode = prober.probeAsStatement(elsePartNode); - // return new SLIfNode(source.createSection(t.val, start, end - start), conditionNode, - // wrappedThenNode, elsePartNode); - // } - - return new SLIfNode(source.createSection(ifToken.val, start, end - start), conditionNode, thenPartNode, elsePartNode); + final SLIfNode ifNode = new SLIfNode(source.createSection(ifToken.val, start, end - start), conditionNode, thenPartNode, elsePartNode); + if (prober != null) { + return prober.probeAsStatement(ifNode); + } + return ifNode; } + /** + * Returns a {@link SLReturnNode} for the given token. This node will be instrumented, tagged as + * a statement and wrapped in an {@link SLStatementWrapper} if an {@link SLASTProber} was + * initialized in this class. ({@link #prober} != null) + * + * @param t The token containing the return node's info + * @param valueNode The value of the return + * @return either: + *
    + *
  • An un-instrumented SLReturnNode if there is no prober
  • + *
  • An {@link SLStatementWrapper} instrumenting this node.
  • + *
+ */ public SLStatementNode createReturn(Token t, SLExpressionNode valueNode) { final int start = t.charPos; final int length = valueNode == null ? t.val.length() : valueNode.getSourceSection().getCharEndIndex() - start; - return new SLReturnNode(source.createSection(t.val, start, length), valueNode); + final SLReturnNode returnNode = new SLReturnNode(source.createSection(t.val, start, length), valueNode); + if (prober != null) { + return prober.probeAsStatement(returnNode); + } + return returnNode; } + /** + * Returns the corresponding subclass of {@link SLExpressionNode} for binary expressions. + *
These nodes are currently not instrumented. + * + * @param opToken The operator of the binary expression + * @param leftNode The left node of the expression + * @param rightNode The right node of the expression + * @return A subclass of SLExpressionNode for the operation given by opToken. + */ public SLExpressionNode createBinary(Token opToken, SLExpressionNode leftNode, SLExpressionNode rightNode) { int start = leftNode.getSourceSection().getCharIndex(); int length = rightNode.getSourceSection().getCharEndIndex() - start; @@ -223,6 +310,20 @@ } } + /** + * Returns a {@link SLInvokeNode} for the given token. This node will be instrumented, tagged as + * a call and wrapped in an {@link SLExpressionWrapper} if an {@link SLASTProber} was + * initialized in this class. ({@link #prober} != null) + * + * @param nameToken The name of the function being called + * @param parameterNodes The parameters of the function call + * @param finalToken A token used to determine the end of the sourceSelection for this call + * @return either: + *
    + *
  • An un-instrumented SLInvokeNode if there is no prober
  • + *
  • An {@link SLExpressionWrapper} instrumenting this node.
  • + *
+ */ public SLExpressionNode createCall(Token nameToken, List parameterNodes, Token finalToken) { final int startPos = nameToken.charPos; final int endPos = finalToken.charPos + finalToken.val.length(); @@ -235,18 +336,44 @@ return SLInvokeNode.create(src, functionNode, parameterNodes.toArray(new SLExpressionNode[parameterNodes.size()])); } + /** + * Returns a {@link SLWriteLocalVariableNode} for the given token. This node will be + * instrumented, tagged as an assignment and wrapped in an {@link SLExpressionWrapper} if an + * {@link SLASTProber} was initialized in this class. ({@link #prober} != null) + * + * @param nameToken The name of the variable being assigned + * @param valueNode The value to be assigned + * @return either: + *
    + *
  • An un-instrumented SLWriteLocalVariableNode if there is no prober
  • + *
  • An {@link SLExpressionWrapper} instrumenting this node.
  • + *
+ */ public SLExpressionNode createAssignment(Token nameToken, SLExpressionNode valueNode) { FrameSlot frameSlot = frameDescriptor.findOrAddFrameSlot(nameToken.val); lexicalScope.locals.put(nameToken.val, frameSlot); final int start = nameToken.charPos; final int length = valueNode.getSourceSection().getCharEndIndex() - start; if (prober != null) { - final SLExpressionNode wrappedNode = prober.probeAsLocalAssignment(valueNode, nameToken.val); - return SLWriteLocalVariableNodeFactory.create(source.createSection("=", start, length), wrappedNode, frameSlot); + SLWriteLocalVariableNode writeNode = SLWriteLocalVariableNodeFactory.create(source.createSection("=", start, length), valueNode, frameSlot); + final SLExpressionNode wrappedNode = prober.probeAsLocalAssignment(writeNode, nameToken.val); + return wrappedNode; } return SLWriteLocalVariableNodeFactory.create(source.createSection("=", start, length), valueNode, frameSlot); } + /** + * Returns a {@link SLReadLocalVariableNode} if this read is a local variable or a + * {@link SLFunctionLiteralNode} if this read is global. In Simple, the only global names are + * functions.
There is currently no instrumentation for this node. + * + * @param nameToken The name of the variable/function being read + * @return either: + *
    + *
  • A SLReadLocalVariableNode representing the local variable being read.
  • + *
  • A SLFunctionLiteralNode representing the function definition
  • + *
+ */ public SLExpressionNode createRead(Token nameToken) { final FrameSlot frameSlot = lexicalScope.locals.get(nameToken.val); final SourceSection src = srcFromToken(nameToken); diff -r bd6b44b04143 -r 2a5ec181dad4 graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java --- a/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java Tue Aug 05 22:00:12 2014 +0200 +++ b/graal/com.oracle.truffle.sl/src/com/oracle/truffle/sl/runtime/SLContext.java Tue Aug 05 16:34:08 2014 -0700 @@ -138,6 +138,16 @@ getFunctionRegistry().register(name, rootNode); } + /** + * This function will parse the given source code, parse the code using the {@link Parser}, and + * then execute the function named main. To use this method with instrumentation, + * setASTNodeProber must have been already called. There is currently no guard to check if this + * is the case.
+ * Due to the experimental nature of the instrumentation framework, the parse that happens in + * this method will remove any previously added instrumentation. + * + * @param source The {@link Source} to execute. + */ public void executeMain(Source source) { if (sourceCallback != null) { @@ -157,8 +167,12 @@ main.getCallTarget().call(); } + /** + * Sets the {@link SLASTProber} for the executeMain method. + * + * @param astProber The prober to use for adding instrumentation for this context. + */ public void setASTNodeProber(SLASTProber astProber) { - // TODO Auto-generated method stub this.astProber = astProber; } }