blob: 0c6d576505a2b7d3e698ce32ec60fe7a1bf25fd4 [file] [log] [blame]
// Copyright (c) 2019, 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 com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.ints.Int2IntAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
abstract class AbstractBackportTest extends TestBase {
protected final TestParameters parameters;
private final ClassInfo targetClass;
private final ClassInfo testClass;
private final Path testJar;
private final String testClassName;
private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap();
private final Set<String> ignoredInvokes = new HashSet<>();
private static class ClassInfo {
private final String name;
private final Class<?> clazz;
private final List<byte[]> classFileData;
private ClassInfo(String name) {
this.name = name;
this.clazz = null;
this.classFileData = null;
}
private ClassInfo(Class<?> clazz) {
this.name = clazz.getName();
this.clazz = clazz;
this.classFileData = null;
}
private ClassInfo(byte[] classFileData) {
this(ImmutableList.of(classFileData));
}
private ClassInfo(List<byte[]> classFileData) {
this.name = extractClassName(classFileData.get(0));
this.clazz = null;
this.classFileData = classFileData;
}
String getName() {
return name;
}
TestBuilder<?, ?> addAsProgramClass(TestBuilder<?, ?> builder) throws IOException {
if (clazz != null) {
return builder.addProgramClassesAndInnerClasses(clazz);
} else {
return builder.addProgramClassFileData(classFileData);
}
}
}
AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
Class<?> testClass) {
this(parameters, new ClassInfo(targetClass), new ClassInfo(testClass), null, null);
}
AbstractBackportTest(
TestParameters parameters, String className, List<byte[]> testClassFileData) {
this(parameters, new ClassInfo(className), new ClassInfo(testClassFileData), null, null);
}
AbstractBackportTest(
TestParameters parameters, byte[] targetClassFileData, List<byte[]> testClassFileData) {
this(
parameters,
new ClassInfo(targetClassFileData),
new ClassInfo(testClassFileData),
null,
null);
}
AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
Path testJar, String testClassName) {
this(parameters, new ClassInfo(targetClass), null, testJar, testClassName);
}
private AbstractBackportTest(
TestParameters parameters,
ClassInfo targetClass,
ClassInfo testClass,
Path testJar,
String testClassName) {
this.parameters = parameters;
this.targetClass = targetClass;
this.testClass = testClass;
this.testJar = testJar;
if (testClass != null) {
assert testJar == null;
assert testClassName == null;
this.testClassName = testClass.getName();
} else {
assert testJar != null;
assert testClassName != null;
this.testClassName = testClassName;
}
// Assume all method calls will be rewritten on the lowest API level.
invokeStaticCounts.put(AndroidApiLevel.B.getLevel(), 0);
}
void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) {
invokeStaticCounts.put(apiLevel.getLevel(), invokeStaticCount);
}
private int getTargetInvokesCount(AndroidApiLevel apiLevel) {
int key = invokeStaticCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
return invokeStaticCounts.get(key);
}
void ignoreInvokes(String methodName) {
ignoredInvokes.add(methodName);
}
private void configureProgram(TestBuilder<?, ?> builder) throws IOException {
builder.addProgramClasses(MiniAssert.class, IgnoreInvokes.class);
if (testClass != null) {
testClass.addAsProgramClass(builder);
} else {
builder.addProgramFiles(testJar);
}
}
@Test
public void desugaring() throws Exception {
if (parameters.isCfRuntime()) {
testForJvm()
.apply(this::configureProgram)
.run(parameters.getRuntime(), testClassName)
.assertSuccess();
} else {
testForD8()
.setMinApi(parameters.getApiLevel())
.apply(this::configureProgram)
.setIncludeClassesChecksum(true)
.compileWithExpectedDiagnostics(
diagnostics -> {
if (diagnostics.getWarnings().isEmpty()) {
diagnostics.assertNoMessages();
return;
}
// When compiling with an old android.jar some tests refer to non-present types.
// Check only java.util types are missing and that none of them are about the target
// type that is being backported.
diagnostics
.assertOnlyWarnings()
.assertAllWarningsMatch(
diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class))
.assertAllWarningsMatch(diagnosticMessage(containsString("java.util")))
.assertNoWarningsMatch(
diagnosticMessage(containsString(targetClass.getName())));
})
.run(parameters.getRuntime(), testClassName)
.assertSuccess()
.inspect(this::assertDesugaring);
}
}
private void assertDesugaring(CodeInspector inspector) {
ClassSubject testSubject = inspector.clazz(testClassName);
assertThat(testSubject, isPresent());
List<InstructionSubject> javaInvokeStatics = testSubject.allMethods()
.stream()
// Do not count @IgnoreInvokes-annotated methods.
.filter(i -> !i.annotation(IgnoreInvokes.class.getName()).isPresent())
.flatMap(MethodSubject::streamInstructions)
.filter(InstructionSubject::isInvoke)
.filter(is -> is.getMethod().holder.toSourceString().equals(targetClass.getName()))
// Do not count invokes if explicitly ignored.
.filter(is -> !ignoredInvokes.contains(is.getMethod().name.toString()))
.collect(toList());
AndroidApiLevel apiLevel = parameters.getApiLevel();
long expectedTargetInvokes = getTargetInvokesCount(apiLevel);
long actualTargetInvokes = javaInvokeStatics.size();
assertEquals("Expected "
+ expectedTargetInvokes
+ " invokes on "
+ targetClass.getName()
+ " but found "
+ actualTargetInvokes
+ ": "
+ javaInvokeStatics, expectedTargetInvokes, actualTargetInvokes);
}
/** JUnit {@link Assert} isn't available in the VM runtime. This is a mini mirror of its API. */
static abstract class MiniAssert {
static void assertTrue(boolean value) {
assertEquals(true, value);
}
static void assertFalse(boolean value) {
assertEquals(false, value);
}
static void assertEquals(boolean expected, boolean actual) {
if (expected != actual) {
throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
}
}
static void assertEquals(int expected, int actual) {
if (expected != actual) {
throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
}
}
static void assertEquals(long expected, long actual) {
if (expected != actual) {
throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
}
}
static void assertEquals(float expected, float actual) {
if (Float.compare(expected, actual) != 0) {
throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
}
}
static void assertEquals(double expected, double actual) {
if (Double.compare(expected, actual) != 0) {
throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
}
}
static void assertEquals(Object expected, Object actual) {
if (expected != actual && (expected == null || !expected.equals(actual))) {
throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
}
}
static void assertSame(Object expected, Object actual) {
if (expected != actual) {
throw new AssertionError(
"Expected <" + expected + "> to be same instance as <" + actual + '>');
}
}
static void fail(String message) {
throw new AssertionError("Failed: " + message);
}
}
}