| // Copyright (c) 2022, 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.internal.startup; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.ArchiveProgramResourceProvider; |
| import com.android.tools.r8.DexIndexedConsumer; |
| import com.android.tools.r8.R8FullTestBuilder; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.ThrowableConsumer; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.experimental.startup.StartupProfileProviderUtils; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.ZipUtils; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameter; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class ChromeStartupTest extends TestBase { |
| |
| private static AndroidApiLevel apiLevel = AndroidApiLevel.N; |
| |
| // Location of dump.zip and startup.txt. |
| private static Path chromeDirectory = Paths.get("build/chrome/startup"); |
| |
| // Location of test artifacts. |
| private static Path artifactDirectory = Paths.get("build/chrome/startup"); |
| |
| // Temporary directory where dump.zip is extracted into. |
| private static Path dumpDirectory; |
| |
| @Parameter(0) |
| public TestParameters parameters; |
| |
| @Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withNoneRuntime().build(); |
| } |
| |
| @BeforeClass |
| public static void setup() throws IOException { |
| assumeTrue(ToolHelper.isLocalDevelopment()); |
| dumpDirectory = getStaticTemp().newFolder().toPath(); |
| ZipUtils.unzip(chromeDirectory.resolve("dump.zip"), dumpDirectory); |
| } |
| |
| // Outputs the instrumented dex in chrome/instrumented. |
| @Test |
| public void buildInstrumentedDex() throws Exception { |
| buildInstrumentedBase(); |
| buildInstrumentedChromeSplit(); |
| } |
| |
| private void buildInstrumentedBase() throws Exception { |
| Files.createDirectories(artifactDirectory.resolve("instrumented/base/dex")); |
| testForD8() |
| .addProgramFiles(dumpDirectory.resolve("program.jar")) |
| .addClasspathFiles(dumpDirectory.resolve("classpath.jar")) |
| .addLibraryFiles(dumpDirectory.resolve("library.jar")) |
| .addOptionsModification( |
| options -> |
| options |
| .getStartupInstrumentationOptions() |
| .setEnableStartupInstrumentation() |
| .setStartupInstrumentationTag("r8")) |
| .setMinApi(apiLevel) |
| .release() |
| .compile() |
| .writeToDirectory(artifactDirectory.resolve("instrumented/base/dex")); |
| } |
| |
| private void buildInstrumentedChromeSplit() throws Exception { |
| Files.createDirectories(artifactDirectory.resolve("instrumented/chrome/dex")); |
| testForD8() |
| // The Chrome split is the feature that contains ChromeApplicationImpl. |
| .addProgramFiles(getChromeSplit()) |
| .addClasspathFiles(dumpDirectory.resolve("program.jar")) |
| .addClasspathFiles(dumpDirectory.resolve("classpath.jar")) |
| .addLibraryFiles(dumpDirectory.resolve("library.jar")) |
| .addOptionsModification( |
| options -> |
| options |
| .getStartupInstrumentationOptions() |
| .setEnableStartupInstrumentation() |
| .setStartupInstrumentationTag("r8")) |
| .setMinApi(apiLevel) |
| .release() |
| .compile() |
| .inspect( |
| inspector -> |
| assertThat( |
| inspector.clazz("org.chromium.chrome.browser.ChromeApplicationImpl"), |
| isPresent())) |
| .writeToDirectory(artifactDirectory.resolve("instrumented/chrome/dex")); |
| } |
| |
| private Path getChromeSplit() { |
| return getFeatureSplit(9); |
| } |
| |
| private Path getFeatureSplit(int index) { |
| return dumpDirectory.resolve("feature-" + index + ".jar"); |
| } |
| |
| // Outputs Chrome built using R8 in chrome/default. |
| @Test |
| public void buildR8Default() throws Exception { |
| buildR8(ThrowableConsumer.empty(), artifactDirectory.resolve("default")); |
| } |
| |
| // Outputs Chrome built using R8 with limited class merging in chrome/default-with-patches. |
| @Test |
| public void buildR8DefaultWithPatches() throws Exception { |
| buildR8( |
| testBuilder -> |
| testBuilder.addOptionsModification( |
| options -> options.horizontalClassMergerOptions().setEnableSameFilePolicy(true)), |
| artifactDirectory.resolve("default-with-patches")); |
| } |
| |
| // Outputs Chrome built using R8 with minimal startup dex and no boundary optimizations in |
| // chrome/optimized-minimal-nooptimize. |
| @Test |
| public void buildR8MinimalStartupDexWithoutBoundaryOptimizations() throws Exception { |
| boolean enableMinimalStartupDex = true; |
| boolean enableStartupBoundaryOptimizations = false; |
| buildR8Startup( |
| enableMinimalStartupDex, |
| enableStartupBoundaryOptimizations, |
| artifactDirectory.resolve("optimized-minimal-nooptimize")); |
| } |
| |
| // Outputs Chrome built using R8 with minimal startup dex and boundary optimizations enabled in |
| // chrome/optimized-minimal-optimize. |
| @Test |
| public void buildR8MinimalStartupDexWithBoundaryOptimizations() throws Exception { |
| boolean enableMinimalStartupDex = true; |
| boolean enableStartupBoundaryOptimizations = true; |
| buildR8Startup( |
| enableMinimalStartupDex, |
| enableStartupBoundaryOptimizations, |
| artifactDirectory.resolve("optimized-minimal-optimize")); |
| } |
| |
| // Outputs Chrome built using R8 with startup layout enabled and no boundary optimizations in |
| // chrome/optimized-nominimal-nooptimize. |
| @Test |
| public void buildR8StartupLayoutWithoutBoundaryOptimizations() throws Exception { |
| boolean enableMinimalStartupDex = false; |
| boolean enableStartupBoundaryOptimizations = false; |
| buildR8Startup( |
| enableMinimalStartupDex, |
| enableStartupBoundaryOptimizations, |
| artifactDirectory.resolve("optimized-nominimal-nooptimize")); |
| } |
| |
| // Outputs Chrome built using R8 with startup layout enabled and no boundary optimizations in |
| // chrome/optimized-nominimal-optimize. |
| @Test |
| public void buildR8StartupLayoutWithBoundaryOptimizations() throws Exception { |
| boolean enableMinimalStartupDex = false; |
| boolean enableStartupBoundaryOptimizations = true; |
| buildR8Startup( |
| enableMinimalStartupDex, |
| enableStartupBoundaryOptimizations, |
| artifactDirectory.resolve("optimized-nominimal-optimize")); |
| } |
| |
| private void buildR8(ThrowableConsumer<R8FullTestBuilder> configuration, Path outDirectory) |
| throws Exception { |
| Files.createDirectories(outDirectory.resolve("base/dex")); |
| Files.createDirectories(outDirectory.resolve("chrome/dex")); |
| testForR8(Backend.DEX) |
| .addProgramFiles(dumpDirectory.resolve("program.jar")) |
| .addClasspathFiles(dumpDirectory.resolve("classpath.jar")) |
| .addLibraryFiles(dumpDirectory.resolve("library.jar")) |
| .addKeepRuleFiles(dumpDirectory.resolve("proguard.config")) |
| .apply( |
| testBuilder -> { |
| int i = 1; |
| boolean seenChromeSplit = false; |
| for (; i <= 12; i++) { |
| Path feature = getFeatureSplit(i); |
| boolean isChromeSplit = feature.equals(getChromeSplit()); |
| seenChromeSplit |= isChromeSplit; |
| assertTrue(feature.toFile().exists()); |
| testBuilder.addFeatureSplit( |
| featureSplitBuilder -> |
| featureSplitBuilder |
| .addProgramResourceProvider( |
| ArchiveProgramResourceProvider.fromArchive(feature)) |
| .setProgramConsumer( |
| isChromeSplit |
| ? new DexIndexedConsumer.DirectoryConsumer( |
| outDirectory.resolve("chrome/dex")) |
| : DexIndexedConsumer.emptyConsumer()) |
| .build()); |
| } |
| assertFalse(dumpDirectory.resolve("feature-" + i + ".jar").toFile().exists()); |
| assertTrue(seenChromeSplit); |
| assertThat( |
| new CodeInspector(getChromeSplit()) |
| .clazz("org.chromium.chrome.browser.ChromeApplicationImpl"), |
| isPresent()); |
| }) |
| .apply(configuration) |
| .apply(this::disableR8StrictMode) |
| .apply(this::disableR8TestingDefaults) |
| .setMinApi(apiLevel) |
| .compile() |
| .writeToDirectory(outDirectory.resolve("base/dex")); |
| } |
| |
| private void buildR8Startup( |
| boolean enableMinimalStartupDex, |
| boolean enableStartupBoundaryOptimizations, |
| Path outDirectory) |
| throws Exception { |
| buildR8( |
| testBuilder -> |
| testBuilder |
| .addOptionsModification( |
| options -> |
| options |
| .getStartupOptions() |
| .setEnableMinimalStartupDex(enableMinimalStartupDex) |
| .setEnableStartupBoundaryOptimizations( |
| enableStartupBoundaryOptimizations)) |
| .addStartupProfileProviders( |
| StartupProfileProviderUtils.createFromHumanReadableArtProfile( |
| chromeDirectory.resolve("startup.txt"))), |
| outDirectory); |
| } |
| |
| private void disableR8StrictMode(R8FullTestBuilder testBuilder) { |
| testBuilder |
| .allowDiagnosticMessages() |
| .allowUnnecessaryDontWarnWildcards() |
| .allowUnusedDontWarnPatterns() |
| .allowUnusedProguardConfigurationRules() |
| .addOptionsModification( |
| options -> options.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces()); |
| } |
| |
| private void disableR8TestingDefaults(R8FullTestBuilder testBuilder) { |
| testBuilder.addOptionsModification( |
| options -> options.horizontalClassMergerOptions().setEnableInterfaceMerging(false)); |
| } |
| } |