com.oracle.truffle.tck.TruffleRunner Maven / Gradle / Ivy
Show all versions of truffle-tck Show documentation
/*
* Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.tck;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.graalvm.polyglot.Context;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized.UseParametersRunnerFactory;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters;
import org.junit.runners.parameterized.ParametersRunnerFactory;
import org.junit.runners.parameterized.TestWithParameters;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.tck.TruffleRunner.Inject;
import com.oracle.truffle.tck.TruffleRunner.RunWithPolyglotRule;
import com.oracle.truffle.tck.TruffleRunner.Warmup;
import com.oracle.truffle.tck.TruffleTestInvoker.TruffleTestClass;
/**
* JUnit test runner for unit testing Truffle AST interpreters.
*
* A test using {@link TruffleRunner} consists of 2 parts, a Truffle AST to be tested, and a test
* method that drives the test, provides input argument values and validates the result.
*
*
Writing a test AST
*
* The Truffle AST to be tested is written as a {@link RootNode} subclass, for example:
*
* {@snippet file="com/oracle/truffle/tck/TruffleRunner.java"
* region="TruffleRunnerSnippets#TestExecuteNode"}
*
*
Writing a test method
*
* The test method is a normal method annotated with {@link Test}. It may have one or more arguments
* of type {@link CallTarget} that are annotated with {@link Inject}. The {@link Inject} annotation
* specifies a {@link RootNode} subclass that is the root of a test AST, and the
* {@link TruffleRunner} will create one {@link CallTarget} for each of these test ASTs. The test
* method can then execute the AST by calling the {@link CallTarget#call} method.
*
* Typically a test method will prepare some arguments, and then do a single call to
* {@link CallTarget#call}. Then it should verify the result by inspecting the return value and
* checking the expected side effects of the test code.
*
* {@snippet file="com/oracle/truffle/tck/TruffleRunner.java"
* region="TruffleRunnerSnippets#ExampleTest"}
*
*
Running a test in the polyglot engine
*
* If a test should be run in the context of a polyglot engine, {@link RunWithPolyglotRule} can be
* used.
*
* {@snippet file="com/oracle/truffle/tck/TruffleRunner.java"
* region="TruffleRunnerSnippets#RunWithPolyglotRule"}
*
* @see Warmup warmup iterations and compilation
* @see ParametersFactory parameterized Truffle AST tests
*
* @since 0.25
*/
public class TruffleRunner extends BlockJUnit4ClassRunner {
private static final TruffleTestInvoker, ?> truffleTestInvoker = TruffleTestInvoker.create();
/**
* A parameter annotated with {@link Inject} specifies the {@link RootNode} of the test AST.
*
* @see TruffleRunner
*
* @since 0.25
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Inject {
/**
* Defines the {@link RootNode root node} of the Truffle tree that should be tested.
*
* @since 0.25
*/
Class extends RootNode> value();
}
/**
* A test method can be annotated with {@link Warmup} to specify how many warmup iterations of a
* test should be done before the Truffle tree is compiled. If this annotation is missing, the
* default value of 3 is used.
*
* {@snippet file="com/oracle/truffle/tck/TruffleRunner.java"
* region="TruffleRunnerSnippets#warmupTest"}
*
* In this example, the test code will in total be run 6 times. The first 5 iterations are
* warmup. The {@link CallTarget#call} invocation will run in the interpreter, simply calling
* the {@link RootNode#execute} method. This allows the AST to specialize itself before it is
* compiled.
*
* After warmup, the resulting specialized AST is compiled, and in the final iteration the
* {@link CallTarget} represents the resulting compiled code.
*
* @see TruffleRunner
*
* @since 0.25
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Warmup {
/**
* The number of warmup iterations to run before a test is compiled.
*
* @since 0.25
*/
int value();
}
/**
* {@link ParametersRunnerFactory} for testing Truffle AST interpreters using
* {@link Parameterized} unit tests. To use the parameters for constructing the test AST, the
* test {@link RootNode} constructor may take the test class as single argument, or
* alternatively the test {@link RootNode} can be a non-static inner class of the test class.
*
* {@snippet file="com/oracle/truffle/tck/TruffleRunner.java"
* region="TruffleRunnerSnippets#ParameterizedTest"}
*
* @see TruffleRunner
*
* @since 0.25
*/
public static final class ParametersFactory implements ParametersRunnerFactory {
/**
* Should not be called directly. To use this class, annotate your test class with
* {@code @Parameterized.UseParametersRunnerFactory(TruffleRunner.ParametersFactory.class)}.
*
* @see TruffleRunner
*
* @since 0.25
*/
public ParametersFactory() {
}
/**
* Internal method used by the JUnit framework. Do not call directly.
*
* @since 0.25
*/
@Override
public Runner createRunnerForTestWithParameters(TestWithParameters test) throws InitializationError {
return new ParameterizedRunner(test);
}
}
/**
* JUnit rule to run the tests in the context of a polyglot engine. This can be used as a
* {@link ClassRule} or as a {@link Rule}.
*
* If used as {@link ClassRule}, a single context is created for all unit tests in this class,
* and all tests (and also other methods like {@link BeforeClass}, {@link Before} {@link After}
* and {@link AfterClass}) are executed in this context.
*
* If used as {@link Rule}, a new context is created for each unit test. The {@link Before} and
* {@link After} actions are also executed in this context. No context is available in the
* {@link BeforeClass} and {@link AfterClass} methods.
*
* @since 0.27
*/
public static final class RunWithPolyglotRule implements TestRule {
Context.Builder contextBuilder;
Context context = null;
TruffleLanguage> testLanguage = null;
Env testEnv = null;
/**
* @since 0.27
*/
public RunWithPolyglotRule() {
this(Context.newBuilder().allowAllAccess(true));
}
/**
* @param contextBuilder a custom context builder
* @since 19.0
*/
public RunWithPolyglotRule(Context.Builder contextBuilder) {
this.contextBuilder = contextBuilder;
}
/**
* Internal method used by the JUnit framework. Do not call directly.
*
* @since 0.27
*/
@Override
public Statement apply(Statement stmt, Description description) {
return TruffleTestInvoker.withTruffleContext(this, stmt);
}
/**
* Get the current {@link Context}. This should only be called from code that is executed by
* the {@link TruffleRunner}. In particular, this method can not be called from static
* initializers and constructors of test classes. Use {@link Before} or {@link BeforeClass}
* methods instead, or put the initialization code into the constructor of the
* {@link RootNode} of the test.
*
* @since 0.27
*/
public Context getPolyglotContext() {
assert context != null;
return context;
}
/**
* Get an environment to access the polyglot engine using interop. This can be used to run
* setup tasks, and to do mock interop access into the polyglot engine. This should only be
* called from code that is executed by the {@link TruffleRunner}. In particular, this
* method can not be called from static initializers and constructors of test classes. Use
* {@link Before} or {@link BeforeClass} methods instead, or put the initialization code
* into the constructor of the {@link RootNode} of the test.
*
* @since 0.27
*/
public Env getTruffleTestEnv() {
assert testEnv != null;
return testEnv;
}
/**
* Get an instance of the {@link TruffleLanguage} that this test is running under. Nodes
* that are used with {@link Inject} should pass this instance to their super constructor.
*
* @since 21.1
*/
public TruffleLanguage> getTestLanguage() {
assert testLanguage != null;
return testLanguage;
}
}
private static final class ParameterizedRunner extends BlockJUnit4ClassRunnerWithParameters {
ParameterizedRunner(TestWithParameters test) throws InitializationError {
super(new TestWithParameters(test.getName(), new TruffleTestClass(test.getTestClass().getJavaClass()), test.getParameters()));
}
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
Statement ret = truffleTestInvoker.createStatement(getTestClass().getJavaClass().getSimpleName() + "#" + testName(method), method, test);
if (ret == null) {
ret = super.methodInvoker(method, test);
}
return ret;
}
@Override
protected void validateTestMethods(List errors) {
TruffleTestInvoker.validateTestMethods(getTestClass(), errors);
}
}
/**
* Should not be called directly. To use this class, annotate your test class with
* {@code @RunWith(TruffleRunner.class)}.
*
* @see TruffleRunner
*
* @since 0.25
*/
public TruffleRunner(Class> klass) throws InitializationError {
super(new TruffleTestClass(klass));
}
/**
* Should not be called directly. To use this class, annotate your test class with
* {@code @RunWith(TruffleRunner.class)}.
*
* @see TruffleRunner
*
* @since 24.2
*/
public TruffleRunner(TestClass testClass) throws InitializationError {
super(new TruffleTestClass(testClass.getJavaClass()));
}
/**
* Internal method used by the JUnit framework. Do not call directly.
*
* @since 0.25
*/
@Override
protected final Statement methodInvoker(FrameworkMethod method, Object test) {
Statement ret = truffleTestInvoker.createStatement(getTestClass().getJavaClass().getSimpleName() + "#" + testName(method), method, test);
if (ret == null) {
ret = super.methodInvoker(method, test);
}
return ret;
}
/**
* Internal method used by the JUnit framework. Do not call directly.
*
* @since 0.25
*/
@Override
protected final void validateTestMethods(List errors) {
TruffleTestInvoker.validateTestMethods(getTestClass(), errors);
}
}
class TruffleRunnerSnippets {
@Rule RunWithPolyglotRule runWithPolyglot;
// Checkstyle: stop
// @start region="TruffleRunnerSnippets#TestExecuteNode"
public class TestExecuteNode extends RootNode {
@Child InteropLibrary interop;
public TestExecuteNode() {
super(runWithPolyglot.getTestLanguage());
interop = InteropLibrary.getFactory().createDispatched(5);
}
@Override
public Object execute(VirtualFrame frame) {
Object obj = frame.getArguments()[0];
try {
return interop.execute(obj);
} catch (InteropException ex) {
CompilerDirectives.transferToInterpreter();
Assert.fail(ex.getMessage());
return null;
}
}
}
// @end region = "TruffleRunnerSnippets#TestExecuteNode"
// Checkstyle: resume
private static TruffleObject prepareArgumentValue() {
return null;
}
private static Object expectedRetValue() {
return null;
}
// @start region = "TruffleRunnerSnippets#ExampleTest"
@RunWith(TruffleRunner.class)
public class ExampleTest {
@Test
public void executeTest(@Inject(TestExecuteNode.class) CallTarget target) {
TruffleObject receiver = prepareArgumentValue();
Object ret = target.call(receiver);
Assert.assertEquals(expectedRetValue(), ret);
}
}
// @end region = "TruffleRunnerSnippets#ExampleTest"
// @start region = "TruffleRunnerSnippets#warmupTest"
@Test
@Warmup(5)
public void warmupTest(@Inject(TestExecuteNode.class) CallTarget target) {
TruffleObject receiver = prepareArgumentValue();
Object ret = target.call(receiver);
Assert.assertEquals(expectedRetValue(), ret);
}
// @end region = "TruffleRunnerSnippets#warmupTest"
// Checkstyle: stop
// @start region="TruffleRunnerSnippets#ParameterizedTest"
@RunWith(Parameterized.class)
@UseParametersRunnerFactory(TruffleRunner.ParametersFactory.class)
public static class ParameterizedTest {
@Parameters(name = "{0}, {1}")
public static Collection