| // Copyright 2016 The Bazel Authors. All rights reserved. | |
| // | |
| // 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 com.google.devtools.build.android.desugar; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.common.truth.Truth.assertWithMessage; | |
| import static java.lang.reflect.Modifier.isFinal; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import static org.junit.Assert.assertThrows; | |
| import static org.junit.Assert.fail; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.io.ByteStreams; | |
| import com.google.devtools.build.android.desugar.testdata.CaptureLambda; | |
| import com.google.devtools.build.android.desugar.testdata.ConcreteFunction; | |
| import com.google.devtools.build.android.desugar.testdata.ConstructorReference; | |
| import com.google.devtools.build.android.desugar.testdata.GuavaLambda; | |
| import com.google.devtools.build.android.desugar.testdata.InnerClassLambda; | |
| import com.google.devtools.build.android.desugar.testdata.InterfaceWithLambda; | |
| import com.google.devtools.build.android.desugar.testdata.Lambda; | |
| import com.google.devtools.build.android.desugar.testdata.LambdaInOverride; | |
| import com.google.devtools.build.android.desugar.testdata.MethodReference; | |
| import com.google.devtools.build.android.desugar.testdata.MethodReferenceInSubclass; | |
| import com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass; | |
| import com.google.devtools.build.android.desugar.testdata.OuterReferenceLambda; | |
| import com.google.devtools.build.android.desugar.testdata.SpecializedFunction; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| import java.util.concurrent.Callable; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Test that exercises classes in the {@code testdata} package. This is meant to be run against a | |
| * desugared version of those classes, which in turn exercise various desugaring features. | |
| */ | |
| @RunWith(JUnit4.class) | |
| public class DesugarFunctionalTest { | |
| private final int expectedBridgesFromSameTarget; | |
| private final int expectedBridgesFromSeparateTarget; | |
| private final boolean expectLambdaMethodsInInterfaces; | |
| public DesugarFunctionalTest() { | |
| this(3, 1, false); | |
| } | |
| /** Constructor for testing desugar while allowing default and static interface methods. */ | |
| protected DesugarFunctionalTest( | |
| boolean expectBridgesFromSeparateTarget, boolean expectDefaultMethods) { | |
| this( | |
| expectDefaultMethods ? 0 : 3, | |
| expectBridgesFromSeparateTarget ? 1 : 0, | |
| expectDefaultMethods); | |
| } | |
| private DesugarFunctionalTest( | |
| int bridgesFromSameTarget, int bridgesFromSeparateTarget, boolean lambdaMethodsInInterfaces) { | |
| this.expectedBridgesFromSameTarget = bridgesFromSameTarget; | |
| this.expectedBridgesFromSeparateTarget = bridgesFromSeparateTarget; | |
| this.expectLambdaMethodsInInterfaces = lambdaMethodsInInterfaces; | |
| } | |
| @Test | |
| public void testGuavaLambda() { | |
| GuavaLambda lambdaUse = new GuavaLambda(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(lambdaUse.as()).containsExactly("Alex"); | |
| } | |
| @Test | |
| public void testJavaLambda() { | |
| Lambda lambdaUse = new Lambda(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(lambdaUse.as()).containsExactly("Alex"); | |
| } | |
| @Test | |
| public void testLambdaForIntersectionType() throws Exception { | |
| assertThat(Lambda.hello().call()).isEqualTo("hello"); | |
| } | |
| @Test | |
| public void testCapturingLambda() { | |
| CaptureLambda lambdaUse = new CaptureLambda(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(lambdaUse.prefixed("L")).containsExactly("Larry"); | |
| } | |
| @Test | |
| public void testOuterReferenceLambda() throws Exception { | |
| OuterReferenceLambda lambdaUse = new OuterReferenceLambda(ImmutableList.of("Sergey", "Larry")); | |
| assertThat(lambdaUse.filter(ImmutableList.of("Larry", "Alex"))).containsExactly("Larry"); | |
| assertThat( | |
| isFinal( | |
| OuterReferenceLambda.class | |
| .getDeclaredMethod("lambda$filter$0$OuterReferenceLambda", String.class) | |
| .getModifiers())) | |
| .isTrue(); | |
| } | |
| /** | |
| * Tests a lambda in a subclass whose generated lambda$ method has the same name and signature as | |
| * a lambda$ method generated by Javac in a superclass and both of these methods are used in the | |
| * implementation of the subclass (by calling super). Naively this leads to wrong behavior (in | |
| * this case, return a non-empty list) because the lambda$ in the superclass is never used once | |
| * its made non-private during desugaring. | |
| */ | |
| @Test | |
| public void testOuterReferenceLambdaInOverride() throws Exception { | |
| OuterReferenceLambda lambdaUse = new LambdaInOverride(ImmutableList.of("Sergey", "Larry")); | |
| assertThat(lambdaUse.filter(ImmutableList.of("Larry", "Alex"))).isEmpty(); | |
| assertThat( | |
| isFinal( | |
| LambdaInOverride.class | |
| .getDeclaredMethod("lambda$filter$0$LambdaInOverride", String.class) | |
| .getModifiers())) | |
| .isTrue(); | |
| } | |
| @Test | |
| public void testLambdaInAnonymousClassReferencesSurroundingMethodParameter() throws Exception { | |
| assertThat(Lambda.mult(21).apply(2).call()).isEqualTo(42); | |
| } | |
| /** Tests a lambda that accesses a method parameter across 2 nested anonymous classes. */ | |
| @Test | |
| public void testLambdaInNestedAnonymousClass() throws Exception { | |
| InnerClassLambda lambdaUse = new InnerClassLambda(ImmutableList.of("Sergey", "Larry")); | |
| assertThat(lambdaUse.prefixFilter("L").apply(ImmutableList.of("Lois", "Larry")).call()) | |
| .containsExactly("Larry"); | |
| } | |
| @Test | |
| public void testClassMethodReference() { | |
| MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| StringBuilder dest = new StringBuilder(); | |
| methodrefUse.appendAll(dest); | |
| assertThat(dest.toString()).isEqualTo("SergeyLarryAlex"); | |
| } | |
| // Regression test for b/33378312 | |
| @Test | |
| public void testHiddenMethodReference() { | |
| MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(methodrefUse.intersect(ImmutableList.of("Alex", "Sundar"))).containsExactly("Alex"); | |
| } | |
| // Regression test for b/33378312 | |
| @Test | |
| public void testHiddenStaticMethodReference() { | |
| MethodReference methodrefUse = | |
| new MethodReference(ImmutableList.of("Sergey", "Larry", "Sundar")); | |
| assertThat(methodrefUse.some()).containsExactly("Sergey", "Sundar"); | |
| } | |
| @Test | |
| public void testDuplicateHiddenMethodReference() { | |
| MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(methodrefUse.onlyIn(ImmutableList.of("Alex", "Sundar"))).containsExactly("Sundar"); | |
| } | |
| // Regression test for b/36201257 | |
| @Test | |
| public void testMethodReferenceThatNeedsBridgeInSubclass() { | |
| MethodReferenceInSubclass methodrefUse = | |
| new MethodReferenceInSubclass(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(methodrefUse.containsE()).containsExactly("Sergey", "Alex"); | |
| assertThat(methodrefUse.startsWithL()).containsExactly("Larry"); | |
| // Test sanity: make sure sub- and superclass have bridge methods with matching descriptors but | |
| // different names | |
| Method superclassBridge = findOnlyBridge(MethodReferenceSuperclass.class); | |
| Method subclassBridge = findOnlyBridge(MethodReferenceInSubclass.class); | |
| assertThat(superclassBridge.getName()).isNotEqualTo(subclassBridge.getName()); | |
| assertThat(superclassBridge.getParameterTypes()).isEqualTo(subclassBridge.getParameterTypes()); | |
| } | |
| private Method findOnlyBridge(Class<?> clazz) { | |
| Method result = null; | |
| for (Method m : clazz.getDeclaredMethods()) { | |
| if (m.getName().startsWith("bridge$")) { | |
| assertWithMessage(m.getName()).that(result).isNull(); | |
| result = m; | |
| } | |
| } | |
| assertWithMessage(clazz.getSimpleName()).that(result).isNotNull(); | |
| return result; | |
| } | |
| // Regression test for b/33378312 | |
| @Test | |
| public void testThrowingPrivateMethodReference() throws Exception { | |
| MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry")); | |
| Callable<?> stringer = methodrefUse.stringer(); | |
| try { | |
| stringer.call(); | |
| fail("IOException expected"); | |
| } catch (IOException expected) { | |
| assertThat(expected).hasMessageThat().isEqualTo("SergeyLarry"); | |
| } catch (Exception e) { | |
| throw e; | |
| } | |
| } | |
| // Regression test for b/33304582 | |
| @Test | |
| public void testInterfaceMethodReference() { | |
| MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| MethodReference.Transformer<String> transform = | |
| new MethodReference.Transformer<String>() { | |
| @Override | |
| public String transform(String input) { | |
| return input.substring(1); | |
| } | |
| }; | |
| assertThat(methodrefUse.transform(transform)).containsExactly("ergey", "arry", "lex"); | |
| } | |
| @Test | |
| public void testConstructorReference() { | |
| ConstructorReference initRefUse = new ConstructorReference(ImmutableList.of("1", "2", "42")); | |
| assertThat(initRefUse.toInt()).containsExactly(1, 2, 42); | |
| } | |
| // Regression test for b/33304582 | |
| @Test | |
| public void testPrivateConstructorReference() { | |
| ConstructorReference initRefUse = ConstructorReference.singleton().apply("17"); | |
| assertThat(initRefUse.toInt()).containsExactly(17); | |
| } | |
| // This test is similar to testPrivateConstructorReference but the private constructor of an inner | |
| // class is used as a method reference. That causes Javac to generate a bridge constructor and | |
| // a lambda body method that calls it, so the desugaring step doesn't need to do anything to make | |
| // the private constructor visible. This is mostly to double-check that we don't interfere with | |
| // this "already-working" scenario. | |
| @Test | |
| public void testPrivateConstructorAccessedThroughJavacGeneratedBridge() { | |
| @SuppressWarnings("ReturnValueIgnored") | |
| RuntimeException expected = | |
| assertThrows( | |
| RuntimeException.class, | |
| () -> ConstructorReference.emptyThroughJavacGeneratedBridge().get()); | |
| assertThat(expected).hasMessageThat().isEqualTo("got it!"); | |
| } | |
| @Test | |
| public void testExpressionMethodReference() { | |
| assertThat( | |
| MethodReference.stringChars(new StringBuilder().append("Larry").append("Sergey")) | |
| .apply(5)) | |
| .isEqualTo('S'); | |
| } | |
| @Test | |
| public void testFieldMethodReference() { | |
| MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(methodrefUse.toPredicate().test("Larry")).isTrue(); | |
| assertThat(methodrefUse.toPredicate().test("Sundar")).isFalse(); | |
| } | |
| @Test | |
| public void testConcreteFunctionWithInheritedBridgeMethods() { | |
| assertThat(new ConcreteFunction().apply("1234567890987654321")).isEqualTo(1234567890987654321L); | |
| assertThat(ConcreteFunction.parseAll(ImmutableList.of("5", "17"), new ConcreteFunction())) | |
| .containsExactly(5L, 17L); | |
| } | |
| @Test | |
| public void testLambdaWithInheritedBridgeMethods() throws Exception { | |
| assertThat(ConcreteFunction.toInt().apply("123456789")).isEqualTo(123456789); | |
| assertThat(ConcreteFunction.parseAll(ImmutableList.of("5", "17"), ConcreteFunction.toInt())) | |
| .containsExactly(5, 17); | |
| // Expect String apply(Number) and any expected bridges | |
| assertThat(ConcreteFunction.toInt().getClass().getDeclaredMethods()) | |
| .hasLength(expectedBridgesFromSameTarget + 1); | |
| // Sanity check that we only copied over methods, no fields, from the functional interface | |
| assertThrows( | |
| NoSuchFieldException.class, | |
| () -> | |
| ConcreteFunction.toInt() | |
| .getClass() | |
| .getDeclaredField("DO_NOT_COPY_INTO_LAMBDA_CLASSES")); | |
| assertThat(SpecializedFunction.class.getDeclaredField("DO_NOT_COPY_INTO_LAMBDA_CLASSES")) | |
| .isNotNull(); // test sanity | |
| } | |
| /** Tests lambdas with bridge methods when the implemented interface is in a separate target. */ | |
| @Test | |
| public void testLambdaWithBridgeMethodsForInterfaceInSeparateTarget() { | |
| assertThat(ConcreteFunction.isInt().test(123456789L)).isTrue(); | |
| assertThat( | |
| ConcreteFunction.doFilter( | |
| ImmutableList.of(123456789L, 1234567890987654321L), ConcreteFunction.isInt())) | |
| .containsExactly(123456789L); | |
| // Expect test(Number) and any expected bridges | |
| assertThat(ConcreteFunction.isInt().getClass().getDeclaredMethods()) | |
| .hasLength(expectedBridgesFromSeparateTarget + 1); | |
| } | |
| @Test | |
| public void testLambdaInInterfaceStaticInitializer() { | |
| assertThat(InterfaceWithLambda.DIGITS).containsExactly("0", "1").inOrder(); | |
| // <clinit> doesn't count but if there's a lambda method then Jacoco adds more methods | |
| assertThat(InterfaceWithLambda.class.getDeclaredMethods().length != 0) | |
| .isEqualTo(expectLambdaMethodsInInterfaces); | |
| } | |
| /** Sanity-checks that the resource file included in the original Jar is still there unchanged. */ | |
| @Test | |
| public void testResourcePreserved() throws Exception { | |
| try (InputStream content = Lambda.class.getResource("testresource.txt").openStream()) { | |
| assertThat(new String(ByteStreams.toByteArray(content), UTF_8)).isEqualTo("test"); | |
| } | |
| } | |
| /** | |
| * Test for b/62456849. After desugar, the method {@code lambda$mult$0} should still be in the | |
| * class. | |
| */ | |
| @Test | |
| public void testCallMethodWithLambdaNamingConvention() | |
| throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { | |
| Method method = Lambda.class.getDeclaredMethod("lambda$mult$0"); | |
| Object value = method.invoke(null); | |
| assertThat(value).isInstanceOf(Integer.class); | |
| assertThat((Integer) value).isEqualTo(0); | |
| } | |
| } |