Skip to content

Commit 0357b07

Browse files
Copilotpascalberger
andcommitted
Add comprehensive cpplint-style JUnit test cases and improve file path extraction
Co-authored-by: pascalberger <2190718+pascalberger@users.noreply.github.com>
1 parent 6a9b898 commit 0357b07

8 files changed

Lines changed: 294 additions & 44 deletions

src/Cake.Issues.JUnit.Tests/JUnitIssuesProviderTests.cs

Lines changed: 213 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -79,25 +79,27 @@ public void Should_Read_Issues_Correct_For_Kubeconform()
7979
// Then
8080
issues.Count.ShouldBe(2);
8181

82-
var issue1 = issues[0];
83-
issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider");
84-
issue1.ProviderName.ShouldBe("JUnit");
85-
issue1.MessageText.ShouldBe("Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string");
86-
issue1.Priority.ShouldBe((int)IssuePriority.Error);
87-
issue1.Rule().ShouldBe("ValidationError");
88-
issue1.AffectedFileRelativePath.ShouldBe("deployment.yaml");
89-
issue1.Line.ShouldBe(10);
90-
issue1.Column.ShouldBe(15);
91-
92-
var issue2 = issues[1];
93-
issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider");
94-
issue2.ProviderName.ShouldBe("JUnit");
95-
issue2.MessageText.ShouldBe("Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service");
96-
issue2.Priority.ShouldBe((int)IssuePriority.Error);
97-
issue2.Rule().ShouldBe("ConfigError");
98-
issue2.AffectedFileRelativePath.ShouldBe("service.yaml");
99-
issue2.Line.ShouldBe(8);
100-
issue2.Column.ShouldBe(5);
82+
IssueChecker.Check(
83+
issues[0],
84+
IssueBuilder.NewIssue(
85+
"Invalid resource definition\ndeployment.yaml:10:15: error validating data: ValidationError(Deployment.spec.template.spec.containers[0].image): invalid value: \"\", expected non-empty string",
86+
"Cake.Issues.JUnit.JUnitIssuesProvider",
87+
"JUnit")
88+
.InFile(@"deployment.yaml", 10, 15)
89+
.OfRule("ValidationError")
90+
.WithPriority(IssuePriority.Error)
91+
.Create());
92+
93+
IssueChecker.Check(
94+
issues[1],
95+
IssueBuilder.NewIssue(
96+
"Port configuration invalid\nservice.yaml:8:5: Port 8080 is already in use by another service",
97+
"Cake.Issues.JUnit.JUnitIssuesProvider",
98+
"JUnit")
99+
.InFile(@"service.yaml", 8, 5)
100+
.OfRule("ConfigError")
101+
.WithPriority(IssuePriority.Error)
102+
.Create());
101103
}
102104

103105
[Fact]
@@ -112,24 +114,27 @@ public void Should_Read_Issues_Correct_For_HtmlHint()
112114
// Then
113115
issues.Count.ShouldBe(2);
114116

115-
var issue1 = issues[0];
116-
issue1.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider");
117-
issue1.ProviderName.ShouldBe("JUnit");
118-
issue1.MessageText.ShouldBe("Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase");
119-
issue1.Priority.ShouldBe((int)IssuePriority.Error);
120-
issue1.Rule().ShouldBe("error");
121-
issue1.AffectedFileRelativePath.ShouldBe("index.html");
122-
issue1.Line.ShouldBe(12);
123-
issue1.Column.ShouldBe(5);
124-
125-
var issue2 = issues[1];
126-
issue2.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider");
127-
issue2.ProviderName.ShouldBe("JUnit");
128-
issue2.MessageText.ShouldBe("Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes.");
129-
issue2.Priority.ShouldBe((int)IssuePriority.Error);
130-
issue2.Rule().ShouldBe("warning");
131-
issue2.AffectedFileRelativePath.ShouldBe("about.html");
132-
issue2.Line.ShouldBe(8);
117+
IssueChecker.Check(
118+
issues[0],
119+
IssueBuilder.NewIssue(
120+
"Tagname must be lowercase\nindex.html(12,5): Tagname 'DIV' must be lowercase",
121+
"Cake.Issues.JUnit.JUnitIssuesProvider",
122+
"JUnit")
123+
.InFile(@"index.html", 12, 5)
124+
.OfRule("error")
125+
.WithPriority(IssuePriority.Error)
126+
.Create());
127+
128+
IssueChecker.Check(
129+
issues[1],
130+
IssueBuilder.NewIssue(
131+
"Attribute value must be in double quotes\nabout.html line 8: The value of attribute 'class' must be in double quotes.",
132+
"Cake.Issues.JUnit.JUnitIssuesProvider",
133+
"JUnit")
134+
.InFile(@"about.html", 8)
135+
.OfRule("warning")
136+
.WithPriority(IssuePriority.Error)
137+
.Create());
133138
}
134139

135140
[Fact]
@@ -144,12 +149,15 @@ public void Should_Read_Issues_Correct_For_CommitLint()
144149
// Then
145150
issues.Count.ShouldBe(1);
146151

147-
var issue = issues[0];
148-
issue.ProviderType.ShouldBe("Cake.Issues.JUnit.JUnitIssuesProvider");
149-
issue.ProviderName.ShouldBe("JUnit");
150-
issue.MessageText.ShouldBe("Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]");
151-
issue.Priority.ShouldBe((int)IssuePriority.Error);
152-
issue.Rule().ShouldBe("error");
152+
IssueChecker.Check(
153+
issues[0],
154+
IssueBuilder.NewIssue(
155+
"Type must be one of the allowed values\ncommit-2: type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test]",
156+
"Cake.Issues.JUnit.JUnitIssuesProvider",
157+
"JUnit")
158+
.OfRule("error")
159+
.WithPriority(IssuePriority.Error)
160+
.Create());
153161
}
154162

155163
[Fact]
@@ -185,5 +193,167 @@ public void Should_Handle_Invalid_XML()
185193
Should.Throw<Exception>(() => fixture.ReadIssues().ToList())
186194
.Message.ShouldContain("Failed to parse JUnit XML");
187195
}
196+
197+
[Fact]
198+
public void Should_Handle_CppLint_Passed_Test()
199+
{
200+
// Given
201+
var fixture = new JUnitIssuesProviderFixture("cpplint-passed.xml");
202+
203+
// When
204+
var issues = fixture.ReadIssues().ToList();
205+
206+
// Then
207+
issues.Count.ShouldBe(0);
208+
}
209+
210+
[Fact]
211+
public void Should_Handle_CppLint_Single_Error()
212+
{
213+
// Given
214+
var fixture = new JUnitIssuesProviderFixture("cpplint-single-error.xml");
215+
216+
// When
217+
var issues = fixture.ReadIssues().ToList();
218+
219+
// Then
220+
issues.Count.ShouldBe(1);
221+
222+
IssueChecker.Check(
223+
issues[0],
224+
IssueBuilder.NewIssue(
225+
"ErrMsg1",
226+
"Cake.Issues.JUnit.JUnitIssuesProvider",
227+
"JUnit")
228+
.OfRule("errors")
229+
.WithPriority(IssuePriority.Error)
230+
.Create());
231+
}
232+
233+
[Fact]
234+
public void Should_Handle_CppLint_Multiple_Errors()
235+
{
236+
// Given
237+
var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-errors.xml");
238+
239+
// When
240+
var issues = fixture.ReadIssues().ToList();
241+
242+
// Then
243+
issues.Count.ShouldBe(1);
244+
245+
IssueChecker.Check(
246+
issues[0],
247+
IssueBuilder.NewIssue(
248+
"ErrMsg1\nErrMsg2",
249+
"Cake.Issues.JUnit.JUnitIssuesProvider",
250+
"JUnit")
251+
.OfRule("errors")
252+
.WithPriority(IssuePriority.Error)
253+
.Create());
254+
}
255+
256+
[Fact]
257+
public void Should_Handle_CppLint_Mixed_Error_And_Failure()
258+
{
259+
// Given
260+
var fixture = new JUnitIssuesProviderFixture("cpplint-mixed-error-failure.xml");
261+
262+
// When
263+
var issues = fixture.ReadIssues().ToList();
264+
265+
// Then
266+
issues.Count.ShouldBe(2);
267+
268+
IssueChecker.Check(
269+
issues[0],
270+
IssueBuilder.NewIssue(
271+
"ErrMsg",
272+
"Cake.Issues.JUnit.JUnitIssuesProvider",
273+
"JUnit")
274+
.OfRule("errors")
275+
.WithPriority(IssuePriority.Error)
276+
.Create());
277+
278+
IssueChecker.Check(
279+
issues[1],
280+
IssueBuilder.NewIssue(
281+
"5: FailMsg [category/subcategory] [3]",
282+
"Cake.Issues.JUnit.JUnitIssuesProvider",
283+
"JUnit")
284+
.InFile("File", 5)
285+
.OfRule("File")
286+
.WithPriority(IssuePriority.Error)
287+
.Create());
288+
}
289+
290+
[Fact]
291+
public void Should_Handle_CppLint_Multiple_Failures_Grouped_By_File()
292+
{
293+
// Given
294+
var fixture = new JUnitIssuesProviderFixture("cpplint-multiple-failures.xml");
295+
296+
// When
297+
var issues = fixture.ReadIssues().ToList();
298+
299+
// Then
300+
issues.Count.ShouldBe(2);
301+
302+
IssueChecker.Check(
303+
issues[0],
304+
IssueBuilder.NewIssue(
305+
"5: FailMsg1 [category/subcategory] [3]\n19: FailMsg3 [category/subcategory] [3]",
306+
"Cake.Issues.JUnit.JUnitIssuesProvider",
307+
"JUnit")
308+
.InFile("File1", 5)
309+
.OfRule("File1")
310+
.WithPriority(IssuePriority.Error)
311+
.Create());
312+
313+
IssueChecker.Check(
314+
issues[1],
315+
IssueBuilder.NewIssue(
316+
"99: FailMsg2 [category/subcategory] [3]",
317+
"Cake.Issues.JUnit.JUnitIssuesProvider",
318+
"JUnit")
319+
.InFile("File2", 99)
320+
.OfRule("File2")
321+
.WithPriority(IssuePriority.Error)
322+
.Create());
323+
}
324+
325+
[Fact]
326+
public void Should_Handle_CppLint_XML_Escaping()
327+
{
328+
// Given
329+
var fixture = new JUnitIssuesProviderFixture("cpplint-xml-escaping.xml");
330+
331+
// When
332+
var issues = fixture.ReadIssues().ToList();
333+
334+
// Then
335+
issues.Count.ShouldBe(2);
336+
337+
IssueChecker.Check(
338+
issues[0],
339+
IssueBuilder.NewIssue(
340+
"&</error>",
341+
"Cake.Issues.JUnit.JUnitIssuesProvider",
342+
"JUnit")
343+
.OfRule("errors")
344+
.WithPriority(IssuePriority.Error)
345+
.Create());
346+
347+
IssueChecker.Check(
348+
issues[1],
349+
IssueBuilder.NewIssue(
350+
"5: &</failure> [category/subcategory] [3]",
351+
"Cake.Issues.JUnit.JUnitIssuesProvider",
352+
"JUnit")
353+
.InFile("File1", 5)
354+
.OfRule("File1")
355+
.WithPriority(IssuePriority.Error)
356+
.Create());
357+
}
188358
}
189359
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuite errors="1" failures="1" name="cpplint" tests="2">
3+
<testcase name="errors">
4+
<error>ErrMsg</error>
5+
</testcase>
6+
<testcase name="File">
7+
<failure>5: FailMsg [category/subcategory] [3]</failure>
8+
</testcase>
9+
</testsuite>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuite errors="2" failures="0" name="cpplint" tests="2">
3+
<testcase name="errors">
4+
<error>ErrMsg1
5+
ErrMsg2</error>
6+
</testcase>
7+
</testsuite>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuite errors="0" failures="3" name="cpplint" tests="3">
3+
<testcase name="File1">
4+
<failure>5: FailMsg1 [category/subcategory] [3]
5+
19: FailMsg3 [category/subcategory] [3]</failure>
6+
</testcase>
7+
<testcase name="File2">
8+
<failure>99: FailMsg2 [category/subcategory] [3]</failure>
9+
</testcase>
10+
</testsuite>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuite errors="0" failures="0" name="cpplint" tests="1">
3+
<testcase name="passed">
4+
</testcase>
5+
</testsuite>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuite errors="1" failures="0" name="cpplint" tests="1">
3+
<testcase name="errors">
4+
<error>ErrMsg1</error>
5+
</testcase>
6+
</testsuite>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<testsuite errors="1" failures="1" name="cpplint" tests="2">
3+
<testcase name="errors">
4+
<error>&amp;&lt;/error&gt;</error>
5+
</testcase>
6+
<testcase name="File1">
7+
<failure>5: &amp;&lt;/failure&gt; [category/subcategory] [3]</failure>
8+
</testcase>
9+
</testsuite>

src/Cake.Issues.JUnit/JUnitIssuesProvider.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,43 @@ private IIssue ProcessTestFailure(XElement failureElement, string className, str
210210
issueBuilder = issueBuilder.OfRule(testName);
211211
}
212212

213-
// Try to extract file information from the message or content
213+
// Try to extract file information from the message or content first
214214
var fileInfo = ExtractFileInfoFromOutput(fullMessage) ?? ExtractFileInfoFromClassName(className);
215215

216+
// For cpplint-style output, if we don't have file info and the test name looks like a file name,
217+
// use the test name as the file name and try to extract line info from the message
218+
if (!fileInfo.HasValue && !string.IsNullOrEmpty(testName) && testName != "errors")
219+
{
220+
// Check if the message contains line info in cpplint format like "5: FailMsg [category/subcategory] [3]"
221+
// This is a strong indicator that it's a cpplint-style failure where the test name is the file name
222+
var lineMatch = Regex.Match(
223+
fullMessage,
224+
@"^(\d+):\s*.*\[.*\].*\[.*\]",
225+
RegexOptions.Multiline);
226+
if (lineMatch.Success && int.TryParse(lineMatch.Groups[1].Value, out var lineNum))
227+
{
228+
fileInfo = (testName, lineNum, null);
229+
}
230+
231+
// Also check for simple line number pattern without the category/subcategory format
232+
else
233+
{
234+
var simpleLineMatch = Regex.Match(
235+
fullMessage,
236+
@"^(\d+):",
237+
RegexOptions.Multiline);
238+
if (simpleLineMatch.Success &&
239+
int.TryParse(simpleLineMatch.Groups[1].Value, out var simpleLineNum))
240+
{
241+
// Only treat as file if the test name doesn't contain hyphens (which are common in rule names)
242+
if (!testName.Contains('-') && !testName.Contains('_'))
243+
{
244+
fileInfo = (testName, simpleLineNum, null);
245+
}
246+
}
247+
}
248+
}
249+
216250
if (fileInfo.HasValue)
217251
{
218252
var (filePath, line, column) = fileInfo.Value;

0 commit comments

Comments
 (0)