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));
+    }
+}