|  | // 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 twr.twrcloseresourceduplication; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  |  | 
|  | 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.ToolHelper; | 
|  | import com.android.tools.r8.references.Reference; | 
|  | import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; | 
|  | import com.android.tools.r8.utils.AndroidApiLevel; | 
|  | import com.android.tools.r8.utils.InternalOptions; | 
|  | 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.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; | 
|  | import org.junit.runners.Parameterized.Parameter; | 
|  | import org.junit.runners.Parameterized.Parameters; | 
|  | import twr.twrcloseresourceduplication.asm.TwrCloseResourceDuplication$BarDump; | 
|  | import twr.twrcloseresourceduplication.asm.TwrCloseResourceDuplication$FooDump; | 
|  | import twr.twrcloseresourceduplication.asm.TwrCloseResourceDuplicationDump; | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class TwrCloseResourceDuplicationTest extends TestBase { | 
|  |  | 
|  | protected static final String MAIN = | 
|  | "twr.twrcloseresourceduplication.TwrCloseResourceDuplication"; | 
|  | protected static final String FOO = | 
|  | "twr.twrcloseresourceduplication.TwrCloseResourceDuplication$Foo"; | 
|  | protected static final String BAR = | 
|  | "twr.twrcloseresourceduplication.TwrCloseResourceDuplication$Bar"; | 
|  |  | 
|  | static final int INPUT_CLASSES = 3; | 
|  |  | 
|  | protected 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"); | 
|  |  | 
|  | @Parameter(0) | 
|  | public TestParameters parameters; | 
|  |  | 
|  | @Parameters(name = "{0}") | 
|  | public static TestParametersCollection data() { | 
|  | return getTestParameters() | 
|  | .withCfRuntimesStartingFromIncluding(CfVm.JDK9) | 
|  | .withDexRuntimes() | 
|  | .withAllApiLevelsAlsoForCf() | 
|  | .build(); | 
|  | } | 
|  |  | 
|  | protected boolean hasTwrCloseResourceSupport(boolean isDesugaring) { | 
|  | return !isDesugaring | 
|  | || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport()); | 
|  | } | 
|  |  | 
|  | protected boolean hasTwrCloseResourceApiOutlines() { | 
|  | return parameters.isDexRuntime() | 
|  | && parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()); | 
|  | } | 
|  |  | 
|  | protected 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(); | 
|  | } | 
|  |  | 
|  | protected static List<byte[]> getProgramInputs() throws Exception { | 
|  | return ImmutableList.of( | 
|  | TwrCloseResourceDuplicationDump.dump(), | 
|  | TwrCloseResourceDuplication$FooDump.dump(), | 
|  | TwrCloseResourceDuplication$BarDump.dump()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testJvm() throws Exception { | 
|  | parameters.assumeJvmTestParameters(); | 
|  | testForJvm(parameters) | 
|  | .addProgramClassFileData(getProgramInputs()) | 
|  | .run(parameters.getRuntime(), MAIN, getZipFile()) | 
|  | .assertSuccessWithOutput(EXPECTED); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testD8() throws Exception { | 
|  | testForD8(parameters.getBackend()) | 
|  | .addProgramClassFileData(getProgramInputs()) | 
|  | .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) | 
|  | .setMinApi(parameters) | 
|  | .run(parameters.getRuntime(), MAIN, getZipFile()) | 
|  | .assertSuccessWithOutput(EXPECTED) | 
|  | .inspect( | 
|  | inspector -> { | 
|  | // NOTE: The $closeResource helper is _only_ generated by the JDK-9 compiler. | 
|  | // | 
|  | // 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 = 0; | 
|  | if (!hasTwrCloseResourceSupport(true)) { | 
|  | expectedSynthetics += 2; | 
|  | } | 
|  | if (hasTwrCloseResourceApiOutlines()) { | 
|  | expectedSynthetics += 1; | 
|  | } | 
|  | InternalOptions options = inspector.getApplication().options; | 
|  | options.setMinApiLevel(parameters.getApiLevel()); | 
|  | if (options.shouldDesugarAutoCloseable()) { | 
|  | expectedSynthetics += 3; | 
|  | } | 
|  | assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testR8() throws Exception { | 
|  | parameters.assumeDexRuntime(); | 
|  | testForR8(parameters.getBackend()) | 
|  | .addProgramClassFileData(getProgramInputs()) | 
|  | .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) | 
|  | .addKeepMainRule(MAIN) | 
|  | .addKeepClassAndMembersRules(FOO, BAR) | 
|  | .setMinApi(parameters) | 
|  | .addDontObfuscate() | 
|  | .run(parameters.getRuntime(), MAIN, 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, BAR, MAIN); | 
|  | if (!hasTwrCloseResourceSupport(parameters.isDexRuntime())) { | 
|  | Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput); | 
|  | classOutputWithSynthetics.add( | 
|  | SyntheticItemsTestUtils.syntheticApiOutlineClass( | 
|  | Reference.classFromTypeName(BAR), 0) | 
|  | .getTypeName()); | 
|  | assertEquals(classOutputWithSynthetics, foundClasses); | 
|  | } else { | 
|  | Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput); | 
|  | if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) { | 
|  | // Above N, the forwarder is inlined in the dispatcher. | 
|  | classOutputWithSynthetics.add( | 
|  | SyntheticItemsTestUtils.syntheticAutoCloseableForwarderClass( | 
|  | Reference.classFromTypeName(BAR), 1) | 
|  | .getTypeName()); | 
|  | } | 
|  | classOutputWithSynthetics.add( | 
|  | SyntheticItemsTestUtils.syntheticAutoCloseableDispatcherClass( | 
|  | Reference.classFromTypeName(BAR), 0) | 
|  | .getTypeName()); | 
|  | assertEquals(classOutputWithSynthetics, foundClasses); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } |