blob: b9eabe6f49c95013a7ea02bf64287ef811cc4254 [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.diagnostics;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8TestBuilder;
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.origin.Origin;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.StringUtils;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hamcrest.Matcher;
import org.hamcrest.core.IsAnything;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ErrorDuringIrConversionTest extends TestBase {
static final Origin ORIGIN =
new Origin(Origin.root()) {
@Override
public String part() {
return "<test-origin>";
}
};
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withNoneRuntime().build();
}
public ErrorDuringIrConversionTest(TestParameters parameters) {}
private ThrowableConsumer<D8TestBuilder> addTestClassWithOrigin() {
return b ->
b.getBuilder().addClassProgramData(ToolHelper.getClassAsBytes(TestClass.class), ORIGIN);
}
private void checkCompilationFailedException(
CompilationFailedException e, Matcher<String> messageMatcher, Matcher<String> stackMatcher) {
// Check that the failure exception exiting the compiler contains origin info in the message.
assertThat(e.getMessage(), messageMatcher);
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
String fullStackTrace = writer.toString();
// Extract the top cause stack.
int topStackTraceEnd = fullStackTrace.indexOf("Caused by:");
String topStackTrace = fullStackTrace.substring(0, topStackTraceEnd);
String restStackTrace = fullStackTrace.substring(topStackTraceEnd);
// Check that top stack trace always has the version marker.
assertThat(topStackTrace, containsString("fakeStackEntry"));
// Check that top stack has the D8 entry (from tests the non-renamed entry is ToolHelper.runD8).
assertThat(topStackTrace, containsString("com.android.tools.r8.ToolHelper.runD8("));
// Check that the stack trace always has the suppressed info.
assertThat(restStackTrace, containsString(StringUtils.LINE_SEPARATOR + "\tSuppressed:"));
// Custom test checks.
assertThat(restStackTrace, stackMatcher);
}
private static void throwNPE() {
throw new NullPointerException("A test NPE");
}
@Test
public void testNPE() throws Exception {
try {
testForD8()
.apply(addTestClassWithOrigin())
.addOptionsModification(options -> options.testing.hookInIrConversion = () -> throwNPE())
.compileWithExpectedDiagnostics(
diagnostics -> {
// Check that the error is reported as an error to the diagnostics handler.
diagnostics
.assertOnlyErrors()
.assertErrorsMatch(
allOf(
diagnosticOrigin(ORIGIN),
diagnosticException(NullPointerException.class),
diagnosticMessage(containsString("A test NPE"))));
});
} catch (CompilationFailedException e) {
checkCompilationFailedException(
e, containsString(ORIGIN.toString()), containsString("throwNPE"));
return;
}
fail("Expected compilation to fail");
}
@Test
public void testFatalError() throws Exception {
try {
testForD8()
.apply(addTestClassWithOrigin())
.addOptionsModification(
options ->
options.testing.hookInIrConversion =
() -> options.reporter.fatalError("My Fatal Error!"))
.compileWithExpectedDiagnostics(
diagnostics -> {
// Check that the error is reported as an error to the diagnostics handler.
diagnostics.assertOnlyErrors();
diagnostics.assertErrorsMatch(
allOf(
diagnosticType(StringDiagnostic.class),
diagnosticMessage(containsString("My Fatal Error")),
// The fatal error is not given an origin, so it can't provide it.
// Note: This could be fixed by delaying reporting and associate the info
// at the top-level handler. It would require mangling of the diagnostic,
// so maybe not that elegant.
diagnosticOrigin(Origin.unknown())));
});
} catch (CompilationFailedException e) {
checkCompilationFailedException(e, containsString(ORIGIN.toString()), new IsAnything<>());
return;
}
fail("Expected compilation to fail");
}
private static void reportErrors(Reporter reporter) {
reporter.error("FOO!");
reporter.error("BAR!");
reporter.error("BAZ!");
}
@Test
public void testThreeErrors() throws Exception {
AtomicBoolean doError = new AtomicBoolean(true);
try {
testForD8()
.apply(addTestClassWithOrigin())
.addOptionsModification(
options ->
options.testing.hookInIrConversion =
() -> {
// Ensure that the errors are reported just once as IR conversion is
// threaded.
if (doError.getAndSet(false)) {
reportErrors(options.reporter);
}
})
.compileWithExpectedDiagnostics(
diagnostics -> {
// Check that the error is reported as an error to the diagnostics handler.
diagnostics
.assertOnlyErrors()
.assertErrorsCount(3)
.assertAllErrorsMatch(
allOf(
diagnosticOrigin(Origin.unknown()),
diagnosticType(StringDiagnostic.class),
diagnosticMessage(
anyOf(
containsString("FOO!"),
containsString("BAR!"),
containsString("BAZ!")))));
});
} catch (CompilationFailedException e) {
checkCompilationFailedException(
e,
// There may be no fail-if-error barrier inside any origin association, thus only the
// top level message can be expected here.
containsString("Compilation failed to complete"),
// The stack trace must contain the reportErrors frame for the hook above, and one
// of the error messages.
allOf(
containsString("reportErrors"),
anyOf(containsString("FOO!"), containsString("BAR!"), containsString("BAZ!"))));
return;
}
fail("Expected compilation to fail");
}
static class TestClass {
public static void main(String[] args) {
System.out.println("Hello, world");
}
}
}