Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ public void doTest() {
.that(result)
.isEqualTo(expectedResult.orElse(Result.OK));
} else {
diagnosticHelper.resetHandledDiagnostics();
ImmutableList.Builder<String> mismatches = ImmutableList.builder();
for (JavaFileObject source : sources) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ public void expectErrorMessage(String key, Predicate<? super String> matcher) {
expectedErrorMsgs.put(key, matcher);
}

/**
* Asserts that the diagnostics contain a diagnostic on each line of the source file that matches
* our bug marker pattern. Parses the bug marker pattern for the specific string to look for in
* the diagnostic. The failure message names every line that fails, each with the first
* expectation it fails.
*
* @param source File in which to find matching lines
*/
private final Set<Diagnostic<? extends JavaFileObject>> handledDiagnostics = new HashSet<>();

void resetHandledDiagnostics() {
handledDiagnostics.clear();
}

/**
* Asserts that the diagnostics contain a diagnostic on each line of the source file that matches
* our bug marker pattern. Parses the bug marker pattern for the specific string to look for in
Expand All @@ -223,6 +237,7 @@ public void expectErrorMessage(String key, Predicate<? super String> matcher) {
public void assertHasDiagnosticOnAllMatchingLines(
JavaFileObject source, LookForCheckNameInDiagnostic lookForCheckNameInDiagnostic)
throws IOException {
resetHandledDiagnostics();
ImmutableList<String> mismatches = findMismatchedLines(source, lookForCheckNameInDiagnostic);
if (!mismatches.isEmpty()) {
fail(describeMismatches(mismatches));
Expand All @@ -238,6 +253,8 @@ ImmutableList<String> findMismatchedLines(
JavaFileObject source, LookForCheckNameInDiagnostic lookForCheckNameInDiagnostic)
throws IOException {
List<Diagnostic<? extends JavaFileObject>> diagnostics = getDiagnostics();
ImmutableList<String> sourceLines =
CharSource.wrap(source.getCharContent(false)).readLines();
ImmutableList.Builder<String> mismatches = ImmutableList.builder();
LineNumberReader reader =
new LineNumberReader(CharSource.wrap(source.getCharContent(false)).openStream());
Expand All @@ -250,6 +267,7 @@ ImmutableList<String> findMismatchedLines(
List<Predicate<? super String>> predicates = null;
// The first expectation this line fails, so that each line gets at most one message.
String mismatch = null;
int failedLineNumber = -1;
if (line.contains(BUG_MARKER_COMMENT_INLINE)) {
// Diagnostic must contain all patterns from the bug marker comment.
List<String> patterns = extractPatterns(line, reader, BUG_MARKER_COMMENT_INLINE);
Expand All @@ -268,6 +286,7 @@ ImmutableList<String> findMismatchedLines(
"No expected error message with key [%s] as expected from line [%s] "
+ "with diagnostic [%s]",
lookupKey, markerLineNumber, line.trim());
failedLineNumber = markerLineNumber;
break;
}
predicates.add(expectedErrorMsgs.get(lookupKey));
Expand All @@ -284,6 +303,7 @@ ImmutableList<String> findMismatchedLines(
mismatch =
String.format(
"Did not see an error on line %s matching %s.", lineNumber, predicate);
failedLineNumber = lineNumber;
}
}

Expand All @@ -299,39 +319,84 @@ ImmutableList<String> findMismatchedLines(
mismatch =
String.format(
"Did not see an error on line %s containing [%s].", lineNumber, checkName);
failedLineNumber = lineNumber;
}
}

if (mismatch == null) {
for (Diagnostic<? extends JavaFileObject> d : diagnostics) {
if (d.getSource() != null
&& d.getSource().toUri().equals(source.toUri())
&& d.getLineNumber() == lineNumber) {
handledDiagnostics.add(d);
}
}
}
} else {
int lineNumber = reader.getLineNumber();
Matcher<? super Iterable<Diagnostic<? extends JavaFileObject>>> matcher =
hasItem(diagnosticOnLine(source.toUri(), lineNumber));
if (matcher.matches(diagnostics)) {
mismatch = "Saw unexpected error on line " + lineNumber + ".";
failedLineNumber = lineNumber;
}
}
if (mismatch != null) {
mismatches.add(mismatch);
int targetLine = failedLineNumber > 0 ? failedLineNumber : reader.getLineNumber();
List<Diagnostic<? extends JavaFileObject>> lineDiagnostics = new ArrayList<>();
for (Diagnostic<? extends JavaFileObject> d : diagnostics) {
if (d.getSource() != null
&& d.getSource().toUri().equals(source.toUri())
&& d.getLineNumber() == targetLine) {
lineDiagnostics.add(d);
}
}
handledDiagnostics.addAll(lineDiagnostics);
if (!lineDiagnostics.isEmpty()) {
mismatches.add(
mismatch
+ "\n"
+ lineDiagnostics.stream()
.map(Object::toString)
.collect(Collectors.joining("\n\n")));
} else {
String sourceLine =
targetLine > 0 && targetLine <= sourceLines.size()
? sourceLines.get(targetLine - 1).trim()
: "";
mismatches.add(
mismatch
+ "\n"
+ source.getName()
+ ":"
+ targetLine
+ ": no diagnostic was reported on this line"
+ (sourceLine.isEmpty() ? "" : "\n " + sourceLine));
}
}
} while (true);
reader.close();
return mismatches.build();
}

/**
* Returns a failure message with each of {@code mismatches} on its own line, followed by every
* diagnostic the compilation reported.
* Returns a failure message with each of {@code mismatches} separated by blank lines, followed by
* any other diagnostics that were not matched by passing lines or reported in mismatches.
*/
String describeMismatches(List<String> mismatches) {
return String.join("\n", mismatches) + "\n" + allErrors(getDiagnostics());
}

private static String allErrors(List<Diagnostic<? extends JavaFileObject>> diagnostics) {
if (diagnostics.isEmpty()) {
return "There were no errors.";
String message = String.join("\n\n", mismatches);
List<Diagnostic<? extends JavaFileObject>> otherDiagnostics =
getDiagnostics().stream()
.filter(d -> !handledDiagnostics.contains(d))
.collect(Collectors.toList());
if (!otherDiagnostics.isEmpty()) {
message +=
"\n\nOther diagnostics:\n"
+ otherDiagnostics.stream()
.map(Object::toString)
.collect(Collectors.joining("\n\n"));
}
return "All errors:\n"
+ diagnostics.stream().map(Object::toString).collect(Collectors.joining("\n\n"));
return message;
}

/** Returns the lookup keys that weren't used. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,12 @@ public void doItAgain() {}
.isEqualTo(
"""
Did not see an error on line 3 matching foo.
/Test.java:3: no diagnostic was reported on this line
public void doIt() {}

Did not see an error on line 6 matching bar.
There were no errors.\
/Test.java:6: no diagnostic was reported on this line
public void doItAgain() {}\
""");
}

Expand All @@ -220,14 +224,9 @@ public boolean doItAgain() {
""");
AssertionError expected =
assertThrows(AssertionError.class, () -> compilationTestHelper.doTest());
assertThat(expected)
.hasMessageThat()
.startsWith(
"""
Saw unexpected error on line 3.
Saw unexpected error on line 7.
All errors:
""");
assertThat(expected).hasMessageThat().contains("Saw unexpected error on line 3.");
assertThat(expected).hasMessageThat().contains("Saw unexpected error on line 7.");
assertThat(expected).hasMessageThat().doesNotContain("All errors:");
}

@Test
Expand All @@ -247,15 +246,12 @@ public void doItAgain() {}
""");
AssertionError expected =
assertThrows(AssertionError.class, () -> compilationTestHelper.doTest());
assertThat(expected).hasMessageThat().contains("Saw unexpected error on line 3.");
assertThat(expected)
.hasMessageThat()
.startsWith(
"""
Saw unexpected error on line 3.
No expected error message with key [X] as expected from line [6] \
with diagnostic [// BUG: Diagnostic matches: X]
All errors:
""");
.contains(
"No expected error message with key [X] as expected from line [6] with diagnostic [// BUG: Diagnostic matches: X]");
assertThat(expected).hasMessageThat().doesNotContain("All errors:");
}

@Test
Expand All @@ -275,14 +271,12 @@ public void doItAgain() {}
""");
AssertionError expected =
assertThrows(AssertionError.class, () -> compilationTestHelper.doTest());
assertThat(expected).hasMessageThat().contains("Saw unexpected error on line 3.");
assertThat(expected).hasMessageThat().contains("Did not see an error on line 7 matching foo.");
assertThat(expected)
.hasMessageThat()
.startsWith(
"""
Saw unexpected error on line 3.
Did not see an error on line 7 matching foo.
All errors:
""");
.contains("/Test.java:7: no diagnostic was reported on this line\n public void doItAgain() {}");
assertThat(expected).hasMessageThat().doesNotContain("All errors:");
}

@Test
Expand All @@ -308,14 +302,12 @@ public void doIt() {}
""");
AssertionError expected =
assertThrows(AssertionError.class, () -> compilationTestHelper.doTest());
assertThat(expected).hasMessageThat().contains("/A.java: Saw unexpected error on line 3.");
assertThat(expected).hasMessageThat().contains("/B.java: Did not see an error on line 3 matching foo.");
assertThat(expected)
.hasMessageThat()
.startsWith(
"""
/A.java: Saw unexpected error on line 3.
/B.java: Did not see an error on line 3 matching foo.
All errors:
""");
.contains("/B.java:3: no diagnostic was reported on this line\n public void doIt() {}");
assertThat(expected).hasMessageThat().doesNotContain("All errors:");
}

@Test
Expand Down Expand Up @@ -400,12 +392,51 @@ public void fileWithSyntaxErrorFails() {
assertThrows(AssertionError.class, () -> compilationTestHelper.doTest());
assertThat(expected)
.hasMessageThat()
.startsWith(
.startsWith("Did not see an error on line 4 containing [ReturnTreeChecker].");
assertThat(expected).hasMessageThat().contains("error: illegal start of expression");
assertThat(expected).hasMessageThat().doesNotContain("All errors:");
}

@Test
public void fourKindsOfLineGroupingExpectationAndReportedDiagnostics() {
var compilationTestHelper =
compilationHelper.addSourceLines(
"Test.java",
"""
Did not see an error on line 4 containing [ReturnTreeChecker].
All errors:
class Test {
boolean passing() {
// BUG: Diagnostic contains: Method may return normally
return true;
}
void missing() {
// BUG: Diagnostic contains: Method may return normally
int a = 1;
}
boolean differentText() {
// BUG: Diagnostic contains: Nonexistent expectation text
return false;
}
boolean unexpected() {
return true;
}
}
""");
assertThat(expected).hasMessageThat().contains("error: illegal start of expression");
AssertionError expected =
assertThrows(AssertionError.class, () -> compilationTestHelper.doTest());
String message = expected.getMessage();
// Missing diagnostic line shows source line:
assertThat(message).contains("Did not see an error on line 8 matching Method may return normally.");
assertThat(message).contains("/Test.java:8: no diagnostic was reported on this line\n int a = 1;");
// Different text shows both expectation and actual reported diagnostic:
assertThat(message).contains("Did not see an error on line 12 matching Nonexistent expectation text.");
assertThat(message).contains("/Test.java:12: error: [ReturnTreeChecker] Method may return normally.");
// Unexpected error line shows unexpected message followed by diagnostic:
assertThat(message).contains("Saw unexpected error on line 15.");
assertThat(message).contains("/Test.java:15: error: [ReturnTreeChecker] Method may return normally.");
// Passing line 4 diagnostic is omitted:
assertThat(message).doesNotContain("/Test.java:4:");
// No redundant "All errors:" section:
assertThat(message).doesNotContain("All errors:");
}

@Test
Expand Down
Loading