| // 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.Path; |
| import java.nio.file.Paths; |
| import java.util.Collections; |
| 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; |
| |
| public abstract class R8RunExamplesTestBase extends TestBase { |
| |
| protected enum Input { |
| 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 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 R8RunExamplesTestBase( |
| 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 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) { |
| 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); |
| } |
| |
| @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 *", "-dontoptimize"), 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 { |
| 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; |
| } |
| |
| 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(DexTool.NONE, 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 JVM output if we have it, otherwise check on art |
| if (shouldMatchJVMOutput(vm.getVersion())) { |
| String d8Output = |
| ToolHelper.runArtNoVerificationErrors( |
| Collections.singletonList(generated.toString()), mainClass, null, vm); |
| String javaOutput = javaResult.stdout; |
| assertEquals("JVM and Art output differ", javaOutput, d8Output); |
| } else { |
| String dxCompiled = getOriginalDexFile().toString(); |
| String output = |
| ToolHelper.checkArtOutputIdentical(dxCompiled, generated.toString(), mainClass, vm); |
| } |
| } |
| |
| private boolean shouldMatchJVMOutput(DexVm.Version version) { |
| TestCondition condition = getOutputNotIdenticalToJVMOutput().get(mainClass); |
| return condition == null || !condition.test(DexTool.NONE, compiler, version, mode); |
| } |
| |
| private boolean shouldSkipVm(DexVm.Version version) { |
| TestCondition condition = getSkip().get(mainClass); |
| return condition != null && condition.test(DexTool.NONE, 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(); |
| } |