| // Copyright 2017 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.devtools.build.lib.testutil.MoreAsserts.assertThrows; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.devtools.build.android.desugar.testdata.java8.AnnotationsOfDefaultMethodsShouldBeKept.AnnotatedInterface; | |
| import com.google.devtools.build.android.desugar.testdata.java8.AnnotationsOfDefaultMethodsShouldBeKept.SomeAnnotation; | |
| import com.google.devtools.build.android.desugar.testdata.java8.ConcreteDefaultInterfaceWithLambda; | |
| import com.google.devtools.build.android.desugar.testdata.java8.ConcreteOverridesDefaultWithLambda; | |
| import com.google.devtools.build.android.desugar.testdata.java8.DefaultInterfaceMethodWithStaticInitializer; | |
| import com.google.devtools.build.android.desugar.testdata.java8.DefaultInterfaceWithBridges; | |
| import com.google.devtools.build.android.desugar.testdata.java8.DefaultMethodFromSeparateJava8Target; | |
| import com.google.devtools.build.android.desugar.testdata.java8.DefaultMethodFromSeparateJava8TargetOverridden; | |
| import com.google.devtools.build.android.desugar.testdata.java8.DefaultMethodTransitivelyFromSeparateJava8Target; | |
| import com.google.devtools.build.android.desugar.testdata.java8.FunctionWithDefaultMethod; | |
| import com.google.devtools.build.android.desugar.testdata.java8.FunctionalInterfaceWithInitializerAndDefaultMethods; | |
| import com.google.devtools.build.android.desugar.testdata.java8.GenericDefaultInterfaceWithLambda; | |
| import com.google.devtools.build.android.desugar.testdata.java8.InterfaceMethod; | |
| import com.google.devtools.build.android.desugar.testdata.java8.InterfaceWithDefaultMethod; | |
| import com.google.devtools.build.android.desugar.testdata.java8.InterfaceWithDuplicateMethods.ClassWithDuplicateMethods; | |
| import com.google.devtools.build.android.desugar.testdata.java8.InterfaceWithInheritedMethods; | |
| import com.google.devtools.build.android.desugar.testdata.java8.Java7InterfaceWithBridges; | |
| import com.google.devtools.build.android.desugar.testdata.java8.Named; | |
| import com.google.devtools.build.android.desugar.testdata.java8.TwoInheritedDefaultMethods; | |
| import com.google.devtools.build.android.desugar.testdata.java8.VisibilityTestClass; | |
| import java.lang.annotation.Annotation; | |
| import java.lang.reflect.Modifier; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| import java.util.function.Function; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** | |
| * Test that exercises classes in the {@code testdata_java8} 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 DesugarJava8FunctionalTest extends DesugarFunctionalTest { | |
| public DesugarJava8FunctionalTest() { | |
| this(true, true); | |
| } | |
| protected DesugarJava8FunctionalTest( | |
| boolean expectBridgesFromSeparateTarget, boolean expectDefaultMethods) { | |
| super(expectBridgesFromSeparateTarget, expectDefaultMethods); | |
| } | |
| @Test | |
| public void testLambdaInDefaultMethod() { | |
| assertThat(new ConcreteDefaultInterfaceWithLambda().defaultWithLambda()) | |
| .containsExactly("0", "1") | |
| .inOrder(); | |
| } | |
| @Test | |
| public void testLambdaInDefaultCallsInterfaceMethod() { | |
| assertThat(new ConcreteDefaultInterfaceWithLambda().defaultCallsInterfaceMethod()) | |
| .containsExactly("1", "2") | |
| .inOrder(); | |
| } | |
| @Test | |
| public void testOverrideLambdaInDefault() { | |
| assertThat(new ConcreteOverridesDefaultWithLambda().defaultWithLambda()) | |
| .containsExactly("2", "3") | |
| .inOrder(); | |
| } | |
| @Test | |
| public void testLambdaInDefaultCallsOverrideMethod() { | |
| assertThat(new ConcreteOverridesDefaultWithLambda().defaultCallsInterfaceMethod()) | |
| .containsExactly("3", "4") | |
| .inOrder(); | |
| } | |
| @Test | |
| public void testDefaultInterfaceMethodReference() { | |
| InterfaceMethod methodrefUse = new InterfaceMethod.Concrete(); | |
| List<String> dest = | |
| methodrefUse.defaultMethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(dest).containsExactly("Sergey"); | |
| } | |
| @Test | |
| public void testStaticInterfaceMethodReference() { | |
| InterfaceMethod methodrefUse = new InterfaceMethod.Concrete(); | |
| List<String> dest = | |
| methodrefUse.staticMethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(dest).containsExactly("Alex"); | |
| } | |
| @Test | |
| public void testLambdaCallsDefaultMethod() { | |
| InterfaceMethod methodrefUse = new InterfaceMethod.Concrete(); | |
| List<String> dest = | |
| methodrefUse.lambdaCallsDefaultMethod(ImmutableList.of("Sergey", "Larry", "Alex")); | |
| assertThat(dest).containsExactly("Sergey"); | |
| } | |
| @Test | |
| public void testBootclasspathMethodInvocations() { | |
| InterfaceMethod concrete = new InterfaceMethod.Concrete(); | |
| assertThat(concrete.defaultInvokingBootclasspathMethods("Larry")).isEqualTo("Larry"); | |
| } | |
| @Test | |
| public void testStaticMethodsInInterface_explicitAndLambdaBody() { | |
| List<Long> result = FunctionWithDefaultMethod.DoubleInts.add(ImmutableList.of(7, 39, 8), 3); | |
| assertThat(result).containsExactly(10L, 42L, 11L).inOrder(); | |
| } | |
| @Test | |
| public void testOverriddenDefaultMethod_inHandwrittenClass() { | |
| FunctionWithDefaultMethod<Integer> doubler = new FunctionWithDefaultMethod.DoubleInts(); | |
| assertThat(doubler.apply(7)).isEqualTo(14); | |
| assertThat(doubler.twice(7)).isEqualTo(35); | |
| } | |
| @Test | |
| public void testOverriddenDefaultMethod_inHandwrittenSuperclass() { | |
| FunctionWithDefaultMethod<Integer> doubler = new FunctionWithDefaultMethod.DoubleInts2(); | |
| assertThat(doubler.apply(7)).isEqualTo(14); | |
| assertThat(doubler.twice(7)).isEqualTo(35); | |
| } | |
| @Test | |
| public void testInheritedDefaultMethod_inLambda() { | |
| FunctionWithDefaultMethod<Integer> doubler = | |
| FunctionWithDefaultMethod.DoubleInts.doubleLambda(); | |
| assertThat(doubler.apply(7)).isEqualTo(14); | |
| assertThat(doubler.twice(7)).isEqualTo(28); | |
| } | |
| @Test | |
| public void testDefaultMethodReference_onLambda() { | |
| FunctionWithDefaultMethod<Integer> plus6 = FunctionWithDefaultMethod.DoubleInts.incTwice(3); | |
| assertThat(plus6.apply(18)).isEqualTo(24); | |
| assertThat(plus6.twice(18)).isEqualTo(30); | |
| } | |
| @Test | |
| public void testDefaultMethodReference_onHandwrittenClass() { | |
| FunctionWithDefaultMethod<Integer> times5 = FunctionWithDefaultMethod.DoubleInts.times5(); | |
| assertThat(times5.apply(6)).isEqualTo(30); | |
| assertThat(times5.twice(6)).isEqualTo(150); // Irrelevant that DoubleInts overrides twice() | |
| } | |
| @Test | |
| public void testStaticInterfaceMethodReferenceReturned() { | |
| Function<Integer, FunctionWithDefaultMethod<Integer>> factory = | |
| FunctionWithDefaultMethod.DoubleInts.incFactory(); | |
| assertThat(factory.apply(6).apply(7)).isEqualTo(13); | |
| assertThat(factory.apply(6).twice(7)).isEqualTo(19); | |
| } | |
| @Test | |
| public void testSuperDefaultMethodInvocation() { | |
| assertThat(new TwoInheritedDefaultMethods().name()).isEqualTo("One:Two"); | |
| assertThat(new Named.DefaultName().name()).isEqualTo("DefaultName-once"); | |
| assertThat(new Named.DefaultNameSubclass().name()).isEqualTo("DefaultNameSubclass-once-twice"); | |
| } | |
| @Test | |
| public void testInheritedPreferredOverDefault() throws Exception { | |
| assertThat(new Named.ExplicitName("hello").name()).isEqualTo("hello"); | |
| // Make sure AbstractName remains abstract, despite default method from implemented interface | |
| assertThat(Modifier.isAbstract(Named.AbstractName.class.getMethod("name").getModifiers())) | |
| .isTrue(); | |
| } | |
| @Test | |
| public void testRedefinedDefaultMethod() throws Exception { | |
| assertThat(new InterfaceWithDefaultMethod.Version2().version()).isEqualTo(2); | |
| } | |
| @Test | |
| public void testDefaultMethodRedefinedInSubclass() throws Exception { | |
| assertThat(new InterfaceWithDefaultMethod.AlsoVersion2().version()).isEqualTo(2); | |
| } | |
| @Test | |
| public void testDefaultMethodVisibility() { | |
| assertThat(new VisibilityTestClass().m()).isEqualTo(42); | |
| } | |
| /** Test for b/38302860 */ | |
| @Test | |
| public void testAnnotationsOfDefaultMethodsAreKept() throws Exception { | |
| { | |
| Annotation[] annotations = AnnotatedInterface.class.getAnnotations(); | |
| assertThat(annotations).hasLength(1); | |
| assertThat(annotations[0]).isInstanceOf(SomeAnnotation.class); | |
| assertThat(((SomeAnnotation) annotations[0]).value()).isEqualTo(1); | |
| } | |
| { | |
| Annotation[] annotations = | |
| AnnotatedInterface.class.getMethod("annotatedAbstractMethod").getAnnotations(); | |
| assertThat(annotations).hasLength(1); | |
| assertThat(annotations[0]).isInstanceOf(SomeAnnotation.class); | |
| assertThat(((SomeAnnotation) annotations[0]).value()).isEqualTo(2); | |
| } | |
| { | |
| Annotation[] annotations = | |
| AnnotatedInterface.class.getMethod("annotatedDefaultMethod").getAnnotations(); | |
| assertThat(annotations).hasLength(1); | |
| assertThat(annotations[0]).isInstanceOf(SomeAnnotation.class); | |
| assertThat(((SomeAnnotation) annotations[0]).value()).isEqualTo(3); | |
| } | |
| } | |
| /** Test for b/38308515 */ | |
| @Test | |
| public void testDefaultAndStaticMethodNameClash() { | |
| final ClassWithDuplicateMethods instance = new ClassWithDuplicateMethods(); | |
| assertThat(instance.getZero()).isEqualTo(0); | |
| assertThat(instance.getZeroFromStaticInterfaceMethod()).isEqualTo(1); | |
| } | |
| /** | |
| * Test for b/38257037 | |
| * | |
| * <p>Note that, we intentionally suppress unchecked warnings, because we expect some | |
| * ClassCastException to test bridge methods. | |
| */ | |
| @SuppressWarnings("unchecked") | |
| @Test | |
| public void testBridgeAndDefaultMethods() { | |
| { | |
| DefaultInterfaceWithBridges object = new DefaultInterfaceWithBridges(); | |
| Integer one = 1; | |
| assertThat(object.copy(one)).isEqualTo(one); | |
| assertThat(object.copy((Number) one)).isEqualTo(one); | |
| assertThrows(ClassCastException.class, () -> object.copy(Double.valueOf(1))); | |
| assertThat(object.getNumber()).isInstanceOf(Double.class); | |
| assertThat(object.getNumber()).isEqualTo(Double.valueOf(2.3d)); | |
| assertThat(object.getDouble()).isEqualTo(Double.valueOf(2.3d)); | |
| } | |
| { | |
| Java7InterfaceWithBridges.ClassAddTwo testObject = | |
| new Java7InterfaceWithBridges.ClassAddTwo(); | |
| assertThat(testObject.add(Integer.valueOf(2))).isEqualTo(4); | |
| @SuppressWarnings("rawtypes") | |
| Java7InterfaceWithBridges top = testObject; | |
| assertThat(top.add(Integer.valueOf(2))).isEqualTo(4); | |
| assertThrows(ClassCastException.class, () -> top.add(new Object())); | |
| assertThrows(ClassCastException.class, () -> top.add(Double.valueOf(1))); | |
| assertThrows(ClassCastException.class, () -> top.add(Long.valueOf(1))); | |
| @SuppressWarnings("rawtypes") | |
| Java7InterfaceWithBridges.LevelOne levelOne = testObject; | |
| assertThat(levelOne.add(Integer.valueOf(2))).isEqualTo(4); | |
| assertThrows(ClassCastException.class, () -> top.add(new Object())); | |
| assertThrows(ClassCastException.class, () -> top.add(Double.valueOf(1))); | |
| assertThrows(ClassCastException.class, () -> top.add(Long.valueOf(1))); | |
| @SuppressWarnings("rawtypes") | |
| Java7InterfaceWithBridges.LevelOne levelTwo = testObject; | |
| assertThat(levelTwo.add(Integer.valueOf(2))).isEqualTo(4); | |
| assertThrows(ClassCastException.class, () -> levelTwo.add(Double.valueOf(1))); | |
| assertThrows(ClassCastException.class, () -> levelTwo.add(Long.valueOf(1))); | |
| } | |
| { | |
| GenericDefaultInterfaceWithLambda.ClassTwo testObject = | |
| new GenericDefaultInterfaceWithLambda.ClassTwo(); | |
| assertThat(testObject.increment(Integer.valueOf(0))).isEqualTo(1); | |
| assertThat(testObject.toString(Integer.valueOf(0))).isEqualTo("0"); | |
| assertThat(testObject.getBaseValue()).isEqualTo(Integer.valueOf(0)); | |
| assertThat(testObject.toList(0)).isEmpty(); | |
| assertThat(testObject.toList(1)).containsExactly(0).inOrder(); | |
| assertThat(testObject.toList(2)).containsExactly(0, 1).inOrder(); | |
| assertThat(((Function<Integer, ArrayList<Integer>>) testObject.toListSupplier()).apply(0)) | |
| .isEmpty(); | |
| assertThat(((Function<Integer, ArrayList<Integer>>) testObject.toListSupplier()).apply(1)) | |
| .containsExactly(0) | |
| .inOrder(); | |
| assertThat(((Function<Integer, ArrayList<Integer>>) testObject.toListSupplier()).apply(2)) | |
| .containsExactly(0, 1) | |
| .inOrder(); | |
| assertThat(testObject.convertToStringList(ImmutableList.of(0))) | |
| .containsExactly("0") | |
| .inOrder(); | |
| assertThat(testObject.convertToStringList(ImmutableList.of(0, 1))) | |
| .containsExactly("0", "1") | |
| .inOrder(); | |
| @SuppressWarnings("rawtypes") | |
| GenericDefaultInterfaceWithLambda top = testObject; | |
| assertThrows(ClassCastException.class, () -> top.increment(Long.valueOf(1))); | |
| assertThrows(ClassCastException.class, () -> top.toString(Long.valueOf(1))); | |
| assertThat(top.increment(Integer.valueOf(0))).isEqualTo(1); | |
| assertThat(top.toString(Integer.valueOf(0))).isEqualTo("0"); | |
| assertThat(top.getBaseValue()).isEqualTo(Integer.valueOf(0)); | |
| assertThat(top.toList(0)).isEmpty(); | |
| assertThat(top.toList(1)).containsExactly(0).inOrder(); | |
| assertThat(top.toList(2)).containsExactly(0, 1).inOrder(); | |
| assertThat(((Function<Integer, ArrayList<Integer>>) top.toListSupplier()).apply(0)).isEmpty(); | |
| assertThat(((Function<Integer, ArrayList<Integer>>) top.toListSupplier()).apply(1)) | |
| .containsExactly(0) | |
| .inOrder(); | |
| assertThat(((Function<Integer, ArrayList<Integer>>) top.toListSupplier()).apply(2)) | |
| .containsExactly(0, 1) | |
| .inOrder(); | |
| assertThat(top.convertToStringList(ImmutableList.of(0))).containsExactly("0").inOrder(); | |
| assertThat(top.convertToStringList(ImmutableList.of(0, 1))) | |
| .containsExactly("0", "1") | |
| .inOrder(); | |
| } | |
| { | |
| @SuppressWarnings("rawtypes") | |
| GenericDefaultInterfaceWithLambda testObject = | |
| new GenericDefaultInterfaceWithLambda.ClassThree(); | |
| assertThat(testObject.getBaseValue()).isEqualTo(Long.valueOf(0)); | |
| assertThat(testObject.increment(Long.valueOf(0))).isEqualTo(Long.valueOf(0 + 1)); | |
| assertThat(testObject.toString(Long.valueOf(0))).isEqualTo(Long.valueOf(0).toString()); | |
| assertThrows(ClassCastException.class, () -> testObject.increment(Integer.valueOf(0))); | |
| assertThrows(ClassCastException.class, () -> testObject.toString(Integer.valueOf(0))); | |
| assertThat(testObject.toList(2)).containsExactly(Long.valueOf(0), Long.valueOf(1)).inOrder(); | |
| assertThat(testObject.convertToStringList(testObject.toList(1))).containsExactly("0"); | |
| assertThat(((Function<Integer, ArrayList<Long>>) testObject.toListSupplier()).apply(2)) | |
| .containsExactly(Long.valueOf(0), Long.valueOf(1)); | |
| } | |
| } | |
| /** | |
| * Test for b/62047432. | |
| * | |
| * <p>When desugaring a functional interface with an executable clinit and default methods, we | |
| * erase the body of clinit to avoid executing it during desugaring. This test makes sure that all | |
| * the constants defined in the interface are still there after desugaring. | |
| */ | |
| @Test | |
| public void testFunctionalInterfaceWithExecutableClinitStillWorkAfterDesugar() { | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.CONSTANT.length("").convert()) | |
| .isEqualTo(0); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.CONSTANT.length("1").convert()) | |
| .isEqualTo(1); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.BOOLEAN).isFalse(); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.CHAR).isEqualTo('h'); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.BYTE).isEqualTo(0); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.SHORT).isEqualTo(0); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.INT).isEqualTo(0); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.FLOAT).isEqualTo(0f); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.LONG).isEqualTo(0); | |
| assertThat(FunctionalInterfaceWithInitializerAndDefaultMethods.DOUBLE).isEqualTo(0d); | |
| } | |
| /** Test for b/38255926. */ | |
| @Test | |
| public void testDefaultMethodInitializationOrder() { | |
| { | |
| assertThat(new DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetOne.C().sum()) | |
| .isEqualTo(11); // To trigger loading the class C and its super interfaces. | |
| assertThat( | |
| DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetOne | |
| .getRealInitializationOrder()) | |
| .isEqualTo( | |
| DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetOne | |
| .getExpectedInitializationOrder()); | |
| } | |
| { | |
| assertThat(new DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetTwo.C().sum()) | |
| .isEqualTo(3); | |
| assertThat( | |
| DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetTwo | |
| .getRealInitializationOrder()) | |
| .isEqualTo( | |
| DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetTwo | |
| .getExpectedInitializationOrder()); | |
| } | |
| { | |
| assertThat(new DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetThree.C().sum()) | |
| .isEqualTo(11); | |
| assertThat( | |
| DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetThree | |
| .getRealInitializationOrder()) | |
| .isEqualTo( | |
| DefaultInterfaceMethodWithStaticInitializer.TestInterfaceSetThree | |
| .getExpectedInitializationOrder()); | |
| } | |
| } | |
| /** | |
| * Tests that default methods on the classpath are correctly handled. We'll also verify the | |
| * metadata that's emitted for this case to make sure the binary-wide double-check for correct | |
| * desugaring of default and static interface methods keeps working (b/65645388). | |
| */ | |
| @Test | |
| public void testDefaultMethodsInSeparateTarget() { | |
| assertThat(new DefaultMethodFromSeparateJava8Target().dflt()).isEqualTo("dflt"); | |
| assertThat(new DefaultMethodTransitivelyFromSeparateJava8Target().dflt()).isEqualTo("dflt"); | |
| assertThat(new DefaultMethodFromSeparateJava8TargetOverridden().dflt()).isEqualTo("override"); | |
| } | |
| /** Regression test for b/73355452 */ | |
| @Test | |
| public void testSuperCallToInheritedDefaultMethod() { | |
| assertThat(new InterfaceWithInheritedMethods.Impl().name()).isEqualTo("Base"); | |
| assertThat(new InterfaceWithInheritedMethods.Impl().suffix()).isEqualTo("!"); | |
| } | |
| } |