blob: 5a90d529bb8d83ea0563e3965498fe16aaf53f83 [file] [log] [blame]
// Copyright (c) 2016, 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;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.R8RunArtTestsTest.DexTool;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
public abstract class R8RunExamplesCommon {
protected enum Input {
DX, JAVAC, JAVAC_ALL, JAVAC_NONE
}
protected enum Output {
DEX,
CF
}
protected static String[] makeTest(
Input input, CompilerUnderTest compiler, CompilationMode mode, String clazz) {
return makeTest(input, compiler, mode, clazz, Output.DEX);
}
protected static String[] makeTest(
Input input,
CompilerUnderTest compiler,
CompilationMode mode,
String clazz,
Output output) {
String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
return new String[] {pkg, input.name(), compiler.name(), mode.name(), clazz, output.name()};
}
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@Rule
public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
private final Input input;
private final CompilerUnderTest compiler;
private final CompilationMode mode;
private final String pkg;
private final String mainClass;
protected final Output output;
public R8RunExamplesCommon(
String pkg,
String input,
String compiler,
String mode,
String mainClass,
String output) {
this.pkg = pkg;
this.input = Input.valueOf(input);
this.compiler = CompilerUnderTest.valueOf(compiler);
this.mode = CompilationMode.valueOf(mode);
this.mainClass = mainClass;
this.output = Output.valueOf(output);
}
private Path getOutputFile() {
return temp.getRoot().toPath().resolve("out.jar");
}
private Path getInputFile() {
switch(input) {
case DX:
return getOriginalDexFile();
case JAVAC:
return getOriginalJarFile("");
case JAVAC_ALL:
return getOriginalJarFile("_debuginfo_all");
case JAVAC_NONE:
return getOriginalJarFile("_debuginfo_none");
default:
throw new Unreachable();
}
}
private R8Command.Builder addInputFile(R8Command.Builder builder) throws NoSuchFileException {
if (input == Input.DX) {
// If input is DEX code, use the tool helper to add the DEX sources as R8 disallows them.
ToolHelper.getAppBuilder(builder).addProgramFiles(getInputFile());
} else {
builder.addProgramFiles(getInputFile());
}
return builder;
}
public Path getOriginalJarFile(String postFix) {
return Paths.get(getExampleDir(), pkg + postFix + JAR_EXTENSION);
}
private Path getOriginalDexFile() {
return Paths.get(getExampleDir(), pkg, ToolHelper.DEFAULT_DEX_FILENAME);
}
private DexTool getTool() {
return input == Input.DX ? DexTool.DX : DexTool.NONE;
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void compile() throws Exception {
if (shouldCompileFail()) {
thrown.expect(Throwable.class);
}
OutputMode outputMode = output == Output.CF ? OutputMode.ClassFile : OutputMode.DexIndexed;
switch (compiler) {
case D8: {
assertTrue(output == Output.DEX);
D8.run(
D8Command.builder()
.addProgramFiles(getInputFile())
.setOutput(getOutputFile(), outputMode)
.setMode(mode)
.build());
break;
}
case R8: {
R8Command command =
addInputFile(R8Command.builder())
.addLibraryFiles(
output == Output.CF
? ToolHelper.getJava8RuntimeJar()
: ToolHelper.getDefaultAndroidJar())
.setOutput(getOutputFile(), outputMode)
.setMode(mode)
.setDisableTreeShaking(true)
.setDisableMinification(true)
.addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.build();
ToolHelper.runR8(command, this::configure);
break;
}
default:
throw new Unreachable();
}
}
protected void configure(InternalOptions options) {
options.lineNumberOptimization = LineNumberOptimization.OFF;
}
private boolean shouldCompileFail() {
if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
return true;
}
if (output == Output.DEX && getFailingCompileCfToDex().contains(mainClass)) {
return true;
}
return false;
}
@Test
public void outputIsIdentical() throws IOException, InterruptedException, ExecutionException {
if (shouldCompileFail()) {
// We expected an exception, but got none.
// Return to ensure that this test fails due to the missing exception.
return;
}
Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
DexVm vm = ToolHelper.getDexVm();
Assume.assumeFalse("Triage (b/144966342)", vm.isNewerThan(DexVm.ART_9_0_0_HOST));
if (shouldSkipVm(vm.getVersion())) {
return;
}
String original = getOriginalDexFile().toString();
Path generated = getOutputFile();
ToolHelper.ProcessResult javaResult = ToolHelper.runJava(getOriginalJarFile(""), mainClass);
if (javaResult.exitCode != 0) {
System.out.println(javaResult.stdout);
System.err.println(javaResult.stderr);
fail("JVM failed for: " + mainClass);
}
TestCondition condition =
output == Output.CF ? getFailingRunCf().get(mainClass) : getFailingRun().get(mainClass);
if (condition != null && condition.test(getTool(), compiler, vm.getVersion(), mode)) {
thrown.expect(Throwable.class);
}
if (output == Output.DEX && getFailingRunCfToDex().contains(mainClass)) {
thrown.expect(Throwable.class);
}
if (output == Output.CF) {
ToolHelper.ProcessResult result = ToolHelper.runJava(generated, mainClass);
if (result.exitCode != 0) {
System.err.println(result.stderr);
fail("JVM failed on compiled output for: " + mainClass);
}
if (!getFailingOutputCf().contains(mainClass)) {
assertEquals(
"JavaC/JVM and " + compiler.name() + "/JVM output differ",
javaResult.stdout,
result.stdout);
}
return;
}
// Check output against Art output on original dex file.
String output =
ToolHelper.checkArtOutputIdentical(original, generated.toString(), mainClass, vm);
// Check output against JVM output.
if (shouldMatchJVMOutput(vm.getVersion())) {
String javaOutput = javaResult.stdout;
assertEquals("JVM and Art output differ", javaOutput, output);
}
}
private boolean shouldMatchJVMOutput(DexVm.Version version) {
TestCondition condition = getOutputNotIdenticalToJVMOutput().get(mainClass);
return condition == null || !condition.test(getTool(), compiler, version, mode);
}
private boolean shouldSkipVm(DexVm.Version version) {
TestCondition condition = getSkip().get(mainClass);
return condition != null && condition.test(getTool(), compiler, version, mode);
}
protected abstract String getExampleDir();
protected abstract Map<String, TestCondition> getFailingRun();
protected abstract Map<String, TestCondition> getFailingRunCf();
protected abstract Set<String> getFailingCompileCfToDex();
// TODO(mathiasr): Add CompilerSet for CfToDex so we can fold this into getFailingRun().
protected abstract Set<String> getFailingRunCfToDex();
protected abstract Set<String> getFailingCompileCf();
protected abstract Set<String> getFailingOutputCf();
protected abstract Map<String, TestCondition> getOutputNotIdenticalToJVMOutput();
protected abstract Map<String, TestCondition> getSkip();
}