| // Copyright (c) 2017, 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 com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeFalse; |
| |
| import com.android.tools.r8.ToolHelper.DexVm; |
| import com.android.tools.r8.ToolHelper.DexVm.Version; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.AndroidApp; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.ListUtils; |
| import com.android.tools.r8.utils.OffOrAuto; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.StringUtils.BraceType; |
| import com.android.tools.r8.utils.TestDescriptionWatcher; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.FoundClassSubject; |
| import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; |
| import com.android.tools.r8.utils.codeinspector.InstructionSubject; |
| import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Sets.SetView; |
| import com.google.common.io.ByteStreams; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| import java.util.zip.ZipFile; |
| import org.junit.Assert; |
| import org.junit.Assume; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| import org.junit.rules.TemporaryFolder; |
| |
| public abstract class RunExamplesAndroidOTest< |
| B extends BaseCommand.Builder<? extends BaseCommand, B>> |
| extends TestBase { |
| static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR; |
| |
| abstract class TestRunner<C extends TestRunner<C>> { |
| final String testName; |
| final String packageName; |
| final String mainClass; |
| final List<String> args = new ArrayList<>(); |
| |
| AndroidApiLevel androidJarVersion = null; |
| |
| final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>(); |
| final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>(); |
| final List<Consumer<B>> builderTransformations = new ArrayList<>(); |
| |
| TestRunner(String testName, String packageName, String mainClass) { |
| this.testName = testName; |
| this.packageName = packageName; |
| this.mainClass = mainClass; |
| } |
| |
| abstract C self(); |
| |
| C withDexCheck(Consumer<CodeInspector> check) { |
| dexInspectorChecks.add(check); |
| return self(); |
| } |
| |
| C withClassCheck(Consumer<FoundClassSubject> check) { |
| return withDexCheck(inspector -> inspector.forAllClasses(check)); |
| } |
| |
| C withMethodCheck(Consumer<FoundMethodSubject> check) { |
| return withClassCheck(clazz -> clazz.forAllMethods(check)); |
| } |
| |
| <T extends InstructionSubject> C withInstructionCheck( |
| Predicate<InstructionSubject> filter, Consumer<T> check) { |
| return withMethodCheck(method -> { |
| if (method.isAbstract()) { |
| return; |
| } |
| Iterator<T> iterator = method.iterateInstructions(filter); |
| while (iterator.hasNext()) { |
| check.accept(iterator.next()); |
| } |
| }); |
| } |
| |
| C withOptionConsumer(Consumer<InternalOptions> consumer) { |
| optionConsumers.add(consumer); |
| return self(); |
| } |
| |
| C withMainDexKeepClassRules(List<String> classes) { |
| return withBuilderTransformation( |
| builder -> { |
| if (builder instanceof D8Command.Builder) { |
| ((D8Command.Builder) builder) |
| .addMainDexRules( |
| ListUtils.map(classes, c -> "-keep class " + c + " { *; }"), |
| Origin.unknown()); |
| } else if (builder instanceof R8Command.Builder) { |
| ((R8Command.Builder) builder) |
| .addMainDexRules( |
| ListUtils.map(classes, c -> "-keep class " + c + " { *; }"), |
| Origin.unknown()); |
| } else { |
| fail("Unexpected builder type: " + builder.getClass()); |
| } |
| }); |
| } |
| |
| C withInterfaceMethodDesugaring(OffOrAuto behavior) { |
| return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior); |
| } |
| |
| C withTryWithResourcesDesugaring(OffOrAuto behavior) { |
| return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior); |
| } |
| |
| C withBuilderTransformation(Consumer<B> builderTransformation) { |
| builderTransformations.add(builderTransformation); |
| return self(); |
| } |
| |
| C withArg(String arg) { |
| args.add(arg); |
| return self(); |
| } |
| |
| void combinedOptionConsumer(InternalOptions options) { |
| for (Consumer<InternalOptions> consumer : optionConsumers) { |
| consumer.accept(options); |
| } |
| } |
| |
| Path build() throws Throwable { |
| Path inputFile = getInputJar(); |
| Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION); |
| |
| build(inputFile, out); |
| return out; |
| } |
| |
| Path getInputJar() { |
| return Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION); |
| } |
| |
| void run() throws Throwable { |
| if (minSdkErrorExpected(testName)) { |
| thrown.expect(CompilationFailedException.class); |
| } |
| |
| String qualifiedMainClass = packageName + "." + mainClass; |
| Path inputFile = getInputJar(); |
| Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION); |
| |
| build(inputFile, out); |
| |
| if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) { |
| return; |
| } |
| |
| if (!dexInspectorChecks.isEmpty()) { |
| CodeInspector inspector = new CodeInspector(out); |
| for (Consumer<CodeInspector> check : dexInspectorChecks) { |
| check.accept(inspector); |
| } |
| } |
| |
| execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out}, args); |
| } |
| |
| abstract C withMinApiLevel(AndroidApiLevel minApiLevel); |
| |
| C withKeepAll() { |
| return self(); |
| } |
| |
| C withAndroidJar(AndroidApiLevel androidJarVersion) { |
| assert this.androidJarVersion == null; |
| this.androidJarVersion = androidJarVersion; |
| return self(); |
| } |
| |
| void build(Path inputFile, Path out) throws Throwable { |
| build(inputFile, out, OutputMode.DexIndexed); |
| } |
| |
| abstract void build(Path inputFile, Path out, OutputMode mode) throws Throwable; |
| } |
| |
| private static List<String> minSdkErrorExpected = |
| ImmutableList.of( |
| "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk"); |
| |
| private static Map<DexVm.Version, List<String>> failsOn; |
| |
| static { |
| ImmutableMap.Builder<DexVm.Version, List<String>> builder = ImmutableMap.builder(); |
| builder |
| .put( |
| DexVm.Version.V4_0_4, |
| ImmutableList.of( |
| // API not supported |
| "paramnames", |
| "repeat_annotations_new_api", |
| // Dex version not supported |
| "invokepolymorphic", |
| "invokecustom", |
| "invokecustom2", |
| "DefaultMethodInAndroidJar25", |
| "StaticMethodInAndroidJar25", |
| "testMissingInterfaceDesugared2AndroidO", |
| "testCallToMissingSuperInterfaceDesugaredAndroidO", |
| "testMissingSuperDesugaredAndroidO")) |
| .put( |
| DexVm.Version.V4_4_4, |
| ImmutableList.of( |
| // API not supported |
| "paramnames", |
| "repeat_annotations_new_api", |
| // Dex version not supported |
| "invokepolymorphic", |
| "invokecustom", |
| "invokecustom2", |
| "DefaultMethodInAndroidJar25", |
| "StaticMethodInAndroidJar25", |
| "testMissingInterfaceDesugared2AndroidO", |
| "testCallToMissingSuperInterfaceDesugaredAndroidO", |
| "testMissingSuperDesugaredAndroidO")) |
| .put( |
| DexVm.Version.V5_1_1, |
| ImmutableList.of( |
| // API not supported |
| "paramnames", |
| "repeat_annotations_new_api", |
| // Dex version not supported |
| "invokepolymorphic", |
| "invokecustom", |
| "invokecustom2", |
| "DefaultMethodInAndroidJar25", |
| "StaticMethodInAndroidJar25", |
| "testMissingInterfaceDesugared2AndroidO", |
| "testCallToMissingSuperInterfaceDesugaredAndroidO", |
| "testMissingSuperDesugaredAndroidO")) |
| .put( |
| DexVm.Version.V6_0_1, |
| ImmutableList.of( |
| // API not supported |
| "paramnames", |
| "repeat_annotations_new_api", |
| // Dex version not supported |
| "invokepolymorphic", |
| "invokecustom", |
| "invokecustom2", |
| "DefaultMethodInAndroidJar25", |
| "StaticMethodInAndroidJar25", |
| "testMissingInterfaceDesugared2AndroidO", |
| "testCallToMissingSuperInterfaceDesugaredAndroidO", |
| "testMissingSuperDesugaredAndroidO")) |
| .put( |
| DexVm.Version.V7_0_0, |
| ImmutableList.of( |
| // API not supported |
| "paramnames", |
| // Dex version not supported |
| "invokepolymorphic", |
| "invokecustom", |
| "invokecustom2", |
| "testMissingInterfaceDesugared2AndroidO", |
| "testCallToMissingSuperInterfaceDesugaredAndroidO", |
| "testMissingSuperDesugaredAndroidO")) |
| .put( |
| DexVm.Version.V9_0_0, |
| ImmutableList.of( |
| // TODO(b/120402963): Triage. |
| "invokecustom", "invokecustom2")) |
| .put( |
| DexVm.Version.V10_0_0, |
| ImmutableList.of( |
| // TODO(b/120402963): Triage. |
| "invokecustom", "invokecustom2")) |
| .put( |
| DexVm.Version.V12_0_0, |
| ImmutableList.of( |
| // TODO(b/120402963): Triage. |
| "invokecustom", "invokecustom2")) |
| .put( |
| Version.V13_0_0, |
| ImmutableList.of( |
| // TODO(b/120402963): Triage. |
| "invokecustom", "invokecustom2")) |
| .put(DexVm.Version.DEFAULT, ImmutableList.of()); |
| failsOn = builder.build(); |
| } |
| |
| @Rule |
| public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); |
| |
| @Rule |
| public ExpectedException thrown = ExpectedException.none(); |
| |
| @Rule |
| public TestDescriptionWatcher watcher = new TestDescriptionWatcher(); |
| |
| boolean failsOn(Map<ToolHelper.DexVm.Version, List<String>> failsOn, String name) { |
| Version vmVersion = ToolHelper.getDexVm().getVersion(); |
| return failsOn.containsKey(vmVersion) |
| && failsOn.get(vmVersion).contains(name); |
| } |
| |
| boolean expectedToFail(String name) { |
| return failsOn(failsOn, name); |
| } |
| |
| boolean skipRunningOnJvm(String name) { |
| return name.equals("stringconcat"); |
| } |
| |
| boolean minSdkErrorExpected(String testName) { |
| return minSdkErrorExpected.contains(testName); |
| } |
| |
| @Test |
| public void stringConcat() throws Throwable { |
| test("stringconcat", "stringconcat", "StringConcat") |
| .withMinApiLevel(AndroidApiLevel.K) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void invokeCustom() throws Throwable { |
| test("invokecustom", "invokecustom", "InvokeCustom") |
| .withMinApiLevel(AndroidApiLevel.O) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void invokeCustom2() throws Throwable { |
| test("invokecustom2", "invokecustom2", "InvokeCustom") |
| .withMinApiLevel(AndroidApiLevel.O) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void invokeCustomErrorDueToMinSdk() throws Throwable { |
| test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom") |
| .withMinApiLevel(AndroidApiLevel.N_MR1) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void invokePolymorphic() throws Throwable { |
| test("invokepolymorphic", "invokepolymorphic", "InvokePolymorphic") |
| .withMinApiLevel(AndroidApiLevel.O) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void invokePolymorphicErrorDueToMinSdk() throws Throwable { |
| test("invokepolymorphic-error-due-to-min-sdk", "invokepolymorphic", "InvokePolymorphic") |
| .withMinApiLevel(AndroidApiLevel.N_MR1) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void lambdaDesugaring() throws Throwable { |
| test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring") |
| .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K)) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void lambdaDesugaringNPlus() throws Throwable { |
| test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods") |
| .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K)) |
| .withInterfaceMethodDesugaring(OffOrAuto.Auto) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void desugarDefaultMethodInAndroidJar25() throws Throwable { |
| test("DefaultMethodInAndroidJar25", "desugaringwithandroidjar25", "DefaultMethodInAndroidJar25") |
| .withMinApiLevel(AndroidApiLevel.K) |
| .withAndroidJar(AndroidApiLevel.O) |
| .withInterfaceMethodDesugaring(OffOrAuto.Auto) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void desugarStaticMethodInAndroidJar25() throws Throwable { |
| test("StaticMethodInAndroidJar25", "desugaringwithandroidjar25", "StaticMethodInAndroidJar25") |
| .withMinApiLevel(AndroidApiLevel.K) |
| .withAndroidJar(AndroidApiLevel.O) |
| .withInterfaceMethodDesugaring(OffOrAuto.Auto) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void lambdaDesugaringValueAdjustments() throws Throwable { |
| test("lambdadesugaring-value-adjustments", "lambdadesugaring", "ValueAdjustments") |
| .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K)) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void paramNames() throws Throwable { |
| test("paramnames", "paramnames", "ParameterNames") |
| .withMinApiLevel(AndroidApiLevel.O) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void repeatAnnotationsNewApi() throws Throwable { |
| // No need to specify minSdk as repeat annotations are handled by javac and we do not have |
| // to do anything to support them. The library methods to access them just have to be in |
| // the system. |
| test("repeat_annotations_new_api", "repeat_annotations", "RepeatAnnotationsNewApi"); |
| } |
| |
| @Test |
| public void repeatAnnotations() throws Throwable { |
| // java.lang.annotation.Repeatable is introduced in Android N. |
| test("repeat_annotations", "repeat_annotations", "RepeatAnnotations") |
| .withAndroidJar(AndroidApiLevel.N) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void testTryWithResources() throws Throwable { |
| test("try-with-resources-simplified", "trywithresources", "TryWithResourcesNotDesugaredTests") |
| .withAndroidJar(AndroidApiLevel.K) |
| .withTryWithResourcesDesugaring(OffOrAuto.Off) |
| .withKeepAll() |
| .run(); |
| } |
| |
| @Test |
| public void testTryWithResourcesDesugared() throws Throwable { |
| test("try-with-resources-simplified", "trywithresources", "TryWithResourcesDesugaredTests") |
| .withAndroidJar(AndroidApiLevel.K) |
| .withTryWithResourcesDesugaring(OffOrAuto.Auto) |
| .withInstructionCheck( |
| InstructionSubject::isInvoke, |
| (InvokeInstructionSubject invoke) -> { |
| assertNotEquals("addSuppressed", invoke.invokedMethod().name.toString()); |
| assertNotEquals("getSuppressed", invoke.invokedMethod().name.toString()); |
| }) |
| .withKeepAll() |
| .run(); |
| } |
| |
| |
| @Test |
| public void testLambdaDesugaringWithMainDexList1() throws Throwable { |
| // Minimal case: there are synthesized classes but not form the main dex class. |
| testIntermediateWithMainDexList( |
| "lambdadesugaring", 1, ImmutableList.of("lambdadesugaring.LambdaDesugaring$I")); |
| } |
| |
| @Test |
| public void testLambdaDesugaringWithMainDexList2() throws Throwable { |
| // Main dex class has many lambdas. |
| testIntermediateWithMainDexList( |
| "lambdadesugaring", 98, ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B")); |
| } |
| |
| @Test |
| public void testInterfaceDesugaringWithMainDexList1() throws Throwable { |
| // Main dex interface has one static method. |
| testIntermediateWithMainDexList( |
| "interfacemethods", |
| Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION), |
| 2, |
| ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC")); |
| } |
| |
| |
| @Test |
| public void testInterfaceDesugaringWithMainDexList2() throws Throwable { |
| // Main dex interface has one default method. |
| testIntermediateWithMainDexList( |
| "interfacemethods", |
| Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION), |
| 2, |
| ImmutableList.of("interfacemethods.I2", "interfacemethods.I2$-CC")); |
| } |
| |
| @Test |
| public void testInterfaceDispatchClasses() throws Throwable { |
| test("interfacedispatchclasses", "interfacedispatchclasses", "TestInterfaceDispatchClasses") |
| .withMinApiLevel(AndroidApiLevel.K) // K to create dispatch classes |
| .withAndroidJar(AndroidApiLevel.O) |
| .withArg(String.valueOf(ToolHelper.getMinApiLevelForDexVm().getLevel() >= 24)) |
| .withKeepAll() |
| .run(); |
| } |
| |
| private void testIntermediateWithMainDexList( |
| String packageName, int expectedMainDexListSize, List<String> mainDexClasses) |
| throws Throwable { |
| testIntermediateWithMainDexList( |
| packageName, |
| Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION), |
| expectedMainDexListSize, |
| mainDexClasses); |
| } |
| |
| protected void testIntermediateWithMainDexList( |
| String packageName, Path input, int expectedMainDexListSize, List<String> mainDexClasses) |
| throws Throwable { |
| // R8 does not support merging intermediate builds via DEX. |
| assumeFalse(this instanceof R8RunExamplesAndroidOTest); |
| |
| AndroidApiLevel minApi = AndroidApiLevel.K; |
| |
| // Full build, will be used as reference. |
| TestRunner<?> full = |
| test(packageName + "full", packageName, "N/A") |
| .withInterfaceMethodDesugaring(OffOrAuto.Auto) |
| .withMinApiLevel(minApi) |
| .withOptionConsumer(option -> option.minimalMainDex = true) |
| .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false) |
| .withMainDexKeepClassRules(mainDexClasses) |
| .withKeepAll(); |
| Path fullDexes = temp.getRoot().toPath().resolve(packageName + "full" + ZIP_EXTENSION); |
| full.build(input, fullDexes); |
| |
| // Builds with intermediate in both output mode. |
| Path dexesThroughIndexedIntermediate = |
| buildDexThroughIntermediate( |
| packageName, input, OutputMode.DexIndexed, minApi, mainDexClasses); |
| Path dexesThroughFilePerInputClassIntermediate = |
| buildDexThroughIntermediate( |
| packageName, input, OutputMode.DexFilePerClassFile, minApi, mainDexClasses); |
| |
| // Collect main dex types. |
| CodeInspector fullInspector = getMainDexInspector(fullDexes); |
| CodeInspector indexedIntermediateInspector = |
| getMainDexInspector(dexesThroughIndexedIntermediate); |
| CodeInspector filePerInputClassIntermediateInspector = |
| getMainDexInspector(dexesThroughFilePerInputClassIntermediate); |
| Set<String> fullMainClasses = new HashSet<>(); |
| fullInspector.forAllClasses( |
| clazz -> fullMainClasses.add(clazz.getFinalDescriptor())); |
| Set<String> indexedIntermediateMainClasses = new HashSet<>(); |
| indexedIntermediateInspector.forAllClasses( |
| clazz -> indexedIntermediateMainClasses.add(clazz.getFinalDescriptor())); |
| Set<String> filePerInputClassIntermediateMainClasses = new HashSet<>(); |
| filePerInputClassIntermediateInspector.forAllClasses( |
| clazz -> filePerInputClassIntermediateMainClasses.add(clazz.getFinalDescriptor())); |
| |
| // Check. |
| Assert.assertEquals(expectedMainDexListSize, fullMainClasses.size()); |
| assertEqualSets(fullMainClasses, indexedIntermediateMainClasses); |
| assertEqualSets(fullMainClasses, filePerInputClassIntermediateMainClasses); |
| } |
| |
| <T> void assertEqualSets(Set<T> expected, Set<T> actual) { |
| SetView<T> missing = Sets.difference(expected, actual); |
| SetView<T> unexpected = Sets.difference(actual, expected); |
| if (missing.isEmpty() && unexpected.isEmpty()) { |
| return; |
| } |
| StringBuilder builder = new StringBuilder("Sets differ."); |
| if (!missing.isEmpty()) { |
| builder.append("\nMissing items: [\n "); |
| StringUtils.append(builder, missing, "\n ", BraceType.NONE); |
| builder.append("\n]"); |
| } |
| if (!unexpected.isEmpty()) { |
| builder.append("\nUnexpected items: [\n "); |
| StringUtils.append(builder, unexpected, "\n ", BraceType.NONE); |
| builder.append("\n]"); |
| } |
| fail(builder.toString()); |
| } |
| |
| protected Path buildDexThroughIntermediate( |
| String packageName, |
| Path input, |
| OutputMode outputMode, |
| AndroidApiLevel minApi, |
| List<String> mainDexClasses) |
| throws Throwable { |
| Path intermediateDex = |
| temp.getRoot().toPath().resolve(packageName + "intermediate" + ZIP_EXTENSION); |
| // Build intermediate with D8. |
| D8Command.Builder command = D8Command.builder() |
| .setOutput(intermediateDex, outputMode) |
| .setMinApiLevel(minApi.getLevel()) |
| .addLibraryFiles(ToolHelper.getAndroidJar(minApi)) |
| .setIntermediate(true) |
| .addProgramFiles(input); |
| ToolHelper.runD8(command, option -> { |
| option.interfaceMethodDesugaring = OffOrAuto.Auto; |
| }); |
| |
| TestRunner<?> end = |
| test(packageName + "dex", packageName, "N/A") |
| .withOptionConsumer(option -> option.minimalMainDex = true) |
| .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false) |
| .withMainDexKeepClassRules(mainDexClasses) |
| .withMinApiLevel(minApi) |
| .withKeepAll(); |
| |
| Path dexesThroughIntermediate = |
| temp.getRoot().toPath().resolve(packageName + "dex" + ZIP_EXTENSION); |
| end.build(intermediateDex, dexesThroughIntermediate); |
| return dexesThroughIntermediate; |
| } |
| |
| abstract RunExamplesAndroidOTest<B>.TestRunner<?> test(String testName, String packageName, |
| String mainClass); |
| |
| void execute(String testName, |
| String qualifiedMainClass, Path[] jars, Path[] dexes) throws IOException { |
| execute(testName, qualifiedMainClass, jars, dexes, Collections.emptyList()); |
| } |
| |
| void execute(String testName, |
| String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) { |
| Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles()); |
| boolean expectedToFail = expectedToFail(testName); |
| try { |
| String output = |
| ToolHelper.runArtNoVerificationErrors( |
| Arrays.stream(dexes).map(Path::toString).collect(Collectors.toList()), |
| qualifiedMainClass, |
| builder -> { |
| for (String arg : args) { |
| builder.appendProgramArgument(arg); |
| } |
| }); |
| if (!expectedToFail |
| && !skipRunningOnJvm(testName) |
| && !ToolHelper.compareAgaintsGoldenFiles()) { |
| ArrayList<String> javaArgs = Lists.newArrayList(args); |
| javaArgs.add(0, qualifiedMainClass); |
| ToolHelper.ProcessResult javaResult = |
| ToolHelper.runJava( |
| ImmutableList.copyOf(jars), javaArgs.toArray(StringUtils.EMPTY_ARRAY)); |
| assertEquals("JVM run failed", javaResult.exitCode, 0); |
| assertTrue( |
| "JVM output does not match art output.\n\tjvm: " |
| + javaResult.stdout |
| + "\n\tart: " |
| + output, |
| output.replace("\r", "").equals(javaResult.stdout.replace("\r", ""))); |
| } |
| } catch (Throwable t) { |
| assertTrue("Test was not expected to fail. Failed with " + t.getMessage(), expectedToFail); |
| } |
| } |
| |
| protected CodeInspector getMainDexInspector(Path zip) throws IOException { |
| try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) { |
| try (InputStream in = |
| zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) { |
| return new CodeInspector( |
| AndroidApp.builder() |
| .addDexProgramData(ByteStreams.toByteArray(in), Origin.unknown()) |
| .build()); |
| } |
| } |
| } |
| |
| } |