| // 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(); | |
| 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(); | |
| } |