blob: 930ae3747181856522bee94a80adb03c22635086 [file] [log] [blame]
// 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.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;
}
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 {
assertEquals(nonSyntheticClassOutput, foundClasses);
}
});
}
}