| // 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; |
| |
| import static com.android.tools.r8.ToolHelper.isLocalDevelopment; |
| import static com.android.tools.r8.ToolHelper.shouldRunSlowTests; |
| import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.assertRewrittenProtoSchemasMatch; |
| import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepAllProtosRule; |
| import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepDynamicMethodSignatureRule; |
| import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepNewMessageInfoSignatureRule; |
| import static org.hamcrest.CoreMatchers.anyOf; |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.CompilationFailedException; |
| import com.android.tools.r8.D8TestCompileResult; |
| import com.android.tools.r8.L8TestCompileResult; |
| import com.android.tools.r8.LibraryDesugaringTestConfiguration; |
| import com.android.tools.r8.R8FullTestBuilder; |
| import com.android.tools.r8.R8TestCompileResult; |
| import com.android.tools.r8.ResourceException; |
| import com.android.tools.r8.StringConsumer.FileConsumer; |
| import com.android.tools.r8.StringResource; |
| import com.android.tools.r8.TestCompileResult; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.ThrowableConsumer; |
| import com.android.tools.r8.tracereferences.TraceReferences; |
| import com.android.tools.r8.tracereferences.TraceReferencesCommand; |
| import com.android.tools.r8.tracereferences.TraceReferencesKeepRules; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.Reporter; |
| 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 java.util.concurrent.ExecutionException; |
| import org.junit.After; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class YouTubeV1719Test extends YouTubeCompilationTestBase { |
| |
| private static final int MAX_APPLICATION_SIZE = 29750000; |
| private static final int MAX_DESUGARED_LIBRARY_SIZE = 425000; |
| |
| private final TestParameters parameters; |
| |
| // Location where test artifacts will be dumped when `local_development` is set. |
| private final Path dumpDirectory = Paths.get("YouTubeV1719-" + System.currentTimeMillis()); |
| |
| // By setting this to an actual startup list, YouTube will be build with layout optimizations |
| // enabled. |
| private final StringResource startupProfile = null; |
| private final boolean enableMinimalStartupDex = true; |
| private final boolean enableStartupBoundaryOptimizations = false; |
| |
| private final Reporter reporter = new Reporter(); |
| |
| @Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.M).build(); |
| } |
| |
| public YouTubeV1719Test(TestParameters parameters) { |
| super(17, 19, parameters.getApiLevel()); |
| this.parameters = parameters; |
| } |
| |
| /** |
| * Running this test will dump an instrumented version of YouTube in the {@link #dumpDirectory}. |
| */ |
| @Test |
| public void testStartupInstrumentation() throws Exception { |
| assumeTrue(isLocalDevelopment()); |
| assumeTrue(shouldRunSlowTests()); |
| |
| // Compile the app with instrumentation and core library desugaring enabled. |
| D8TestCompileResult d8CompileResult = |
| testForD8() |
| .addProgramFiles(getProgramFiles()) |
| .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary()) |
| .addOptionsModification( |
| options -> |
| options |
| .getStartupInstrumentationOptions() |
| .setEnableStartupInstrumentation() |
| .setStartupInstrumentationTag("r8")) |
| .enableCoreLibraryDesugaring( |
| LibraryDesugaringTestConfiguration.builder() |
| .addDesugaredLibraryConfiguration( |
| StringResource.fromFile(getDesugaredLibraryConfiguration())) |
| .build()) |
| .release() |
| .setMinApi(parameters.getApiLevel()) |
| .compile(); |
| |
| // Compile desugared library using cf backend (without keep rules). |
| L8TestCompileResult l8CompileResult = compileDesugaredLibraryWithL8(); |
| |
| // Generate keep rules for desugared library using trace references. |
| Path generatedKeepRules = temp.newFile().toPath(); |
| TraceReferences.run( |
| TraceReferencesCommand.builder() |
| .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary()) |
| .addSourceFiles(d8CompileResult.writeToZip()) |
| .addTargetFiles(l8CompileResult.writeToZip()) |
| .setConsumer( |
| TraceReferencesKeepRules.builder() |
| .setAllowObfuscation(false) |
| .setOutputConsumer(new FileConsumer(generatedKeepRules)) |
| .build()) |
| .build()); |
| |
| // Compile desugared library using the generated keep rules. |
| R8TestCompileResult l8R8CompileResult = |
| compileDesugaredLibraryWithR8(l8CompileResult, generatedKeepRules); |
| |
| if (isLocalDevelopment()) { |
| dump(d8CompileResult, l8R8CompileResult); |
| } |
| } |
| |
| /** |
| * Running this test will dump an R8 build of YouTube in the {@link #dumpDirectory}, where the |
| * desugared library keep rules are generated using trace references. |
| * |
| * <p>If {@link #startupProfile} is set to a concrete startup list, YouTube will be build with |
| * layout optimizations enabled. |
| */ |
| @Test |
| public void testR8() throws Exception { |
| assumeTrue(isLocalDevelopment()); |
| assumeTrue(shouldRunSlowTests()); |
| |
| // Compile app using R8, passing the startup list. |
| R8TestCompileResult r8CompileResult = |
| compileApplicationWithR8( |
| testBuilder -> |
| testBuilder.addOptionsModification( |
| options -> { |
| if (startupProfile != null) { |
| options |
| .getStartupOptions() |
| .setStartupProfile(startupProfile) |
| .setEnableMinimalStartupDex(enableMinimalStartupDex) |
| .setEnableStartupBoundaryOptimizations( |
| enableStartupBoundaryOptimizations); |
| } |
| })); |
| |
| // Compile desugared library using cf backend (without keep rules). |
| L8TestCompileResult l8CompileResult = compileDesugaredLibraryWithL8(); |
| |
| // Generate keep rules for desugared library using trace references. |
| Path generatedKeepRules = temp.newFile().toPath(); |
| TraceReferences.run( |
| TraceReferencesCommand.builder() |
| .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary()) |
| .addSourceFiles(r8CompileResult.writeToZip()) |
| .addTargetFiles(l8CompileResult.writeToZip()) |
| .setConsumer( |
| TraceReferencesKeepRules.builder() |
| .setAllowObfuscation(false) |
| .setOutputConsumer(new FileConsumer(generatedKeepRules)) |
| .build()) |
| .build()); |
| |
| // Compile desugared library using the generated keep rules. |
| R8TestCompileResult l8R8CompileResult = |
| compileDesugaredLibraryWithR8(l8CompileResult, generatedKeepRules); |
| |
| if (isLocalDevelopment()) { |
| dump(r8CompileResult, l8R8CompileResult); |
| } |
| |
| inspect(r8CompileResult, l8CompileResult); |
| } |
| |
| /** |
| * Validates that when all protos are kept and the proto optmization is enabled, the generated |
| * proto schemas are identical to the proto schemas in the input. |
| */ |
| @Test |
| public void testProtoRewriting() throws Exception { |
| assumeTrue(shouldRunSlowTests()); |
| |
| R8TestCompileResult r8CompileResult = |
| compileApplicationWithR8( |
| builder -> |
| builder |
| .addKeepRules( |
| keepAllProtosRule(), |
| keepDynamicMethodSignatureRule(), |
| keepNewMessageInfoSignatureRule()) |
| .allowCheckDiscardedErrors(true)); |
| assertRewrittenProtoSchemasMatch( |
| new CodeInspector(getProgramFiles()), r8CompileResult.inspector()); |
| } |
| |
| @After |
| public void teardown() { |
| reporter.failIfPendingErrors(); |
| } |
| |
| private R8TestCompileResult compileApplicationWithR8( |
| ThrowableConsumer<R8FullTestBuilder> configuration) |
| throws IOException, CompilationFailedException { |
| return testForR8(parameters.getBackend()) |
| .addProgramFiles(getProgramFiles()) |
| .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary()) |
| .addKeepRuleFiles(getKeepRuleFiles()) |
| .addDontWarn("android.app.Activity$TranslucentConversionListener") |
| .apply(configuration) |
| .apply(this::disableR8StrictMode) |
| .apply(this::disableR8TestingDefaults) |
| .setMinApi(getApiLevel()) |
| .enableCoreLibraryDesugaring( |
| LibraryDesugaringTestConfiguration.builder() |
| .addDesugaredLibraryConfiguration( |
| StringResource.fromFile(getDesugaredLibraryConfiguration())) |
| .build()) |
| .compile() |
| .assertAllInfoMessagesMatch( |
| anyOf( |
| containsString("Ignoring option: -optimizations"), |
| containsString("Proguard configuration rule does not match anything"), |
| containsString("Invalid signature"))) |
| .apply(this::printProtoStats); |
| } |
| |
| private L8TestCompileResult compileDesugaredLibraryWithL8() |
| throws CompilationFailedException, IOException, ExecutionException { |
| return testForL8(getApiLevel(), Backend.CF) |
| .setDesugaredLibrarySpecification(getDesugaredLibraryConfiguration()) |
| .addProgramFiles(getDesugaredLibraryJDKLibs()) |
| .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary()) |
| .compile(); |
| } |
| |
| private R8TestCompileResult compileDesugaredLibraryWithR8( |
| L8TestCompileResult l8CompileResult, Path generatedKeepRules) |
| throws IOException, CompilationFailedException { |
| return testForR8(parameters.getBackend()) |
| .addProgramFiles(l8CompileResult.writeToZip()) |
| .addLibraryFiles(getLibraryFileWithoutDesugaredLibrary()) |
| .addKeepRuleFiles(getDesugaredLibraryKeepRuleFiles(generatedKeepRules)) |
| .apply(this::disableR8StrictMode) |
| .apply(this::disableR8TestingDefaults) |
| .setMinApi(parameters.getApiLevel()) |
| .compile(); |
| } |
| |
| private void inspect(R8TestCompileResult r8CompileResult, L8TestCompileResult l8CompileResult) |
| throws IOException, ResourceException { |
| r8CompileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors(); |
| l8CompileResult.runDex2Oat(parameters.getRuntime()).assertNoVerificationErrors(); |
| |
| int applicationSize = r8CompileResult.getApp().applicationSize(); |
| if (applicationSize > MAX_APPLICATION_SIZE) { |
| reporter.error( |
| "Expected application size to be <=" |
| + MAX_APPLICATION_SIZE |
| + ", but was " |
| + applicationSize); |
| } |
| |
| int desugaredLibrarySize = l8CompileResult.getApp().applicationSize(); |
| if (desugaredLibrarySize > MAX_DESUGARED_LIBRARY_SIZE) { |
| reporter.error( |
| "Expected desugared library size to be <=" |
| + MAX_DESUGARED_LIBRARY_SIZE |
| + ", but was " |
| + desugaredLibrarySize); |
| } |
| |
| if (isLocalDevelopment()) { |
| System.out.println("Dex size (application, excluding desugared library): " + applicationSize); |
| System.out.println("Dex size (desugared library): " + desugaredLibrarySize); |
| System.out.println("Dex size (total): " + (applicationSize + desugaredLibrarySize)); |
| } |
| } |
| |
| private void dump(TestCompileResult<?, ?> compileResult, R8TestCompileResult l8R8CompileResult) |
| throws IOException { |
| assertTrue(isLocalDevelopment()); |
| Files.createDirectories(dumpDirectory); |
| |
| // Dump dex. |
| compileResult.writeToDirectory(dumpDirectory); |
| |
| // Dump mapping. |
| if (compileResult instanceof R8TestCompileResult) { |
| R8TestCompileResult r8TestCompileResult = (R8TestCompileResult) compileResult; |
| r8TestCompileResult.writeProguardMap(dumpDirectory.resolve("mapping.txt")); |
| } |
| |
| // Dump desugared library dex. |
| int i = 2; |
| while (true) { |
| Path desugaredLibraryDexFile = dumpDirectory.resolve("classes" + i + ".dex"); |
| if (!desugaredLibraryDexFile.toFile().exists()) { |
| l8R8CompileResult.writeSingleDexOutputToFile(desugaredLibraryDexFile); |
| break; |
| } |
| i++; |
| } |
| |
| // Dump desugared library mapping. |
| l8R8CompileResult.writeProguardMap(dumpDirectory.resolve("mapping-desugared-library.txt")); |
| } |
| |
| private void disableR8StrictMode(R8FullTestBuilder testBuilder) { |
| testBuilder |
| .allowDiagnosticMessages() |
| .allowUnusedDontWarnPatterns() |
| .allowUnusedProguardConfigurationRules(); |
| } |
| |
| private void disableR8TestingDefaults(R8FullTestBuilder testBuilder) { |
| testBuilder.addOptionsModification( |
| options -> options.horizontalClassMergerOptions().setEnableInterfaceMerging(false)); |
| } |
| } |