| // 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 com.google.common.collect.ImmutableSet; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Map; |
| import java.util.Set; |
| 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(); |
| } |
| } |
| |
| public R8Command.Builder addInputFile(R8Command.Builder builder) { |
| 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(getOriginalDexFile()); |
| } 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.getMostRecentAndroidJar()) |
| .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 (input == Input.DX && getFailingCompileDxToDex().contains(mainClass)) { |
| assert output == Output.DEX; |
| return true; |
| } |
| 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 { |
| 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(); |
| |
| protected Set<String> getFailingCompileDxToDex() { |
| return ImmutableSet.of(); |
| } |
| |
| // 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(); |
| } |