| // Copyright (c) 2021, 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.twr; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.TestRuntime.CfVm; |
| import com.android.tools.r8.examples.JavaExampleClassProxy; |
| import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.ZipUtils; |
| import com.android.tools.r8.utils.codeinspector.FoundClassSubject; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class TwrCloseResourceDuplicationTest extends TestBase { |
| |
| private static final String PKG = "twrcloseresourceduplication"; |
| private static final String EXAMPLE = "examplesJava9/" + PKG; |
| private final JavaExampleClassProxy MAIN = |
| new JavaExampleClassProxy(EXAMPLE, PKG + "/TwrCloseResourceDuplication"); |
| private final JavaExampleClassProxy FOO = |
| new JavaExampleClassProxy(EXAMPLE, PKG + "/TwrCloseResourceDuplication$Foo"); |
| private final JavaExampleClassProxy BAR = |
| new JavaExampleClassProxy(EXAMPLE, PKG + "/TwrCloseResourceDuplication$Bar"); |
| |
| static final int INPUT_CLASSES = 3; |
| |
| static final String EXPECTED = |
| StringUtils.lines( |
| "foo opened 1", |
| "foo post close 1", |
| "foo opened 2", |
| "foo caught from 2: RuntimeException", |
| "foo post close 2", |
| "bar opened 1", |
| "bar post close 1", |
| "bar opened 2", |
| "bar caught from 2: RuntimeException", |
| "bar post close 2"); |
| |
| private final TestParameters parameters; |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters() |
| .withCfRuntimesStartingFromIncluding(CfVm.JDK9) |
| .withDexRuntimes() |
| .withAllApiLevelsAlsoForCf() |
| .build(); |
| } |
| |
| public TwrCloseResourceDuplicationTest(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| private String getZipFile() throws IOException { |
| return ZipUtils.ZipBuilder.builder(temp.newFile("file.zip").toPath()) |
| // DEX VMs from 4.4 up-to 9.0 including, will fail if no entry is added. |
| .addBytes("entry", new byte[1]) |
| .build() |
| .toString(); |
| } |
| |
| private List<Path> getProgramInputs() throws Exception { |
| return ImmutableList.of(JavaExampleClassProxy.examplesJar(EXAMPLE)); |
| } |
| |
| @Test |
| public void testJvm() throws Exception { |
| assumeTrue(parameters.isCfRuntime()); |
| testForJvm() |
| .addProgramFiles(getProgramInputs()) |
| .run(parameters.getRuntime(), MAIN.typeName(), getZipFile()) |
| .assertSuccessWithOutput(EXPECTED); |
| } |
| |
| @Test |
| public void testD8() throws Exception { |
| testForD8(parameters.getBackend()) |
| .addProgramFiles(getProgramInputs()) |
| .setMinApi(parameters.getApiLevel()) |
| .run(parameters.getRuntime(), MAIN.typeName(), getZipFile()) |
| .assertSuccessWithOutput(EXPECTED) |
| .inspect( |
| inspector -> { |
| // There should be two synthetic classes besides the three program classes. |
| // One for the desugar version of TWR $closeResource and one for the |
| // Throwable.addSuppressed that is still present in the original $closeResource. |
| // TODO(b/214329923): If the original $closeResource is pruned this will decrease. |
| // TODO(b/168568827): Once we support a nested addSuppressed this will increase. |
| int expectedSynthetics = |
| parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()) |
| ? 2 |
| : 0; |
| assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size()); |
| }); |
| } |
| |
| @Test |
| public void testR8() throws Exception { |
| assumeTrue(parameters.isDexRuntime()); |
| testForR8(parameters.getBackend()) |
| .addProgramFiles(getProgramInputs()) |
| .addKeepMainRule(MAIN.typeName()) |
| .addKeepClassAndMembersRules(FOO.typeName(), BAR.typeName()) |
| // TODO(b/214250388): Don't warn about synthetic code. |
| .applyIf( |
| parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()), |
| builder -> builder.addDontWarn("java.lang.AutoCloseable")) |
| .setMinApi(parameters.getApiLevel()) |
| .addDontObfuscate() |
| .run(parameters.getRuntime(), MAIN.typeName(), getZipFile()) |
| .assertSuccessWithOutput(EXPECTED) |
| .inspect( |
| inspector -> { |
| List<FoundClassSubject> foundClassSubjects = inspector.allClasses(); |
| Set<String> foundClasses = |
| foundClassSubjects.stream() |
| .map(FoundClassSubject::getFinalName) |
| .collect(Collectors.toSet()); |
| // R8 will optimize the generated methods for the two cases below where the thrown |
| // exception is known or not, thus the synthetic methods will be 2. |
| Set<String> nonSyntheticClassOutput = |
| ImmutableSet.of(FOO.typeName(), BAR.typeName(), MAIN.typeName()); |
| if (parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())) { |
| Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput); |
| classOutputWithSynthetics.add( |
| SyntheticItemsTestUtils.syntheticBackportClass(BAR.getClassReference(), 0) |
| .getTypeName()); |
| classOutputWithSynthetics.add( |
| SyntheticItemsTestUtils.syntheticTwrCloseResourceClass( |
| BAR.getClassReference(), 1) |
| .getTypeName()); |
| assertEquals(classOutputWithSynthetics, foundClasses); |
| } else { |
| assertEquals(nonSyntheticClassOutput, foundClasses); |
| } |
| }); |
| } |
| |
| } |