-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added checkstyle reporter Co-authored-by: Jonathan Drude <jonathan.drude@linetgroup.com>
- Loading branch information
1 parent
325b9e0
commit f01f208
Showing
5 changed files
with
326 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
env NO_COLOR=1 | ||
! exec pint --no-color lint --min-severity=info --checkstyle=checkstyle.xml rules | ||
cmp checkstyle.xml checkstyle_check.xml | ||
|
||
-- rules/0001.yml -- | ||
groups: | ||
- name: test | ||
rules: | ||
- alert: Example | ||
expr: up | ||
- alert: Example | ||
expr: sum(xxx) with() | ||
-- checkstyle_check.xml -- | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<checkstyle version="4.3"> | ||
<file name="rules/0001.yml"> | ||
<error line="5" severity="Warning" message="Text:Alert query doesn't have any condition, it will always fire if the metric exists.
 Details:Prometheus alerting rules will trigger an alert for each query that returns *any* result.
Unless you do want an alert to always fire you should write your query in a way that returns results only when some condition is met.
In most cases this can be achieved by having some condition in the query expression.
For example `up == 0` or `rate(error_total[2m]) > 0`.
Be careful as some PromQL operations will cause the query to always return the results, for example using the [bool modifier](https://prometheus.io/docs/prometheus/latest/querying/operators/#comparison-binary-operators)." source="alerts/comparison"></error> | ||
<error line="7" severity="Fatal" message="Text:Prometheus failed to parse the query with this PromQL error: unexpected identifier "with".
 Details:[Click here](https://prometheus.io/docs/prometheus/latest/querying/basics/) for PromQL documentation." source="promql/syntax"></error> | ||
</file> | ||
</checkstyle> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package reporter | ||
|
||
import ( | ||
"encoding/xml" | ||
"fmt" | ||
"io" | ||
"strconv" | ||
) | ||
|
||
func NewCheckStyleReporter(output io.Writer) CheckStyleReporter { | ||
return CheckStyleReporter{ | ||
output: output, | ||
} | ||
} | ||
|
||
type CheckStyleReporter struct { | ||
output io.Writer | ||
} | ||
|
||
type checkstyleReport map[string][]Report | ||
|
||
func createCheckstyleReport(summary Summary) checkstyleReport { | ||
x := make(checkstyleReport) | ||
for _, report := range summary.reports { | ||
x[report.Path.Name] = append(x[report.Path.Name], report) | ||
} | ||
return x | ||
} | ||
|
||
func (d checkstyleReport) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { | ||
err := e.EncodeToken(xml.StartElement{ | ||
Name: xml.Name{Local: "checkstyle"}, | ||
Attr: []xml.Attr{ | ||
{ | ||
Name: xml.Name{Local: "version"}, | ||
Value: "4.3", | ||
}, | ||
}, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
for dir, reports := range d { | ||
errEnc := e.EncodeToken(xml.StartElement{ | ||
Name: xml.Name{Local: "file"}, | ||
Attr: []xml.Attr{ | ||
{ | ||
Name: xml.Name{Local: "name"}, | ||
Value: dir, | ||
}, | ||
}, | ||
}) | ||
if errEnc != nil { | ||
return errEnc | ||
} | ||
for _, report := range reports { | ||
errEnc2 := e.Encode(report) | ||
if errEnc2 != nil { | ||
return errEnc2 | ||
} | ||
} | ||
errEnc = e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "file"}}) | ||
if errEnc != nil { | ||
return errEnc | ||
} | ||
} | ||
err = e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "checkstyle"}}) | ||
return err | ||
} | ||
|
||
func (r Report) MarshalXML(e *xml.Encoder, _ xml.StartElement) error { | ||
startel := xml.StartElement{ | ||
Name: xml.Name{Local: "error"}, | ||
Attr: []xml.Attr{ | ||
{ | ||
Name: xml.Name{Local: "line"}, | ||
Value: strconv.Itoa(r.Problem.Lines.First), | ||
}, | ||
{ | ||
Name: xml.Name{Local: "severity"}, | ||
Value: r.Problem.Severity.String(), | ||
}, | ||
{ | ||
Name: xml.Name{Local: "message"}, | ||
Value: fmt.Sprintf("Text:%s\n Details:%s", r.Problem.Text, r.Problem.Details), | ||
}, | ||
{ | ||
Name: xml.Name{Local: "source"}, | ||
Value: r.Problem.Reporter, | ||
}, | ||
}, | ||
} | ||
err := e.EncodeToken(startel) | ||
if err != nil { | ||
return err | ||
} | ||
err = e.EncodeToken(xml.EndElement{Name: xml.Name{Local: "error"}}) | ||
|
||
return err | ||
} | ||
|
||
func (cs CheckStyleReporter) Submit(summary Summary) error { | ||
checkstyleReport := createCheckstyleReport(summary) | ||
xmlString, err := xml.MarshalIndent(checkstyleReport, "", " ") | ||
if err != nil { | ||
fmt.Printf("%v", err) | ||
} | ||
fmt.Fprint(cs.output, string(xml.Header)+string(xmlString)+"\n") | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package reporter_test | ||
|
||
import ( | ||
"bytes" | ||
"log/slog" | ||
"testing" | ||
|
||
"github.com/neilotoole/slogt" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/cloudflare/pint/internal/checks" | ||
"github.com/cloudflare/pint/internal/discovery" | ||
"github.com/cloudflare/pint/internal/parser" | ||
"github.com/cloudflare/pint/internal/reporter" | ||
) | ||
|
||
func TestCheckstyleReporter(t *testing.T) { | ||
type testCaseT struct { | ||
description string | ||
output string | ||
err string | ||
summary reporter.Summary | ||
} | ||
|
||
p := parser.NewParser(false) | ||
mockRules, _ := p.Parse([]byte(` | ||
- record: target is down | ||
expr: up == 0 | ||
`)) | ||
|
||
testCases := []testCaseT{ | ||
{ | ||
description: "no reports", | ||
summary: reporter.Summary{}, | ||
output: `<?xml version="1.0" encoding="UTF-8"?> | ||
<checkstyle version="4.3"></checkstyle> | ||
`, | ||
}, | ||
{ | ||
description: "info report", | ||
summary: reporter.NewSummary([]reporter.Report{ | ||
{ | ||
Path: discovery.Path{ | ||
SymlinkTarget: "foo.txt", | ||
Name: "foo.txt", | ||
}, | ||
ModifiedLines: []int{2, 4, 5}, | ||
Rule: mockRules[0], | ||
Problem: checks.Problem{ | ||
Lines: parser.LineRange{ | ||
First: 5, | ||
Last: 6, | ||
}, | ||
Reporter: "mock", | ||
Text: "mock text", | ||
Details: "mock details", | ||
Severity: checks.Information, | ||
}, | ||
}, | ||
}), | ||
output: `<?xml version="1.0" encoding="UTF-8"?> | ||
<checkstyle version="4.3"> | ||
<file name="foo.txt"> | ||
<error line="5" severity="Information" message="Text:mock text
 Details:mock details" source="mock"></error> | ||
</file> | ||
</checkstyle> | ||
`, | ||
}, | ||
{ | ||
description: "bug report", | ||
summary: reporter.NewSummary([]reporter.Report{ | ||
{ | ||
Path: discovery.Path{ | ||
SymlinkTarget: "foo.txt", | ||
Name: "foo.txt", | ||
}, | ||
ModifiedLines: []int{2, 4, 5}, | ||
Rule: mockRules[0], | ||
Problem: checks.Problem{ | ||
Lines: parser.LineRange{ | ||
First: 5, | ||
Last: 6, | ||
}, | ||
Reporter: "mock", | ||
Text: "mock text", | ||
Severity: checks.Bug, | ||
}, | ||
}, | ||
}), | ||
output: `<?xml version="1.0" encoding="UTF-8"?> | ||
<checkstyle version="4.3"> | ||
<file name="foo.txt"> | ||
<error line="5" severity="Bug" message="Text:mock text
 Details:" source="mock"></error> | ||
</file> | ||
</checkstyle> | ||
`, | ||
}, | ||
{ | ||
description: "escaping characters", | ||
summary: reporter.NewSummary([]reporter.Report{ | ||
{ | ||
Path: discovery.Path{ | ||
SymlinkTarget: "foo.txt", | ||
Name: "foo.txt", | ||
}, | ||
ModifiedLines: []int{2, 4, 5}, | ||
Rule: mockRules[0], | ||
Problem: checks.Problem{ | ||
Lines: parser.LineRange{ | ||
First: 5, | ||
Last: 6, | ||
}, | ||
Reporter: "mock", | ||
Text: `mock text | ||
with [new lines] and pipe| chars that are 'quoted' | ||
`, | ||
Severity: checks.Bug, | ||
}, | ||
}, | ||
}), | ||
output: `<?xml version="1.0" encoding="UTF-8"?> | ||
<checkstyle version="4.3"> | ||
<file name="foo.txt"> | ||
<error line="5" severity="Bug" message="Text:mock text
		with [new lines] and pipe| chars that are 'quoted'
		
 Details:" source="mock"></error> | ||
</file> | ||
</checkstyle> | ||
`, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.description, func(t *testing.T) { | ||
slog.SetDefault(slogt.New(t)) | ||
|
||
out := bytes.NewBuffer(nil) | ||
|
||
reporter := reporter.NewCheckStyleReporter(out) | ||
err := reporter.Submit(tc.summary) | ||
|
||
if tc.err != "" { | ||
require.EqualError(t, err, tc.err) | ||
} else { | ||
require.NoError(t, err) | ||
require.Equal(t, tc.output, out.String()) | ||
} | ||
}) | ||
} | ||
} |