| // 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 java.lang.reflect.Modifier.isFinal; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| 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$")) { |
| assertThat(result).named(m.getName()).isNull(); |
| result = m; |
| } |
| } |
| assertThat(result).named(clazz.getSimpleName()).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).hasMessage("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() { |
| try { |
| @SuppressWarnings("unused") // local is needed to make ErrorProne happy |
| ConstructorReference unused = ConstructorReference.emptyThroughJavacGeneratedBridge().get(); |
| fail("RuntimeException expected"); |
| } catch (RuntimeException expected) { |
| assertThat(expected).hasMessage("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 |
| try { |
| ConcreteFunction.toInt().getClass().getDeclaredField("DO_NOT_COPY_INTO_LAMBDA_CLASSES"); |
| fail("NoSuchFieldException expected"); |
| } catch (NoSuchFieldException expected) {} |
| 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); |
| } |
| } |