blob: 2913fe594594256b5924bf8e30e0f318c0679e73 [file] [log] [blame]
// 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.backports;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
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.desugar.backports.AbstractBackportTest.MiniAssert;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class BackportDuplicationTest extends TestBase {
static final String EXPECTED = StringUtils.lines("Hello, world");
static final List<Class<?>> CLASSES =
ImmutableList.of(MiniAssert.class, TestClass.class, User1.class, User2.class);
static final List<String> CLASS_TYPE_NAMES =
CLASSES.stream().map(Class::getTypeName).collect(Collectors.toList());
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.J).build();
}
public BackportDuplicationTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testR8() throws Exception {
runR8(false);
runR8(true);
}
private void runR8(boolean minify) throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(CLASSES)
.addKeepMainRule(TestClass.class)
.addKeepClassAndMembersRules(MiniAssert.class)
.setMinApi(parameters.getApiLevel())
.minification(minify)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
.inspect(this::checkNoInternalSyntheticNames);
}
@Test
public void testD8() throws Exception {
List<String> run1 = getClassesAfterD8CompileAndRun();
List<String> run2 = getClassesAfterD8CompileAndRun();
assertEquals("Non deterministic synthesis", run1, run2);
}
private List<String> getClassesAfterD8CompileAndRun() throws Exception {
return testForD8(parameters.getBackend())
.addProgramClasses(CLASSES)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED)
.inspect(this::checkNoInternalSyntheticNames)
.inspect(this::checkExpectedOutput)
.inspector()
.allClasses()
.stream()
.filter(c -> !CLASS_TYPE_NAMES.contains(c.getFinalName()))
.flatMap(c -> c.allMethods().stream().map(m -> m.asMethodReference().toString()))
.sorted()
.collect(Collectors.toList());
}
private void checkNoInternalSyntheticNames(CodeInspector inspector) {
inspector.forAllClasses(
clazz -> {
assertThat(
clazz.getFinalName(),
not(containsString(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
});
}
private void checkExpectedOutput(CodeInspector inspector) {
// TODO(b/158159959): Once synthetic methods can be grouped in classes this should become 1.
int expectedSynthesizedClasses = 3;
// Total number of synthetic methods should be 3 ({Boolean,Character,Long}.compare).
int expectedSynthesizedMethods = 3;
// Desugaring should add exactly one class with one desugared method.
assertEquals(expectedSynthesizedClasses, inspector.allClasses().size() - CLASSES.size());
assertThat(
inspector.allClasses().stream()
.map(ClassSubject::getOriginalName)
.collect(Collectors.toList()),
hasItems(CLASS_TYPE_NAMES.toArray()));
List<FoundMethodSubject> methods =
inspector.allClasses().stream()
.filter(clazz -> !CLASS_TYPE_NAMES.contains(clazz.getOriginalName()))
.flatMap(clazz -> clazz.allMethods().stream())
.collect(Collectors.toList());
assertEquals(expectedSynthesizedMethods, methods.size());
}
static class User1 {
private static void testBooleanCompare() {
// These 4 calls should share the same synthetic method.
MiniAssert.assertTrue(Boolean.compare(true, false) > 0);
MiniAssert.assertTrue(Boolean.compare(true, true) == 0);
MiniAssert.assertTrue(Boolean.compare(false, false) == 0);
MiniAssert.assertTrue(Boolean.compare(false, true) < 0);
}
private static void testCharacterCompare() {
// All 6 (User1 and User2) calls should share the same synthetic method.
MiniAssert.assertTrue(Character.compare('b', 'a') > 0);
MiniAssert.assertTrue(Character.compare('a', 'a') == 0);
MiniAssert.assertTrue(Character.compare('a', 'b') < 0);
}
}
static class User2 {
private static void testCharacterCompare() {
// All 6 (User1 and User2) calls should share the same synthetic method.
MiniAssert.assertTrue(Character.compare('y', 'x') > 0);
MiniAssert.assertTrue(Character.compare('x', 'x') == 0);
MiniAssert.assertTrue(Character.compare('x', 'y') < 0);
}
private static void testIntegerCompare() {
// These 3 calls should share the same synthetic method.
MiniAssert.assertTrue(Integer.compare(2, 0) > 0);
MiniAssert.assertTrue(Integer.compare(0, 0) == 0);
MiniAssert.assertTrue(Integer.compare(0, 2) < 0);
}
}
static class TestClass {
public static void main(String[] args) {
User1.testBooleanCompare();
User1.testCharacterCompare();
User2.testCharacterCompare();
User2.testIntegerCompare();
System.out.println("Hello, world");
}
}
}