blob: 8b2f1836e10b926e042fe236e18a1569e20e1199 [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 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.OutputMode;
import com.android.tools.r8.TestBase;
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.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.junit.Assume;
// TODO(shertz) also run with backend 1.8
public abstract class AbstractR8KotlinTestBase extends TestBase {
protected final boolean allowAccessModification;
protected AbstractR8KotlinTestBase(boolean allowAccessModification) {
this.allowAccessModification = allowAccessModification;
}
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 static ClassSubject checkClassExists(DexInspector inspector, String className) {
ClassSubject classSubject = inspector.clazz(className);
assertNotNull(classSubject);
assertTrue("No class " + className, classSubject.isPresent());
return classSubject;
}
private static MethodSubject checkMethod(ClassSubject classSubject, String methodName,
String methodReturnType, List<String> methodParameterTypes, boolean isPresent) {
return checkMethod(classSubject,
new MethodSignature(methodName, methodReturnType, methodParameterTypes), isPresent);
}
protected static MethodSubject checkMethod(ClassSubject classSubject,
MethodSignature methodSignature, boolean isPresent) {
MethodSubject methodSubject = classSubject.method(methodSignature);
assertNotNull(methodSubject);
if (isPresent) {
assertTrue("No method " + methodSignature.name, methodSubject.isPresent());
} else {
assertFalse("Method " + methodSignature.name + " exists", 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();
}
private static DexCode extractCodeFor(DexInspector dexInspector, String className,
String methodName,
String methodReturnType, List<String> methodParameterTypes) {
ClassSubject classSubject = checkClassExists(dexInspector, className);
MethodSubject methodSubject = checkMethodIsPresent(classSubject, methodName, methodReturnType,
methodParameterTypes);
return getDexCode(methodSubject);
}
private static String getNonOptimizedDexFile(String pkg) {
return Paths
.get(ToolHelper.EXAMPLES_KOTLIN_BUILD_DIR, pkg, ToolHelper.DEFAULT_DEX_FILENAME).toString();
}
protected static MethodSubject checkMethodIsPresent(ClassSubject classSubject, String methodName,
String methodReturnType,
List<String> methodParameterTypes) {
return checkMethod(classSubject, methodName, methodReturnType, methodParameterTypes, true);
}
private String buildProguardRules(String mainClass) {
ProguardRulesBuilder proguardRules = new ProguardRulesBuilder();
proguardRules.appendWithLineSeparator(keepMainProguardConfiguration(mainClass));
proguardRules.appendWithLineSeparator(keepTestMethodProguardConfiguration(mainClass));
proguardRules.dontObfuscate();
if (allowAccessModification) {
proguardRules.allowAccessModification();
}
return proguardRules.toString();
}
private String keepTestMethodProguardConfiguration(String clazz) {
return "-keep class " + clazz + " {" + System.lineSeparator() +
"public void testMethod();" +
"}";
}
protected void buildAndInspect(String folder, String mainClass, AndroidAppInspector inspector)
throws Exception {
buildRunAndInspect(folder, mainClass, inspector, true);
}
protected void buildRunAndInspect(String folder, String mainClass, AndroidAppInspector inspector)
throws Exception {
buildRunAndInspect(folder, mainClass, inspector, false);
}
public void buildRunAndInspect(String folder, String mainClass, AndroidAppInspector inspector,
boolean skipExecution)
throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
Path jarFile =
Paths.get(ToolHelper.EXAMPLES_KOTLIN_BUILD_DIR, folder + FileUtils.JAR_EXTENSION);
String proguardRules = buildProguardRules(mainClass);
// Build with R8
AndroidApp.Builder builder = AndroidApp.builder();
builder.addProgramFiles(jarFile);
AndroidApp app = compileWithR8(builder.build(), proguardRules.toString());
// Compare original and generated DEX files.
String originalDexFile = getNonOptimizedDexFile(folder);
// Materialize file for execution.
Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
app.writeToZip(generatedDexFile, OutputMode.DexIndexed);
if (!skipExecution) {
// Run with ART.
String artOutput = ToolHelper
.checkArtOutputIdentical(originalDexFile, generatedDexFile.toString(), mainClass,
ToolHelper.getDexVm());
// Compare with Java.
ToolHelper.ProcessResult javaResult = ToolHelper.runJava(jarFile, mainClass);
if (javaResult.exitCode != 0) {
System.out.println(javaResult.stdout);
System.err.println(javaResult.stderr);
fail("JVM failed for: " + mainClass);
}
assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
}
inspector.inspectApp(app);
}
@FunctionalInterface
interface AndroidAppInspector {
void inspectApp(AndroidApp androidApp) throws Exception;
}
}