package com.oracle.truffle.api.test.polyglot;
import static com.oracle.truffle.api.test.polyglot.AbstractPolyglotTest.assertFails;
import static com.oracle.truffle.tck.tests.ValueAssert.assertUnsupported;
import static com.oracle.truffle.tck.tests.ValueAssert.assertValue;
import static com.oracle.truffle.tck.tests.ValueAssert.Trait.PROXY_OBJECT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.PolyglotException.StackFrame;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.Proxy;
import org.graalvm.polyglot.proxy.ProxyArray;
import org.graalvm.polyglot.proxy.ProxyDate;
import org.graalvm.polyglot.proxy.ProxyDuration;
import org.graalvm.polyglot.proxy.ProxyExecutable;
import org.graalvm.polyglot.proxy.ProxyInstant;
import org.graalvm.polyglot.proxy.ProxyInstantiable;
import org.graalvm.polyglot.proxy.ProxyObject;
import org.graalvm.polyglot.proxy.ProxyTime;
import org.graalvm.polyglot.proxy.ProxyTimeZone;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.oracle.truffle.tck.tests.ValueAssert.Trait;
public class ProxyAPITest {
private Context context;
@Before
public void setUp() {
context = Context.newBuilder().allowHostAccess(HostAccess.ALL).build();
}
@After
public void tearDown() {
context.close();
}
@Test
public void testProxy() {
Proxy proxy = new Proxy() {
};
Value value = context.asValue(proxy);
assertTrue(value.isProxyObject());
assertSame(proxy, value.asProxyObject());
assertUnsupported(value, PROXY_OBJECT);
}
static class ProxyArrayTest implements ProxyArray {
Function<Long, Object> get;
BiFunction<Long, Value, Void> set;
Supplier<Long> getSize;
int getCounter;
int setCounter;
int getSizeCounter;
public Object get(long index) {
getCounter++;
return get.apply(index);
}
public void set(long index, Value value) {
setCounter++;
set.apply(index, value);
}
public long getSize() {
getSizeCounter++;
return getSize.get();
}
}
@Test
public void testProxyArray() {
ProxyArrayTest proxy = new ProxyArrayTest();
Value value = context.asValue(proxy);
assertTrue(value.isProxyObject());
assertSame(proxy, value.asProxyObject());
assertTrue(value.hasArrayElements());
assertEquals(0, proxy.getCounter);
assertEquals(0, proxy.setCounter);
assertEquals(0, proxy.getSizeCounter);
proxy.get = (index) -> 42;
proxy.getSize = () -> 43L;
assertEquals(42, value.getArrayElement(42).asInt());
assertEquals(1, proxy.getCounter);
assertEquals(0, proxy.setCounter);
proxy.getCounter = 0;
proxy.get = null;
Object setObject = new Object();
proxy.set = (index, v) -> {
assertSame(setObject, v.asHostObject());
return null;
};
value.setArrayElement(42, setObject);
assertEquals(0, proxy.getCounter);
assertEquals(1, proxy.setCounter);
proxy.setCounter = 0;
proxy.set = null;
proxy.getSize = () -> 42L;
assertEquals(42L, value.getArraySize());
assertEquals(0, proxy.getCounter);
assertEquals(0, proxy.setCounter);
proxy.getSize = null;
proxy.getCounter = 0;
RuntimeException ex = new RuntimeException();
proxy.getSize = () -> {
return 0L;
};
proxy.get = (index) -> {
throw ex;
};
assertFails(() -> value.getArrayElement(42), PolyglotException.class, (e) -> {
assertTrue(e.isHostException());
assertSame(ex, e.asHostException());
assertFalse(e.isInternalError());
});
proxy.get = null;
proxy.set = (index, v) -> {
throw ex;
};
assertFails(() -> value.setArrayElement(42, null), PolyglotException.class, (e) -> {
assertTrue(e.isHostException());
assertSame(ex, e.asHostException());
assertFalse(e.isInternalError());
});
proxy.set = null;
proxy.getSize = () -> {
throw ex;
};
assertFails(() -> value.getArraySize(), PolyglotException.class, (e) -> {
assertTrue(e.isHostException());
assertSame(ex, e.asHostException());
assertFalse(e.isInternalError());
});
proxy.getSize = null;
}
static class ProxyExecutableTest implements ProxyExecutable {
Function<Value[], Object> execute;
int executeCounter;
public Object execute(Value... arguments) {
executeCounter++;
return execute.apply(arguments);
}
}
@Test
public void testProxyExecutable() {
ProxyExecutableTest proxy = new ProxyExecutableTest();
Value value = context.asValue(proxy);
assertTrue(value.canExecute());
assertSame(proxy, value.asProxyObject());
assertEquals(0, proxy.executeCounter);
proxy.execute = (args) -> {
assertEquals(2, args.length);
assertEquals("a", args[0].asString());
assertEquals('a', args[0].as(Object.class));
assertValue(args[0], Trait.STRING);
assertTrue(args[1].isNumber());
assertEquals((byte) 42, args[1].asByte());
assertEquals((short) 42, args[1].asShort());
assertEquals(42, args[1].asInt());
assertEquals(42L, args[1].asLong());
assertEquals(42, args[1].as(Object.class));
assertValue(args[1], Trait.NUMBER);
return 42;
};
assertEquals(42, value.execute('a', 42).asInt());
assertEquals(1, proxy.executeCounter);
final RuntimeException ex = new RuntimeException();
proxy.execute = (args) -> {
throw ex;
};
try {
value.execute();
Assert.fail();
} catch (PolyglotException e) {
assertTrue(e.isHostException());
assertSame(ex, e.asHostException());
assertEquals(2, proxy.executeCounter);
}
assertValue(value, Trait.PROXY_OBJECT, Trait.EXECUTABLE);
}
static class ProxyInstantiableTest implements ProxyInstantiable {
Function<Value[], Object> newInstance;
int newInstanceCounter;
public Object newInstance(Value... arguments) {
newInstanceCounter++;
return newInstance.apply(arguments);
}
}
@Test
public void testProxyInstantiable() {
ProxyInstantiableTest proxy = new ProxyInstantiableTest();
Value value = context.asValue(proxy);
assertTrue(value.canInstantiate());
assertSame(proxy, value.asProxyObject());
assertEquals(0, proxy.newInstanceCounter);
proxy.newInstance = (args) -> {
assertEquals(2, args.length);
assertEquals("a", args[0].asString());
assertEquals('a', args[0].as(Object.class));
assertValue(args[0], Trait.STRING);
assertTrue(args[1].isNumber());
assertEquals((byte) 42, args[1].asByte());
assertEquals((short) 42, args[1].asShort());
assertEquals(42, args[1].asInt());
assertEquals(42L, args[1].asLong());
assertEquals(42, args[1].as(Object.class));
assertValue(args[1], Trait.NUMBER);
return 42;
};
assertEquals(42, value.newInstance('a', 42).asInt());
assertEquals(1, proxy.newInstanceCounter);
final RuntimeException ex = new RuntimeException();
proxy.newInstance = (args) -> {
throw ex;
};
try {
value.newInstance();
Assert.fail();
} catch (PolyglotException e) {
assertTrue(e.isHostException());
assertSame(ex, e.asHostException());
assertEquals(2, proxy.newInstanceCounter);
}
assertValue(value, Trait.PROXY_OBJECT, Trait.INSTANTIABLE);
}
static class ProxyObjectTest implements ProxyObject {
Function<String, Object> getMember;
int getMemberCounter;
Supplier<Object> getMemberKeys;
int getMemberKeysCounter;
Function<String, Boolean> hasMember;
int hasMemberCounter;
BiFunction<String, Value, Void> putMember;
int putMemberCounter;
public Object getMember(String key) {
getMemberCounter++;
return getMember.apply(key);
}
public Object getMemberKeys() {
getMemberKeysCounter++;
return getMemberKeys.get();
}
public boolean hasMember(String key) {
hasMemberCounter++;
return hasMember.apply(key);
}
public void putMember(String key, Value value) {
putMemberCounter++;
putMember.apply(key, value);
}
}
@Test
public void testProxyObject() {
ProxyObjectTest proxy = new ProxyObjectTest();
Value value = context.asValue(proxy);
assertTrue(value.hasMembers());
assertSame(proxy, value.asProxyObject());
proxy.hasMember = (key) -> {
assertEquals("foo", key);
return true;
};
proxy.putMember = (key, v) -> {
assertEquals("foo", key);
assertEquals(42, v.asInt());
assertValue(v, Trait.NUMBER);
return null;
};
value.putMember("foo", 42);
assertEquals(1, proxy.putMemberCounter);
proxy.hasMember = (key) -> {
assertEquals("foo", key);
return true;
};
proxy.hasMemberCounter = 0;
assertTrue(value.hasMember("foo"));
assertTrue(proxy.hasMemberCounter > 0);
proxy.getMember = (key) -> {
assertEquals("foo", key);
return 42;
};
assertEquals(42, value.getMember("foo").asInt());
assertEquals(1, proxy.getMemberCounter);
List<String> testKeys = Arrays.asList("a", "b", "c");
proxy.hasMember = (key) -> {
return testKeys.contains(key);
};
proxy.getMemberKeys = () -> {
return testKeys;
};
assertEquals(new HashSet<>(testKeys), value.getMemberKeys());
assertEquals(1, proxy.getMemberKeysCounter);
proxy.getMemberKeys = () -> {
return testKeys.toArray();
};
assertEquals(new HashSet<>(testKeys), value.getMemberKeys());
assertEquals(2, proxy.getMemberKeysCounter);
proxy.getMemberKeys = () -> {
return ProxyArray.fromArray(testKeys.toArray());
};
assertEquals(new HashSet<>(testKeys), value.getMemberKeys());
assertEquals(3, proxy.getMemberKeysCounter);
proxy.getMemberKeys = () -> {
return null;
};
assertEquals(new HashSet<>(), value.getMemberKeys());
assertEquals(4, proxy.getMemberKeysCounter);
proxy.getMemberKeys = () -> {
return new Object[]{"a", 'c'};
};
assertEquals(new HashSet<>(Arrays.asList("a", "c")), value.getMemberKeys());
assertEquals(5, proxy.getMemberKeysCounter);
proxy.hasMember = (k) -> {
return true;
};
proxy.getMemberKeys = () -> {
return new Object[]{42};
};
assertNull(value.getMemberKeys().iterator().next());
assertEquals(6, proxy.getMemberKeysCounter);
proxy.getMemberKeys = () -> {
return new Object();
};
assertFails(() -> value.getMemberKeys(), PolyglotException.class, (e) -> {
assertTrue(e.isHostException());
assertTrue(e.asHostException() instanceof IllegalStateException);
});
assertEquals(7, proxy.getMemberKeysCounter);
proxy.getMemberKeys = () -> {
return testKeys.toArray();
};
proxy.hasMember = (key) -> {
return testKeys.contains(key);
};
proxy.getMember = (key) -> {
return 42;
};
proxy.putMember = (key, v) -> {
return null;
};
assertValue(value, Trait.PROXY_OBJECT, Trait.MEMBERS);
}
@Test
public void testExceptionFrames() {
Value innerInner = context.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
throw new RuntimeException("foobar");
}
});
Value inner = context.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
return innerInner.execute();
}
});
Value outer = context.asValue(new ProxyExecutable() {
public Object execute(Value... arguments) {
return inner.execute();
}
});
try {
outer.execute();
Assert.fail();
} catch (PolyglotException e) {
assertTrue(e.isHostException());
assertTrue(e.asHostException() instanceof RuntimeException);
assertEquals("foobar", e.getMessage());
Iterator<StackFrame> frameIterator = e.getPolyglotStackTrace().iterator();
StackFrame frame;
for (int i = 0; i < 6; i++) {
frame = frameIterator.next();
assertTrue(frame.isHostFrame());
assertEquals("execute", frame.toHostFrame().getMethodName());
}
frame = frameIterator.next();
assertTrue(frame.isHostFrame());
assertEquals("testExceptionFrames", frame.toHostFrame().getMethodName());
}
}
static class DummyObject {
}
@Test
public void testInvalidArrayCopying() {
Value executable = context.asValue(new ProxyExecutable() {
@Override
public Object execute(Value... args) {
return args[0];
}
});
Value instantiable = context.asValue(new ProxyInstantiable() {
@Override
public Object newInstance(Value... args) {
return args[0];
}
});
DummyObject[] arg0 = new DummyObject[]{new DummyObject()};
DummyObject[] arg1 = new DummyObject[]{new DummyObject(), new DummyObject()};
assertSame(arg0[0], executable.execute((Object[]) arg0).asHostObject());
assertSame(arg1[0], executable.execute((Object[]) arg1).asHostObject());
assertSame(arg0[0], instantiable.newInstance((Object[]) arg0).asHostObject());
assertSame(arg1[0], instantiable.newInstance((Object[]) arg1).asHostObject());
}
@Test
public void testInvalidReturnValue() {
Value date = context.asValue(new ProxyDate() {
public LocalDate asDate() {
return null;
}
});
assertFails(() -> date.asDate(), PolyglotException.class, (e) -> assertTrue(e.asHostException() instanceof AssertionError));
Value time = context.asValue(new ProxyTime() {
public LocalTime asTime() {
return null;
}
});
assertFails(() -> time.asTime(), PolyglotException.class, (e) -> assertTrue(e.asHostException() instanceof AssertionError));
Value timeZone = context.asValue(new ProxyTimeZone() {
public ZoneId asTimeZone() {
return null;
}
});
assertFails(() -> timeZone.asTimeZone(), PolyglotException.class, (e) -> assertTrue(e.asHostException() instanceof AssertionError));
Value instant = context.asValue(new ProxyInstant() {
public Instant asInstant() {
return null;
}
});
assertFails(() -> instant.asInstant(), PolyglotException.class, (e) -> assertTrue(e.asHostException() instanceof AssertionError));
Value duration = context.asValue(new ProxyDuration() {
public Duration asDuration() {
return null;
}
});
assertFails(() -> duration.asDuration(), PolyglotException.class, (e) -> assertTrue(e.asHostException() instanceof AssertionError));
}
static final class P0 implements Proxy {
@Override
public boolean equals(Object obj) {
return obj instanceof P0 || obj instanceof P1;
}
@Override
public int hashCode() {
return 0;
}
}
static final class P1 implements Proxy {
@Override
public boolean equals(Object obj) {
return obj instanceof P0 || obj instanceof P1;
}
@Override
public int hashCode() {
return 0;
}
}
@Test
public void testProxyEquality() {
Value p0 = context.asValue(new P0());
Value p1 = context.asValue(new P1());
assertEquals(p0, p0);
assertEquals(p1, p1);
assertNotEquals(p0, p1);
assertNotEquals(p1, p0);
}
}