1// Copyright 2016 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14package com.google.devtools.build.android.desugar; 15 16import static com.google.common.truth.Truth.assertThat; 17import static java.lang.reflect.Modifier.isFinal; 18import static java.nio.charset.StandardCharsets.UTF_8; 19import static org.junit.Assert.fail; 20 21import com.google.common.collect.ImmutableList; 22import com.google.common.io.ByteStreams; 23import com.google.devtools.build.android.desugar.testdata.CaptureLambda; 24import com.google.devtools.build.android.desugar.testdata.ConcreteFunction; 25import com.google.devtools.build.android.desugar.testdata.ConstructorReference; 26import com.google.devtools.build.android.desugar.testdata.GuavaLambda; 27import com.google.devtools.build.android.desugar.testdata.InnerClassLambda; 28import com.google.devtools.build.android.desugar.testdata.InterfaceWithLambda; 29import com.google.devtools.build.android.desugar.testdata.Lambda; 30import com.google.devtools.build.android.desugar.testdata.LambdaInOverride; 31import com.google.devtools.build.android.desugar.testdata.MethodReference; 32import com.google.devtools.build.android.desugar.testdata.MethodReferenceInSubclass; 33import com.google.devtools.build.android.desugar.testdata.MethodReferenceSuperclass; 34import com.google.devtools.build.android.desugar.testdata.OuterReferenceLambda; 35import com.google.devtools.build.android.desugar.testdata.SpecializedFunction; 36import java.io.IOException; 37import java.io.InputStream; 38import java.lang.reflect.InvocationTargetException; 39import java.lang.reflect.Method; 40import java.util.concurrent.Callable; 41import org.junit.Test; 42import org.junit.runner.RunWith; 43import org.junit.runners.JUnit4; 44 45/** 46 * Test that exercises classes in the {@code testdata} package. This is meant to be run against a 47 * desugared version of those classes, which in turn exercise various desugaring features. 48 */ 49@RunWith(JUnit4.class) 50public class DesugarFunctionalTest { 51 52 private final int expectedBridgesFromSameTarget; 53 private final int expectedBridgesFromSeparateTarget; 54 private final boolean expectLambdaMethodsInInterfaces; 55 56 public DesugarFunctionalTest() { 57 this(3, 1, false); 58 } 59 60 /** Constructor for testing desugar while allowing default and static interface methods. */ 61 protected DesugarFunctionalTest( 62 boolean expectBridgesFromSeparateTarget, boolean expectDefaultMethods) { 63 this( 64 expectDefaultMethods ? 0 : 3, 65 expectBridgesFromSeparateTarget ? 1 : 0, 66 expectDefaultMethods); 67 } 68 69 private DesugarFunctionalTest(int bridgesFromSameTarget, int bridgesFromSeparateTarget, 70 boolean lambdaMethodsInInterfaces) { 71 this.expectedBridgesFromSameTarget = bridgesFromSameTarget; 72 this.expectedBridgesFromSeparateTarget = bridgesFromSeparateTarget; 73 this.expectLambdaMethodsInInterfaces = lambdaMethodsInInterfaces; 74 } 75 76 @Test 77 public void testGuavaLambda() { 78 GuavaLambda lambdaUse = new GuavaLambda(ImmutableList.of("Sergey", "Larry", "Alex")); 79 assertThat(lambdaUse.as()).containsExactly("Alex"); 80 } 81 82 @Test 83 public void testJavaLambda() { 84 Lambda lambdaUse = new Lambda(ImmutableList.of("Sergey", "Larry", "Alex")); 85 assertThat(lambdaUse.as()).containsExactly("Alex"); 86 } 87 88 @Test 89 public void testLambdaForIntersectionType() throws Exception { 90 assertThat(Lambda.hello().call()).isEqualTo("hello"); 91 } 92 93 @Test 94 public void testCapturingLambda() { 95 CaptureLambda lambdaUse = new CaptureLambda(ImmutableList.of("Sergey", "Larry", "Alex")); 96 assertThat(lambdaUse.prefixed("L")).containsExactly("Larry"); 97 } 98 99 @Test 100 public void testOuterReferenceLambda() throws Exception { 101 OuterReferenceLambda lambdaUse = new OuterReferenceLambda(ImmutableList.of("Sergey", "Larry")); 102 assertThat(lambdaUse.filter(ImmutableList.of("Larry", "Alex"))).containsExactly("Larry"); 103 assertThat( 104 isFinal( 105 OuterReferenceLambda.class 106 .getDeclaredMethod("lambda$filter$0$OuterReferenceLambda", String.class) 107 .getModifiers())) 108 .isTrue(); 109 } 110 111 /** 112 * Tests a lambda in a subclass whose generated lambda$ method has the same name and signature 113 * as a lambda$ method generated by Javac in a superclass and both of these methods are used 114 * in the implementation of the subclass (by calling super). Naively this leads to wrong 115 * behavior (in this case, return a non-empty list) because the lambda$ in the superclass is never 116 * used once its made non-private during desugaring. 117 */ 118 @Test 119 public void testOuterReferenceLambdaInOverride() throws Exception { 120 OuterReferenceLambda lambdaUse = new LambdaInOverride(ImmutableList.of("Sergey", "Larry")); 121 assertThat(lambdaUse.filter(ImmutableList.of("Larry", "Alex"))).isEmpty(); 122 assertThat( 123 isFinal( 124 LambdaInOverride.class 125 .getDeclaredMethod("lambda$filter$0$LambdaInOverride", String.class) 126 .getModifiers())) 127 .isTrue(); 128 } 129 130 @Test 131 public void testLambdaInAnonymousClassReferencesSurroundingMethodParameter() throws Exception { 132 assertThat(Lambda.mult(21).apply(2).call()).isEqualTo(42); 133 } 134 135 /** Tests a lambda that accesses a method parameter across 2 nested anonymous classes. */ 136 @Test 137 public void testLambdaInNestedAnonymousClass() throws Exception { 138 InnerClassLambda lambdaUse = new InnerClassLambda(ImmutableList.of("Sergey", "Larry")); 139 assertThat(lambdaUse.prefixFilter("L").apply(ImmutableList.of("Lois", "Larry")).call()) 140 .containsExactly("Larry"); 141 } 142 143 @Test 144 public void testClassMethodReference() { 145 MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); 146 StringBuilder dest = new StringBuilder(); 147 methodrefUse.appendAll(dest); 148 assertThat(dest.toString()).isEqualTo("SergeyLarryAlex"); 149 } 150 151 // Regression test for b/33378312 152 @Test 153 public void testHiddenMethodReference() { 154 MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); 155 assertThat(methodrefUse.intersect(ImmutableList.of("Alex", "Sundar"))).containsExactly("Alex"); 156 } 157 158 // Regression test for b/33378312 159 @Test 160 public void testHiddenStaticMethodReference() { 161 MethodReference methodrefUse = 162 new MethodReference(ImmutableList.of("Sergey", "Larry", "Sundar")); 163 assertThat(methodrefUse.some()).containsExactly("Sergey", "Sundar"); 164 } 165 166 @Test 167 public void testDuplicateHiddenMethodReference() { 168 MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); 169 assertThat(methodrefUse.onlyIn(ImmutableList.of("Alex", "Sundar"))).containsExactly("Sundar"); 170 } 171 172 // Regression test for b/36201257 173 @Test 174 public void testMethodReferenceThatNeedsBridgeInSubclass() { 175 MethodReferenceInSubclass methodrefUse = 176 new MethodReferenceInSubclass(ImmutableList.of("Sergey", "Larry", "Alex")); 177 assertThat(methodrefUse.containsE()).containsExactly("Sergey", "Alex"); 178 assertThat(methodrefUse.startsWithL()).containsExactly("Larry"); 179 // Test sanity: make sure sub- and superclass have bridge methods with matching descriptors but 180 // different names 181 Method superclassBridge = findOnlyBridge(MethodReferenceSuperclass.class); 182 Method subclassBridge = findOnlyBridge(MethodReferenceInSubclass.class); 183 assertThat(superclassBridge.getName()).isNotEqualTo(subclassBridge.getName()); 184 assertThat(superclassBridge.getParameterTypes()).isEqualTo(subclassBridge.getParameterTypes()); 185 } 186 187 private Method findOnlyBridge(Class<?> clazz) { 188 Method result = null; 189 for (Method m : clazz.getDeclaredMethods()) { 190 if (m.getName().startsWith("bridge$")) { 191 assertThat(result).named(m.getName()).isNull(); 192 result = m; 193 } 194 } 195 assertThat(result).named(clazz.getSimpleName()).isNotNull(); 196 return result; 197 } 198 199 // Regression test for b/33378312 200 @Test 201 public void testThrowingPrivateMethodReference() throws Exception { 202 MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry")); 203 Callable<?> stringer = methodrefUse.stringer(); 204 try { 205 stringer.call(); 206 fail("IOException expected"); 207 } catch (IOException expected) { 208 assertThat(expected).hasMessage("SergeyLarry"); 209 } catch (Exception e) { 210 throw e; 211 } 212 } 213 214 // Regression test for b/33304582 215 @Test 216 public void testInterfaceMethodReference() { 217 MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); 218 MethodReference.Transformer<String> transform = new MethodReference.Transformer<String>() { 219 @Override 220 public String transform(String input) { 221 return input.substring(1); 222 } 223 }; 224 assertThat(methodrefUse.transform(transform)).containsExactly("ergey", "arry", "lex"); 225 } 226 227 @Test 228 public void testConstructorReference() { 229 ConstructorReference initRefUse = new ConstructorReference(ImmutableList.of("1", "2", "42")); 230 assertThat(initRefUse.toInt()).containsExactly(1, 2, 42); 231 } 232 233 // Regression test for b/33304582 234 @Test 235 public void testPrivateConstructorReference() { 236 ConstructorReference initRefUse = ConstructorReference.singleton().apply("17"); 237 assertThat(initRefUse.toInt()).containsExactly(17); 238 } 239 240 // This test is similar to testPrivateConstructorReference but the private constructor of an inner 241 // class is used as a method reference. That causes Javac to generate a bridge constructor and 242 // a lambda body method that calls it, so the desugaring step doesn't need to do anything to make 243 // the private constructor visible. This is mostly to double-check that we don't interfere with 244 // this "already-working" scenario. 245 @Test 246 public void testPrivateConstructorAccessedThroughJavacGeneratedBridge() { 247 try { 248 @SuppressWarnings("unused") // local is needed to make ErrorProne happy 249 ConstructorReference unused = ConstructorReference.emptyThroughJavacGeneratedBridge().get(); 250 fail("RuntimeException expected"); 251 } catch (RuntimeException expected) { 252 assertThat(expected).hasMessage("got it!"); 253 } 254 } 255 256 @Test 257 public void testExpressionMethodReference() { 258 assertThat( 259 MethodReference.stringChars(new StringBuilder().append("Larry").append("Sergey")) 260 .apply(5)) 261 .isEqualTo('S'); 262 } 263 264 @Test 265 public void testFieldMethodReference() { 266 MethodReference methodrefUse = new MethodReference(ImmutableList.of("Sergey", "Larry", "Alex")); 267 assertThat(methodrefUse.toPredicate().test("Larry")).isTrue(); 268 assertThat(methodrefUse.toPredicate().test("Sundar")).isFalse(); 269 } 270 271 @Test 272 public void testConcreteFunctionWithInheritedBridgeMethods() { 273 assertThat(new ConcreteFunction().apply("1234567890987654321")).isEqualTo(1234567890987654321L); 274 assertThat(ConcreteFunction.parseAll(ImmutableList.of("5", "17"), new ConcreteFunction())) 275 .containsExactly(5L, 17L); 276 } 277 278 @Test 279 public void testLambdaWithInheritedBridgeMethods() throws Exception { 280 assertThat(ConcreteFunction.toInt().apply("123456789")).isEqualTo(123456789); 281 assertThat(ConcreteFunction.parseAll(ImmutableList.of("5", "17"), ConcreteFunction.toInt())) 282 .containsExactly(5, 17); 283 // Expect String apply(Number) and any expected bridges 284 assertThat(ConcreteFunction.toInt().getClass().getDeclaredMethods()) 285 .hasLength(expectedBridgesFromSameTarget + 1); 286 // Sanity check that we only copied over methods, no fields, from the functional interface 287 try { 288 ConcreteFunction.toInt().getClass().getDeclaredField("DO_NOT_COPY_INTO_LAMBDA_CLASSES"); 289 fail("NoSuchFieldException expected"); 290 } catch (NoSuchFieldException expected) {} 291 assertThat(SpecializedFunction.class.getDeclaredField("DO_NOT_COPY_INTO_LAMBDA_CLASSES")) 292 .isNotNull(); // test sanity 293 } 294 295 /** Tests lambdas with bridge methods when the implemented interface is in a separate target.*/ 296 @Test 297 public void testLambdaWithBridgeMethodsForInterfaceInSeparateTarget() { 298 assertThat(ConcreteFunction.isInt().test(123456789L)).isTrue(); 299 assertThat( 300 ConcreteFunction.doFilter( 301 ImmutableList.of(123456789L, 1234567890987654321L), 302 ConcreteFunction.isInt())) 303 .containsExactly(123456789L); 304 // Expect test(Number) and any expected bridges 305 assertThat(ConcreteFunction.isInt().getClass().getDeclaredMethods()) 306 .hasLength(expectedBridgesFromSeparateTarget + 1); 307 } 308 309 @Test 310 public void testLambdaInInterfaceStaticInitializer() { 311 assertThat(InterfaceWithLambda.DIGITS).containsExactly("0", "1").inOrder(); 312 // <clinit> doesn't count but if there's a lambda method then Jacoco adds more methods 313 assertThat(InterfaceWithLambda.class.getDeclaredMethods().length != 0) 314 .isEqualTo(expectLambdaMethodsInInterfaces); 315 } 316 317 /** 318 * Sanity-checks that the resource file included in the original Jar is still there unchanged. 319 */ 320 @Test 321 public void testResourcePreserved() throws Exception { 322 try (InputStream content = Lambda.class.getResource("testresource.txt").openStream()) { 323 assertThat(new String(ByteStreams.toByteArray(content), UTF_8)).isEqualTo("test"); 324 } 325 } 326 327 /** 328 * Test for b/62456849. After desugar, the method {@code lambda$mult$0} should still be in the 329 * class. 330 */ 331 @Test 332 public void testCallMethodWithLambdaNamingConvention() 333 throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 334 Method method = Lambda.class.getDeclaredMethod("lambda$mult$0"); 335 Object value = method.invoke(null); 336 assertThat(value).isInstanceOf(Integer.class); 337 assertThat((Integer) value).isEqualTo(0); 338 } 339} 340