All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.tck.TruffleTCK Maven / Gradle / Ivy

Go to download

A collection of tests that can certify language implementation to be compliant with most recent requirements of the Truffle infrastructure and tooling.

There is a newer version: 1.0.0-rc7
Show newest version
/*
 * Copyright (c) 2015, 2018, 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.tck;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
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 static org.junit.Assume.assumeFalse;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.DoubleBinaryOperator;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.debug.Debugger;
import com.oracle.truffle.api.debug.DebuggerSession;
import com.oracle.truffle.api.debug.SuspendedCallback;
import com.oracle.truffle.api.debug.SuspendedEvent;
import com.oracle.truffle.api.impl.Accessor;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.interop.KeyInfo;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.java.*;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.vm.*;
import com.oracle.truffle.tck.Schema.Type;
import com.oracle.truffle.tck.impl.LongBinaryOperation;
import com.oracle.truffle.tck.impl.ObjectBinaryOperation;
import com.oracle.truffle.tck.impl.TckInstrument;
import com.oracle.truffle.tck.impl.TestObject;

/**
 * @since 0.8 or earlier
 * @deprecated Use the new
 *             TCK instead.
 */
@SuppressWarnings({"deprecation", "unchecked", "rawtypes"})
@java.lang.Deprecated
public abstract class TruffleTCK {

    private static volatile PolyglotEngine previousVMReference;

    private static final Random RANDOM = new Random();
    private PolyglotEngine tckVM;
    private Object prev;

    /** @since 0.8 or earlier */
    protected TruffleTCK() {
    }

    /**
     * @since 0.12
     */
    @AfterClass
    public static void disposePreviousVM() {
        if (previousVMReference != null) {
            previousVMReference.dispose();
            previousVMReference = null;
        }
    }

    /**
     * @since 0.30
     */
    @Before
    public final void enterTCK() throws Exception {
        PolyglotEngine vm = vm();
        this.prev = TruffleTCKAccessor.engineAccess().legacyTckEnter(vm);
    }

    /**
     * @since 0.30
     */
    @After
    public final void afterTCK() throws Exception {
        TruffleTCKAccessor.engineAccess().legacyTckLeave(vm(), this.prev);
    }

    /**
     * @since 0.8 or earlier
     */
    protected PolyglotEngine prepareVM() throws Exception {
        return prepareVM(PolyglotEngine.newBuilder());
    }

    /**
     * @since 0.12
     */
    protected PolyglotEngine prepareVM(@SuppressWarnings("unused") PolyglotEngine.Builder preparedBuilder) throws Exception {
        throw new UnsupportedOperationException();
    }

    /**
     * @since 0.8 or earlier
     */
    protected abstract String mimeType();

    /**
     * @since 0.8 or earlier
     */
    protected abstract String fourtyTwo();

    /**
     * @since 0.8 or earlier
     */
    protected abstract String returnsNull();

    /**
     * @since 0.8 or earlier
     */
    protected String plusInt() {
        throw new UnsupportedOperationException("Override plus(Class,Class) method!");
    }

    /**
     * @since 0.8 or earlier
     */
    @SuppressWarnings("unused")
    protected String plus(Class type1, Class type2) {
        return plusInt();
    }

    /**
     * @since 0.8 or earlier
     */
    protected abstract String applyNumbers();

    /**
     * @since 0.8 or earlier
     */
    protected String identity() {
        throw new UnsupportedOperationException("identity() method not implemented");
    }

    /**
     * @since 0.8 or earlier
     */
    protected String complexAdd() {
        throw new UnsupportedOperationException("complexAdd() method not implemented");
    }

    /**
     * @since 0.8 or earlier
     */
    protected String complexAddWithMethod() {
        throw new UnsupportedOperationException("complexAddWithMethod() method not implemented");
    }

    /**
     * @since 0.8 or earlier
     */
    protected String complexSumReal() {
        throw new UnsupportedOperationException("complexSumReal() method not implemented");
    }

    /**
     * @since 0.8 or earlier
     */
    protected String complexCopy() {
        throw new UnsupportedOperationException("complexCopy() method not implemented");
    }

    /**
     * @since 0.8 or earlier
     */
    protected String globalObject() {
        throw new UnsupportedOperationException("globalObject() method not implemented");
    }

    /**
     * @since 0.8 or earlier
     */
    protected String evaluateSource() {
        throw new UnsupportedOperationException("evaluateSource() method not implemented");
    }

    /**
     * @since 0.8 or earlier
     */
    @SuppressWarnings("unused")
    protected String multiplyCode(String firstName, String secondName) {
        throw new UnsupportedOperationException("multiply(String,String) method not implemeted!");
    }

    /**
     *
     * @since 0.14
     */
    protected String addToArray() {
        throw new UnsupportedOperationException("implement addToArray() method");
    }

    /**
     * @since 0.16
     */
    protected String objectWithValueProperty() {
        throw new UnsupportedOperationException("implement objectWithValueProperty() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String objectWithValueAndAddProperty() {
        throw new UnsupportedOperationException("implement objectWithValueAndAddProperty() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String objectWithElement() {
        throw new UnsupportedOperationException("implement objectWithElement() method");
    }

    /**
     * @since 0.26
     */
    protected String objectWithKeyInfoAttributes() {
        throw new UnsupportedOperationException("implement objectWithKeyInfoAttributes() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String functionAddNumbers() {
        throw new UnsupportedOperationException("implement functionAddNumbers() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String readValueFromForeign() {
        throw new UnsupportedOperationException("implement readValueFromForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String readElementFromForeign() {
        throw new UnsupportedOperationException("implement readElementFromForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String writeValueToForeign() {
        throw new UnsupportedOperationException("implement readValueFromForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String writeElementToForeign() {
        throw new UnsupportedOperationException("implement writeElementToForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String getSizeOfForeign() {
        throw new UnsupportedOperationException("implement getSizeOfForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String hasSizeOfForeign() {
        throw new UnsupportedOperationException("implement getHasSizeOfForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String isNullForeign() {
        throw new UnsupportedOperationException("implement getIsNullForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String isExecutableOfForeign() {
        throw new UnsupportedOperationException("implement getIsExecutableForeign() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String callFunction() {
        throw new UnsupportedOperationException("implement callFunction() method");
    }

    /**
     *
     * @since 0.16
     */
    protected String callMethod() {
        throw new UnsupportedOperationException("implement callMethod() method");
    }

    /**
     *
     * @since 0.8 or earlier
     */
    protected abstract String countInvocations();

    /**
     *
     * @since 0.8 or earlier
     */
    protected abstract String invalidCode();

    /**
     *
     * @since 0.8 or earlier
     */
    protected String compoundObject() {
        throw new UnsupportedOperationException("compoundObject() method not implemented");
    }

    /**
     *
     * @since 0.8 or earlier
     */
    protected String valuesObject() {
        throw new UnsupportedOperationException("valuesObject() method not implemented");
    }

    /**
     *
     * @since 0.15
     */
    protected String countUpWhile() {
        throw new UnsupportedOperationException("countUpWhile() method not implemented");
    }

    /**
     *
     * @since 0.8 or earlier
     */
    protected void assertDouble(String msg, double expectedValue, double actualValue) {
        assertEquals(msg, expectedValue, actualValue, 0.1);
    }

    /**
     *
     * @since 0.22
     */
    protected String[] metaObjects() {
        throw new UnsupportedOperationException("metaObjects() method not implemented");
    }

    /**
     *
     * @since 0.22
     */
    protected String valueWithSource() {
        throw new UnsupportedOperationException("valueWithSource() method not implemented");
    }

    private PolyglotEngine vm() throws Exception {
        if (previousVMReference != null) {
            return previousVMReference;
        }
        previousVMReference = prepareVM();
        return previousVMReference;
    }

    //
    // The tests
    //
    /** @since 0.8 or earlier */
    @Test
    public void testFortyTwo() throws Exception {
        PolyglotEngine.Value fourtyTwo = findGlobalSymbol(fourtyTwo());

        Object res = fourtyTwo.execute().get();

        assert res instanceof Number : "should yield a number, but was: " + res;

        Number n = (Number) res;

        assert 42 == n.intValue() : "The value is 42 =  " + n.intValue();
    }

    /** @since 0.8 or earlier */
    @Test
    public void testFortyTwoWithCompoundObject() throws Exception {
        CompoundObject obj = findCompoundSymbol();
        if (obj == null) {
            return;
        }
        Number res = obj.fourtyTwo();
        assertEquals("Should be 42", 42, res.intValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testNull() throws Exception {
        PolyglotEngine.Value retNull = findGlobalSymbol(returnsNull());

        Object res = retNull.execute().get();

        assertNull("Should yield real Java null", res);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testNullCanBeCastToAnything() throws Exception {
        PolyglotEngine.Value retNull = findGlobalSymbol(returnsNull());

        Object res = retNull.execute().as(CompoundObject.class);

        assertNull("Should yield real Java null", res);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testNullInCompoundObject() throws Exception {
        CompoundObject obj = findCompoundSymbol();
        if (obj == null) {
            return;
        }
        Object res = obj.returnsNull();
        assertNull("Should yield real Java null", res);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithInts() throws Exception {
        int a = RANDOM.nextInt(100);
        int b = RANDOM.nextInt(100);
        doPlusWithInts(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithOneNegativeInt() throws Exception {
        int a = -RANDOM.nextInt(100);
        int b = RANDOM.nextInt(100);
        doPlusWithInts(a, b);
    }

    private void doPlusWithInts(int a, int b) throws Exception {
        PolyglotEngine.Value plus = findGlobalSymbol(plus(int.class, int.class));

        Number n = plus.execute(a, b).as(Number.class);
        assert a + b == n.intValue() : "(" + a + " + " + b + ") =  " + n.intValue();
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithBytes() throws Exception {
        int a = RANDOM.nextInt(50);
        int b = RANDOM.nextInt(50);
        doPlusWithBytes(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithOneNegativeByte() throws Exception {
        int a = -RANDOM.nextInt(50);
        int b = RANDOM.nextInt(50);
        doPlusWithBytes(a, b);
    }

    private void doPlusWithBytes(int a, int b) throws Exception {
        PolyglotEngine.Value plus = findGlobalSymbol(plus(byte.class, byte.class));

        Number n = plus.execute((byte) a, (byte) b).as(Number.class);
        assert a + b == n.intValue() : "(" + a + " + " + b + ") =  " + n.intValue();
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithShort() throws Exception {
        int a = RANDOM.nextInt(100);
        int b = RANDOM.nextInt(100);
        doPlusWithShorts(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithOneNegativeShort() throws Exception {
        int a = RANDOM.nextInt(100);
        int b = -RANDOM.nextInt(100);
        doPlusWithShorts(a, b);
    }

    private void doPlusWithShorts(int a, int b) throws Exception {
        PolyglotEngine.Value plus = findGlobalSymbol(plus(short.class, short.class));

        Number n = plus.execute((short) a, (short) b).as(Number.class);
        assert a + b == n.intValue() : "(" + a + " + " + b + ") =  " + n.intValue();
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithLong() throws Exception {
        long a = RANDOM.nextInt(100);
        long b = RANDOM.nextInt(100);
        doPlusWithLong(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithLongMaxIntMinInt() throws Exception {
        doPlusWithLong(Integer.MAX_VALUE, Integer.MIN_VALUE);
    }

    private void doPlusWithLong(long a, long b) throws Exception {
        PolyglotEngine.Value plus = findGlobalSymbol(plus(long.class, long.class));

        Number n = plus.execute(a, b).as(Number.class);
        assert a + b == n.longValue() : "(" + a + " + " + b + ") =  " + n.longValue();
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleFloatSameAsInt() throws Exception {
        int x = RANDOM.nextInt(100);
        int y = RANDOM.nextInt(100);
        float a = x;
        float b = y;
        double u = a;
        double v = b;

        PolyglotEngine.Value floatPlus = findGlobalSymbol(plus(float.class, float.class));
        PolyglotEngine.Value doublePlus = findGlobalSymbol(plus(double.class, double.class));
        PolyglotEngine.Value intPlus = findGlobalSymbol(plus(int.class, int.class));

        Number floatResult = floatPlus.execute(a, b).as(Number.class);
        Number doubleResult = doublePlus.execute(u, v).as(Number.class);
        Number intResult = intPlus.execute(x, y).as(Number.class);

        assertEquals("Correct value computed via int: (" + a + " + " + b + ")", x + y, intResult.intValue());
        assertEquals("Correct value computed via float: (" + a + " + " + b + ")", intResult.intValue(), floatResult.intValue());
        assertEquals("Correct value computed via double: (" + a + " + " + b + ")", intResult.intValue(), doubleResult.intValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithFloat() throws Exception {
        float a = RANDOM.nextFloat() * 100.0f;
        float b = RANDOM.nextFloat() * 100.0f;

        doPlusWithFloat(a, b);
    }

    private void doPlusWithFloat(float a, float b) throws Exception {
        PolyglotEngine.Value plus = findGlobalSymbol(plus(float.class, float.class));

        Number n = plus.execute(a, b).as(Number.class);
        assertDouble("Correct value computed: (" + a + " + " + b + ")", a + b, n.floatValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDouble() throws Exception {
        double a = RANDOM.nextDouble() * 100.0;
        double b = RANDOM.nextDouble() * 100.0;
        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleRound() throws Exception {
        double a = RANDOM.nextInt(1000);
        double b = RANDOM.nextInt(1000);

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleMaxInt() throws Exception {
        double a = Integer.MAX_VALUE;
        double b = 1;

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleMaxMinInt() throws Exception {
        double a = Integer.MAX_VALUE;
        double b = Integer.MIN_VALUE;

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleMinIntMinusOne() throws Exception {
        double a = -1;
        double b = Integer.MIN_VALUE;

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleMaxIntPlusOne() throws Exception {
        double a = 1;
        double b = Integer.MAX_VALUE;

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleNaNPlusNegInf() throws Exception {
        double a = Double.NaN;
        double b = Double.NEGATIVE_INFINITY;

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleNaNPlusPosInf() throws Exception {
        double a = Double.NaN;
        double b = Double.POSITIVE_INFINITY;

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleMaxIntPlusPosInf() throws Exception {
        double a = Integer.MAX_VALUE;
        double b = Double.POSITIVE_INFINITY;

        doPlusWithDouble(a, b);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithDoubleMaxIntPlusNegInf() throws Exception {
        double a = Integer.MAX_VALUE;
        double b = Double.NEGATIVE_INFINITY;

        doPlusWithDouble(a, b);
    }

    private void doPlusWithDouble(double a, double b) throws Exception {
        PolyglotEngine.Value plus = findGlobalSymbol(plus(double.class, double.class));

        Number n = plus.execute(a, b).as(Number.class);
        assertDouble("Correct value computed: (" + a + " + " + b + ")", a + b, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPlusWithIntsOnCompoundObject() throws Exception {
        int a = RANDOM.nextInt(100);
        int b = RANDOM.nextInt(100);

        CompoundObject obj = findCompoundSymbol();
        if (obj == null) {
            return;
        }

        Number n = obj.plus(a, b);
        assert a + b == n.intValue() : "(" + a + " + " + b + ") =  " + n.intValue();
    }

    /** @since 0.8 or earlier */
    @Test(expected = Exception.class)
    public void testInvalidTestMethod() throws Exception {
        String mime = mimeType();
        String code = invalidCode();
        // @formatter:off
        Source invalidCode = Source.newBuilder(code).
            name("Invalid code").
            mimeType(mime).
            build();
        // @formatter:on
        Object ret = vm().eval(invalidCode).get();
        fail("Should yield IOException, but returned " + ret);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testMaxOrMinValue() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        TruffleObject fn = JavaInterop.asTruffleFunction(LongBinaryOperation.class, new MaxMinObject(true));
        Object res = apply.execute(fn).get();

        assert res instanceof Number : "result should be a number: " + res;

        Number n = (Number) res;

        assert 42 == n.intValue() : "32 > 18 and plus 10";
    }

    /** @since 0.8 or earlier */
    @Test
    public void testMaxOrMinValue2() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        TruffleObject fn = JavaInterop.asTruffleFunction(LongBinaryOperation.class, new MaxMinObject(false));
        final PolyglotEngine.Value result = apply.execute(fn);

        try {
            Boolean res = result.as(Boolean.class);
            fail("Cannot be converted to Boolean: " + res);
        } catch (ClassCastException ex) {
            // correct
        }

        Number n = result.as(Number.class);
        assert 28 == n.intValue() : "18 < 32 and plus 10";
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveReturnTypeByte() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        byte value = (byte) RANDOM.nextInt(100);

        TruffleObject fn = JavaInterop.asTruffleFunction(ObjectBinaryOperation.class, new ConstantFunction(value));
        Number n = apply.execute(fn).as(Number.class);
        assertEquals("The same value returned (" + value + " + 10): ", value + 10, n.byteValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveReturnTypeShort() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        short value = (short) RANDOM.nextInt(100);

        TruffleObject fn = JavaInterop.asTruffleFunction(ObjectBinaryOperation.class, new ConstantFunction(value));
        Number n = apply.execute(fn).as(Number.class);
        assertEquals("The same value returned (" + value + " + 10): ", value + 10, n.shortValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveReturnTypeInt() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        int value = RANDOM.nextInt(100);

        TruffleObject fn = JavaInterop.asTruffleFunction(ObjectBinaryOperation.class, new ConstantFunction(value));
        Number n = apply.execute(fn).as(Number.class);
        assertEquals("The same value returned (" + value + " + 10): ", value + 10, n.intValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveReturnTypeLong() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        long value = RANDOM.nextInt(1000);

        TruffleObject fn = JavaInterop.asTruffleFunction(ObjectBinaryOperation.class, new ConstantFunction(value));
        Number n = apply.execute(fn).as(Number.class);
        assertEquals("The same value returned (" + value + " + 10): ", value + 10, n.longValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveReturnTypeFloat() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        float value = RANDOM.nextInt(1000) + RANDOM.nextFloat();

        TruffleObject fn = JavaInterop.asTruffleFunction(ObjectBinaryOperation.class, new ConstantFunction(value));
        Number n = apply.execute(fn).as(Number.class);
        assertDouble("The same value returned (" + value + " + 10): ", value + 10, n.floatValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveReturnTypeDouble() throws Exception {
        PolyglotEngine.Value apply = findGlobalSymbol(applyNumbers());

        double value = RANDOM.nextInt(1000) + RANDOM.nextDouble();

        TruffleObject fn = JavaInterop.asTruffleFunction(ObjectBinaryOperation.class, new ConstantFunction(value));
        Number n = apply.execute(fn).as(Number.class);
        assertDouble("The same value returned (" + value + " + 10): ", value + 10, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityByte() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        byte value = (byte) RANDOM.nextInt(100);

        Number n = (Number) apply.execute(value).get();
        assertEquals("The same value returned", value, n.byteValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityBoxedByte() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        byte value = (byte) RANDOM.nextInt(100);
        BoxedValue boxed = new BoxedValue(value);

        Number n = (Number) apply.execute(boxed).get();
        assertEquals("The same value returned", value, n.byteValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityShort() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        short value = (short) RANDOM.nextInt(100);
        Number n = (Number) apply.execute(value).get();
        assertEquals("The same value returned", value, n.shortValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityBoxedShort() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        short value = (short) RANDOM.nextInt(100);
        BoxedValue boxed = new BoxedValue(value);

        Number n = (Number) apply.execute(boxed).get();
        assertEquals("The same value returned", value, n.shortValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityInt() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        int value = RANDOM.nextInt(100);

        Number n = (Number) apply.execute(value).get();
        assertEquals("The same value returned", value, n.intValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityBoxedInt() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        int value = RANDOM.nextInt(100);
        BoxedValue boxed = new BoxedValue(value);

        Number n = (Number) apply.execute(boxed).get();
        assertEquals("The same value returned", value, n.intValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityLong() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        long value = RANDOM.nextInt(1000);

        Number n = (Number) apply.execute(value).get();
        assertEquals("The same value returned", value, n.longValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityBoxedLong() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        long value = RANDOM.nextInt(1000);
        BoxedValue boxed = new BoxedValue(value);

        Number n = (Number) apply.execute(boxed).get();
        assertEquals("The same value returned", value, n.longValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityFloat() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        float value = RANDOM.nextInt(1000) + RANDOM.nextFloat();

        Number n = (Number) apply.execute(value).get();
        assertDouble("The same value returned", value, n.floatValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityBoxedFloat() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        float value = RANDOM.nextInt(1000) + RANDOM.nextFloat();
        BoxedValue boxed = new BoxedValue(value);

        Number n = (Number) apply.execute(boxed).get();
        assertDouble("The same value returned", value, n.floatValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityDouble() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        double value = RANDOM.nextInt(1000) + RANDOM.nextDouble();

        Number n = (Number) apply.execute(value).get();
        assertDouble("The same value returned", value, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityBoxedDouble() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        double value = RANDOM.nextInt(1000) + RANDOM.nextDouble();
        BoxedValue boxed = new BoxedValue(value);

        Number n = (Number) apply.execute(boxed).get();
        assertDouble("The same value returned", value, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityString() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        String value = "Value" + RANDOM.nextInt(1000) + RANDOM.nextDouble();

        String ret = (String) apply.execute(value).get();
        assertEquals("The same value returned", value, ret);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveidentityBoxedString() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        String value = "Value" + RANDOM.nextInt(1000) + RANDOM.nextDouble();
        BoxedValue boxed = new BoxedValue(value);

        String ret = (String) apply.execute(boxed).get();
        assertEquals("The same value returned", value, ret);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testPrimitiveIdentityForeignObject() throws Exception {
        String id = identity();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        Object fn = new Object();
        Object ret = apply.execute(fn).get();
        assertSameTruffleObject("The same value returned", fn, ret);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testCoExistanceOfMultipleLanguageInstances() throws Exception {
        final String countMethod = countInvocations();
        PolyglotEngine.Builder builder = PolyglotEngine.newBuilder().executor(Executors.newSingleThreadExecutor());
        PolyglotEngine vm2 = prepareVM(builder);
        try {
            PolyglotEngine.Value count2 = vm2.findGlobalSymbol(countMethod);

            PolyglotEngine.Value count1 = findGlobalSymbol(countMethod);
            PolyglotEngine vm1 = tckVM;

            assertNotSame("Two virtual machines allocated", vm1, vm2);

            int prev1 = 0;
            int prev2 = 0;
            StringBuilder log = new StringBuilder();
            for (int i = 0; i < 10; i++) {
                int quantum = RANDOM.nextInt(10);
                log.append("quantum" + i + " is " + quantum + "\n");
                for (int j = 0; j < quantum; j++) {
                    Object res = count1.execute().get();
                    assert res instanceof Number : "expecting number: " + res + "\n" + log;
                    ++prev1;
                    assert ((Number) res).intValue() == prev1 : "expecting " + prev1 + " but was " + res + "\n" + log;
                }
                for (int j = 0; j < quantum; j++) {
                    Object res = count2.execute().get();
                    assert res instanceof Number : "expecting number: " + res + "\n" + log;
                    ++prev2;
                    assert ((Number) res).intValue() == prev2 : "expecting " + prev2 + " but was " + res + "\n" + log;
                }
                assert prev1 == prev2 : "At round " + i + " the same number of invocations " + prev1 + " vs. " + prev2 + "\n" + log;
            }
        } finally {
            vm2.dispose();
        }
    }

    /** @since 0.8 or earlier */
    @Test
    public void testGlobalObjectIsAccessible() throws Exception {
        String globalObjectFunction = globalObject();
        if (globalObjectFunction == null) {
            return;
        }

        PolyglotEngine.Language language = vm().getLanguages().get(mimeType());
        assertNotNull("Language for " + mimeType() + " found", language);

        PolyglotEngine.Value function = vm().findGlobalSymbol(globalObjectFunction);
        Object global = function.execute().get();
        assertEquals("Global from the language same with Java obtained one", language.getGlobalObject().get(), global);
        assertIsObjectOfLanguage(global);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testEvaluateSource() throws Exception {
        PolyglotEngine.Language language = vm().getLanguages().get(mimeType());
        assertNotNull("Language for " + mimeType() + " found", language);

        PolyglotEngine.Value function = vm().findGlobalSymbol(evaluateSource());
        assertNotNull(evaluateSource() + " found", function);

        double expect = Math.floor(RANDOM.nextDouble() * 100000.0) / 10.0;
        Object parsed = function.execute("application/x-tck", "" + expect).get();
        assertTrue("Expecting numeric result, was:" + expect, parsed instanceof Number);
        double value = ((Number) parsed).doubleValue();
        assertDouble("Gets the double", expect, value);
    }

    /** @since 0.8 or earlier */
    @Test
    public void multiplyTwoVariables() throws Exception {
        final String firstVar = "var" + (char) ('A' + RANDOM.nextInt(24));
        final String secondVar = "var" + (char) ('0' + RANDOM.nextInt(10));
        String mulCode = multiplyCode(firstVar, secondVar);
        // @formatter:off
        Source source = Source.newBuilder("TCK42:" + mimeType() + ":" + mulCode).
            name("evaluate " + firstVar + " * " + secondVar).
            mimeType("application/x-tck").
            build();
        // @formatter:on
        final PolyglotEngine.Value evalSource = vm().eval(source);
        final PolyglotEngine.Value invokeMul = evalSource.execute(firstVar, secondVar);
        Object result = invokeMul.get();
        assertTrue("Expecting numeric result, was:" + result + " for " + firstVar + " and " + secondVar, result instanceof Number);
        assertEquals("Right value for " + firstVar + " and " + secondVar, 42, ((Number) result).intValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testAddComplexNumbers() throws Exception {
        String id = complexAdd();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        ComplexNumber a = new ComplexNumber(32, 10);
        ComplexNumber b = new ComplexNumber(10, 32);

        apply.execute(a, b);

        assertEquals(42.0, a.get(ComplexNumber.REAL_IDENTIFIER), 0.1);
        assertEquals(42.0, a.get(ComplexNumber.IMAGINARY_IDENTIFIER), 0.1);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testAddComplexNumbersWithMethod() throws Exception {
        String id = complexAddWithMethod();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        ComplexNumber a = new ComplexNumber(32, 10);
        ComplexNumber b = new ComplexNumber(10, 32);

        apply.execute(a, b);

        assertDouble("The same value returned", 42.0, a.get(ComplexNumber.REAL_IDENTIFIER));
        assertDouble("The same value returned", 42.0, a.get(ComplexNumber.IMAGINARY_IDENTIFIER));
    }

    /** @since 0.13 */
    @Test
    public void testPropertiesInteropMessage() throws Exception {
        PolyglotEngine.Value values = findGlobalSymbol(valuesObject());
        PolyglotEngine.Value valueObj = values.execute();
        assertIsObjectOfLanguage(valueObj.get());
        Map res = valueObj.as(Map.class);

        Map expected = new HashMap<>();
        expected.put("intValue", 0);
        expected.put("byteValue", 0);
        expected.put("doubleValue", 0.0);

        for (Map.Entry entry : res.entrySet()) {
            Object key = entry.getKey();
            Object value = entry.getValue();

            Object expValue = expected.remove(key);
            if (expValue != null) {
                assertEquals("For key " + key, ((Number) expValue).doubleValue(), ((Number) value).doubleValue(), 0.01);
            }
        }

        assertTrue("All expected properties found: " + expected, expected.isEmpty());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testSumRealOfComplexNumbersA() throws Exception {
        String id = complexSumReal();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        ComplexNumbersA numbers = new ComplexNumbersA(new double[]{2, -1, 30, -1, 10, -1});

        Number n = (Number) apply.execute(numbers).get();
        assertDouble("The same value returned", 42.0, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testSumRealOfComplexNumbersB() throws Exception {
        String id = complexSumReal();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        ComplexNumbersB numbers = new ComplexNumbersB(new double[]{2, 30, 10}, new double[]{-1, -1, -1});

        Number n = (Number) apply.execute(numbers).get();
        assertDouble("The same value returned", 42.0, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testSumRealOfComplexNumbersAsStructuredDataRowBased() throws Exception {
        String id = complexSumReal();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        Schema schema = new Schema(3, true, Arrays.asList(ComplexNumber.REAL_IDENTIFIER, ComplexNumber.IMAGINARY_IDENTIFIER), Arrays.asList(Type.DOUBLE, Type.DOUBLE));
        byte[] buffer = new byte[(6 * Double.SIZE / Byte.SIZE)];
        putDoubles(buffer, new double[]{2, -1, 30, -1, 10, -1});
        StructuredData numbers = new StructuredData(buffer, schema);

        Number n = (Number) apply.execute(numbers).get();
        assertDouble("The same value returned", 42.0, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testSumRealOfComplexNumbersAsStructuredDataColumnBased() throws Exception {
        String id = complexSumReal();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        Schema schema = new Schema(3, false, Arrays.asList(ComplexNumber.REAL_IDENTIFIER, ComplexNumber.IMAGINARY_IDENTIFIER), Arrays.asList(Type.DOUBLE, Type.DOUBLE));
        byte[] buffer = new byte[6 * Double.SIZE / Byte.SIZE];
        putDoubles(buffer, new double[]{2, 30, 10, -1, -1, -1});

        StructuredData numbers = new StructuredData(buffer, schema);

        Number n = (Number) apply.execute(numbers).get();
        assertDouble("The same value returned", 42.0, n.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void testCopyComplexNumbersA() throws Exception {
        String id = complexCopy();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        ComplexNumbersA a = new ComplexNumbersA(new double[]{-1, -1, -1, -1, -1, -1});
        ComplexNumbersA b = new ComplexNumbersA(new double[]{41, 42, 43, 44, 45, 46});

        apply.execute(a, b);

        Assert.assertArrayEquals(new double[]{41, 42, 43, 44, 45, 46}, a.getData(), 0.1);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testCopyComplexNumbersB() throws Exception {
        String id = complexCopy();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        ComplexNumbersB a = new ComplexNumbersB(new double[]{-1, -1, -1}, new double[]{-1, -1, -1});
        ComplexNumbersB b = new ComplexNumbersB(new double[]{41, 43, 45}, new double[]{42, 44, 46});

        apply.execute(a, b);

        Assert.assertArrayEquals(new double[]{41, 42, 43, 44, 45, 46}, a.getData(), 0.1);
    }

    /** @since 0.8 or earlier */
    @Test
    public void testCopyStructuredComplexToComplexNumbersA() throws Exception {
        String id = complexCopy();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        ComplexNumbersA a = new ComplexNumbersA(new double[]{-1, -1, -1, -1, -1, -1});

        Schema schema = new Schema(3, true, Arrays.asList(ComplexNumber.REAL_IDENTIFIER, ComplexNumber.IMAGINARY_IDENTIFIER), Arrays.asList(Type.DOUBLE, Type.DOUBLE));
        byte[] buffer = new byte[6 * Double.SIZE / Byte.SIZE];
        putDoubles(buffer, new double[]{41, 42, 43, 44, 45, 46});

        StructuredData b = new StructuredData(buffer, schema);

        apply.execute(a, b);

        Assert.assertArrayEquals(new double[]{41, 42, 43, 44, 45, 46}, a.getData(), 0.1);
    }

    /** @since 0.8 or earlier */
    @Test
    public void readWriteByteValue() throws Exception {
        String id = valuesObject();
        ValuesObject values = findGlobalSymbol(id).execute().as(ValuesObject.class);
        assertEquals("Zero", 0, values.byteValue());
        final byte value = (byte) RANDOM.nextInt(128);
        values.byteValue(value);
        assertEquals("Correct value", value, values.byteValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void readWriteShortValue() throws Exception {
        String id = valuesObject();
        ValuesObject values = findGlobalSymbol(id).execute().as(ValuesObject.class);
        assertEquals("Zero", 0, values.shortValue());
        final short value = (short) RANDOM.nextInt(32768);
        values.shortValue(value);
        assertEquals("Correct value", value, values.shortValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void readWriteIntValue() throws Exception {
        String id = valuesObject();
        ValuesObject values = findGlobalSymbol(id).execute().as(ValuesObject.class);
        assertEquals("Zero", 0, values.intValue());
        final int value = RANDOM.nextInt();
        values.intValue(value);
        assertEquals("Correct value", value, values.intValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void readWriteFloatValue() throws Exception {
        String id = valuesObject();
        ValuesObject values = findGlobalSymbol(id).execute().as(ValuesObject.class);
        assertDouble("Zero", 0, values.floatValue());
        final float value = RANDOM.nextFloat() * 1000.0f;
        values.floatValue(value);
        assertDouble("Correct value", value, values.floatValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void readWriteDoubleValue() throws Exception {
        String id = valuesObject();
        ValuesObject values = findGlobalSymbol(id).execute().as(ValuesObject.class);
        assertDouble("Zero", 0, values.doubleValue());
        final double value = RANDOM.nextDouble() * 1000.0;
        values.doubleValue(value);
        assertDouble("Correct value", value, values.doubleValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void readWriteCharValue() throws Exception {
        String id = valuesObject();
        ValuesObject values = findGlobalSymbol(id).execute().as(ValuesObject.class);
        assertEquals("Zero", '0', values.charValue());
        String letters = "P\u0159\u00EDli\u0161 \u017Elu\u0165ou\u010Dk\u00FD k\u016F\u0148 \u00FAp\u011Bl \u010F\u00E1belsk\u00E9 \u00F3dy";
        final char value = letters.charAt(RANDOM.nextInt(letters.length()));
        values.charValue(value);
        assertEquals("Correct value", value, values.charValue());
    }

    /** @since 0.8 or earlier */
    @Test
    public void readWriteBooleanValue() throws Exception {
        String id = valuesObject();
        ValuesObject values = findGlobalSymbol(id).execute().as(ValuesObject.class);
        assertEquals("False", false, values.booleanValue());
        values.booleanValue(true);
        assertEquals("Correct value", true, values.booleanValue());
        values.booleanValue(false);
        assertEquals("Correct value2", false, values.booleanValue());
    }

    /**
     * Test for array access. Creates a {@link TruffleObject} around a Java array, fills it with
     * integers and asks the language to add one to each of the array elements.
     *
     * @since 0.14
     */
    @Test
    public void addOneToAnArrayElement() throws Exception {
        String id = addToArray();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value add = findGlobalSymbol(id);

        Number[] arr = new Number[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
        TruffleObject truffleArr = JavaInterop.asTruffleObject(arr);

        int index = RANDOM.nextInt(arr.length - 1);

        add.execute(truffleArr, index, 1);
        final Number valueAtIndex = arr[index];
        final Number valueAfterIndex = arr[index + 1];

        assertNotNull("Non-null value expected at index " + index, valueAtIndex);
        assertNotNull("Non-null value expected at index " + (index + 1), valueAfterIndex);
        assertEquals("Expecting same value at both indexes", valueAtIndex.intValue(), valueAfterIndex.intValue());
    }

    /**
     * Tests whether execution can be suspended in debugger.
     *
     * @since 0.15
     */
    @Test
    public void timeOutTest() throws Exception {
        assumeFalse("Crashes on AArch64 in C2 (GR-8733)", System.getProperty("os.arch").equalsIgnoreCase("aarch64"));
        final ExecWithTimeOut timeOutExecution = new ExecWithTimeOut();
        ScheduledExecutorService executor = new MockExecutorService();

        timeOutExecution.engine = prepareVM(PolyglotEngine.newBuilder());
        PolyglotEngine.Value counting = timeOutExecution.engine.findGlobalSymbol(countUpWhile());

        int index = RANDOM.nextInt(50) + 50;
        CountAndKill obj = new CountAndKill(index, executor);

        timeOutExecution.executeWithTimeOut(executor, counting, obj);
        assertEquals("Executed " + index + " times, and counted down to zero", 0, obj.countDown);
        assertTrue("Last number bigger than requested", index <= obj.lastParameter);
        assertTrue("All tasks processed", executor.isShutdown());
    }

    /** @since 0.15 */
    @Test
    public void testRootNodeName() throws Exception {
        final int[] haltCount = new int[1];
        final String name = applyNumbers();
        final String[] actualName = new String[1];
        final PolyglotEngine engine = prepareVM(PolyglotEngine.newBuilder());
        final PolyglotEngine.Value apply = engine.findGlobalSymbol(name);
        final int value = RANDOM.nextInt(100);
        final TruffleObject fn = JavaInterop.asTruffleFunction(ObjectBinaryOperation.class, new ConstantFunction(value));
        try (DebuggerSession session = Debugger.find(engine).startSession(new SuspendedCallback() {
            public void onSuspend(SuspendedEvent ev) {
                actualName[0] = ev.getTopStackFrame().getName();
                haltCount[0] = haltCount[0] + 1;
            }
        })) {
            session.suspendNextExecution();
            apply.execute(fn).as(Number.class);
        }

        assertEquals(1, haltCount[0]);
        assertEquals(name, actualName[0]);
    }

    /** @since 0.16 */
    @Test
    public void testReadFromObjectWithValueProperty() throws Exception {
        String id = objectWithValueProperty();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        TruffleObject truffleObject = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(truffleObject);
        ObjectWithValueInterface object = JavaInterop.asJavaObject(ObjectWithValueInterface.class, truffleObject);

        Assert.assertEquals(42.0, object.value(), 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testReadFromObjectWithElement() throws Exception {
        String id = objectWithElement();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        TruffleObject truffleObject = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(truffleObject);
        List object = JavaInterop.asJavaObject(List.class, truffleObject);

        Assert.assertEquals(42.0, ((Number) object.get(2)).doubleValue(), 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testWriteToObjectWithValueProperty() throws Exception {
        String id = objectWithValueProperty();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        TruffleObject truffleObject = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(truffleObject);
        ObjectWithValueInterface object = JavaInterop.asJavaObject(ObjectWithValueInterface.class, truffleObject);
        Assert.assertEquals(42.0, object.value(), 0.1);
        object.value(13.0);
        Assert.assertEquals(13.0, object.value(), 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testWriteToObjectWithElement() throws Exception {
        String id = objectWithElement();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        TruffleObject truffleObject = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(truffleObject);
        List object = JavaInterop.asJavaObject(List.class, truffleObject);

        Assert.assertEquals(42.0, ((Number) object.get(2)).doubleValue(), 0.1);
        object.set(2, 13.0);
        Assert.assertEquals(13.0, ((Number) object.get(2)).doubleValue(), 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testGetSize() throws Exception {
        String id = objectWithElement();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        List object = JavaInterop.asJavaObject(List.class, (TruffleObject) apply.execute().get());

        Assert.assertEquals(4, object.size());
    }

    /** @since 0.16 */
    @Test
    public void testHasSize() throws Exception {
        String id = objectWithElement();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        MessageInterface object = JavaInterop.asJavaObject(MessageInterface.class, (TruffleObject) apply.execute().get());

        Assert.assertEquals(true, object.hasSize());
    }

    /** @since 0.16 */
    @Test
    public void testIsNotNull() throws Exception {
        String id = objectWithValueProperty();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        MessageInterface object = JavaInterop.asJavaObject(MessageInterface.class, (TruffleObject) apply.execute().get());

        Assert.assertEquals(false, object.isNull());
    }

    /** @since 0.16 */
    @Test
    public void testIsExecutable() throws Exception {
        String id = functionAddNumbers();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        TruffleObject truffleObject = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(truffleObject);

        MessageInterface object = JavaInterop.asJavaObject(MessageInterface.class, truffleObject);

        Assert.assertEquals(true, object.isExecutable());
    }

    private interface MessageInterface {
        @MethodMessage(message = "GET_SIZE")
        int length();

        @MethodMessage(message = "IS_NULL")
        boolean isNull();

        @MethodMessage(message = "IS_EXECUTABLE")
        boolean isExecutable();

        @MethodMessage(message = "HAS_SIZE")
        boolean hasSize();

    }

    /** @since 0.16 */
    @Test
    public void testObjectWithValueAndAddProperty() throws Exception {
        String id = objectWithValueAndAddProperty();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        TruffleObject truffleObject = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(truffleObject);
        ObjectWithValueInterface object = JavaInterop.asJavaObject(ObjectWithValueInterface.class, truffleObject);
        object.add(20.0);
        object.add(22.0);

        Assert.assertEquals(42.0, object.value(), 0.1);
    }

    private interface ObjectWithValueInterface {
        @MethodMessage(message = "READ")
        double value();

        @MethodMessage(message = "WRITE")
        void value(double v);

        @MethodMessage(message = "INVOKE")
        double add(double arg);
    }

    /** @since 0.16 */
    @Test
    public void testFunctionAddNumbers() throws Exception {
        String id = functionAddNumbers();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        TruffleObject truffleObject = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(truffleObject);
        DoubleBinaryOperator object = JavaInterop.asJavaFunction(DoubleBinaryOperator.class, truffleObject);

        Assert.assertEquals(42.0, object.applyAsDouble(20.0, 22.0), 0.1);
    }

    /** @since 0.26 */
    @Test
    public void testObjectWithKeyInfoAttributes() throws Exception {
        String id = objectWithKeyInfoAttributes();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        TruffleObject obj = (TruffleObject) apply.execute().get();
        assertIsObjectOfLanguage(obj);
        KeyInfoInterface object = JavaInterop.asJavaObject(KeyInfoInterface.class, obj);

        int numKeys = KeyInfo.NONE;
        int keyInfo = object.unknown();
        assertFalse("An unknown property", KeyInfo.isExisting(keyInfo));
        int ro = object.ro();
        if (KeyInfo.isExisting(ro)) {
            assertTrue(KeyInfo.isReadable(ro));
            assertFalse(KeyInfo.isWritable(ro));
            assertFalse(KeyInfo.isInternal(ro));
            numKeys++;
        }
        int wo = object.wo();
        if (KeyInfo.isExisting(wo)) {
            assertFalse(KeyInfo.isReadable(wo));
            assertTrue(KeyInfo.isWritable(wo));
            assertFalse(KeyInfo.isInternal(wo));
            numKeys++;
        }
        int rw = object.rw();
        if (KeyInfo.isExisting(rw)) {
            assertTrue(KeyInfo.isReadable(rw));
            assertTrue(KeyInfo.isWritable(rw));
            assertFalse(KeyInfo.isInternal(rw));
            numKeys++;
        }
        int rm = object.rm();
        if (KeyInfo.isExisting(rm)) {
            assertTrue(KeyInfo.isRemovable(rm));
            numKeys++;
        }
        int invocable = object.invocable();
        if (KeyInfo.isExisting(invocable)) {
            assertTrue(KeyInfo.isInvocable(invocable));
            assertFalse(KeyInfo.isInternal(invocable));
            numKeys++;
        }
        int intern = object.intern();
        if (KeyInfo.isExisting(intern)) {
            assertTrue(KeyInfo.isInternal(intern));
        }

        Map map = JavaInterop.asJavaObject(Map.class, obj);
        assertEquals(map.toString(), numKeys, map.size());
        if (KeyInfo.isExisting(ro)) {
            assertTrue(map.containsKey("ro"));
        }
        if (KeyInfo.isExisting(wo)) {
            assertTrue(map.containsKey("wo"));
        }
        if (KeyInfo.isExisting(rw)) {
            assertTrue(map.containsKey("rw"));
        }
        if (KeyInfo.isExisting(rm)) {
            assertTrue(map.containsKey("rm"));
        }
        if (KeyInfo.isExisting(invocable)) {
            assertTrue(map.containsKey("invocable"));
        }
        assertFalse(map.containsKey("intern"));

        map = JavaInterop.getMapView(map, true);
        if (KeyInfo.isExisting(intern)) {
            assertEquals(numKeys + 1, map.size());
            assertTrue(map.containsKey("intern"));
        } else {
            assertEquals(numKeys, map.size());
        }
    }

    private interface KeyInfoInterface {

        @MethodMessage(message = "KEY_INFO")
        int ro();

        @MethodMessage(message = "KEY_INFO")
        int wo();

        @MethodMessage(message = "KEY_INFO")
        int rw();

        @MethodMessage(message = "KEY_INFO")
        int rm();

        @MethodMessage(message = "KEY_INFO")
        int invocable();

        @MethodMessage(message = "KEY_INFO")
        int intern();

        @MethodMessage(message = "KEY_INFO")
        int unknown();

    }

    /** @since 0.16 */
    @Test
    public void testReadValueFromForeign() throws Exception {
        String id = readValueFromForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        Assert.assertEquals(42.0, ((Number) apply.execute(JavaInterop.asTruffleObject(new TestObject(42.0))).get()).doubleValue(), 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testReadElementFromForeign() throws Exception {
        String id = readElementFromForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        Assert.assertEquals(42.0, ((Number) apply.execute(JavaInterop.asTruffleObject(new double[]{-1, -2, 42.0, -4})).get()).doubleValue(), 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testWriteValueToForeign() throws Exception {
        String id = writeValueToForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        TestObject obj = new TestObject(-1.5);
        apply.execute(JavaInterop.asTruffleObject(obj));
        Assert.assertEquals(42.0, obj.value, 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testWriteElementOfForeign() throws Exception {
        String id = writeElementToForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        double[] arr = {-1, -1, -1, -1};
        apply.execute(JavaInterop.asTruffleObject(arr));
        Assert.assertEquals(42.0, arr[2], 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testGetSizeOfForeign() throws Exception {
        String id = getSizeOfForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        double[] arr = {-1, -1, -1, -1};
        Number size = (Number) apply.execute(JavaInterop.asTruffleObject(arr)).get();
        Assert.assertEquals(4, size.intValue(), 0.1);
    }

    /** @since 0.16 */
    @Test
    public void testHasSizeOfForeign() throws Exception {
        String id = hasSizeOfForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        double[] arr = {-1, -1, -1, -1};
        boolean result = (boolean) apply.execute(JavaInterop.asTruffleObject(arr)).get();
        Assert.assertEquals(true, result);
    }

    /** @since 0.16 */
    @Test
    public void testIsNullOfForeign() throws Exception {
        String id = isNullForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);

        boolean result = (boolean) apply.execute(JavaInterop.asTruffleObject(null)).get();
        Assert.assertEquals(true, result);
    }

    /** @since 0.16 */
    @Test
    public void testIsExecutableOfForeign() throws Exception {
        String id = isExecutableOfForeign();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        boolean result = (boolean) apply.execute(JavaInterop.asTruffleFunction(DoubleBinaryOperator.class, new DoubleBinaryOperator() {

            public double applyAsDouble(double a, double b) {
                if (a != 41.0 || b != 42.0) {
                    throw new AssertionError("Expected [41.5, 42.5] but was [" + a + "," + b + "]");
                }
                return 0;
            }
        })).get();
        Assert.assertEquals(true, result);
    }

    /** @since 0.16 */
    @Test
    public void testCallFunction() throws Exception {
        String id = callFunction();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        apply.execute(JavaInterop.asTruffleFunction(DoubleBinaryOperator.class, new DoubleBinaryOperator() {

            public double applyAsDouble(double a, double b) {
                if (a != 41.0 || b != 42.0) {
                    throw new AssertionError("Expected [41.0, 42.0] but was [" + a + "," + b + "]");
                }
                return 0;
            }
        }));
    }

    /** @since 0.16 */
    @Test
    public void testCallMethod() throws Exception {
        String id = callMethod();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value apply = findGlobalSymbol(id);
        TestObject obj = new TestObject(0);
        apply.execute(JavaInterop.asTruffleObject(obj));
        Assert.assertEquals(obj.arg1, 41.0, 0.1);
        Assert.assertEquals(obj.arg2, 42.0, 0.1);
    }

    /** @since 0.22 */
    @Test
    public void testMetaObject() throws Exception {
        String[] ids = metaObjects();
        if (ids == null) {
            return;
        }
        assert (ids.length > 0);
        assert (ids.length % 2 == 0);
        for (int i = 0; i < ids.length; i += 2) {
            PolyglotEngine.Value valueFunction = findGlobalSymbol(ids[i]);
            String metaObjectStr;
            PolyglotRuntime.Instrument instr = vm().getRuntime().getInstruments().get(TckInstrument.ID);
            instr.setEnabled(true);
            try {
                PolyglotEngine.Value value = valueFunction.execute();
                metaObjectStr = value.getMetaObject().as(String.class);
            } finally {
                instr.setEnabled(false);
            }
            PolyglotEngine.Value moFunction = findGlobalSymbol(ids[i + 1]);
            Object mo = moFunction.execute().get();

            assertEquals(mo, metaObjectStr);
        }
    }

    /** @since 0.22 */
    @Test
    public void testValueWithSource() throws Exception {
        String id = valueWithSource();
        if (id == null) {
            return;
        }
        PolyglotEngine.Value valueFunction = findGlobalSymbol(id);
        SourceSection sourceLocation;
        PolyglotRuntime.Instrument instr = vm().getRuntime().getInstruments().get(TckInstrument.ID);
        instr.setEnabled(true);
        try {
            PolyglotEngine.Value value = valueFunction.execute();
            TckInstrument tckInstrument = instr.lookup(TckInstrument.class);
            assertNotNull(tckInstrument);
            TruffleInstrument.Env env = tckInstrument.getEnvironment();
            assertNotNull(env);
            sourceLocation = value.getSourceLocation();
            assertNotNull(sourceLocation);
            List lss = env.getInstrumenter().querySourceSections(SourceSectionFilter.ANY);
            assertTrue("Source section not among loaded sections", lss.contains(sourceLocation));
        } finally {
            instr.setEnabled(false);
        }
    }

    private static void putDoubles(byte[] buffer, double[] values) {
        for (int index = 0; index < values.length; index++) {
            int doubleSize = Double.SIZE / Byte.SIZE;
            byte[] bytes = new byte[doubleSize];
            ByteBuffer.wrap(bytes).putDouble(values[index]);
            for (int i = 0; i < doubleSize; i++) {
                buffer[index * doubleSize + i] = bytes[i];
            }
        }
    }

    private PolyglotEngine.Value findGlobalSymbol(String name) throws Exception {
        PolyglotEngine.Value s = vm().findGlobalSymbol(name);
        assert s != null : "Symbol " + name + " is not found!";
        return s;
    }

    private CompoundObject findCompoundSymbol() throws Exception {
        final String compoundObjectName = compoundObject();
        PolyglotEngine.Value s = vm().findGlobalSymbol(compoundObjectName);
        assert s != null : "Symbol " + compoundObjectName + " is not found!";
        final PolyglotEngine.Value value = s.execute();
        assertIsObjectOfLanguage(value.get());
        CompoundObject obj = value.as(CompoundObject.class);
        assertNotNull("Compound object for " + value + " found", obj);
        int traverse = RANDOM.nextInt(10);
        for (int i = 1; i <= traverse; i++) {
            obj = obj.returnsThis();
            assertNotNull("Remains non-null even after " + i + " iteration", obj);
        }
        return obj;
    }

    private static void assertSameTruffleObject(String msg, Object expected, Object actual) {
        Object unExpected = unwrapTruffleObject(expected);
        Object unAction = unwrapTruffleObject(actual);
        assertSame(msg, unExpected, unAction);
    }

    private void assertIsObjectOfLanguage(Object obj) throws Exception {
        enterTCK(); // hack to ensure entered
        PolyglotRuntime.Instrument instr = vm().getRuntime().getInstruments().get(TckInstrument.ID);
        TruffleLanguage.Env env = TruffleTCKAccessor.engineAccess().getEnvForInstrument(instr, null, mimeType());
        assertTrue(obj.toString(), TruffleTCKAccessor.langAccess().isObjectOfLanguage(env, obj));
    }

    private static Object unwrapTruffleObject(Object obj) {
        try {
            if (obj instanceof TruffleObject) {
                Class eto = Class.forName("com.oracle.truffle.api.vm.EngineTruffleObject");
                if (eto.isInstance(obj)) {
                    final Field field = eto.getDeclaredField("delegate");
                    field.setAccessible(true);
                    return field.get(obj);
                }
            }
            return obj;
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    interface CompoundObject {
        Number fourtyTwo();

        Number plus(int x, int y);

        Object returnsNull();

        CompoundObject returnsThis();
    }

    interface ValuesObject {
        byte byteValue();

        @MethodMessage(message = "WRITE")
        void byteValue(byte v);

        short shortValue();

        @MethodMessage(message = "WRITE")
        void shortValue(short v);

        int intValue();

        @MethodMessage(message = "WRITE")
        void intValue(int v);

        long longValue();

        @MethodMessage(message = "WRITE")
        void longValue(long v);

        float floatValue();

        @MethodMessage(message = "WRITE")
        void floatValue(float v);

        double doubleValue();

        @MethodMessage(message = "WRITE")
        void doubleValue(double v);

        char charValue();

        @MethodMessage(message = "WRITE")
        void charValue(char v);

        boolean booleanValue();

        @MethodMessage(message = "WRITE")
        void booleanValue(boolean v);
    }

    static final TruffleTCKAccessor ACCESSOR = new TruffleTCKAccessor();

    static final class TruffleTCKAccessor extends Accessor {

        static Accessor.LanguageSupport langAccess() {
            return ACCESSOR.languageSupport();
        }

        static Accessor.EngineSupport engineAccess() {
            return ACCESSOR.engineSupport();
        }

    }
}