Lambda implementation / reflection tests
This test checks a subset of lambda compiler and
runtime behavior via java.lang and java.lang.reflect
APIs.
Change-Id: I1bd6ad7925253ff3b6f6f950045ec0d9c87bca8c
diff --git a/luni/src/test/java/libcore/java/lang/LambdaImplementationTest.java b/luni/src/test/java/libcore/java/lang/LambdaImplementationTest.java
new file mode 100644
index 0000000..d32fa20
--- /dev/null
+++ b/luni/src/test/java/libcore/java/lang/LambdaImplementationTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package libcore.java.lang;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+public class LambdaImplementationTest extends TestCase {
+
+ private static final String MSG = "Hello World";
+
+ public void testNonCapturingLambda() throws Exception {
+ Callable<String> r1 = () -> MSG;
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaImplementsInterfaces(r1, Callable.class);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertNonSerializableLambdaCharacteristics(r1);
+ assertCallableBehavior(r1, MSG);
+
+ Callable<String> r2 = () -> MSG;
+ assertMultipleInstanceCharacteristics(r1, r2);
+ }
+
+ interface Condition<T> {
+ boolean check(T arg);
+ }
+
+ public void testInstanceMethodReferenceLambda() throws Exception {
+ Condition<String> c = String::isEmpty;
+ Class<?> lambdaClass = c.getClass();
+ assertGeneralLambdaClassCharacteristics(c);
+ assertLambdaImplementsInterfaces(c, Condition.class);
+ assertLambdaMethodCharacteristics(c, Condition.class);
+ assertNonSerializableLambdaCharacteristics(c);
+
+ // Check the behavior of the lambda's method.
+ assertTrue(c.check(""));
+ assertFalse(c.check("notEmpty"));
+
+ Method implCallMethod = lambdaClass.getMethod(
+ "check", Object.class /* type erasure => not String.class */);
+ assertTrue((Boolean) implCallMethod.invoke(c, ""));
+ assertFalse((Boolean) implCallMethod.invoke(c, "notEmpty"));
+
+ Method interfaceCallMethod = Condition.class.getDeclaredMethod(
+ "check", Object.class /* type erasure => not String.class */);
+ assertTrue((Boolean) interfaceCallMethod.invoke(c, ""));
+ assertFalse((Boolean) interfaceCallMethod.invoke(c, "notEmpty"));
+ }
+
+ public void testStaticMethodReferenceLambda() throws Exception {
+ Callable<String> r1 = LambdaImplementationTest::staticMethod;
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaImplementsInterfaces(r1, Callable.class);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertNonSerializableLambdaCharacteristics(r1);
+
+ assertCallableBehavior(r1, MSG);
+
+ Callable<String> r2 = LambdaImplementationTest::staticMethod;
+ assertMultipleInstanceCharacteristics(r1, r2);
+ }
+
+ public void testObjectMethodReferenceLambda() throws Exception {
+ StringBuilder o = new StringBuilder(MSG);
+ Callable<String> r1 = o::toString;
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaImplementsInterfaces(r1, Callable.class);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertNonSerializableLambdaCharacteristics(r1);
+
+ assertCallableBehavior(r1, MSG);
+
+ Callable<String> r2 = o::toString;
+ assertMultipleInstanceCharacteristics(r1, r2);
+ }
+
+ public void testArgumentCapturingLambda() throws Exception {
+ String msg = MSG;
+ Callable<String> r1 = () -> msg;
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaImplementsInterfaces(r1, Callable.class);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertNonSerializableLambdaCharacteristics(r1);
+
+ assertCallableBehavior(r1, MSG);
+
+ Callable<String> r2 = () -> msg;
+ assertMultipleInstanceCharacteristics(r1, r2);
+ }
+
+ public void testSerializableLambda_withoutState() throws Exception {
+ Callable<String> r1 = (Callable<String> & Serializable) () -> MSG;
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaImplementsInterfaces(r1, Callable.class, Serializable.class);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertSerializableLambdaCharacteristics(r1);
+
+ assertCallableBehavior(r1, MSG);
+
+ Callable<String> r2 = (Callable<String> & Serializable) () -> MSG;
+ assertMultipleInstanceCharacteristics(r1, r2);
+ }
+
+ public void testSerializableLambda_withState() throws Exception {
+ final int state = 123;
+ Callable<String> r1 = (Callable<String> & Serializable) () -> MSG + state;
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaImplementsInterfaces(r1, Callable.class, Serializable.class);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertSerializableLambdaCharacteristics(r1);
+
+ assertCallableBehavior(r1, MSG + state);
+
+ Callable<String> deserializedR1 = roundtripSerialization(r1);
+ assertEquals(r1.call(), deserializedR1.call());
+ }
+
+ public void testBadSerializableLambda() throws Exception {
+ final Object state = new Object(); // Not Serializable
+ Callable<String> r1 = (Callable<String> & Serializable) () -> "Hello world: " + state;
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertLambdaImplementsInterfaces(r1, Callable.class, Serializable.class);
+
+ try {
+ serializeObject(r1);
+ fail();
+ } catch (NotSerializableException expected) {
+ }
+ }
+
+ public void testMultipleInterfaceLambda() throws Exception {
+ Callable<String> r1 = (Callable<String> & MarkerInterface) () -> MSG;
+ assertTrue(r1 instanceof MarkerInterface);
+ assertGeneralLambdaClassCharacteristics(r1);
+ assertLambdaMethodCharacteristics(r1, Callable.class);
+ assertLambdaImplementsInterfaces(r1, Callable.class, MarkerInterface.class);
+ assertNonSerializableLambdaCharacteristics(r1);
+
+ assertCallableBehavior(r1, MSG);
+ }
+
+ private static void assertSerializableLambdaCharacteristics(Object r1) throws Exception {
+ assertTrue(r1 instanceof Serializable);
+
+ Object deserializedR1 = roundtripSerialization(r1);
+ assertFalse(deserializedR1.equals(r1));
+ assertNotSame(deserializedR1, r1);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T roundtripSerialization(T r1) throws Exception {
+ byte[] bytes = serializeObject(r1);
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ try (ObjectInputStream is = new ObjectInputStream(bais)) {
+ return (T) is.readObject();
+ }
+ }
+
+ private static <T> byte[] serializeObject(T r1) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ObjectOutputStream os = new ObjectOutputStream(baos)) {
+ os.writeObject(r1);
+ os.flush();
+ }
+ return baos.toByteArray();
+ }
+
+ private static <T> void assertLambdaImplementsInterfaces(T r1, Class<?>... expectedInterfaces)
+ throws Exception {
+ Class<?> lambdaClass = r1.getClass();
+
+ // Check directly implemented interfaces. Ordering is well-defined.
+ Class<?>[] actualInterfaces = lambdaClass.getInterfaces();
+ assertEquals(expectedInterfaces.length, actualInterfaces.length);
+ List<Class<?>> actual = Arrays.asList(actualInterfaces);
+ List<Class<?>> expected = Arrays.asList(expectedInterfaces);
+ assertEquals(expected, actual);
+
+ // Confirm that the only method declared on the lambda's class are those defined by
+ // interfaces it implements. i.e. there's no additional public contract.
+ Set<Method> declaredMethods = new HashSet<>();
+ addNonStaticPublicMethods(lambdaClass, declaredMethods);
+ Set<Method> expectedMethods = new HashSet<>();
+ for (Class<?> interfaceClass : expectedInterfaces) {
+ // Obtain methods declared by super-interfaces too.
+ while (interfaceClass != null) {
+ addNonStaticPublicMethods(interfaceClass, expectedMethods);
+ interfaceClass = interfaceClass.getSuperclass();
+ }
+ }
+ assertEquals(expectedMethods.size(), declaredMethods.size());
+
+ // Check the method signatures are compatible.
+ for (Method expectedMethod : expectedMethods) {
+ Method actualMethod =
+ lambdaClass.getMethod(expectedMethod.getName(),
+ expectedMethod.getParameterTypes());
+ assertEquals(expectedMethod.getReturnType(), actualMethod.getReturnType());
+ }
+ }
+
+ private static void addNonStaticPublicMethods(Class<?> clazz, Set<Method> methodSet) {
+ for (Method interfaceMethod : clazz.getDeclaredMethods()) {
+ int modifiers = interfaceMethod.getModifiers();
+ if ((!Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
+ methodSet.add(interfaceMethod);
+ }
+ }
+ }
+
+ private static void assertNonSerializableLambdaCharacteristics(Object r1) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ObjectOutputStream os = new ObjectOutputStream(baos)) {
+ os.writeObject(r1);
+ os.flush();
+ fail();
+ } catch (NotSerializableException expected) {
+ }
+ }
+
+ private static void assertMultipleInstanceCharacteristics(Object r1, Object r2)
+ throws Exception {
+
+ // Unclear if any of this is *guaranteed* to be true.
+
+ // Check the objects are not the same and do not equal. This could influence collection
+ // behavior.
+ assertNotSame(r1, r2);
+ assertTrue(!r1.equals(r2));
+
+ // Confirm the classes differ.
+ Class<?> lambda1Class = r1.getClass();
+ Class<?> lambda2Class = r2.getClass();
+ assertNotSame(lambda1Class, lambda2Class);
+ }
+
+ private static void assertGeneralLambdaClassCharacteristics(Object r1) throws Exception {
+ Class<?> lambdaClass = r1.getClass();
+
+ // Lambda objects have classes that have names.
+ assertNotNull(lambdaClass.getName());
+ assertNotNull(lambdaClass.getSimpleName());
+ assertNotNull(lambdaClass.getCanonicalName());
+
+ // Lambda classes are "synthetic classes" that are not arrays.
+ assertFalse(lambdaClass.isAnnotation());
+ assertFalse(lambdaClass.isInterface());
+ assertFalse(lambdaClass.isArray());
+ assertFalse(lambdaClass.isEnum());
+ assertFalse(lambdaClass.isPrimitive());
+ assertTrue(lambdaClass.isSynthetic());
+ assertNull(lambdaClass.getComponentType());
+
+ // Expected modifiers
+ int classModifiers = lambdaClass.getModifiers();
+ assertTrue(Modifier.isFinal(classModifiers));
+
+ // Unexpected modifiers
+ assertFalse(Modifier.isPrivate(classModifiers));
+ assertFalse(Modifier.isPublic(classModifiers));
+ assertFalse(Modifier.isProtected(classModifiers));
+ assertFalse(Modifier.isStatic(classModifiers));
+ assertFalse(Modifier.isSynchronized(classModifiers));
+ assertFalse(Modifier.isVolatile(classModifiers));
+ assertFalse(Modifier.isTransient(classModifiers));
+ assertFalse(Modifier.isNative(classModifiers));
+ assertFalse(Modifier.isInterface(classModifiers));
+ assertFalse(Modifier.isAbstract(classModifiers));
+ assertFalse(Modifier.isStrict(classModifiers));
+
+ // Check the classloader, inheritance hierarchy and package.
+ assertSame(LambdaImplementationTest.class.getClassLoader(), lambdaClass.getClassLoader());
+ assertSame(Object.class, lambdaClass.getSuperclass());
+ assertSame(Object.class, lambdaClass.getGenericSuperclass());
+ assertEquals(LambdaImplementationTest.class.getPackage(), lambdaClass.getPackage());
+
+ // Check the implementation of the non-final public methods that all Objects possess.
+ assertNotNull(r1.toString());
+ assertTrue(r1.equals(r1));
+ assertEquals(System.identityHashCode(r1), r1.hashCode());
+ }
+
+ private static <T> void assertLambdaMethodCharacteristics(T r1, Class<?> samInterfaceClass)
+ throws Exception {
+ // Find the single abstract method on the interface.
+ Method singleAbstractMethod = null;
+ for (Method method : samInterfaceClass.getDeclaredMethods()) {
+ if (Modifier.isAbstract(method.getModifiers())) {
+ singleAbstractMethod = method;
+ break;
+ }
+ }
+ assertNotNull(singleAbstractMethod);
+
+ // Confirm the lambda implements the method as expected.
+ Method implementationMethod = r1.getClass().getMethod(
+ singleAbstractMethod.getName(), singleAbstractMethod.getParameterTypes());
+ assertSame(singleAbstractMethod.getReturnType(), implementationMethod.getReturnType());
+ assertSame(r1.getClass(), implementationMethod.getDeclaringClass());
+ assertFalse(implementationMethod.isSynthetic());
+ assertFalse(implementationMethod.isBridge());
+ assertFalse(implementationMethod.isDefault());
+ }
+
+ private static String staticMethod() {
+ return MSG;
+ }
+
+ private interface MarkerInterface {
+ }
+
+ private static <T> void assertCallableBehavior(Callable<T> r1, T expectedResult)
+ throws Exception {
+ assertEquals(expectedResult, r1.call());
+
+ Method implCallMethod = r1.getClass().getDeclaredMethod("call");
+ assertEquals(expectedResult, implCallMethod.invoke(r1));
+
+ Method interfaceCallMethod = Callable.class.getDeclaredMethod("call");
+ assertEquals(expectedResult, interfaceCallMethod.invoke(r1));
+ }
+}