blob: 8c6e7c2052a50b2e061f2b5cba861233e7dc5e13 [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;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.base.Suppliers;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public abstract class TestCompilerBuilder<
C extends BaseCompilerCommand,
B extends BaseCompilerCommand.Builder<C, B>,
CR extends TestCompileResult<CR, RR>,
RR extends TestRunResult,
T extends TestCompilerBuilder<C, B, CR, RR, T>>
extends TestBaseBuilder<C, B, CR, RR, T> {
public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
options -> {
options.testing.allowClassInlinerGracefulExit = false;
options.testing.allowUnusedProguardConfigurationRules = false;
options.testing.reportUnusedProguardConfigurationRules = true;
};
final Backend backend;
// Default initialized setup. Can be overwritten if needed.
private boolean useDefaultRuntimeLibrary = true;
private final List<Path> additionalRunClassPath = new ArrayList<>();
private ProgramConsumer programConsumer;
private StringConsumer mainDexListConsumer;
private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
private PrintStream stdout = null;
protected OutputMode outputMode = OutputMode.DexIndexed;
TestCompilerBuilder(TestState state, B builder, Backend backend) {
super(state, builder);
this.backend = backend;
if (backend == Backend.DEX) {
setOutputMode(OutputMode.DexIndexed);
} else {
assert backend == Backend.CF;
setOutputMode(OutputMode.ClassFile);
}
}
abstract CR internalCompile(
B builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
throws CompilationFailedException;
public T addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
if (optionsConsumer != null) {
this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer);
}
return self();
}
public CR compile() throws CompilationFailedException {
AndroidAppConsumers sink = new AndroidAppConsumers();
builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
builder.setMainDexListConsumer(mainDexListConsumer);
if (backend == Backend.DEX && defaultMinApiLevel != null) {
assert !builder.isMinApiLevelSet()
: "Don't set the API level directly through BaseCompilerCommand.Builder in tests";
builder.setMinApiLevel(defaultMinApiLevel.getLevel());
}
if (useDefaultRuntimeLibrary) {
if (backend == Backend.DEX && builder.isMinApiLevelSet()) {
builder.addLibraryFiles(
ToolHelper.getFirstSupportedAndroidJar(
AndroidApiLevel.getAndroidApiLevel(builder.getMinApiLevel())));
} else {
builder.addLibraryFiles(TestBase.runtimeJar(backend));
}
}
PrintStream oldOut = System.out;
try {
if (stdout != null) {
System.setOut(stdout);
}
CR cr = internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
cr.addRunClasspathFiles(additionalRunClassPath);
return cr;
} finally {
if (stdout != null) {
System.setOut(oldOut);
}
}
}
public CR compileWithExpectedDiagnostics(
Consumer<TestDiagnosticMessages> diagnosticsConsumer) throws CompilationFailedException {
TestDiagnosticMessages diagnosticsHandler = getState().getDiagnosticsMessages();
try {
CR result = compile();
diagnosticsConsumer.accept(diagnosticsHandler);
return result;
} catch (CompilationFailedException e) {
diagnosticsConsumer.accept(diagnosticsHandler);
throw e;
}
}
@Override
@Deprecated
public RR run(String mainClass)
throws CompilationFailedException, ExecutionException, IOException {
return compile().run(mainClass);
}
@Override
public RR run(TestRuntime runtime, String mainClass, String... args)
throws CompilationFailedException, ExecutionException, IOException {
return compile().run(runtime, mainClass, args);
}
@Override
public DebugTestConfig debugConfig() {
// Rethrow exceptions since debug config is usually used in a delayed wrapper which
// does not declare exceptions.
try {
return compile().debugConfig();
} catch (CompilationFailedException e) {
throw new RuntimeException(e);
}
}
public T setMode(CompilationMode mode) {
builder.setMode(mode);
return self();
}
public T debug() {
return setMode(CompilationMode.DEBUG);
}
public T release() {
return setMode(CompilationMode.RELEASE);
}
public T setMinApiThreshold(AndroidApiLevel minApiThreshold) {
assert backend == Backend.DEX;
AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVmNoHigherThan(minApiThreshold);
return setMinApi(minApi);
}
public T setMinApiThreshold(TestRuntime runtime) {
if (runtime.isDex()) {
setMinApiThreshold(runtime.asDex().getMinApiLevel());
}
return self();
}
public T setMinApi(AndroidApiLevel minApiLevel) {
assert builder.getMinApiLevel() > 0 || this.defaultMinApiLevel != null
: "Tests must use this method to set min API level, and not"
+ " BaseCompilerCommand.Builder.setMinApiLevel()";
if (backend == Backend.DEX) {
this.defaultMinApiLevel = null;
builder.setMinApiLevel(minApiLevel.getLevel());
}
return self();
}
public T setMinApi(int minApiLevel) {
if (backend == Backend.DEX) {
this.defaultMinApiLevel = null;
builder.setMinApiLevel(minApiLevel);
}
return self();
}
/** @deprecated use {@link #setMinApi(AndroidApiLevel)} instead. */
@Deprecated
public T setMinApi(TestRuntime runtime) {
if (runtime.isDex()) {
setMinApi(runtime.asDex().getMinApiLevel());
}
return self();
}
public OutputMode getOutputMode() {
if (programConsumer instanceof DexIndexedConsumer) {
return OutputMode.DexIndexed;
}
if (programConsumer instanceof DexFilePerClassFileConsumer) {
return ((DexFilePerClassFileConsumer) programConsumer)
.combineSyntheticClassesWithPrimaryClass()
? OutputMode.DexFilePerClassFile
: OutputMode.DexFilePerClass;
}
assert programConsumer instanceof ClassFileConsumer;
return OutputMode.ClassFile;
}
public T setOutputMode(OutputMode outputMode) {
assert ToolHelper.verifyValidOutputMode(backend, outputMode);
switch (outputMode) {
case DexIndexed:
programConsumer = DexIndexedConsumer.emptyConsumer();
break;
case DexFilePerClassFile:
programConsumer = DexFilePerClassFileConsumer.emptyConsumer();
break;
case DexFilePerClass:
programConsumer =
new DexFilePerClassFileConsumer.ForwardingConsumer(null) {
@Override
public boolean combineSyntheticClassesWithPrimaryClass() {
return false;
}
};
break;
case ClassFile:
programConsumer = ClassFileConsumer.emptyConsumer();
break;
}
return self();
}
public T setProgramConsumer(ProgramConsumer programConsumer) {
assert programConsumer != null;
this.programConsumer = programConsumer;
return self();
}
public T setMainDexListConsumer(StringConsumer consumer) {
assert consumer != null;
this.mainDexListConsumer = consumer;
return self();
}
public T setIncludeClassesChecksum(boolean include) {
builder.setIncludeClassesChecksum(include);
return self();
}
@Override
public T addLibraryFiles(Collection<Path> files) {
useDefaultRuntimeLibrary = false;
return super.addLibraryFiles(files);
}
@Override
public T addLibraryClasses(Collection<Class<?>> classes) {
useDefaultRuntimeLibrary = false;
return super.addLibraryClasses(classes);
}
@Override
public T addLibraryProvider(ClassFileResourceProvider provider) {
useDefaultRuntimeLibrary = false;
return super.addLibraryProvider(provider);
}
public T addClasspathClasses(Class<?>... classes) {
return addClasspathClasses(Arrays.asList(classes));
}
public abstract T addClasspathClasses(Collection<Class<?>> classes);
public T addClasspathFiles(Path... files) {
return addClasspathFiles(Arrays.asList(files));
}
public abstract T addClasspathFiles(Collection<Path> files);
public T noDesugaring() {
builder.setDisableDesugaring(true);
return self();
}
public T redirectStdOut(PrintStream printStream) {
assert stdout == null;
stdout = printStream;
return self();
}
public T enableCoreLibraryDesugaring(AndroidApiLevel minAPILevel) {
return enableCoreLibraryDesugaring(minAPILevel, null);
}
public T enableCoreLibraryDesugaring(
AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
throw new Unreachable("Should be overridden or is unsupported.");
}
@Override
public T addRunClasspathFiles(Collection<Path> files) {
additionalRunClassPath.addAll(files);
return self();
}
public T addAssertionsConfiguration(
Function<AssertionsConfiguration.Builder, AssertionsConfiguration>
assertionsConfigurationGenerator) {
builder.addAssertionsConfiguration(assertionsConfigurationGenerator);
return self();
}
}