| // 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 static com.google.common.base.Predicates.not; | |
| import static org.junit.Assert.assertNotNull; | |
| import static org.junit.Assert.assertNull; | |
| import static org.junit.Assert.assertTrue; | |
| import com.android.tools.r8.TestBase.Backend; | |
| import com.android.tools.r8.benchmarks.BenchmarkResults; | |
| import com.android.tools.r8.debug.DebugTestConfig; | |
| import com.android.tools.r8.dump.CompilerDump; | |
| import com.android.tools.r8.dump.DumpOptions; | |
| import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorEventConsumer; | |
| import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference; | |
| import com.android.tools.r8.profile.art.model.ExternalArtProfile; | |
| import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; | |
| import com.android.tools.r8.testing.AndroidBuildVersion; | |
| 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.AndroidSdkIntFullEncoding; | |
| import com.android.tools.r8.utils.DescriptorUtils; | |
| import com.android.tools.r8.utils.ForwardingOutputStream; | |
| import com.android.tools.r8.utils.InternalOptions; | |
| import com.android.tools.r8.utils.ThrowingOutputStream; | |
| import com.android.tools.r8.utils.UncheckedApiLevel; | |
| import com.android.tools.r8.utils.codeinspector.ArgumentPropagatorCodeScannerResultInspector; | |
| import com.android.tools.r8.utils.codeinspector.EnumUnboxingInspector; | |
| import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; | |
| import com.android.tools.r8.utils.codeinspector.MinificationInspector; | |
| import com.android.tools.r8.utils.codeinspector.RepackagingInspector; | |
| import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector; | |
| import com.android.tools.r8.utils.codeinspector.VirtualMethodHoisterInspector; | |
| import com.android.tools.r8.utils.internal.OptionalBool; | |
| import com.android.tools.r8.utils.internal.SetUtils; | |
| import com.android.tools.r8.utils.internal.exceptions.Unimplemented; | |
| import com.google.common.base.Suppliers; | |
| import com.google.common.collect.ImmutableSet; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.IOException; | |
| import java.io.PrintStream; | |
| import java.nio.file.Path; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.concurrent.ExecutionException; | |
| import java.util.function.Consumer; | |
| import java.util.function.Function; | |
| import java.util.function.Supplier; | |
| import java.util.stream.Collectors; | |
| import java.util.stream.Stream; | |
| public abstract class TestCompilerBuilder< | |
| C extends BaseCompilerCommand, | |
| B extends BaseCompilerCommand.Builder<C, B>, | |
| CR extends TestCompileResult<CR, RR>, | |
| RR extends TestRunResult<RR>, | |
| 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.enableTestAssertions = true; | |
| options.getBottomUpOutlinerOptions().enable = OptionalBool.TRUE; | |
| options.getBottomUpOutlinerOptions().enableStringBuilderOutlining = true; | |
| }; | |
| public static final Consumer<InternalOptions> DEFAULT_D8_OPTIONS = | |
| DEFAULT_OPTIONS.andThen( | |
| options -> { | |
| options.enableStringConcatInstruction = true; | |
| }); | |
| public static final Consumer<InternalOptions> DEFAULT_D8_IN_R8_PARTIAL_OPTIONS = | |
| DEFAULT_D8_OPTIONS.andThen( | |
| options -> { | |
| options.enableStringConcatInstruction = false; | |
| }); | |
| public static final Consumer<InternalOptions> DEFAULT_R8_OPTIONS = | |
| DEFAULT_OPTIONS.andThen( | |
| options -> { | |
| options.testing.allowUnusedDontWarnRules = false; | |
| options.testing.allowUnnecessaryDontWarnWildcards = false; | |
| options.testing.enableRepackagingByDefaultForCf = true; | |
| options.testing.forcePruneMetaInfManifestMf = true; | |
| options.horizontalClassMergerOptions().enableForTesting(); | |
| options.horizontalClassMergerOptions().setEnableInterfaceMerging(); | |
| options | |
| .getCfCodeAnalysisOptions() | |
| .setAllowUnreachableCfBlocks(false) | |
| .setEnableUnverifiableCodeReporting(true); | |
| options.getOpenClosedInterfacesOptions().disallowOpenInterfaces(); | |
| }); | |
| public static final Consumer<InternalOptions> DEFAULT_R8_ASSISTANT_OPTIONS = | |
| DEFAULT_OPTIONS.andThen( | |
| options -> options.getArtProfileOptions().setEnableCompletenessCheckForTesting(false)); | |
| public static final Consumer<InternalOptions> DEFAULT_R8_IN_R8_PARTIAL_OPTIONS = | |
| DEFAULT_R8_OPTIONS.andThen( | |
| options -> { | |
| // By default, skip tracing of inner classes in trace references of R8 partial. | |
| // This generally leads to unintended, hidden keep rules in R8 partial tests. | |
| options.getTraceReferencesOptions().skipInnerClassesForTesting = true; | |
| // Required since it's also in DEFAULT_D8_OPTIONS. | |
| options.getBottomUpOutlinerOptions().enableStringBuilderOutlining = true; | |
| }); | |
| final Backend backend; | |
| // Default initialized setup. Can be overwritten if needed. | |
| private boolean allowStdoutMessages = false; | |
| private boolean allowStderrMessages = false; | |
| private boolean useDefaultRuntimeLibrary = true; | |
| private final List<Path> additionalRunClassPath = new ArrayList<>(); | |
| private ProgramConsumer programConsumer; | |
| private MainDexClassesCollector mainDexClassesCollector; | |
| private StringConsumer mainDexListConsumer; | |
| // TODO(b/186010707): This could become implicit once min always be set when fixed. | |
| private boolean noMinApiLevel = false; | |
| private UncheckedApiLevel minApiLevel = null; | |
| private boolean optimizeMultidexForLinearAlloc = false; | |
| private Consumer<InternalOptions> optionsConsumer; | |
| private ByteArrayOutputStream stdout = null; | |
| private boolean stdOutForwarding = true; | |
| private PrintStream oldStdout = null; | |
| private ByteArrayOutputStream stderr = null; | |
| private PrintStream oldStderr = null; | |
| protected OutputMode outputMode = OutputMode.DexIndexed; | |
| private boolean isBenchmarkRunner = false; | |
| private boolean isAndroidBuildVersionAdded = false; | |
| private UncheckedApiLevel androidBuildVersion = null; | |
| private static final Map<UncheckedApiLevel, Set<String>> allGlobalSynthetics = | |
| new ConcurrentHashMap<>(); | |
| private static final Map<UncheckedApiLevel, Set<String>> definiteGlobalSynthetics = | |
| new ConcurrentHashMap<>(); | |
| LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration = | |
| LibraryDesugaringTestConfiguration.DISABLED; | |
| public boolean isTestShrinkerBuilder() { | |
| return false; | |
| } | |
| public T addAndroidBuildVersion() { | |
| return addAndroidBuildVersion((UncheckedApiLevel) null); | |
| } | |
| public T addAndroidBuildVersion(AndroidApiLevel specifiedApiLevel) { | |
| return addAndroidBuildVersion( | |
| specifiedApiLevel == null ? null : specifiedApiLevel.asUnchecked()); | |
| } | |
| public T addAndroidBuildVersion(UncheckedApiLevel specifiedApiLevel) { | |
| addProgramClasses(AndroidBuildVersion.class); | |
| return markAndroidBuildVersionAsActive(specifiedApiLevel); | |
| } | |
| public T markAndroidBuildVersionAsActive(AndroidApiLevel specifiedApiLevel) { | |
| return markAndroidBuildVersionAsActive( | |
| specifiedApiLevel == null ? null : specifiedApiLevel.asUnchecked()); | |
| } | |
| public T markAndroidBuildVersionAsActive(UncheckedApiLevel specifiedApiLevel) { | |
| isAndroidBuildVersionAdded = true; | |
| androidBuildVersion = specifiedApiLevel; | |
| return self(); | |
| } | |
| 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); | |
| } | |
| if (isD8TestBuilder()) { | |
| optionsConsumer = DEFAULT_D8_OPTIONS; | |
| } else if (isR8TestBuilder()) { | |
| optionsConsumer = DEFAULT_R8_OPTIONS; | |
| } else if (isR8AssistantTestBuilder()) { | |
| optionsConsumer = DEFAULT_R8_ASSISTANT_OPTIONS; | |
| } else if (isR8PartialTestBuilder()) { | |
| optionsConsumer = | |
| DEFAULT_OPTIONS.andThen( | |
| options -> { | |
| options.partialCompilationConfiguration.d8DexOptionsConsumer = | |
| DEFAULT_D8_IN_R8_PARTIAL_OPTIONS; | |
| options.partialCompilationConfiguration.r8OptionsConsumer = | |
| DEFAULT_R8_IN_R8_PARTIAL_OPTIONS; | |
| }); | |
| } else { | |
| optionsConsumer = DEFAULT_OPTIONS; | |
| } | |
| } | |
| public UncheckedApiLevel getMinApiLevel() { | |
| // TODO(b/186010707): Enable assert minApiLevel != null; | |
| return minApiLevel; | |
| } | |
| abstract CR internalCompile( | |
| B builder, | |
| Consumer<InternalOptions> optionsConsumer, | |
| Supplier<AndroidApp> app, | |
| BenchmarkResults benchmarkResults) | |
| throws CompilationFailedException; | |
| public T addArgumentPropagatorCodeScannerResultInspector( | |
| ThrowableConsumer<ArgumentPropagatorCodeScannerResultInspector> inspector) { | |
| return addOptionsModification( | |
| options -> | |
| options.testing.argumentPropagatorEventConsumer = | |
| options.testing.argumentPropagatorEventConsumer.andThen( | |
| new ArgumentPropagatorEventConsumer() { | |
| @Override | |
| public void acceptCodeScannerResult( | |
| MethodStateCollectionByReference methodStates) { | |
| inspector.acceptWithRuntimeException( | |
| new ArgumentPropagatorCodeScannerResultInspector( | |
| options.dexItemFactory(), methodStates)); | |
| } | |
| })); | |
| } | |
| // Overridden in D8TestBuilder and R8TestBuilder. | |
| public T addArtProfileForRewriting(ExternalArtProfile externalArtProfile) { | |
| throw new Unimplemented(); | |
| } | |
| public T addOptionsModification(Consumer<InternalOptions> optionsConsumer) { | |
| if (optionsConsumer != null) { | |
| this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer); | |
| } | |
| return self(); | |
| } | |
| public T addR8PartialOptionsModification(Consumer<InternalOptions> optionsConsumer) { | |
| // Intentionally empty. Overridden in R8PartialTestBuilder. | |
| return self(); | |
| } | |
| public T addR8PartialD8OptionsModification(Consumer<InternalOptions> optionsConsumer) { | |
| // Intentionally empty. Overridden in R8PartialTestBuilder. | |
| return self(); | |
| } | |
| public T addR8PartialR8OptionsModification(Consumer<InternalOptions> optionsConsumer) { | |
| // Intentionally empty. Overridden in R8PartialTestBuilder. | |
| return self(); | |
| } | |
| public T allowCheckDiscardedErrors() { | |
| return addOptionsModification( | |
| options -> options.testing.dontReportFailingCheckDiscarded = true); | |
| } | |
| public T addEnumUnboxingInspector(Consumer<EnumUnboxingInspector> inspector) { | |
| return addOptionsModification( | |
| options -> | |
| options.testing.unboxedEnumsConsumer = | |
| ((dexItemFactory, unboxedEnums) -> | |
| inspector.accept(new EnumUnboxingInspector(dexItemFactory, unboxedEnums)))); | |
| } | |
| public T addHorizontallyMergedClassesInspector( | |
| ThrowableConsumer<HorizontallyMergedClassesInspector> inspector) { | |
| return addOptionsModification( | |
| options -> | |
| options.testing.horizontallyMergedClassesConsumer = | |
| (appView, horizontallyMergedClasses) -> | |
| inspector.acceptWithRuntimeException( | |
| new HorizontallyMergedClassesInspector( | |
| appView, horizontallyMergedClasses))); | |
| } | |
| public T addVirtualMethodHoisterInspector( | |
| ThrowableConsumer<VirtualMethodHoisterInspector> inspector) { | |
| return addOptionsModification( | |
| options -> | |
| options.testing.virtualMethodHoisterConsumer = | |
| (appView, hoisted, candidatesNotHoisted) -> | |
| inspector.acceptWithRuntimeException( | |
| new VirtualMethodHoisterInspector(appView, hoisted, candidatesNotHoisted))); | |
| } | |
| public T addHorizontallyMergedClassesInspectorIf( | |
| boolean condition, ThrowableConsumer<HorizontallyMergedClassesInspector> inspector) { | |
| if (condition) { | |
| return addHorizontallyMergedClassesInspector(inspector); | |
| } | |
| return self(); | |
| } | |
| public T addMinificationInspector(ThrowableConsumer<MinificationInspector> inspector) { | |
| return addOptionsModification( | |
| options -> | |
| options.testing.namingLensConsumer = | |
| ((dexItemFactory, namingLens) -> | |
| inspector.acceptWithRuntimeException( | |
| new MinificationInspector(dexItemFactory, namingLens)))); | |
| } | |
| public T addRepackagingInspector(ThrowableConsumer<RepackagingInspector> inspector) { | |
| return addOptionsModification( | |
| options -> | |
| options.testing.repackagingLensConsumer = | |
| ((dexItemFactory, repackagingLens) -> | |
| inspector.acceptWithRuntimeException( | |
| new RepackagingInspector(dexItemFactory, repackagingLens)))); | |
| } | |
| public T addVerticallyMergedClassesInspector( | |
| Consumer<VerticallyMergedClassesInspector> inspector) { | |
| return addOptionsModification( | |
| options -> | |
| options.testing.verticallyMergedClassesConsumer = | |
| ((dexItemFactory, verticallyMergedClasses) -> | |
| inspector.accept( | |
| new VerticallyMergedClassesInspector( | |
| dexItemFactory, verticallyMergedClasses)))); | |
| } | |
| public T addVerticallyMergedClassesInspectorIf( | |
| boolean condition, Consumer<VerticallyMergedClassesInspector> inspector) { | |
| if (condition) { | |
| return addVerticallyMergedClassesInspector(inspector); | |
| } | |
| return self(); | |
| } | |
| public T collectSyntheticItems() { | |
| assert getState().getSyntheticItems() == null; | |
| SyntheticItemsTestUtils.Builder syntheticItemsBuilder = new SyntheticItemsTestUtils.Builder(); | |
| getState().setSyntheticItems(syntheticItemsBuilder); | |
| if (isR8PartialTestBuilder()) { | |
| return addR8PartialD8OptionsModification( | |
| options -> options.testing.syntheticItemsConsumer = syntheticItemsBuilder::add) | |
| .addR8PartialR8OptionsModification( | |
| options -> options.testing.syntheticItemsConsumer = syntheticItemsBuilder::add); | |
| } else { | |
| return addOptionsModification( | |
| options -> options.testing.syntheticItemsConsumer = syntheticItemsBuilder::add); | |
| } | |
| } | |
| public CR benchmarkCompile(BenchmarkResults results) throws CompilationFailedException { | |
| if (System.getProperty("com.android.tools.r8.printtimes") != null) { | |
| allowStdoutMessages(); | |
| } | |
| isBenchmarkRunner = true; | |
| return internalCompileAndBenchmark(results); | |
| } | |
| public CR compile() throws CompilationFailedException { | |
| return internalCompileAndBenchmark(null); | |
| } | |
| private Collection<Path> getDefaultLibraryFiles() { | |
| if (backend == Backend.DEX) { | |
| if (!builder.isMinApiLevelSet()) { | |
| return Collections.singletonList(ToolHelper.getMostRecentAndroidJar()); | |
| } | |
| return Collections.singletonList( | |
| ToolHelper.getFirstSupportedAndroidJar( | |
| AndroidApiLevel.getAndroidApiLevel(builder.getUncheckedMinApiLevel()))); | |
| } else { | |
| assert backend == Backend.CF; | |
| return Collections.singletonList(TestDeps.getJava8RuntimeJar()); | |
| } | |
| } | |
| private CR internalCompileAndBenchmark(BenchmarkResults benchmark) | |
| throws CompilationFailedException { | |
| AndroidAppConsumers sink = new AndroidAppConsumers(); | |
| builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer)); | |
| if (mainDexClassesCollector != null || mainDexListConsumer != null) { | |
| builder.setMainDexListConsumer( | |
| ChainedStringConsumer.builder() | |
| .addIfNotNull(mainDexClassesCollector) | |
| .addIfNotNull(mainDexListConsumer) | |
| .build()); | |
| } | |
| if (!noMinApiLevel && (backend.isDex() || !isTestShrinkerBuilder())) { | |
| assert !builder.isMinApiLevelSet() | |
| : "Don't set the API level directly through BaseCompilerCommand.Builder in tests"; | |
| // TODO(b/186010707): This will always be set when fixed. | |
| if (getMinApiLevel() != null) { | |
| builder.setMinApiLevel(getMinApiLevel()); | |
| } | |
| } | |
| if (!noMinApiLevel | |
| && backend.isDex() | |
| && (isD8TestBuilder() || isR8TestBuilder() || isR8PartialTestBuilder()) | |
| && !isBenchmarkRunner) { | |
| UncheckedApiLevel minApiLevel = builder.getUncheckedMinApiLevel(); | |
| Consumer<InternalOptions> previousConsumer = optionsConsumer; | |
| optionsConsumer = | |
| options -> { | |
| options.testing.globalSyntheticCreatedCallback = | |
| programClass -> { | |
| String descriptor = programClass.getType().toDescriptorString(); | |
| boolean isGlobalSynthetic = | |
| definiteGlobalSynthetics | |
| .computeIfAbsent( | |
| minApiLevel, computeDefiniteGlobalSynthetics(options)) | |
| .contains(descriptor) | |
| || allGlobalSynthetics | |
| .computeIfAbsent( | |
| minApiLevel, TestCompilerBuilder::computeAllGlobalSynthetics) | |
| .contains(descriptor); | |
| assertTrue(isGlobalSynthetic); | |
| }; | |
| if (previousConsumer != null) { | |
| previousConsumer.accept(options); | |
| } | |
| }; | |
| } | |
| if (isD8TestBuilder() || isR8TestBuilder()) { | |
| addOptionsModification( | |
| o -> o.getArtProfileOptions().setEnableCompletenessCheckForTesting(!isBenchmarkRunner)); | |
| } else if (isR8PartialTestBuilder()) { | |
| asR8PartialTestBuilder() | |
| .addGlobalOptionsModification( | |
| o -> { | |
| o.getArtProfileOptions().setEnableCompletenessCheckForTesting(!isBenchmarkRunner); | |
| }); | |
| } | |
| builder.setOptimizeMultidexForLinearAlloc(optimizeMultidexForLinearAlloc); | |
| if (useDefaultRuntimeLibrary) { | |
| builder.addLibraryFiles(getDefaultLibraryFiles()); | |
| } | |
| assertNull(oldStdout); | |
| oldStdout = System.out; | |
| assertNull(oldStderr); | |
| oldStderr = System.err; | |
| CR cr; | |
| try { | |
| if (stdout != null) { | |
| assertTrue(allowStdoutMessages); | |
| if (stdOutForwarding) { | |
| System.setOut(new PrintStream(new ForwardingOutputStream(stdout, System.out))); | |
| } else { | |
| System.setOut(new PrintStream(stdout)); | |
| } | |
| } else if (!allowStdoutMessages) { | |
| System.setOut( | |
| new PrintStream( | |
| new ThrowingOutputStream<>( | |
| () -> new AssertionError("Unexpected print to stdout")))); | |
| } | |
| if (stderr != null) { | |
| assertTrue(allowStderrMessages); | |
| System.setErr(new PrintStream(new ForwardingOutputStream(stderr, System.err))); | |
| } else if (!allowStderrMessages) { | |
| System.setErr( | |
| new PrintStream( | |
| new ThrowingOutputStream<>( | |
| () -> new AssertionError("Unexpected print to stderr")))); | |
| } | |
| cr = | |
| internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build), benchmark) | |
| .addRunClasspathFiles(additionalRunClassPath); | |
| if (isAndroidBuildVersionAdded) { | |
| UncheckedApiLevel version = | |
| androidBuildVersion != null ? androidBuildVersion : builder.getUncheckedMinApiLevel(); | |
| cr.setSystemProperty(AndroidBuildVersion.PROPERTY, Integer.toString(version.getMajor())); | |
| cr.setSystemProperty( | |
| AndroidBuildVersion.VERSION_FULL_PROPERTY, | |
| Integer.toString(AndroidSdkIntFullEncoding.encode(version))); | |
| } | |
| return cr; | |
| } finally { | |
| if (mainDexClassesCollector != null) { | |
| getState().setMainDexClasses(mainDexClassesCollector.getMainDexClasses()); | |
| } | |
| if (stdout != null) { | |
| getState().setStdout(stdout.toString()); | |
| } | |
| System.setOut(oldStdout); | |
| if (stderr != null) { | |
| getState().setStderr(stderr.toString()); | |
| } | |
| System.setErr(oldStderr); | |
| } | |
| } | |
| public T enableExperimentalMapFileVersion() { | |
| addOptionsModification(o -> o.testing.enableExperimentalMapFileVersion = true); | |
| return self(); | |
| } | |
| @FunctionalInterface | |
| public interface DiagnosticsConsumer<E extends Exception> { | |
| void accept(TestDiagnosticMessages diagnostics) throws E; | |
| } | |
| public <E extends Exception> CR compileWithExpectedDiagnostics( | |
| DiagnosticsConsumer<E> diagnosticsConsumer) throws CompilationFailedException, E { | |
| TestDiagnosticMessages diagnosticsHandler = getState().getDiagnosticsMessages(); | |
| try { | |
| CR result = compile(); | |
| diagnosticsConsumer.accept(diagnosticsHandler); | |
| return result; | |
| } catch (CompilationFailedException e) { | |
| diagnosticsConsumer.accept(diagnosticsHandler); | |
| throw e; | |
| } | |
| } | |
| @Override | |
| public RR run(TestRuntime runtime, String mainClass, String... args) | |
| throws CompilationFailedException, ExecutionException, IOException { | |
| return compile().run(runtime, mainClass, args); | |
| } | |
| 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 final T setMinApiThreshold(TestParameters parameters, AndroidApiLevel minApiThreshold) { | |
| assert backend == Backend.DEX; | |
| return setMinApi(parameters.getApiLevel().min(minApiThreshold)); | |
| } | |
| @Deprecated | |
| public final T setMinApi(int minApiMajor) { | |
| return setMinApi(new UncheckedApiLevel(minApiMajor)); | |
| } | |
| public final T setMinApi(AndroidApiLevel minApiLevel) { | |
| return setMinApi(minApiLevel == null ? null : minApiLevel.asUnchecked()); | |
| } | |
| public T setMinApi(TestParameters parameters) { | |
| parameters.configureApiLevel(this); | |
| return self(); | |
| } | |
| public T setMinApi(UncheckedApiLevel minApiLevel) { | |
| assert minApiLevel != null; | |
| this.minApiLevel = minApiLevel; | |
| return self(); | |
| } | |
| public T setNoMinApi() { | |
| this.minApiLevel = null; | |
| this.noMinApiLevel = true; | |
| return self(); | |
| } | |
| public T setOptimizeMultidexForLinearAlloc() { | |
| this.optimizeMultidexForLinearAlloc = true; | |
| return self(); | |
| } | |
| public T disableDesugaring() { | |
| builder.setDisableDesugaring(true); | |
| 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; | |
| assert backend == Backend.fromConsumer(programConsumer); | |
| this.programConsumer = programConsumer; | |
| return self(); | |
| } | |
| public T collectMainDexClasses() { | |
| assert mainDexClassesCollector == null; | |
| mainDexClassesCollector = new MainDexClassesCollector(); | |
| 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 setUseDefaultRuntimeLibrary(boolean useDefaultRuntimeLibrary) { | |
| this.useDefaultRuntimeLibrary = useDefaultRuntimeLibrary; | |
| return self(); | |
| } | |
| @Override | |
| public T allowStdoutMessages() { | |
| allowStdoutMessages = true; | |
| return self(); | |
| } | |
| public T collectStdout() { | |
| assert stdout == null; | |
| stdout = new ByteArrayOutputStream(); | |
| return allowStdoutMessages(); | |
| } | |
| public T collectStdoutWithoutForwarding() { | |
| assert stdout == null; | |
| stdout = new ByteArrayOutputStream(); | |
| stdOutForwarding = false; | |
| return allowStdoutMessages(); | |
| } | |
| /** | |
| * If {@link #allowStdoutMessages} is false, then {@link System#out} will be replaced temporarily | |
| * by a {@link ThrowingOutputStream}. To allow the testing infrastructure to print messages to the | |
| * terminal, this method provides a reference to the original {@link System#out}. | |
| */ | |
| public PrintStream getStdoutForTesting() { | |
| assertNotNull(oldStdout); | |
| return oldStdout; | |
| } | |
| public T allowStderrMessages() { | |
| allowStderrMessages = true; | |
| return self(); | |
| } | |
| public T collectStderr() { | |
| assert stderr == null; | |
| stderr = new ByteArrayOutputStream(); | |
| return allowStderrMessages(); | |
| } | |
| public T enableCoreLibraryDesugaring(LibraryDesugaringTestConfiguration configuration) { | |
| this.libraryDesugaringTestConfiguration = configuration; | |
| return self(); | |
| } | |
| @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(); | |
| } | |
| @Override | |
| public DebugTestConfig debugConfig(TestRuntime runtime) throws Exception { | |
| return compile().debugConfig(runtime); | |
| } | |
| private static Set<String> computeAllGlobalSynthetics(UncheckedApiLevel minApiLevel) { | |
| try { | |
| Set<String> generatedGlobalSynthetics = SetUtils.newConcurrentHashSet(); | |
| GlobalSyntheticsGeneratorCommand command = | |
| GlobalSyntheticsGeneratorCommand.builder() | |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL)) | |
| .setMinApiLevel(minApiLevel.getMajor(), minApiLevel.getMinor()) | |
| .setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer.emptyConsumer()) | |
| .build(); | |
| InternalOptions internalOptions = command.getInternalOptions(); | |
| internalOptions.testing.globalSyntheticCreatedCallback = | |
| programClass -> | |
| generatedGlobalSynthetics.add(programClass.getType().toDescriptorString()); | |
| GlobalSyntheticsGenerator.runForTesting(command.getInputApp(), internalOptions); | |
| return generatedGlobalSynthetics; | |
| } catch (Exception e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| private static <T> Function<T, Set<String>> computeDefiniteGlobalSynthetics( | |
| InternalOptions options) { | |
| return ignored -> { | |
| ImmutableSet.Builder<String> builder = ImmutableSet.builder(); | |
| GlobalSyntheticsGeneratorVerifier.forEachExpectedClass( | |
| options, type -> builder.add(type.toDescriptorString())); | |
| return builder.build(); | |
| }; | |
| } | |
| private static class ChainedStringConsumer implements StringConsumer { | |
| private final List<StringConsumer> consumers; | |
| ChainedStringConsumer(List<StringConsumer> consumers) { | |
| this.consumers = consumers; | |
| } | |
| static Builder builder() { | |
| return new Builder(); | |
| } | |
| @Override | |
| public void accept(String string, DiagnosticsHandler handler) { | |
| consumers.forEach(consumer -> consumer.accept(string, handler)); | |
| } | |
| @Override | |
| public void finished(DiagnosticsHandler handler) { | |
| consumers.forEach(consumer -> consumer.finished(handler)); | |
| } | |
| static class Builder { | |
| private final List<StringConsumer> consumers = new ArrayList<>(); | |
| Builder add(StringConsumer consumer) { | |
| assert consumer != null; | |
| consumers.add(consumer); | |
| return this; | |
| } | |
| Builder addIfNotNull(StringConsumer consumer) { | |
| return consumer != null ? add(consumer) : this; | |
| } | |
| ChainedStringConsumer build() { | |
| return new ChainedStringConsumer(consumers); | |
| } | |
| } | |
| } | |
| private static class MainDexClassesCollector implements StringConsumer { | |
| private StringBuilder builder = new StringBuilder(); | |
| private Set<String> mainDexClasses; | |
| public Set<String> getMainDexClasses() { | |
| assert mainDexClasses != null; | |
| return mainDexClasses; | |
| } | |
| @Override | |
| public void accept(String string, DiagnosticsHandler handler) { | |
| builder.append(string); | |
| } | |
| @Override | |
| public void finished(DiagnosticsHandler handler) { | |
| mainDexClasses = | |
| Stream.of(builder.toString().split(System.lineSeparator())) | |
| .filter(not(String::isEmpty)) | |
| .map( | |
| line -> { | |
| assert line.endsWith(".class"); | |
| return line.substring(0, line.length() - ".class".length()); | |
| }) | |
| .map(DescriptorUtils::getJavaTypeFromInternalName) | |
| .collect(Collectors.toSet()); | |
| builder = null; | |
| } | |
| } | |
| public T setApiDatabasePath(Path apiDatabasePath) { | |
| builder.setApiDatabasePath(apiDatabasePath); | |
| return self(); | |
| } | |
| public T applyCompilerDump(CompilerDump dump) throws IOException { | |
| super.applyCompilerDump(dump); | |
| DumpOptions options = dump.getBuildProperties(); | |
| setMinApi(options.getMinApi()); | |
| applyIf( | |
| dump.hasDesugaredLibrary(), | |
| b -> | |
| b.enableCoreLibraryDesugaring( | |
| LibraryDesugaringTestConfiguration.forSpecification( | |
| dump.getDesugaredLibraryFile()))); | |
| applyIf(dump.hasApiDatabase(), b -> b.setApiDatabasePath(dump.getApiDatabaseFile())); | |
| setMode(options.getCompilationMode()); | |
| return self(); | |
| } | |
| } |