Skip to content

Commit a79b1de

Browse files
authored
Show details of the error message in test failures (#7513)
1 parent a5ecc22 commit a79b1de

4 files changed

Lines changed: 121 additions & 59 deletions

File tree

docs/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ In `AnnotationClassLoader`:
2525
The method now returns `true` for annotations bearing `@InvisibleQualifier`
2626
or `@SubtypeOf`, in addition to the existing `@Target(TYPE_USE)` check.
2727

28+
In `TestDiagnostic`:
29+
30+
* Renamed field `message` to `key`.
31+
* Added new nullable field `message` for the full message without the key.
32+
2833
Removed classes and methods that have been deprecated for more than two years.
2934

3035
### Closed issues

framework-test/src/main/java/org/checkerframework/framework/test/TypecheckExecutor.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,7 @@ public CompilationResult compile(TestConfiguration configuration) {
8686
nonJvmOptions.add("-ApermitMissingJdk");
8787
nonJvmOptions.add("-Anocheckjdk"); // temporary, for backward compatibility
8888

89-
// -Anomsgtext is needed to ensure expected errors can be matched.
90-
// Note: Since "-Anomsgtext" is always added to the non-JVM options,
91-
// we are passing `true` as the `noMsgText` argument to all invocations
92-
// of `TestDiagnosticUtils.fromJavaxDiagnosticList`.
93-
nonJvmOptions.add("-Anomsgtext");
89+
nonJvmOptions.add("-Aonelinemsg");
9490

9591
options.addAll(nonJvmOptions);
9692

framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnostic.java

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,51 @@
1212
*/
1313
public class TestDiagnostic {
1414

15+
/** The file to which the diagnostic applies. */
1516
private final String filename;
17+
18+
/** The line number to which the diagnostic applies. */
1619
private final long lineNumber;
20+
21+
/** The kind of diagnostic. */
1722
private final DiagnosticKind kind;
1823

1924
/**
20-
* An error key or full error message that usually appears between parentheses in diagnostic
25+
* An error key or full error message that usually appears between square brackets in diagnostic
2126
* messages.
2227
*/
23-
private final String message;
28+
private final String key;
2429

25-
/** Returns true if this diagnostic should no longer be reported after whole program inference. */
26-
private final boolean isFixable;
30+
/** The full error message, without the key. Null if the key is the whole message. */
31+
private final @Nullable String message;
2732

28-
/** True if the toString representation should omit the parentheses around the message. */
29-
private final boolean omitParentheses;
33+
/** True if this diagnostic should no longer be reported after whole-program inference. */
34+
private final boolean isFixable;
3035

31-
/** Basic constructor that sets the immutable fields of this diagnostic. */
36+
/**
37+
* Basic constructor that sets the immutable fields of this diagnostic.
38+
*
39+
* @param filename the file to which the diagnostic applies
40+
* @param lineNumber the line number to which the diagnostic applies
41+
* @param kind kind of diagnostic
42+
* @param key an error key or full error message
43+
* @param message the full error message, without the key; null if the key is the whole message
44+
* @param isFixable true if this diagnostic should no longer be reported after whole-program
45+
* inference
46+
*/
3247
public TestDiagnostic(
3348
String filename,
3449
long lineNumber,
3550
DiagnosticKind kind,
36-
String message,
37-
boolean isFixable,
38-
boolean omitParentheses) {
51+
String key,
52+
@Nullable String message,
53+
boolean isFixable) {
3954
this.filename = filename;
4055
this.lineNumber = lineNumber;
4156
this.kind = kind;
57+
this.key = key;
4258
this.message = message;
4359
this.isFixable = isFixable;
44-
this.omitParentheses = omitParentheses;
4560
}
4661

4762
public String getFilename() {
@@ -56,28 +71,38 @@ public DiagnosticKind getKind() {
5671
return kind;
5772
}
5873

59-
public String getMessage() {
60-
return message;
74+
/**
75+
* Returns the error message key.
76+
*
77+
* @return the error message key
78+
*/
79+
public String getKey() {
80+
return key;
6181
}
6282

63-
public boolean isFixable() {
64-
return isFixable;
83+
/**
84+
* Returns the full error message, without the key.
85+
*
86+
* @return the full error message, without the key
87+
*/
88+
public @Nullable String getMessage() {
89+
return message;
6590
}
6691

6792
/**
68-
* Returns true if the printed representation should omit parentheses around the message.
93+
* Returns true if this diagnostic should no longer be reported after whole-program inference.
6994
*
70-
* @return true if the printed representation should omit parentheses around the message
95+
* @return true if this diagnostic should no longer be reported after whole-program inference
7196
*/
72-
public boolean shouldOmitParentheses() {
73-
return omitParentheses;
97+
public boolean isFixable() {
98+
return isFixable;
7499
}
75100

76101
/**
77-
* Equality is compared without isFixable/omitParentheses.
102+
* Equality is compared without fields {@code message} and {@code isFixable}.
78103
*
79104
* @return true if this and otherObj are equal according to filename, lineNumber, kind, and
80-
* message
105+
* message key
81106
*/
82107
@Override
83108
public boolean equals(@Nullable Object otherObj) {
@@ -89,12 +114,12 @@ public boolean equals(@Nullable Object otherObj) {
89114
return other.filename.equals(this.filename)
90115
&& other.lineNumber == lineNumber
91116
&& other.kind == this.kind
92-
&& other.message.equals(this.message);
117+
&& other.key.equals(this.key);
93118
}
94119

95120
@Override
96121
public int hashCode() {
97-
return Objects.hash(filename, lineNumber, kind, message);
122+
return Objects.hash(filename, lineNumber, kind, key);
98123
}
99124

100125
/**
@@ -104,13 +129,14 @@ public int hashCode() {
104129
*/
105130
@Override
106131
public String toString() {
132+
String loc = filename + ":" + lineNumber + ": ";
133+
String key = "(" + this.key + ")";
134+
String msg = message == null ? "" : " " + message;
107135
if (kind == DiagnosticKind.JSpecify) {
108-
return filename + ":" + lineNumber + ": " + message;
109-
}
110-
if (omitParentheses) {
111-
return filename + ":" + lineNumber + ": " + kind.parseString + ": " + message;
136+
return loc + key;
137+
} else {
138+
return loc + kind.parseString + ": " + key + msg;
112139
}
113-
return filename + ":" + lineNumber + ": " + kind.parseString + ": (" + message + ")";
114140
}
115141

116142
/**
@@ -120,7 +146,7 @@ public String toString() {
120146
*/
121147
public String repr() {
122148
return String.format(
123-
"[TestDiagnostic: filename=%s, lineNumber=%d, kind=%s, message=%s]",
124-
filename, lineNumber, kind, message);
149+
"[TestDiagnostic: filename=%s, lineNumber=%d, kind=%s, key=%s]",
150+
filename, lineNumber, kind, key);
125151
}
126152
}

framework-test/src/main/java/org/checkerframework/framework/test/diagnostics/TestDiagnosticUtils.java

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import javax.tools.JavaFileObject;
1313
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
1414
import org.checkerframework.checker.nullness.qual.Nullable;
15+
import org.checkerframework.javacutil.BugInCF;
1516
import org.plumelib.util.CollectionsPlume;
1617
import org.plumelib.util.IPair;
1718

@@ -64,8 +65,8 @@ public static TestDiagnostic fromDiagnosticFileString(String stringFromDiagnosti
6465
}
6566

6667
/**
67-
* Instantiate the diagnostic from a string that would appear in a Java file, e.g.: "error:
68-
* (message)"
68+
* Instantiate the diagnostic from a string that would appear in a Java test file, e.g.: "error:
69+
* (error-message-key)"
6970
*
7071
* @param filename the file containing the diagnostic (and the error)
7172
* @param lineNumber the line number of the line immediately below the diagnostic comment in the
@@ -117,17 +118,18 @@ public static TestDiagnostic fromJSpecifyFileComment(
117118
lineNumber,
118119
DiagnosticKind.JSpecify,
119120
stringFromJavaFile,
120-
/* isFixable= */ false,
121-
/* omitParentheses= */ true);
121+
null,
122+
/* isFixable= */ false);
122123
}
123124

124125
/**
125-
* Instantiate the diagnostic via pattern-matching against patterns.
126+
* Instantiate the diagnostic via pattern-matching against the given patterns.
126127
*
127128
* @param diagnosticPattern a pattern that matches any diagnostic
128129
* @param warningPattern a pattern that matches a warning diagnostic
129130
* @param filename the file name
130-
* @param lineNumber the line number
131+
* @param lineNumber the line number. Either this is non-null, or the pattern's first matching
132+
* group is the line number.
131133
* @param diagnosticString the string to parse
132134
* @return a diagnostic parsed from the given string
133135
*/
@@ -139,9 +141,9 @@ protected static TestDiagnostic fromPatternMatching(
139141
@Nullable Long lineNumber,
140142
String diagnosticString) {
141143
final DiagnosticKind kind;
144+
final String key;
142145
final String message;
143146
final boolean isFixable;
144-
final boolean noParentheses;
145147
long lineNo = -1;
146148
int capturingGroupOffset = 1;
147149

@@ -152,13 +154,33 @@ protected static TestDiagnostic fromPatternMatching(
152154

153155
Matcher diagnosticMatcher = diagnosticPattern.matcher(diagnosticString);
154156
if (diagnosticMatcher.matches()) {
155-
IPair<DiagnosticKind, Boolean> categoryToFixable =
157+
158+
IPair<DiagnosticKind, Boolean> categoryAndFixable =
156159
categoryAndFixable(diagnosticMatcher.group(1 + capturingGroupOffset));
157-
kind = categoryToFixable.first;
158-
isFixable = categoryToFixable.second;
160+
kind = categoryAndFixable.first;
161+
isFixable = categoryAndFixable.second;
159162
String msg = diagnosticMatcher.group(2 + capturingGroupOffset).trim();
160-
noParentheses = msg.equals("") || msg.charAt(0) != '(' || msg.charAt(msg.length() - 1) != ')';
161-
message = noParentheses ? msg : msg.substring(1, msg.length() - 1);
163+
164+
char firstChar = msg.isEmpty() ? ' ' : msg.charAt(0);
165+
if (firstChar == '(' || firstChar == '[') {
166+
char closeDelimiter = firstChar == '(' ? ')' : ']';
167+
int closeDelimiterPos = msg.indexOf(closeDelimiter);
168+
int msgLength = msg.length();
169+
if (closeDelimiterPos == msgLength - 1) {
170+
key = msg.substring(1, msgLength - 1);
171+
message = null;
172+
} else if (closeDelimiterPos != -1) {
173+
key = msg.substring(1, closeDelimiterPos);
174+
message = msg.substring(closeDelimiterPos + 1).trim();
175+
} else {
176+
throw new BugInCF(
177+
"No closing delimiter '%s' found in %s", closeDelimiter, diagnosticString);
178+
}
179+
} else {
180+
// The message does not start with "(" or "[".
181+
key = msg;
182+
message = null;
183+
}
162184

163185
if (lineNumber == null) {
164186
try {
@@ -173,8 +195,8 @@ protected static TestDiagnostic fromPatternMatching(
173195
if (warningMatcher.matches()) {
174196
kind = DiagnosticKind.Warning;
175197
isFixable = false;
176-
message = warningMatcher.group(1 + capturingGroupOffset);
177-
noParentheses = true;
198+
key = warningMatcher.group(1 + capturingGroupOffset);
199+
message = null;
178200

179201
if (lineNumber == null) {
180202
try {
@@ -187,8 +209,8 @@ protected static TestDiagnostic fromPatternMatching(
187209
} else if (diagnosticString.startsWith("warning:")) {
188210
kind = DiagnosticKind.Warning;
189211
isFixable = false;
190-
message = diagnosticString.substring("warning:".length()).trim();
191-
noParentheses = true;
212+
key = diagnosticString.substring("warning:".length()).trim();
213+
message = null;
192214
if (lineNumber != null) {
193215
lineNo = lineNumber;
194216
} else {
@@ -198,19 +220,23 @@ protected static TestDiagnostic fromPatternMatching(
198220
} else {
199221
kind = DiagnosticKind.Other;
200222
isFixable = false;
201-
message = diagnosticString;
202-
noParentheses = true;
223+
key = diagnosticString;
224+
message = null;
203225

204-
// this should only happen if we are parsing a Java Diagnostic from the compiler
205-
// that we did do not handle
226+
// This should only happen if we are parsing a Java Diagnostic from the compiler
227+
// that we did do not handle.
206228
if (lineNumber == null) {
207229
lineNo = -1;
208230
}
209231
}
210232
}
211-
return new TestDiagnostic(filename, lineNo, kind, message, isFixable, noParentheses);
233+
return new TestDiagnostic(filename, lineNo, kind, key, message, isFixable);
212234
}
213235

236+
/** Matches an absolute filename (with delimiters). */
237+
// TODO: This only handles Unix paths, not Windows paths.
238+
static final Pattern filenamePattern = Pattern.compile(" (?:/[^: ]*/)([^/: ]+\\.[a-z]+):");
239+
214240
/**
215241
* Given a javax diagnostic, return a pair of (trimmed, filename), where "trimmed" is the first
216242
* line of the message, without the leading filename.
@@ -224,17 +250,26 @@ public static IPair<String, String> messageAndFilename(String original, boolean
224250
String filename = "";
225251
if (noMsgText) {
226252
if (!retainAllLines(trimmed)) {
253+
254+
// Retain only the first line.
227255
int lineSepPos = trimmed.indexOf(System.lineSeparator());
228256
if (lineSepPos != -1) {
229257
trimmed = trimmed.substring(0, lineSepPos);
230258
}
231259

232260
int extensionPos = trimmed.indexOf(".java:");
233261
if (extensionPos != -1) {
234-
int basenameStart = trimmed.lastIndexOf(File.separator);
262+
int basenameStart = trimmed.lastIndexOf(File.separator, extensionPos);
235263
filename = trimmed.substring(basenameStart + 1, extensionPos + 5).trim();
236264
trimmed = trimmed.substring(extensionPos + 5).trim();
237265
}
266+
267+
// Retain only the file basename, without directories, when embedded in message.
268+
Matcher m = filenamePattern.matcher(trimmed);
269+
if (m.find()) {
270+
trimmed =
271+
trimmed.substring(0, m.start()) + " " + m.group(1) + ":" + trimmed.substring(m.end());
272+
}
238273
}
239274
}
240275

@@ -363,8 +398,8 @@ public static TestDiagnosticLine fromJavaSourceLine(
363398
lineNumber,
364399
DiagnosticKind.Error,
365400
"Use \"// ::\", not \"//::\"",
366-
false,
367-
true);
401+
null,
402+
false);
368403
return new TestDiagnosticLine(
369404
filename, lineNumber, line, Collections.singletonList(diagnostic));
370405
} else if (trimmedLine.startsWith("// jspecify_")) {

0 commit comments

Comments
 (0)