blob: a4b4d35bfbe1ed9c3202562f70ccd7ede5ec2ebb [file] [log] [blame]
// Copyright (c) 2018, 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.kotlin;
import static com.android.tools.r8.ToolHelper.getKotlinAnnotationJar;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DescriptorUtils;
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.FieldSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.junit.Assume;
public abstract class AbstractR8KotlinTestBase extends KotlinTestBase {
// This is the name of the Jasmin-generated class which contains the "main" method which will
// invoke the tested method.
private static final String JASMIN_MAIN_CLASS = "TestMain";
protected final boolean allowAccessModification;
private final List<Path> classpath = new ArrayList<>();
private final List<Path> extraClasspath = new ArrayList<>();
protected final TestParameters testParameters;
// Some tests defined in subclasses, e.g., Metadata tests, don't care about access relaxation.
protected AbstractR8KotlinTestBase(
TestParameters parameters, KotlinTestParameters kotlinParameters) {
this(parameters, kotlinParameters, false);
}
protected AbstractR8KotlinTestBase(
TestParameters parameters,
KotlinTestParameters kotlinParameters,
boolean allowAccessModification) {
super(kotlinParameters);
this.testParameters = parameters;
this.allowAccessModification = allowAccessModification;
}
protected void addExtraClasspath(Path path) {
extraClasspath.add(path);
}
protected static void checkMethodIsInvokedAtLeastOnce(
DexCode dexCode, MethodSignature... methodSignatures) {
for (MethodSignature methodSignature : methodSignatures) {
checkMethodIsInvokedAtLeastOnce(dexCode, methodSignature);
}
}
private static void checkMethodIsInvokedAtLeastOnce(
DexCode dexCode, MethodSignature methodSignature) {
assertTrue("No invoke to '" + methodSignature.toString() + "'",
Arrays.stream(dexCode.instructions)
.filter((instr) -> instr.getMethod() != null)
.anyMatch((instr) -> instr.getMethod().name.toString().equals(methodSignature.name)));
}
protected static void checkMethodIsNeverInvoked(
DexCode dexCode, MethodSignature... methodSignatures) {
for (MethodSignature methodSignature : methodSignatures) {
checkMethodIsNeverInvoked(dexCode, methodSignature);
}
}
private static void checkMethodIsNeverInvoked(DexCode dexCode, MethodSignature methodSignature) {
assertTrue("At least one invoke to '" + methodSignature.toString() + "'",
Arrays.stream(dexCode.instructions)
.filter((instr) -> instr.getMethod() != null)
.noneMatch((instr) -> instr.getMethod().name.toString().equals(methodSignature.name)));
}
protected static void checkMethodsPresence(
ClassSubject classSubject, Map<MethodSignature, Boolean> presenceMap) {
presenceMap.forEach(((methodSignature, isPresent) -> {
MethodSubject methodSubject = classSubject.method(methodSignature);
String methodDesc = methodSignature.toString();
String failureMessage = isPresent
? "Method '" + methodDesc + "' should be present"
: "Method '" + methodDesc + "' should not be present";
assertEquals(failureMessage, isPresent, methodSubject.isPresent());
}));
}
protected ClassSubject checkClassIsKept(CodeInspector inspector, String className) {
checkClassExistsInInput(className);
ClassSubject classSubject = inspector.clazz(className);
assertNotNull(classSubject);
assertTrue("No class " + className, classSubject.isPresent());
return classSubject;
}
protected void checkClassIsRemoved(CodeInspector inspector, String className) {
checkClassExistsInInput(className);
ClassSubject classSubject = inspector.clazz(className);
assertNotNull(classSubject);
assertThat(classSubject, not(isPresent()));
}
protected FieldSubject checkFieldIsKept(
ClassSubject classSubject, String fieldType, String fieldName) {
// Field must exist in the input.
checkFieldPresenceInInput(classSubject.getOriginalName(), fieldType, fieldName, true);
FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
assertTrue("No field " + fieldName + " in " + classSubject.getOriginalName(),
fieldSubject.isPresent());
return fieldSubject;
}
protected FieldSubject checkFieldIsKept(ClassSubject classSubject, String fieldName) {
FieldSubject fieldSubject = classSubject.uniqueFieldWithName(fieldName);
assertThat(fieldSubject, isPresent());
return fieldSubject;
}
protected void checkFieldIsAbsent(ClassSubject classSubject, String fieldType, String fieldName) {
// Field must NOT exist in the input.
checkFieldPresenceInInput(classSubject.getOriginalName(), fieldType, fieldName, false);
FieldSubject fieldSubject = classSubject.field(fieldType, fieldName);
assertNotNull(fieldSubject);
assertFalse(fieldSubject.isPresent());
}
protected void checkMethodIsAbsent(ClassSubject classSubject, MethodSignature methodSignature) {
checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, false);
checkMethodPresenceInOutput(classSubject, methodSignature, false);
}
protected MethodSubject checkMethodIsKept(
ClassSubject classSubject, MethodSignature methodSignature) {
checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, true);
return checkMethodIsKeptOrRemoved(classSubject, methodSignature, true);
}
protected MethodSubject checkMethodIsKept(ClassSubject classSubject, String methodName) {
MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
assertThat(methodSubject, isPresent());
return methodSubject;
}
protected void checkMethodIsRemoved(ClassSubject classSubject, MethodSignature methodSignature) {
checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, true);
checkMethodIsKeptOrRemoved(classSubject, methodSignature, false);
}
protected MethodSubject checkMethodIsKeptOrRemoved(
ClassSubject classSubject, MethodSignature methodSignature, boolean isPresent) {
checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, true);
return checkMethodPresenceInOutput(classSubject, methodSignature, isPresent);
}
private MethodSubject checkMethodPresenceInOutput(
ClassSubject classSubject, MethodSignature methodSignature, boolean isPresent) {
MethodSubject methodSubject = classSubject.method(methodSignature);
assertNotNull(methodSubject);
String methodSig = methodSignature.name + methodSignature.toDescriptor();
if (isPresent) {
assertTrue("No method " + methodSig + " in output", methodSubject.isPresent());
} else {
assertFalse("Method " + methodSig + " exists in output", methodSubject.isPresent());
}
return methodSubject;
}
protected static DexCode getDexCode(MethodSubject methodSubject) {
Code code = methodSubject.getMethod().getCode();
assertNotNull("No code for method " + methodSubject.getMethod().descriptor(), code);
assertTrue(code.isDexCode());
return code.asDexCode();
}
protected String keepClassMethod(String className, MethodSignature methodSignature) {
return StringUtils.lines(
"-keep class " + className + " {",
methodSignature.toString() + ";",
"}");
}
protected String neverInlineMethod(String className, MethodSignature methodSignature) {
return StringUtils.lines(
"-neverinline class " + className + " {",
methodSignature.toString() + ";",
"}");
}
protected R8TestRunResult runTest(String folder, String mainClass) throws Exception {
return runTest(folder, mainClass, null);
}
protected R8TestRunResult runTest(
String folder, String mainClass, ThrowableConsumer<R8FullTestBuilder> configuration)
throws Exception {
Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
Path kotlinJarFile =
getCompileMemoizer(getKotlinFilesInResource(folder), folder)
.configure(kotlinCompilerTool -> kotlinCompilerTool.includeRuntime().noReflect())
.getForConfiguration(kotlinc, targetVersion);
// Build classpath for compilation (and java execution)
classpath.clear();
classpath.add(kotlinJarFile);
classpath.add(getJavaJarFile(folder));
classpath.add(getKotlinAnnotationJar(kotlinc));
classpath.addAll(extraClasspath);
// Compare with Java.
ToolHelper.ProcessResult javaResult = ToolHelper.runJava(classpath, mainClass);
if (javaResult.exitCode != 0) {
System.out.println(javaResult.stdout);
System.err.println(javaResult.stderr);
fail("JVM failed for: " + mainClass);
}
// Build with R8
return testForR8(testParameters.getBackend())
.addProgramFiles(classpath)
.addKeepMainRule(mainClass)
.allowAccessModification(allowAccessModification)
.allowDiagnosticWarningMessages()
.enableProguardTestOptions()
.noMinification()
.setMinApi(testParameters.getApiLevel())
.apply(configuration)
.compile()
.assertAllWarningMessagesMatch(
containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
.run(testParameters.getRuntime(), mainClass)
.assertSuccessWithOutput(javaResult.stdout);
}
protected void checkClassExistsInInput(String className) {
if (!AsmUtils.doesClassExist(classpath, className)) {
throw new AssertionError("Class " + className + " does not exist in input");
}
}
private void checkMethodPresenceInInput(
String className, MethodSignature methodSignature, boolean isPresent) {
boolean foundMethod = AsmUtils.doesMethodExist(classpath, className,
methodSignature.name, methodSignature.toDescriptor());
if (isPresent != foundMethod) {
throw new AssertionError(
"Method " + methodSignature.name + methodSignature.toDescriptor()
+ " " + (foundMethod ? "exists" : "does not exist")
+ " in input class " + className + " but is expected to be "
+ (isPresent ? "present" : "absent"));
}
}
private void checkFieldPresenceInInput(
String className, String fieldType, String fieldName, boolean isPresent) {
boolean foundField = AsmUtils.doesFieldExist(classpath, className, fieldName, fieldType);
if (isPresent != foundField) {
throw new AssertionError(
"Field " + fieldName + " " + (foundField ? "exists" : "does not exist")
+ " in input class " + className + " but is expected to be "
+ (isPresent ? "present" : "absent"));
}
}
/**
* Generates a "main" class which invokes the given static method (which has no argument and
* return void type). This new class is then added to the test classpath.
*
* @param methodClass the class of the static method to invoke
* @param methodName the name of the static method to invoke
* @return the name of the generated class
*/
protected String addMainToClasspath(String methodClass, String methodName) throws Exception {
JasminBuilder builder = new JasminBuilder();
ClassBuilder mainClassBuilder =
builder.addClass(DescriptorUtils.getBinaryNameFromJavaType(JASMIN_MAIN_CLASS));
mainClassBuilder.addMainMethod(
"invokestatic " + methodClass + "/" + methodName + "()V",
"return"
);
Path output = writeToJar(builder);
addExtraClasspath(output);
return JASMIN_MAIN_CLASS;
}
}