/* * 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 java.io.IOException; import java.util.ArrayList; import java.util.List; import org.junit.Assert; import org.junit.Test; import com.oracle.truffle.api.instrumentation.LoadSourceEvent; import com.oracle.truffle.api.instrumentation.LoadSourceListener; import com.oracle.truffle.api.instrumentation.SourceSectionFilter; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.instrumentation.SourceSectionFilter.IndexRange; import com.oracle.truffle.api.instrumentation.TruffleInstrument.Registration; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; import static org.junit.Assert.assertTrue; import com.oracle.truffle.api.vm.PolyglotRuntime; public class SourceListenerTest extends AbstractInstrumentationTest { @Test public void testLoadSource1() throws IOException { testLoadSourceImpl(1); } @Test public void testLoadSource2() throws IOException { testLoadSourceImpl(2); } @Test public void testLoadSource3() throws IOException { testLoadSourceImpl(5); } private void testLoadSourceImpl(int runTimes) throws IOException { int initialQueryCount = InstrumentationTestLanguage.getRootSourceSectionQueryCount(); PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testLoadSource1"); Source source1 = lines("STATEMENT(EXPRESSION, EXPRESSION)"); // running the same source multiple times should not have any effect on the test result. for (int i = 0; i < runTimes; i++) { run(source1); } Assert.assertEquals("unexpected getSourceSection calls without source listeners", initialQueryCount, InstrumentationTestLanguage.getRootSourceSectionQueryCount()); TestLoadSource1 impl = instrument.lookup(TestLoadSource1.class); assertTrue("Lookup of registered service enables the instrument", instrument.isEnabled()); Source source2 = lines("ROOT(DEFINE(f1, STATEMENT(EXPRESSION)), DEFINE(f2, STATEMENT)," + "BLOCK(CALL(f1), CALL(f2)))"); for (int i = 0; i < runTimes; i++) { run(source2); } Assert.assertNotEquals("expecting getSourceSection calls because of source listeners", initialQueryCount, InstrumentationTestLanguage.getRootSourceSectionQueryCount()); assertEvents(impl.onlyNewEvents, source2); assertEvents(impl.allEvents, source1, source2); instrument.setEnabled(false); Source source3 = lines("STATEMENT(EXPRESSION, EXPRESSION, EXPRESSION)"); for (int i = 0; i < runTimes; i++) { run(source3); } assertEvents(impl.onlyNewEvents, source2); assertEvents(impl.allEvents, source1, source2); instrument.setEnabled(true); // new instrument needs update impl = instrument.lookup(TestLoadSource1.class); assertEvents(impl.onlyNewEvents); assertEvents(impl.allEvents, source1, source2, source3); } private static void assertEvents(List<Source> actualSources, Source... expectedSources) { Assert.assertEquals(expectedSources.length, actualSources.size()); for (int i = 0; i < expectedSources.length; i++) { Assert.assertSame("index " + i, expectedSources[i], actualSources.get(i)); } } @Registration(id = "testLoadSource1", services = SourceListenerTest.TestLoadSource1.class) public static class TestLoadSource1 extends TruffleInstrument { List<Source> onlyNewEvents = new ArrayList<>(); List<Source> allEvents = new ArrayList<>(); @Override protected void onCreate(Env env) { env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.ANY, new LoadSourceListener() { public void onLoad(LoadSourceEvent event) { onlyNewEvents.add(event.getSource()); } }, false); env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.ANY, new LoadSourceListener() { public void onLoad(LoadSourceEvent event) { allEvents.add(event.getSource()); } }, true); env.registerService(this); } } @Test public void testLoadSourceException() throws IOException { engine.getRuntime().getInstruments().get("testLoadSourceException").setEnabled(true); run(""); Assert.assertTrue(getErr().contains("TestLoadSourceExceptionClass")); } private static class TestLoadSourceExceptionClass extends RuntimeException { private static final long serialVersionUID = 1L; } @Registration(id = "testLoadSourceException") public static class TestLoadSourceException extends TruffleInstrument { @Override protected void onCreate(Env env) { env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.ANY, new LoadSourceListener() { public void onLoad(LoadSourceEvent source) { throw new TestLoadSourceExceptionClass(); } }, true); } @Override protected void onDispose(Env env) { } } @Test public void testAllowOnlySourceQueries() throws IOException { PolyglotRuntime.Instrument instrument = engine.getRuntime().getInstruments().get("testAllowOnlySourceQueries"); instrument.setEnabled(true); Source source = lines(""); run(source); TestAllowOnlySourceQueries impl = instrument.lookup(TestAllowOnlySourceQueries.class); Assert.assertTrue(impl.success); } @Registration(id = "testAllowOnlySourceQueries") public static class TestAllowOnlySourceQueries extends TruffleInstrument { boolean success; @Override protected void onCreate(Env env) { LoadSourceListener dummySourceListener = new LoadSourceListener() { public void onLoad(LoadSourceEvent source) { } }; env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().sourceIs(lines("")).build(), dummySourceListener, true); env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().sourceIs(lines("")).build(), dummySourceListener, true); env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().mimeTypeIs(InstrumentationTestLanguage.MIME_TYPE).build(), dummySourceListener, true); try { env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().indexIn(IndexRange.between(0, 1)).build(), dummySourceListener, true); throw new AssertionError(); } catch (IllegalArgumentException e) { } try { env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().indexNotIn(IndexRange.between(1, 2)).build(), dummySourceListener, true); } catch (IllegalArgumentException e) { } SourceSection unavailable = Source.newBuilder("").name("a").mimeType("").build().createUnavailableSection(); try { env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().sourceSectionEquals(unavailable).build(), dummySourceListener, true); } catch (IllegalArgumentException e) { } try { env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().rootSourceSectionEquals(unavailable).build(), dummySourceListener, true); } catch (IllegalArgumentException e) { } try { env.getInstrumenter().attachLoadSourceListener(SourceSectionFilter.newBuilder().lineIs(1).build(), dummySourceListener, true); } catch (IllegalArgumentException e) { } success = true; env.registerService(this); } @Override protected void onDispose(Env env) { } } }