Merge pull request #160 from wongherlung/junit-xml-output

JUnit XML output
This commit is contained in:
Grant Murphy 2018-01-30 12:12:30 +10:00 committed by GitHub
commit e92170b49a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 2 deletions

View file

@ -104,7 +104,7 @@ $ gas -nosec=true ./...
### Output formats ### Output formats
Gas currently supports text, json and csv output formats. By default Gas currently supports text, json, csv and JUnit XML output formats. By default
results will be reported to stdout, but can also be written to an output results will be reported to stdout, but can also be written to an output
file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows: file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows:

View file

@ -59,7 +59,7 @@ var (
flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
// format output // format output
flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, html, or text") flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, junit-xml, html, or text")
// output file // output file
flagOutput = flag.String("out", "", "Set output file for results") flagOutput = flag.String("out", "", "Set output file for results")

View file

@ -17,6 +17,7 @@ package output
import ( import (
"encoding/csv" "encoding/csv"
"encoding/json" "encoding/json"
"encoding/xml"
htmlTemplate "html/template" htmlTemplate "html/template"
"io" "io"
plainTemplate "text/template" plainTemplate "text/template"
@ -36,6 +37,9 @@ const (
// ReportCSV set the output format to csv // ReportCSV set the output format to csv
ReportCSV // CSV format ReportCSV // CSV format
// ReportJUnitXML set the output format to junit xml
ReportJUnitXML // JUnit XML format
) )
var text = `Results: var text = `Results:
@ -70,6 +74,8 @@ func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas.
err = reportJSON(w, data) err = reportJSON(w, data)
case "csv": case "csv":
err = reportCSV(w, data) err = reportCSV(w, data)
case "junit-xml":
err = reportJUnitXML(w, data)
case "html": case "html":
err = reportFromHTMLTemplate(w, html, data) err = reportFromHTMLTemplate(w, html, data)
case "text": case "text":
@ -112,6 +118,25 @@ func reportCSV(w io.Writer, data *reportInfo) error {
return nil return nil
} }
func reportJUnitXML(w io.Writer, data *reportInfo) error {
groupedData := groupDataByRules(data)
junitXMLStruct := createJUnitXMLStruct(groupedData)
raw, err := xml.MarshalIndent(junitXMLStruct, "", "\t")
if err != nil {
return err
}
xmlHeader := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
raw = append(xmlHeader, raw...)
_, err = w.Write(raw)
if err != nil {
return err
}
return nil
}
func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error { func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error {
t, e := plainTemplate.New("gas").Parse(reportTemplate) t, e := plainTemplate.New("gas").Parse(reportTemplate)
if e != nil { if e != nil {

View file

@ -0,0 +1,73 @@
package output
import (
"encoding/xml"
"strconv"
"github.com/GoASTScanner/gas"
)
type junitXMLReport struct {
XMLName xml.Name `xml:"testsuites"`
Testsuites []testsuite `xml:"testsuite"`
}
type testsuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Testcases []testcase `xml:"testcase"`
}
type testcase struct {
XMLName xml.Name `xml:"testcase"`
Name string `xml:"name,attr"`
Failure failure `xml:"failure"`
}
type failure struct {
XMLName xml.Name `xml:"failure"`
Message string `xml:"message,attr"`
Text string `xml:",innerxml"`
}
func generatePlaintext(issue *gas.Issue) string {
return "Results:\n" +
"[" + issue.File + ":" + issue.Line + "] - " +
issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) +
", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" + "> " + issue.Code
}
func groupDataByRules(data *reportInfo) map[string][]*gas.Issue {
groupedData := make(map[string][]*gas.Issue)
for _, issue := range data.Issues {
if _, ok := groupedData[issue.What]; ok {
groupedData[issue.What] = append(groupedData[issue.What], issue)
} else {
groupedData[issue.What] = []*gas.Issue{issue}
}
}
return groupedData
}
func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) junitXMLReport {
var xmlReport junitXMLReport
for what, issues := range groupedData {
testsuite := testsuite{
Name: what,
Tests: len(issues),
}
for _, issue := range issues {
testcase := testcase{
Name: issue.File,
Failure: failure{
Message: "Found 1 vulnerability. See stacktrace for details.",
Text: generatePlaintext(issue),
},
}
testsuite.Testcases = append(testsuite.Testcases, testcase)
}
xmlReport.Testsuites = append(xmlReport.Testsuites, testsuite)
}
return xmlReport
}