blob: 7a71837aa3ab05efade107954078f6a38b822266 [file] [log] [blame]
// Copyright (c) 2020, 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.profile.art.ArtProfile;
import com.android.tools.r8.profile.art.ArtProfileConsumer;
import com.android.tools.r8.profile.art.ArtProfileForRewriting;
import com.android.tools.r8.profile.art.ArtProfileProvider;
import com.android.tools.r8.profile.startup.profile.StartupProfile;
import com.android.tools.r8.profile.startup.profile.StartupProfileRule;
import com.android.tools.r8.startup.StartupProfileProvider;
import com.android.tools.r8.startup.diagnostic.MissingStartupProfileItemsDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
public abstract class CommandTestBase<C extends BaseCompilerCommand> extends TestBase {
public boolean isL8() {
return false;
}
private void mapDiagnosticsMissingArguments(String... args) {
try {
DiagnosticsChecker.checkErrorsContains(
"Missing argument(s) for --map-diagnostics", handler -> parse(handler, args));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void mapDiagnosticsMissingArguments() throws Exception {
mapDiagnosticsMissingArguments("--map-diagnostics");
mapDiagnosticsMissingArguments("--map-diagnostics", "error");
mapDiagnosticsMissingArguments("--map-diagnostics", "warning");
mapDiagnosticsMissingArguments("--map-diagnostics", "info");
mapDiagnosticsMissingArguments("--map-diagnostics", "xxx");
}
private void mapDiagnosticsInvalidArguments(String... args) {
try {
DiagnosticsChecker.checkErrorsContains(
"Invalid diagnostics level 'xxx'", handler -> parse(handler, args));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void mapDiagnosticsInvalidArguments() throws Exception {
mapDiagnosticsInvalidArguments("--map-diagnostics", "error", "xxx");
mapDiagnosticsInvalidArguments("--map-diagnostics", "warning", "xxx");
mapDiagnosticsInvalidArguments("--map-diagnostics", "info", "xxx");
mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "xxx");
mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "error");
mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "warning");
mapDiagnosticsInvalidArguments("--map-diagnostics", "xxx", "info");
mapDiagnosticsInvalidArguments("--debug", "--map-diagnostics", "error", "xxx", "--debug");
}
@Test
public void mapDiagnosticsInvalidArgumentsMoreErrors() {
try {
DiagnosticsChecker.checkErrorsContains(
ImmutableList.of("Invalid diagnostics level 'xxx'", "Unknown option: --xxx"),
handler -> parse(handler, "--debug", "--map-diagnostics", "error", "xxx", "--xxx"));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void errorsToWarnings() throws Exception {
DiagnosticsChecker.checkWarningsContains(
"Error",
handler -> {
C command = parseWithRequiredArgs(handler, "--map-diagnostics", "error", "warning");
command.getReporter().error("Error");
});
try {
DiagnosticsChecker.checkErrorsContains(
"Test diagnostic",
handler -> {
C command =
parseWithRequiredArgs(handler, "--map-diagnostics:a.b.C", "error", "warning");
command.getReporter().error(new TestDiagnostic());
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Failure expected");
} catch (CompilationFailedException e) {
// Expected.
}
DiagnosticsChecker.checkWarningsContains(
"Test diagnostic",
handler -> {
C command =
parseWithRequiredArgs(
handler,
"--map-diagnostics:com.android.tools.r8.TestDiagnostic",
"error",
"warning");
command.getReporter().error(new TestDiagnostic());
});
DiagnosticsChecker.checkWarningsContains(
"Test diagnostic",
handler -> {
C command =
parseWithRequiredArgs(
handler, "--map-diagnostics:TestDiagnostic", "error", "warning");
command.getReporter().error(new TestDiagnostic());
});
}
@Test
public void errorsToInfo() throws Exception {
DiagnosticsChecker.checkInfosContains(
"Error",
handler -> {
C command = parseWithRequiredArgs(handler, "--map-diagnostics", "error", "info");
command.getReporter().error("Error");
});
try {
DiagnosticsChecker.checkErrorsContains(
"Test diagnostic",
handler -> {
C command = parseWithRequiredArgs(handler, "--map-diagnostics:a.b.C", "error", "info");
command.getReporter().error(new TestDiagnostic());
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Failure expected");
} catch (CompilationFailedException e) {
// Expected.
}
DiagnosticsChecker.checkInfosContains(
"Test diagnostic",
handler -> {
C command =
parseWithRequiredArgs(handler, "--map-diagnostics:TestDiagnostic", "error", "info");
command.getReporter().error(new TestDiagnostic());
});
}
@Test
public void warningsToInfo() throws Exception {
DiagnosticsChecker.checkInfosContains(
"Warning",
handler -> {
C command = parseWithRequiredArgs(handler, "--map-diagnostics", "warning", "info");
command.getReporter().warning("Warning");
});
DiagnosticsChecker.checkInfosContains(
"Test diagnostic",
handler -> {
C command =
parseWithRequiredArgs(handler, "--map-diagnostics:TestDiagnostic", "warning", "info");
command.getReporter().warning(new TestDiagnostic());
});
}
@Test
public void warningsToError() {
try {
DiagnosticsChecker.checkErrorsContains(
"Warning",
handler -> {
C command = parseWithRequiredArgs(handler, "--map-diagnostics", "warning", "error");
command.getReporter().warning("Warning");
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
try {
DiagnosticsChecker.checkErrorsContains(
"Test diagnostic",
handler -> {
C command =
parseWithRequiredArgs(
handler, "--map-diagnostics:TestDiagnostic", "warning", "error");
command.getReporter().warning(new TestDiagnostic());
try {
command.getReporter().failIfPendingErrors();
} catch (RuntimeException e) {
throw InternalCompilationFailedExceptionUtils.createForTesting();
}
});
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void errorsToWarningsWarningsToInfos() throws Exception {
DiagnosticsChecker.checkInfosContains(
"Error",
handler -> {
C command =
parseWithRequiredArgs(
handler,
"--map-diagnostics",
"error",
"warning",
"--map-diagnostics",
"warning",
"info");
command.getReporter().error("Error");
});
DiagnosticsChecker.checkInfosContains(
"Test diagnostic",
handler -> {
C command =
parseWithRequiredArgs(
handler,
"--map-diagnostics:TestDiagnostic",
"error",
"warning",
"--map-diagnostics",
"warning",
"info");
command.getReporter().error(new TestDiagnostic());
});
}
@Test
public void warningsToInfosErrorsToWarnings() throws Exception {
DiagnosticsChecker.checkDiagnostics(
diagnostics -> {
assertEquals(0, diagnostics.errors.size());
diagnostics.checkWarningsContains("Error");
diagnostics.checkInfosContains("Warning");
},
handler -> {
C command =
parseWithRequiredArgs(
handler,
"--map-diagnostics",
"warning",
"info",
"--map-diagnostics",
"error",
"warning");
command.getReporter().error("Error");
command.getReporter().warning("Warning");
});
DiagnosticsChecker.checkDiagnostics(
diagnostics -> {
assertEquals(0, diagnostics.errors.size());
diagnostics.checkWarningsContains("Test diagnostic");
diagnostics.checkInfosContains("Test diagnostic");
},
handler -> {
C command =
parseWithRequiredArgs(
handler,
"--map-diagnostics:TestDiagnostic",
"warning",
"info",
"--map-diagnostics:TestDiagnostic",
"error",
"warning");
command.getReporter().error(new TestDiagnostic());
command.getReporter().warning(new TestDiagnostic());
});
}
@Test
public void artProfileFlagAbsentTest() throws Exception {
assertTrue(parseWithRequiredArgs().getArtProfilesForRewriting().isEmpty());
}
@Test
public void artProfileFlagPresentTest() throws Exception {
// Create a simple profile.
Path profile = temp.newFile("profile.txt").toPath();
Path residualProfile = temp.newFile("profile-out.txt").toPath();
String profileRuleFlags = "HSP";
String profileRuleDescriptor = "Lfoo/bar/Baz;->qux()V";
FileUtils.writeTextFile(profile, profileRuleFlags + profileRuleDescriptor);
// Pass the profile on the command line.
List<String> args =
new ArrayList<>(
ImmutableList.of("--art-profile", profile.toString(), residualProfile.toString()));
// L8 only accepts an art profile when shrinking.
if (this instanceof L8CommandTest) {
Path path = temp.newFile("proguard-rules.pro").toPath();
FileUtils.writeTextFile(path, "-dontshrink");
args.addAll(ImmutableList.of("--pg-conf", path.toString()));
}
List<ArtProfileForRewriting> artProfilesForRewriting =
parseWithRequiredArgs(args.toArray(new String[0])).getArtProfilesForRewriting();
assertEquals(1, artProfilesForRewriting.size());
// Extract inputs.
ArtProfileForRewriting artProfileForRewriting = artProfilesForRewriting.get(0);
ArtProfileProvider artProfileProvider = artProfileForRewriting.getArtProfileProvider();
ArtProfileConsumer residualArtProfileConsumer =
artProfileForRewriting.getResidualArtProfileConsumer();
InternalOptions options = new InternalOptions();
// Build provided ART profile.
ArtProfile.Builder artProfileBuilder =
ArtProfile.builderForInitialArtProfile(artProfileProvider, options);
artProfileProvider.getArtProfile(artProfileBuilder);
ArtProfile artProfile = artProfileBuilder.build();
// Verify we found the same rule.
assertEquals(1, artProfile.size());
IntBox count = new IntBox();
artProfile.forEachRule(
classRule -> fail(),
methodRule -> {
assertEquals(profileRuleDescriptor, methodRule.getMethod().toSmaliString());
assertTrue(methodRule.getMethodRuleInfo().isHot());
assertTrue(methodRule.getMethodRuleInfo().isStartup());
assertTrue(methodRule.getMethodRuleInfo().isPostStartup());
count.increment();
});
assertEquals(1, count.get());
// Supply the rule back to the consumer.
artProfile.supplyConsumer(residualArtProfileConsumer, options.reporter);
assertEquals(
ImmutableList.of(profileRuleFlags + profileRuleDescriptor),
FileUtils.readAllLines(residualProfile));
}
@Test
public void artProfileFlagMissingInputOutputParameterTest() {
String expectedErrorContains = "Missing parameter for --art-profile.";
try {
DiagnosticsChecker.checkErrorsContains(
expectedErrorContains, handler -> parseWithRequiredArgs(handler, "--art-profile"));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void artProfileFlagMissingOutputParameterTest() throws Exception {
String expectedErrorContains = "Missing parameter for --art-profile.";
Path profile = temp.newFile("profile.txt").toPath();
FileUtils.writeTextFile(profile, "");
try {
DiagnosticsChecker.checkErrorsContains(
expectedErrorContains,
handler -> parseWithRequiredArgs(handler, "--art-profile", profile.toString()));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
@Test
public void startupProfileFlagAbsentTest() throws Exception {
assertTrue(parseWithRequiredArgs().getStartupProfileProviders().isEmpty());
}
@Test
public void startupProfileFlagPresentTest() throws Exception {
// Create a simple profile.
Path profile = temp.newFile("profile.txt").toPath();
String profileRule = "Lfoo/bar/Baz;->qux()V";
FileUtils.writeTextFile(profile, profileRule);
// Pass the profile on the command line.
List<StartupProfileProvider> startupProfileProviders;
try {
startupProfileProviders =
parseWithRequiredArgs(
"--min-api",
Integer.toString(AndroidApiLevel.L.getLevel()),
"--startup-profile",
profile.toString())
.getStartupProfileProviders();
} catch (CompilationFailedException e) {
assertTrue(isL8());
return;
}
assertEquals(1, startupProfileProviders.size());
// Construct the internal profile representation using the provider.
InternalOptions options = new InternalOptions();
MissingStartupProfileItemsDiagnostic.Builder missingStartupProfileItemsDiagnosticBuilder =
MissingStartupProfileItemsDiagnostic.Builder.nop();
StartupProfileProvider startupProfileProvider = startupProfileProviders.get(0);
StartupProfile.Builder startupProfileBuilder =
StartupProfile.builder(
options, missingStartupProfileItemsDiagnosticBuilder, startupProfileProvider);
startupProfileProvider.getStartupProfile(startupProfileBuilder);
// Verify we found the same rule.
StartupProfile startupProfile = startupProfileBuilder.build();
Collection<StartupProfileRule> startupItems =
ListUtils.newArrayList(consumer -> startupProfile.forEachRule(consumer::accept));
assertEquals(1, startupItems.size());
StartupProfileRule startupItem = startupItems.iterator().next();
startupItem.accept(
startupClass -> fail(),
startupMethod -> assertEquals(profileRule, startupMethod.getReference().toSmaliString()));
}
@Test
public void startupProfileFlagMissingParameterTest() {
String expectedErrorContains =
isL8() ? "Unknown option: --startup-profile" : "Missing parameter for --startup-profile.";
try {
DiagnosticsChecker.checkErrorsContains(
expectedErrorContains, handler -> parseWithRequiredArgs(handler, "--startup-profile"));
fail("Expected failure");
} catch (CompilationFailedException e) {
// Expected.
}
}
private String[] prepareArgs(String[] args) {
String[] actualTestArgs;
String[] additionalTestArgs = requiredArgsForTest();
if (additionalTestArgs.length > 0) {
actualTestArgs = new String[args.length + additionalTestArgs.length];
System.arraycopy(additionalTestArgs, 0, actualTestArgs, 0, additionalTestArgs.length);
System.arraycopy(args, 0, actualTestArgs, additionalTestArgs.length, args.length);
} else {
actualTestArgs = args;
}
return actualTestArgs;
}
private C parseWithRequiredArgs(String... args) throws CompilationFailedException {
return parse(prepareArgs(args));
}
private C parseWithRequiredArgs(DiagnosticsHandler handler, String... args)
throws CompilationFailedException {
return parse(handler, prepareArgs(args));
}
protected InternalOptions getOptionsWithLoadedDesugaredLibraryConfiguration(
C command, boolean libraryCompilation) throws IOException {
InternalOptions options = command.getInternalOptions();
LibraryDesugaringSpecification spec = LibraryDesugaringSpecification.JDK11;
options.loadMachineDesugaredLibrarySpecification(
Timing.empty(), spec.getAppForTesting(options, libraryCompilation));
return options;
}
/**
* Tests in this class are executed for all of D8, R8 and L8. When testing arguments this can add
* additional required arguments to all tests
*/
abstract String[] requiredArgsForTest();
abstract C parse(String... args) throws CompilationFailedException;
abstract C parse(DiagnosticsHandler handler, String... args) throws CompilationFailedException;
}