/*
* Copyright (c) 2016, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.instrumentation.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Reader;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Registration;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.vm.PolyglotEngine;
import com.oracle.truffle.api.vm.PolyglotRuntime;
import com.oracle.truffle.api.vm.PolyglotRuntime.Instrument;
public class InstrumentationTest extends AbstractInstrumentationTest {
/*
* Test that metadata is properly propagated to Instrument handles.
*/
@Test
public void testMetadata() {
PolyglotRuntime.Instrument instrumentHandle1 = engine.getRuntime().getInstruments().get("testMetadataType1");
Assert.assertEquals("name", instrumentHandle1.getName());
Assert.assertEquals("version", instrumentHandle1.getVersion());
Assert.assertEquals("testMetadataType1", instrumentHandle1.getId());
Assert.assertFalse(instrumentHandle1.isEnabled());
}
@Registration(name = "name", version = "version", id = "testMetadataType1")
public static class MetadataInstrument extends TruffleInstrument {
@Override
protected void onCreate(Env env) {
}
}
@Registration(name = "name", version = "version", id = "testBrokenRegistration", services = Runnable.class)
public static class BrokenRegistrationInstrument extends TruffleInstrument {
@Override
protected void onCreate(Env env) {
}
}
@Test
public void forgetsToRegisterADeclaredService() throws Exception {
PolyglotRuntime.Instrument handle = engine.getRuntime().getInstruments().get("testBrokenRegistration");
assertNotNull(handle);
handle.setEnabled(true);
Runnable r = handle.lookup(Runnable.class);
assertNull("The service isn't there", r);
if (!err.toString().contains("declares service java.lang.Runnable but doesn't register it")) {
fail(err.toString());
}
}
@Registration(name = "name", version = "version", id = "beforeUse", services = Runnable.class)
public static class BeforeUseInstrument extends TruffleInstrument implements Runnable {
private Env env;
@Override
protected void onCreate(Env anEnv) {
this.env = anEnv;
this.env.registerService(this);
}
@Override
public void run() {
LanguageInfo info = env.getLanguages().get(InstrumentationTestLanguage.MIME_TYPE);
SpecialService ss = env.lookup(info, SpecialService.class);
assertNotNull("Service found", ss);
assertEquals("The right extension", ss.fileExtension(), InstrumentationTestLanguage.FILENAME_EXTENSION);
assertNull("Can't query object", env.lookup(info, Object.class));
assertNull("Can't query language", env.lookup(info, TruffleLanguage.class));
}
}
@Test
public void queryInstrumentsBeforeUseAndObtainSpecialService() throws Exception {
final PolyglotRuntime runtime = PolyglotRuntime.newBuilder().setErr(err).build();
Runnable start = null;
for (PolyglotRuntime.Instrument instr : runtime.getInstruments().values()) {
Runnable r = instr.lookup(Runnable.class);
if (r != null) {
start = r;
start.run();
assertTrue("Now enabled: " + instr, instr.isEnabled());
}
}
assertNotNull("At least one Runnable found", start);
}
@Test
public void queryInstrumentsAfterDisposeDoesnotEnable() throws Exception {
final PolyglotRuntime runtime = PolyglotRuntime.newBuilder().build();
runtime.dispose();
Runnable start = null;
for (PolyglotRuntime.Instrument instr : runtime.getInstruments().values()) {
assertFalse("Instrument is disabled", instr.isEnabled());
instr.setEnabled(true);
assertFalse("Instrument cannot be enabled", instr.isEnabled());
Runnable r = instr.lookup(Runnable.class);
if (r != null) {
start = r;
start.run();
assertTrue("Now enabled: " + instr, instr.isEnabled());
}
assertFalse("Instrument left disabled", instr.isEnabled());
}
assertNull("No Runnable found", start);
}
/*
* Test that metadata is properly propagated to Instrument handles.
*/
@Test
public void testDefaultId() {
PolyglotRuntime.Instrument descriptor1 = engine.getRuntime().getInstruments().get(MetadataInstrument2.class.getName());
Assert.assertEquals("", descriptor1.getName());
Assert.assertEquals("", descriptor1.getVersion());
Assert.assertEquals(MetadataInstrument2.class.getName(), descriptor1.getId());
Assert.assertFalse(descriptor1.isEnabled());
}
@Registration
public static class MetadataInstrument2 extends TruffleInstrument {
@Override
protected void onCreate(Env env) {
}
}
/*
* Test onCreate and onDispose invocations for multiple instrument instances.
*/
@Test
public void testMultipleInstruments() throws IOException {
run(""); // initialize
MultipleInstanceInstrument.onCreateCounter = 0;
MultipleInstanceInstrument.onDisposeCounter = 0;
MultipleInstanceInstrument.constructor = 0;
PolyglotRuntime.Instrument instrument1 = engine.getRuntime().getInstruments().get("testMultipleInstruments");
instrument1.setEnabled(true);
Assert.assertEquals(1, MultipleInstanceInstrument.constructor);
Assert.assertEquals(1, MultipleInstanceInstrument.onCreateCounter);
Assert.assertEquals(0, MultipleInstanceInstrument.onDisposeCounter);
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testMultipleInstruments");
instrument.setEnabled(true);
Assert.assertEquals(1, MultipleInstanceInstrument.constructor);
Assert.assertEquals(1, MultipleInstanceInstrument.onCreateCounter);
Assert.assertEquals(0, MultipleInstanceInstrument.onDisposeCounter);
instrument.setEnabled(false);
Assert.assertEquals(1, MultipleInstanceInstrument.constructor);
Assert.assertEquals(1, MultipleInstanceInstrument.onCreateCounter);
Assert.assertEquals(1, MultipleInstanceInstrument.onDisposeCounter);
instrument.setEnabled(true);
Assert.assertEquals(2, MultipleInstanceInstrument.constructor);
Assert.assertEquals(2, MultipleInstanceInstrument.onCreateCounter);
Assert.assertEquals(1, MultipleInstanceInstrument.onDisposeCounter);
instrument.setEnabled(false);
Assert.assertEquals(2, MultipleInstanceInstrument.constructor);
Assert.assertEquals(2, MultipleInstanceInstrument.onCreateCounter);
Assert.assertEquals(2, MultipleInstanceInstrument.onDisposeCounter);
}
@Registration(id = "testMultipleInstruments")
public static class MultipleInstanceInstrument extends TruffleInstrument {
private static int onCreateCounter = 0;
private static int onDisposeCounter = 0;
private static int constructor = 0;
public MultipleInstanceInstrument() {
constructor++;
}
@Override
protected void onCreate(Env env) {
onCreateCounter++;
}
@Override
protected void onDispose(Env env) {
onDisposeCounter++;
}
}
/*
* Test exceptions from language instrumentation are not wrapped into InstrumentationExceptions.
* Test that one language cannot instrument another.
*/
@Test
public void testLanguageInstrumentationAndExceptions() throws IOException {
TestLanguageInstrumentationLanguage.installInstrumentsCounter = 0;
TestLanguageInstrumentationLanguage.createContextCounter = 0;
try {
engine.eval(Source.newBuilder("ROOT(EXPRESSION)").name("unknown").mimeType("testLanguageInstrumentation").build());
Assert.fail("expected exception");
} catch (MyLanguageException e) {
// we assert that MyLanguageException is not wrapped
}
Assert.assertEquals(1, TestLanguageInstrumentationLanguage.installInstrumentsCounter);
Assert.assertEquals(1, TestLanguageInstrumentationLanguage.createContextCounter);
// this should run isolated from the language instrumentation.
run("STATEMENT");
}
@SuppressWarnings("serial")
private static class MyLanguageException extends RuntimeException {
}
@TruffleLanguage.Registration(name = "", version = "", mimeType = "testLanguageInstrumentation")
@ProvidedTags({InstrumentationTestLanguage.ExpressionNode.class, StandardTags.StatementTag.class})
public static class TestLanguageInstrumentationLanguage extends InstrumentationTestLanguage {
static int installInstrumentsCounter = 0;
static int createContextCounter = 0;
public TestLanguageInstrumentationLanguage() {
}
private static void installInstruments(Instrumenter instrumenter) {
installInstrumentsCounter++;
instrumenter.attachListener(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.EXPRESSION).build(), new ExecutionEventListener() {
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
public void onEnter(EventContext context, VirtualFrame frame) {
// since we are a language instrumentation we can throw exceptions
// without getting wrapped into Instrumentation exception.
throw new MyLanguageException();
}
});
instrumenter.attachListener(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.STATEMENT).build(), new ExecutionEventListener() {
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
throw new AssertionError();
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
throw new AssertionError();
}
public void onEnter(EventContext context, VirtualFrame frame) {
throw new AssertionError();
}
});
}
@Override
protected Context createContext(com.oracle.truffle.api.TruffleLanguage.Env env) {
createContextCounter++;
Instrumenter instrumenter = env.lookup(Instrumenter.class);
Assert.assertNotNull("Instrumenter found", instrumenter);
installInstruments(instrumenter);
return super.createContext(env);
}
@Override
protected CallTarget parse(ParsingRequest request) {
return Truffle.getRuntime().createCallTarget(new RootNode(this) {
@Child private BaseNode base = parse(request.getSource());
@Override
public Object execute(VirtualFrame frame) {
return base.execute(frame);
}
});
}
@Override
protected boolean isObjectOfLanguage(Object object) {
return false;
}
}
@Test
public void testInstrumentException1() {
engine.getRuntime().getInstruments().get("testInstrumentException1").setEnabled(true);
Assert.assertTrue(getErr().contains("MyLanguageException"));
}
@Registration(name = "", version = "", id = "testInstrumentException1")
public static class TestInstrumentException1 extends TruffleInstrument {
@Override
protected void onCreate(Env env) {
throw new MyLanguageException();
}
@Override
protected void onDispose(Env env) {
}
}
/*
* We test that instrumentation exceptions are wrapped, onReturnExceptional is invoked properly
* and not onReturnValue,
*/
@Test
public void testInstrumentException2() throws IOException {
TestInstrumentException2.returnedExceptional = 0;
TestInstrumentException2.returnedValue = 0;
engine.getRuntime().getInstruments().get("testInstrumentException2").setEnabled(true);
run("ROOT(EXPRESSION)");
Assert.assertTrue(getErr().contains("MyLanguageException"));
Assert.assertEquals(0, TestInstrumentException2.returnedExceptional);
Assert.assertEquals(1, TestInstrumentException2.returnedValue);
}
@Registration(name = "", version = "", id = "testInstrumentException2")
public static class TestInstrumentException2 extends TruffleInstrument {
static int returnedExceptional = 0;
static int returnedValue = 0;
@Override
protected void onCreate(Env env) {
env.getInstrumenter().attachListener(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.EXPRESSION).build(), new ExecutionEventListener() {
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
returnedValue++;
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
returnedExceptional++;
}
public void onEnter(EventContext context, VirtualFrame frame) {
throw new MyLanguageException();
}
});
}
@Override
protected void onDispose(Env env) {
}
}
/*
* Test that instrumentation exceptions in the onReturnExceptional are attached as suppressed
* exceptions.
*/
@Test
public void testInstrumentException3() throws IOException {
TestInstrumentException3.returnedExceptional = 0;
TestInstrumentException3.onEnter = 0;
engine.getRuntime().getInstruments().get("testInstrumentException3").setEnabled(true);
run("ROOT(EXPRESSION)");
Assert.assertTrue(getErr().contains("MyLanguageException"));
Assert.assertEquals(0, TestInstrumentException3.returnedExceptional);
Assert.assertEquals(1, TestInstrumentException3.onEnter);
}
@Registration(name = "", version = "", id = "testInstrumentException3")
public static class TestInstrumentException3 extends TruffleInstrument {
static int returnedExceptional = 0;
static int onEnter = 0;
@Override
protected void onCreate(Env env) {
env.getInstrumenter().attachListener(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.EXPRESSION).build(), new ExecutionEventListener() {
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
throw new MyLanguageException();
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
returnedExceptional++;
}
public void onEnter(EventContext context, VirtualFrame frame) {
onEnter++;
}
});
}
}
/*
* Test that event nodes are created lazily on first execution.
*/
@Test
public void testLazyProbe1() throws IOException {
TestLazyProbe1.createCalls = 0;
TestLazyProbe1.onEnter = 0;
TestLazyProbe1.onReturnValue = 0;
TestLazyProbe1.onReturnExceptional = 0;
engine.getRuntime().getInstruments().get("testLazyProbe1").setEnabled(true);
run("ROOT(DEFINE(foo, EXPRESSION))");
run("ROOT(DEFINE(bar, ROOT(EXPRESSION,EXPRESSION)))");
Assert.assertEquals(0, TestLazyProbe1.createCalls);
Assert.assertEquals(0, TestLazyProbe1.onEnter);
Assert.assertEquals(0, TestLazyProbe1.onReturnValue);
Assert.assertEquals(0, TestLazyProbe1.onReturnExceptional);
run("ROOT(CALL(foo))");
Assert.assertEquals(1, TestLazyProbe1.createCalls);
Assert.assertEquals(1, TestLazyProbe1.onEnter);
Assert.assertEquals(1, TestLazyProbe1.onReturnValue);
Assert.assertEquals(0, TestLazyProbe1.onReturnExceptional);
run("ROOT(CALL(bar))");
Assert.assertEquals(3, TestLazyProbe1.createCalls);
Assert.assertEquals(3, TestLazyProbe1.onEnter);
Assert.assertEquals(3, TestLazyProbe1.onReturnValue);
Assert.assertEquals(0, TestLazyProbe1.onReturnExceptional);
run("ROOT(CALL(bar))");
Assert.assertEquals(3, TestLazyProbe1.createCalls);
Assert.assertEquals(5, TestLazyProbe1.onEnter);
Assert.assertEquals(5, TestLazyProbe1.onReturnValue);
Assert.assertEquals(0, TestLazyProbe1.onReturnExceptional);
run("ROOT(CALL(foo))");
Assert.assertEquals(3, TestLazyProbe1.createCalls);
Assert.assertEquals(6, TestLazyProbe1.onEnter);
Assert.assertEquals(6, TestLazyProbe1.onReturnValue);
Assert.assertEquals(0, TestLazyProbe1.onReturnExceptional);
}
@Registration(name = "", version = "", id = "testLazyProbe1")
public static class TestLazyProbe1 extends TruffleInstrument {
static int createCalls = 0;
static int onEnter = 0;
static int onReturnValue = 0;
static int onReturnExceptional = 0;
@Override
protected void onCreate(Env env) {
env.getInstrumenter().attachFactory(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.EXPRESSION).build(), new ExecutionEventNodeFactory() {
public ExecutionEventNode create(EventContext context) {
createCalls++;
return new ExecutionEventNode() {
@Override
public void onReturnValue(VirtualFrame frame, Object result) {
onReturnValue++;
}
@Override
public void onReturnExceptional(VirtualFrame frame, Throwable exception) {
onReturnExceptional++;
}
@Override
public void onEnter(VirtualFrame frame) {
onEnter++;
}
};
}
});
}
}
/*
* Test that parsing and executing foreign languages work.
*/
@Test
public void testEnvParse1() throws IOException {
TestEnvParse1.onExpression = 0;
TestEnvParse1.onStatement = 0;
engine.getRuntime().getInstruments().get("testEnvParse1").setEnabled(true);
run("STATEMENT");
Assert.assertEquals(1, TestEnvParse1.onExpression);
Assert.assertEquals(1, TestEnvParse1.onStatement);
run("STATEMENT");
Assert.assertEquals(2, TestEnvParse1.onExpression);
Assert.assertEquals(2, TestEnvParse1.onStatement);
}
@Registration(name = "", version = "", id = "testEnvParse1")
public static class TestEnvParse1 extends TruffleInstrument {
static int onExpression = 0;
static int onStatement = 0;
@Override
protected void onCreate(final Env env) {
env.getInstrumenter().attachFactory(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.STATEMENT).build(), new ExecutionEventNodeFactory() {
public ExecutionEventNode create(EventContext context) {
final CallTarget target;
try {
target = env.parse(Source.newBuilder("EXPRESSION").name("unknown").mimeType(InstrumentationTestLanguage.MIME_TYPE).build());
} catch (Exception e) {
throw new AssertionError();
}
return new ExecutionEventNode() {
@Child private DirectCallNode directCall = Truffle.getRuntime().createDirectCallNode(target);
@Override
public void onEnter(VirtualFrame frame) {
onStatement++;
directCall.call(new Object[0]);
}
};
}
});
env.getInstrumenter().attachListener(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.EXPRESSION).build(), new ExecutionEventListener() {
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
public void onEnter(EventContext context, VirtualFrame frame) {
onExpression++;
}
});
}
}
/*
* Test that parsing and executing foreign languages with context work.
*/
@Test
public void testEnvParse2() throws IOException {
TestEnvParse2.onExpression = 0;
TestEnvParse2.onStatement = 0;
engine.getRuntime().getInstruments().get("testEnvParse2").setEnabled(true);
run("STATEMENT");
Assert.assertEquals(1, TestEnvParse2.onExpression);
Assert.assertEquals(1, TestEnvParse2.onStatement);
run("STATEMENT");
Assert.assertEquals(2, TestEnvParse2.onExpression);
Assert.assertEquals(2, TestEnvParse2.onStatement);
}
@Registration(name = "", version = "", id = "testEnvParse2")
public static class TestEnvParse2 extends TruffleInstrument {
static int onExpression = 0;
static int onStatement = 0;
@Override
protected void onCreate(final Env env) {
env.getInstrumenter().attachFactory(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.STATEMENT).build(), new ExecutionEventNodeFactory() {
public ExecutionEventNode create(EventContext context) {
final CallTarget target;
try {
target = context.parseInContext(Source.newBuilder("EXPRESSION").name("unknown").mimeType(InstrumentationTestLanguage.MIME_TYPE).build());
} catch (IOException e) {
throw new AssertionError();
}
return new ExecutionEventNode() {
@Child private DirectCallNode directCall = Truffle.getRuntime().createDirectCallNode(target);
@Override
public void onEnter(VirtualFrame frame) {
onStatement++;
directCall.call(new Object[0]);
}
};
}
});
env.getInstrumenter().attachListener(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.EXPRESSION).build(), new ExecutionEventListener() {
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
public void onEnter(EventContext context, VirtualFrame frame) {
onExpression++;
}
});
}
}
/*
* Test instrument all with any filter. Ensure that root nodes are not tried to be instrumented.
*/
@Test
public void testInstrumentAll() throws IOException {
TestInstrumentAll1.onStatement = 0;
engine.getRuntime().getInstruments().get("testInstrumentAll").setEnabled(true);
run("STATEMENT");
Assert.assertEquals(1, TestInstrumentAll1.onStatement);
}
@Registration(id = "testInstrumentAll")
public static class TestInstrumentAll1 extends TruffleInstrument {
static int onStatement = 0;
@Override
protected void onCreate(final Env env) {
env.getInstrumenter().attachListener(SourceSectionFilter.ANY, new ExecutionEventListener() {
public void onEnter(EventContext context, VirtualFrame frame) {
onStatement++;
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
});
}
}
/*
* Define is not instrumentable but has a source section.
*/
@Test
public void testInstrumentNonInstrumentable() throws IOException {
TestInstrumentNonInstrumentable1.onStatement = 0;
engine.getRuntime().getInstruments().get("testInstrumentNonInstrumentable").setEnabled(true);
run("DEFINE(foo, ROOT())");
Assert.assertEquals(0, TestInstrumentNonInstrumentable1.onStatement);
}
@Registration(id = "testInstrumentNonInstrumentable")
public static class TestInstrumentNonInstrumentable1 extends TruffleInstrument {
static int onStatement = 0;
@Override
protected void onCreate(final Env env) {
env.getInstrumenter().attachListener(SourceSectionFilter.ANY, new ExecutionEventListener() {
public void onEnter(EventContext context, VirtualFrame frame) {
onStatement++;
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
});
}
}
@Test
public void testOutputConsumer() throws IOException {
// print without instruments
String rout = run("PRINT(OUT, InitialToStdOut)");
Assert.assertEquals("InitialToStdOut", rout);
run("PRINT(ERR, InitialToStdErr)");
Assert.assertEquals("InitialToStdErr", err.toString());
err.reset();
// turn instruments on
engine.getRuntime().getInstruments().get("testOutputConsumerArray").setEnabled(true);
engine.getRuntime().getInstruments().get("testOutputConsumerPiped").setEnabled(true);
engine.eval(lines("PRINT(OUT, OutputToStdOut)"));
engine.eval(lines("PRINT(ERR, OutputToStdErr)"));
// test that the output goes eveywhere
Assert.assertEquals("OutputToStdOut", getOut());
Assert.assertEquals("OutputToStdOut", TestOutputConsumerArray.getOut());
Assert.assertEquals("OutputToStdErr", getErr());
Assert.assertEquals("OutputToStdErr", TestOutputConsumerArray.getErr());
CharBuffer buff = CharBuffer.allocate(100);
TestOutputConsumerPiped.fromOut.read(buff);
buff.flip();
Assert.assertEquals("OutputToStdOut", buff.toString());
buff.rewind();
TestOutputConsumerPiped.fromErr.read(buff);
buff.flip();
Assert.assertEquals("OutputToStdErr", buff.toString());
buff.rewind();
// close piped err stream and test that print still works
TestOutputConsumerPiped.fromErr.close();
engine.eval(lines("PRINT(OUT, MoreOutputToStdOut)"));
engine.eval(lines("PRINT(ERR, MoreOutputToStdErr)"));
Assert.assertEquals("OutputToStdOutMoreOutputToStdOut", out.toString());
Assert.assertEquals("OutputToStdOutMoreOutputToStdOut", TestOutputConsumerArray.getOut());
String errorMsg = "java.lang.Exception: Output operation write(B[II) failed for java.io.PipedOutputStream";
Assert.assertTrue(err.toString(), err.toString().startsWith("OutputToStdErr" + errorMsg));
Assert.assertTrue(err.toString(), err.toString().endsWith("MoreOutputToStdErr"));
Assert.assertEquals("OutputToStdErrMoreOutputToStdErr", TestOutputConsumerArray.getErr());
buff.limit(buff.capacity());
TestOutputConsumerPiped.fromOut.read(buff);
buff.flip();
Assert.assertEquals("MoreOutputToStdOut", buff.toString());
out.reset();
err.reset();
// the I/O error is not printed again
engine.eval(lines("PRINT(ERR, EvenMoreOutputToStdErr)"));
Assert.assertEquals("EvenMoreOutputToStdErr", err.toString());
Assert.assertEquals("OutputToStdErrMoreOutputToStdErrEvenMoreOutputToStdErr", TestOutputConsumerArray.getErr());
// instruments disabled
engine.getRuntime().getInstruments().get("testOutputConsumerArray").setEnabled(false);
engine.getRuntime().getInstruments().get("testOutputConsumerPiped").setEnabled(false);
out.reset();
err.reset();
engine.eval(lines("PRINT(OUT, FinalOutputToStdOut)"));
engine.eval(lines("PRINT(ERR, FinalOutputToStdErr)"));
Assert.assertEquals("FinalOutputToStdOut", out.toString());
Assert.assertEquals("FinalOutputToStdErr", err.toString());
// nothing more printed to the disabled instrument
Assert.assertEquals("OutputToStdOutMoreOutputToStdOut", TestOutputConsumerArray.getOut());
Assert.assertEquals("OutputToStdErrMoreOutputToStdErrEvenMoreOutputToStdErr", TestOutputConsumerArray.getErr());
}
@Registration(id = "testOutputConsumerArray")
public static class TestOutputConsumerArray extends TruffleInstrument {
static ByteArrayOutputStream out = new ByteArrayOutputStream();
static ByteArrayOutputStream err = new ByteArrayOutputStream();
@Override
protected void onCreate(Env env) {
env.getInstrumenter().attachOutConsumer(out);
env.getInstrumenter().attachErrConsumer(err);
}
static String getOut() {
return new String(out.toByteArray());
}
static String getErr() {
return new String(err.toByteArray());
}
}
@Registration(id = "testOutputConsumerPiped")
public static class TestOutputConsumerPiped extends TruffleInstrument {
static PipedOutputStream out = new PipedOutputStream();
static Reader fromOut;
static PipedOutputStream err = new PipedOutputStream();
static Reader fromErr;
public TestOutputConsumerPiped() throws IOException {
fromOut = new InputStreamReader(new PipedInputStream(out));
fromErr = new InputStreamReader(new PipedInputStream(err));
}
@Override
protected void onCreate(Env env) {
env.getInstrumenter().attachOutConsumer(out);
env.getInstrumenter().attachErrConsumer(err);
}
Reader fromOut() {
return fromOut;
}
Reader fromErr() {
return fromErr;
}
}
/*
* Tests for debugger or any other clients that cancel execution while halted
*/
@Test
public void testKillExceptionOnEnter() throws IOException {
engine.getRuntime().getInstruments().get("testKillQuitException").setEnabled(true);
TestKillQuitException.exceptionOnEnter = new MyKillException();
TestKillQuitException.exceptionOnReturnValue = null;
TestKillQuitException.returnExceptionalCount = 0;
try {
run("STATEMENT");
Assert.fail("KillException in onEnter() cancels engine execution");
} catch (MyKillException ex) {
}
Assert.assertEquals("KillException is not an execution event", 0, TestKillQuitException.returnExceptionalCount);
}
@Test
public void testKillExceptionOnReturnValue() throws IOException {
engine.getRuntime().getInstruments().get("testKillQuitException").setEnabled(true);
TestKillQuitException.exceptionOnEnter = null;
TestKillQuitException.exceptionOnReturnValue = new MyKillException();
TestKillQuitException.returnExceptionalCount = 0;
try {
run("STATEMENT");
Assert.fail("KillException in onReturnValue() cancels engine execution");
} catch (MyKillException ex) {
}
Assert.assertEquals("KillException is not an execution event", 0, TestKillQuitException.returnExceptionalCount);
}
@Registration(id = "testKillQuitException")
public static class TestKillQuitException extends TruffleInstrument {
static Error exceptionOnEnter = null;
static Error exceptionOnReturnValue = null;
static int returnExceptionalCount = 0;
@Override
protected void onCreate(final Env env) {
env.getInstrumenter().attachListener(SourceSectionFilter.ANY, new ExecutionEventListener() {
public void onEnter(EventContext context, VirtualFrame frame) {
if (exceptionOnEnter != null) {
throw exceptionOnEnter;
}
}
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
if (exceptionOnReturnValue != null) {
throw exceptionOnReturnValue;
}
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
returnExceptionalCount++;
}
});
}
}
/*
* Use tags that are not declarded as required.
*/
@Test
public void testUsedTagNotRequired1() throws IOException {
TestInstrumentNonInstrumentable1.onStatement = 0;
engine.getRuntime().getInstruments().get("testUsedTagNotRequired1").setEnabled(true);
run("ROOT()");
Assert.assertEquals(0, TestInstrumentNonInstrumentable1.onStatement);
}
@Registration(id = "testUsedTagNotRequired1")
public static class TestUsedTagNotRequired1 extends TruffleInstrument {
private static class Foobar {
}
@Override
protected void onCreate(final Env env) {
try {
env.getInstrumenter().attachListener(SourceSectionFilter.newBuilder().tagIs(Foobar.class).build(), new ExecutionEventListener() {
public void onEnter(EventContext context, VirtualFrame frame) {
}
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
});
Assert.fail();
} catch (IllegalArgumentException e) {
Assert.assertEquals(
"The attached filter SourceSectionFilter[tag is one of [foobar0]] references the " +
"following tags [foobar0] which are not declared as required by the instrument. To fix " +
"this annotate the instrument class com.oracle.truffle.api.instrumentation." +
"InstrumentationTest$TestUsedTagNotRequired1 with @RequiredTags({foobar0}).",
e.getMessage());
}
}
}
/*
* Test behavior of queryTags when used with instruments
*/
@Test
public void testQueryTags1() throws IOException {
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testIsNodeTaggedWith1");
instrument.setEnabled(true);
Instrumenter instrumenter = instrument.lookup(Instrumenter.class);
TestIsNodeTaggedWith1.expressionNode = null;
TestIsNodeTaggedWith1.statementNode = null;
Assert.assertTrue(instrumenter.queryTags(new Node() {
}).isEmpty());
run("STATEMENT(EXPRESSION)");
assertTags(instrumenter.queryTags(TestIsNodeTaggedWith1.expressionNode), InstrumentationTestLanguage.EXPRESSION);
assertTags(instrumenter.queryTags(TestIsNodeTaggedWith1.statementNode), InstrumentationTestLanguage.STATEMENT);
try {
instrumenter.queryTags(null);
Assert.fail();
} catch (NullPointerException e) {
}
}
private static void assertTags(Set<Class<?>> tags, Class<?>... expectedTags) {
Assert.assertEquals(expectedTags.length, tags.size());
for (Class<?> clazz : expectedTags) {
Assert.assertTrue("Tag: " + clazz, tags.contains(clazz));
}
}
/*
* Test behavior of queryTags when used with languages
*/
@Test
public void testQueryTags2() throws IOException {
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testIsNodeTaggedWith1");
instrument.setEnabled(true);
TestIsNodeTaggedWith1.expressionNode = null;
TestIsNodeTaggedWith1.statementNode = null;
TestIsNodeTaggedWith1Language.instrumenter = null;
Source otherLanguageSource = Source.newBuilder("STATEMENT(EXPRESSION)").name("unknown").mimeType("testIsNodeTaggedWith1").build();
run(otherLanguageSource);
Instrumenter instrumenter = TestIsNodeTaggedWith1Language.instrumenter;
Node languageExpression = TestIsNodeTaggedWith1.expressionNode;
Node languageStatement = TestIsNodeTaggedWith1.statementNode;
assertTags(instrumenter.queryTags(languageExpression), InstrumentationTestLanguage.EXPRESSION);
assertTags(instrumenter.queryTags(languageStatement), InstrumentationTestLanguage.STATEMENT);
TestIsNodeTaggedWith1.expressionNode = null;
TestIsNodeTaggedWith1.statementNode = null;
run("EXPRESSION");
// fail if called with nodes from a different language
Node otherLanguageExpression = TestIsNodeTaggedWith1.expressionNode;
try {
instrumenter.queryTags(otherLanguageExpression);
Assert.fail();
} catch (IllegalArgumentException e) {
}
}
@Test
public void testInstrumentsWhenForked() throws IOException {
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testIsNodeTaggedWith1");
instrument.setEnabled(true);
TestIsNodeTaggedWith1 service = instrument.lookup(TestIsNodeTaggedWith1.class);
assertEquals(1, service.onCreateCalls);
Source otherLanguageSource = Source.newBuilder("STATEMENT(EXPRESSION)").name("unknown").mimeType("testIsNodeTaggedWith1").build();
run(otherLanguageSource);
PolyglotEngine forked = createEngine(langMimeType);
assertEquals(1, service.onCreateCalls);
final Map<String, ? extends PolyglotRuntime.Instrument> instruments = forked.getRuntime().getInstruments();
assertSame(instrument, instruments.get("testIsNodeTaggedWith1"));
assertSame(service, instruments.get("testIsNodeTaggedWith1").lookup(TestIsNodeTaggedWith1.class));
assertEquals(instruments.size(), engine.getRuntime().getInstruments().size());
for (String key : instruments.keySet()) {
assertSame(engine.getRuntime().getInstruments().get(key), instruments.get(key));
}
assertEquals(0, service.onDisposeCalls);
engine.dispose();
assertEquals(0, service.onDisposeCalls);
forked.dispose();
forked.getRuntime().dispose();
// dispose if all engines are disposed
assertEquals(1, service.onDisposeCalls);
engine = null; // avoid disposal in @After event
}
@TruffleLanguage.Registration(name = "", version = "", mimeType = "testIsNodeTaggedWith1")
@ProvidedTags({InstrumentationTestLanguage.ExpressionNode.class, StandardTags.StatementTag.class})
public static class TestIsNodeTaggedWith1Language extends InstrumentationTestLanguage {
static Instrumenter instrumenter;
public TestIsNodeTaggedWith1Language() {
}
@Override
protected Context createContext(com.oracle.truffle.api.TruffleLanguage.Env env) {
instrumenter = env.lookup(Instrumenter.class);
return super.createContext(env);
}
@Override
protected CallTarget parse(ParsingRequest request) {
return Truffle.getRuntime().createCallTarget(new RootNode(this) {
@Child private BaseNode base = parse(request.getSource());
@Override
public Object execute(VirtualFrame frame) {
return base.execute(frame);
}
});
}
@Override
protected boolean isObjectOfLanguage(Object object) {
return false;
}
}
@Registration(id = "testIsNodeTaggedWith1")
public static class TestIsNodeTaggedWith1 extends TruffleInstrument {
static Node expressionNode;
static Node statementNode;
int onCreateCalls = 0;
int onDisposeCalls = 0;
@Override
protected void onCreate(final Env env) {
onCreateCalls++;
env.registerService(this);
env.registerService(env.getInstrumenter());
env.getInstrumenter().attachFactory(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.EXPRESSION).build(), new ExecutionEventNodeFactory() {
public ExecutionEventNode create(EventContext context) {
expressionNode = context.getInstrumentedNode();
return new ExecutionEventNode() {
};
}
});
env.getInstrumenter().attachFactory(SourceSectionFilter.newBuilder().tagIs(InstrumentationTestLanguage.STATEMENT).build(), new ExecutionEventNodeFactory() {
public ExecutionEventNode create(EventContext context) {
statementNode = context.getInstrumentedNode();
return new ExecutionEventNode() {
};
}
});
}
@Override
protected void onDispose(Env env) {
onDisposeCalls++;
}
}
private void setupEngine(Source initSource, boolean runInitAfterExec) {
PolyglotEngine.Builder builder = PolyglotEngine.newBuilder();
builder.runtime(getRuntime());
builder.config(InstrumentationTestLanguage.MIME_TYPE, "initSource", initSource);
builder.config(InstrumentationTestLanguage.MIME_TYPE, "runInitAfterExec", runInitAfterExec);
engine = builder.build();
}
@Test
public void testAccessInstruments() {
Instrument instrument = engine.getRuntime().getInstruments().get("testAccessInstruments");
TestAccessInstruments access = instrument.lookup(TestAccessInstruments.class);
InstrumentInfo info = access.env.getInstruments().get("testAccessInstruments");
assertNotNull(info);
assertEquals("testAccessInstruments", info.getId());
assertEquals("name", info.getName());
assertEquals("version", info.getVersion());
try {
access.env.lookup(info, TestAccessInstruments.class);
fail();
} catch (IllegalArgumentException e) {
// expected
}
TestAccessInstrumentsOther.initializedCount = 0;
InstrumentInfo other = access.env.getInstruments().get("testAccessInstrumentsOther");
assertNotNull(other);
assertEquals("testAccessInstrumentsOther", other.getId());
assertEquals("otherName", other.getName());
assertEquals("otherVersion", other.getVersion());
assertEquals(0, TestAccessInstrumentsOther.initializedCount);
// invalid service, should not trigger onCreate
assertNull(access.env.lookup(other, Object.class));
assertEquals(0, TestAccessInstrumentsOther.initializedCount);
// valide service, should trigger onCreate
assertNotNull(access.env.lookup(other, TestAccessInstrumentsOther.class));
assertEquals(1, TestAccessInstrumentsOther.initializedCount);
}
@Test
public void testAccessLanguages() {
Instrument instrument = engine.getRuntime().getInstruments().get("testAccessInstruments");
TestAccessInstruments access = instrument.lookup(TestAccessInstruments.class);
LanguageInfo info = access.env.getLanguages().get(InstrumentationTestLanguage.MIME_TYPE);
assertNotNull(info);
assertTrue(info.getMimeTypes().contains(InstrumentationTestLanguage.MIME_TYPE));
assertEquals("InstrumentTestLang", info.getName());
assertEquals("2.0", info.getVersion());
assertNotNull(access.env.lookup(info, SpecialService.class));
assertEquals(InstrumentationTestLanguage.FILENAME_EXTENSION, access.env.lookup(info, SpecialService.class).fileExtension());
}
@Registration(id = "testAccessInstruments", name = "name", version = "version", services = TestAccessInstruments.class)
@SuppressWarnings("hiding")
public static class TestAccessInstruments extends TruffleInstrument {
Env env;
@Override
protected void onCreate(final Env env) {
this.env = env;
env.registerService(this);
}
@Override
protected void onDispose(Env env) {
}
}
@Registration(id = "testAccessInstrumentsOther", name = "otherName", version = "otherVersion", services = TestAccessInstrumentsOther.class)
public static class TestAccessInstrumentsOther extends TruffleInstrument {
static int initializedCount = 0;
@Override
protected void onCreate(final Env env) {
env.registerService(this);
initializedCount++;
}
@Override
protected void onDispose(Env env) {
}
}
public class ReturnLanguageEnv {
public static final String KEY = "envReturner";
public TruffleLanguage.Env env;
}
@Test
public void testAccessInstrumentFromLanguage() {
ReturnLanguageEnv envReturner = new ReturnLanguageEnv();
PolyglotEngine e = PolyglotEngine.newBuilder().setErr(err).config(InstrumentationTestLanguage.MIME_TYPE, ReturnLanguageEnv.KEY, envReturner).build();
e.eval(Source.newBuilder("").mimeType(InstrumentationTestLanguage.MIME_TYPE).name("").build());
assertNotNull(envReturner.env);
TruffleLanguage.Env env = envReturner.env;
LanguageInfo langInfo = env.getLanguages().get(InstrumentationTestLanguage.MIME_TYPE);
assertNotNull(langInfo);
assertTrue(langInfo.getMimeTypes().contains(InstrumentationTestLanguage.MIME_TYPE));
assertEquals("InstrumentTestLang", langInfo.getName());
assertEquals("2.0", langInfo.getVersion());
InstrumentInfo instrInfo = env.getInstruments().get("testAccessInstruments");
assertNotNull(instrInfo);
assertEquals("testAccessInstruments", instrInfo.getId());
assertEquals("name", instrInfo.getName());
assertEquals("version", instrInfo.getVersion());
assertNotNull(env.lookup(instrInfo, TestAccessInstruments.class));
assertNull(env.lookup(instrInfo, SpecialService.class));
try {
// cannot load services from current languages to avoid cycles.
env.lookup(langInfo, SpecialService.class);
fail();
} catch (Exception e1) {
// expected
}
}
@Test
public void testLanguageInitializedOrNot() throws Exception {
Source initSource = Source.newBuilder("STATEMENT(EXPRESSION, EXPRESSION)").name("<init>").mimeType(InstrumentationTestLanguage.MIME_TYPE).build();
setupEngine(initSource, false);
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testLangInitialized");
// Events during language initialization phase are included:
TestLangInitialized.initializationEvents = true;
instrument.setEnabled(true);
TestLangInitialized service = instrument.lookup(TestLangInitialized.class);
run("LOOP(2, STATEMENT())");
assertEquals("[StatementNode, false, ExpressionNode, false, ExpressionNode, false, LoopNode, true, StatementNode, true, StatementNode, true]", service.getEnteredNodes());
instrument.setEnabled(false);
}
@Test
public void testLanguageInitializedOnly() throws Exception {
Source initSource = Source.newBuilder("STATEMENT(EXPRESSION, EXPRESSION)").name("<init>").mimeType(InstrumentationTestLanguage.MIME_TYPE).build();
setupEngine(initSource, false);
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testLangInitialized");
// Events during language initialization phase are excluded:
TestLangInitialized.initializationEvents = false;
instrument.setEnabled(true);
TestLangInitialized service = instrument.lookup(TestLangInitialized.class);
run("LOOP(2, STATEMENT())");
assertEquals("[LoopNode, true, StatementNode, true, StatementNode, true]", service.getEnteredNodes());
instrument.setEnabled(false);
}
@Test
public void testLanguageInitializedOrNotAppend() throws Exception {
Source initSource = Source.newBuilder("STATEMENT(EXPRESSION, EXPRESSION)").name("<init>").mimeType(InstrumentationTestLanguage.MIME_TYPE).build();
setupEngine(initSource, true);
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testLangInitialized");
// Events during language initialization phase are prepended and appended:
TestLangInitialized.initializationEvents = true;
instrument.setEnabled(true);
TestLangInitialized service = instrument.lookup(TestLangInitialized.class);
run("LOOP(2, STATEMENT())");
assertEquals("[StatementNode, false, ExpressionNode, false, ExpressionNode, false, LoopNode, true, StatementNode, true, StatementNode, true, StatementNode, true, ExpressionNode, true, ExpressionNode, true]",
service.getEnteredNodes());
instrument.setEnabled(false);
}
@Test
public void testLanguageInitializedOnlyAppend() throws Exception {
Source initSource = Source.newBuilder("STATEMENT(EXPRESSION, EXPRESSION)").name("<init>").mimeType(InstrumentationTestLanguage.MIME_TYPE).build();
setupEngine(initSource, true);
PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testLangInitialized");
// Events during language initialization phase are excluded,
// but events from the same nodes used for initialization are appended:
TestLangInitialized.initializationEvents = false;
instrument.setEnabled(true);
TestLangInitialized service = instrument.lookup(TestLangInitialized.class);
run("LOOP(2, STATEMENT())");
assertEquals("[LoopNode, true, StatementNode, true, StatementNode, true, StatementNode, true, ExpressionNode, true, ExpressionNode, true]", service.getEnteredNodes());
instrument.setEnabled(false);
}
@Registration(id = "testLangInitialized")
public static class TestLangInitialized extends TruffleInstrument implements ExecutionEventListener {
static boolean initializationEvents;
private final List<String> enteredNodes = new ArrayList<>();
@Override
protected void onCreate(Env env) {
env.registerService(this);
env.getInstrumenter().attachListener(SourceSectionFilter.ANY, this);
}
@Override
public void onEnter(EventContext context, VirtualFrame frame) {
if (!initializationEvents && !context.isLanguageContextInitialized()) {
// Skipt language context initialization if initializationEvents is false
return;
}
enteredNodes.add(context.getInstrumentedNode().getClass().getSimpleName());
enteredNodes.add(Boolean.toString(context.isLanguageContextInitialized()));
}
@Override
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
@Override
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
String getEnteredNodes() {
return enteredNodes.toString();
}
}
private static final class MyKillException extends ThreadDeath {
static final long serialVersionUID = 1;
}
}