| // Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.android.tools.r8.desugar.staticinterfacemethod; |
| |
| import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticDesugarTest.Library.foo; |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeFalse; |
| |
| import com.android.tools.r8.DesugarTestConfiguration; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestRunResult; |
| import com.android.tools.r8.ToolHelper.DexVm; |
| import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.BooleanUtils; |
| import com.android.tools.r8.utils.IntBox; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; |
| import com.google.common.collect.ImmutableList; |
| import java.nio.file.Path; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Set; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class InvokeStaticDesugarTest extends TestBase { |
| |
| private final TestParameters parameters; |
| private final boolean intermediate; |
| private final String EXPECTED = "Hello World!"; |
| |
| @Parameters(name = "{0}, intermediate in first step: {1}") |
| public static Collection<Object[]> data() { |
| return buildParameters( |
| getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(), |
| BooleanUtils.values()); |
| } |
| |
| public InvokeStaticDesugarTest(TestParameters parameters, boolean intermediate) { |
| this.parameters = parameters; |
| this.intermediate = intermediate; |
| } |
| |
| @Test |
| public void testDesugar() throws Exception { |
| // Intermediate not used in this test. |
| assumeFalse(intermediate); |
| |
| final TestRunResult<?> runResult = |
| testForDesugaring(parameters) |
| .addLibraryClasses(Library.class) |
| .addProgramClasses(Main.class) |
| .addRunClasspathFiles(compileRunClassPath()) |
| .run(parameters.getRuntime(), Main.class); |
| if (parameters.isDexRuntime() |
| && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) { |
| runResult.assertFailureWithErrorThatMatches(containsString("java.lang.VerifyError")); |
| } else { |
| runResult.assertSuccessWithOutputLines(EXPECTED); |
| } |
| } |
| |
| @Test |
| public void testDoubleDesugar() throws Exception { |
| // Desugar using API level that cannot leave static interface invokes. |
| Path jar = |
| testForD8(Backend.CF) |
| .addLibraryClasses(Library.class) |
| .addProgramClasses(Main.class) |
| .setMinApi(AndroidApiLevel.B) |
| .setIntermediate(intermediate) |
| .compile() |
| .inspect(i -> assertEquals(1, getSyntheticMethods(i).size())) |
| .writeToZip(); |
| |
| testForDesugaring(parameters) |
| .addLibraryClasses(Library.class) |
| .addProgramFiles(jar) |
| .addRunClasspathFiles(compileRunClassPath()) |
| .run(parameters.getRuntime(), Main.class) |
| .applyIf( |
| // When double desugaring to API level below L two synthetics are seen. |
| c -> |
| DesugarTestConfiguration.isDesugared(c) |
| && (parameters.isCfRuntime() |
| || parameters |
| .getRuntime() |
| .asDex() |
| .getVm() |
| .isNewerThan(DexVm.ART_4_4_4_HOST)) |
| && parameters.getApiLevel().isLessThan(AndroidApiLevel.L), |
| r -> { |
| assertEquals(intermediate ? 1 : 2, countSynthetics(r)); |
| r.assertSuccessWithOutputLines(EXPECTED); |
| }, |
| // Don't inspect failing code, as inspection is only supported when run succeeds, |
| // and testForDesugaring does not have separate compile where the code can be |
| // inspected before running. |
| c -> |
| parameters.isDexRuntime() |
| && parameters |
| .getRuntime() |
| .asDex() |
| .getVm() |
| .isOlderThanOrEqual(DexVm.ART_4_4_4_HOST), |
| r -> r.assertFailureWithErrorThatMatches(containsString("java.lang.VerifyError")), |
| // When double desugaring to API level L and above one synthetics seen. |
| r -> { |
| assertEquals(1, countSynthetics(r)); |
| r.assertSuccessWithOutputLines(EXPECTED); |
| }); |
| } |
| |
| private Path compileRunClassPath() throws Exception { |
| if (parameters.isCfRuntime()) { |
| return compileToZip(parameters, ImmutableList.of(), Library.class); |
| } else { |
| assert parameters.isDexRuntime(); |
| return testForD8(parameters.getBackend()) |
| .addProgramClasses(Library.class) |
| .setMinApi(parameters.getApiLevel()) |
| .disableDesugaring() |
| .addOptionsModification( |
| options -> { |
| options.testing.allowStaticInterfaceMethodsForPreNApiLevel = true; |
| }) |
| .compile() |
| .writeToZip(); |
| } |
| } |
| |
| private int countSynthetics(TestRunResult<?> r) { |
| IntBox box = new IntBox(); |
| try { |
| r.inspect(inspector -> box.set(getSyntheticMethods(inspector).size())); |
| } catch (Exception e) { |
| box.set(-1); |
| fail(); |
| } |
| return box.get(); |
| } |
| |
| private Set<FoundMethodSubject> getSyntheticMethods(CodeInspector inspector) { |
| Set<FoundMethodSubject> methods = new HashSet<>(); |
| inspector |
| .allClasses() |
| .forEach( |
| c -> |
| assertTrue( |
| !SyntheticItemsTestUtils.isExternalSynthetic(c.getFinalReference()) |
| || SyntheticItemsTestUtils.isExternalStaticInterfaceCall( |
| c.getFinalReference()))); |
| inspector.allClasses().stream() |
| .filter(c -> SyntheticItemsTestUtils.isExternalStaticInterfaceCall(c.getFinalReference())) |
| .forEach(c -> methods.addAll(c.allMethods(m -> !m.isInstanceInitializer()))); |
| return methods; |
| } |
| |
| public interface Library { |
| |
| static void foo() { |
| System.out.println("Hello World!"); |
| } |
| } |
| |
| public static class Main { |
| |
| public static void main(String[] args) { |
| foo(); |
| } |
| } |
| } |