Fix junit output, also ensure junit output is deterministic

This commit is contained in:
Yann Hamon 2023-12-24 17:16:02 +01:00
parent 808e6d4aa5
commit 4746a78fbd
3 changed files with 104 additions and 30 deletions

View file

@ -5,7 +5,7 @@ RELEASE_VERSION ?= latest
.PHONY: local-test local-build local-build-static docker-test docker-build docker-build-static build-bats docker-acceptance release update-deps build-single-target .PHONY: local-test local-build local-build-static docker-test docker-build docker-build-static build-bats docker-acceptance release update-deps build-single-target
local-test: local-test:
go test -race ./... go test -race ./... -count=1
local-build: local-build:
git config --global --add safe.directory $$PWD git config --global --add safe.directory $$PWD

View file

@ -65,13 +65,13 @@ type TestCaseError struct {
} }
type junito struct { type junito struct {
id int id int
w io.Writer w io.Writer
withSummary bool withSummary bool
verbose bool verbose bool
suites map[string]*TestSuite // map filename to corresponding suite suitesIndex map[string]int // map filename to index in suites
nValid, nInvalid, nErrors, nSkipped int suites []TestSuite
startTime time.Time startTime time.Time
} }
func junitOutput(w io.Writer, withSummary bool, isStdin, verbose bool) Output { func junitOutput(w io.Writer, withSummary bool, isStdin, verbose bool) Output {
@ -80,29 +80,28 @@ func junitOutput(w io.Writer, withSummary bool, isStdin, verbose bool) Output {
w: w, w: w,
withSummary: withSummary, withSummary: withSummary,
verbose: verbose, verbose: verbose,
suites: make(map[string]*TestSuite), suites: []TestSuite{},
nValid: 0, suitesIndex: make(map[string]int),
nInvalid: 0,
nErrors: 0,
nSkipped: 0,
startTime: time.Now(), startTime: time.Now(),
} }
} }
// Write adds a result to the report. // Write adds a result to the report.
func (o *junito) Write(result validator.Result) error { func (o *junito) Write(result validator.Result) error {
var suite *TestSuite var suite TestSuite
suite, found := o.suites[result.Resource.Path] i, found := o.suitesIndex[result.Resource.Path]
if !found { if !found {
o.id++ o.id++
suite = &TestSuite{ suite = TestSuite{
Name: result.Resource.Path, Name: result.Resource.Path,
Id: o.id, Id: o.id,
Tests: 0, Failures: 0, Errors: 0, Disabled: 0, Skipped: 0, Tests: 0, Failures: 0, Errors: 0, Disabled: 0, Skipped: 0,
Cases: make([]TestCase, 0), Cases: make([]TestCase, 0),
} }
o.suites[result.Resource.Path] = suite o.suites = append(o.suites, suite)
i = len(o.suites) - 1
o.suitesIndex[result.Resource.Path] = i
} }
sig, _ := result.Resource.Signature() sig, _ := result.Resource.Signature()
@ -117,23 +116,22 @@ func (o *junito) Write(result validator.Result) error {
switch result.Status { switch result.Status {
case validator.Valid: case validator.Valid:
o.nValid++
case validator.Invalid: case validator.Invalid:
o.nInvalid++ o.suites[i].Failures++
failure := TestCaseError{Message: result.Err.Error()} failure := TestCaseError{Message: result.Err.Error()}
testCase.Failure = append(testCase.Failure, failure) testCase.Failure = append(testCase.Failure, failure)
case validator.Error: case validator.Error:
o.nErrors++ o.suites[i].Errors++
testCase.Error = &TestCaseError{Message: result.Err.Error()} testCase.Error = &TestCaseError{Message: result.Err.Error()}
case validator.Skipped: case validator.Skipped:
testCase.Skipped = &TestCaseSkipped{} testCase.Skipped = &TestCaseSkipped{}
o.nSkipped++ o.suites[i].Skipped++
case validator.Empty: case validator.Empty:
return nil return nil
} }
suite.Tests++ o.suites[i].Tests++
suite.Cases = append(suite.Cases, testCase) o.suites[i].Cases = append(o.suites[i].Cases, testCase)
return nil return nil
} }
@ -142,19 +140,33 @@ func (o *junito) Write(result validator.Result) error {
func (o *junito) Flush() error { func (o *junito) Flush() error {
runtime := time.Now().Sub(o.startTime) runtime := time.Now().Sub(o.startTime)
var suites = make([]TestSuite, 0) totalValid := 0
totalInvalid := 0
totalErrors := 0
totalSkipped := 0
for _, suite := range o.suites { for _, suite := range o.suites {
suites = append(suites, *suite) for _, tCase := range suite.Cases {
if tCase.Error != nil {
totalErrors++
} else if tCase.Skipped != nil {
totalSkipped++
} else if len(tCase.Failure) > 0 {
totalInvalid++
} else {
totalValid++
}
}
} }
root := TestSuiteCollection{ root := TestSuiteCollection{
Name: "kubeconform", Name: "kubeconform",
Time: runtime.Seconds(), Time: runtime.Seconds(),
Tests: o.nValid + o.nInvalid + o.nErrors + o.nSkipped, Tests: totalValid + totalInvalid + totalErrors + totalSkipped,
Failures: o.nInvalid, Failures: totalInvalid,
Errors: o.nErrors, Errors: totalErrors,
Disabled: o.nSkipped, Disabled: totalSkipped,
Suites: suites, Suites: o.suites,
} }
// 2-space indentation // 2-space indentation

View file

@ -2,6 +2,7 @@ package output
import ( import (
"bytes" "bytes"
"fmt"
"regexp" "regexp"
"testing" "testing"
@ -85,6 +86,67 @@ metadata:
" </testsuite>\n" + " </testsuite>\n" +
"</testsuites>\n", "</testsuites>\n",
}, },
{
"one error, one invalid",
true,
false,
false,
[]validator.Result{
{
Resource: resource.Resource{
Path: "deployment.yml",
Bytes: []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: "my-app"
`),
},
Status: validator.Error,
Err: fmt.Errorf("error validating deployment.yml"),
},
{
Resource: resource.Resource{
Path: "deployment2.yml",
Bytes: []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: "my-app"
`),
},
Status: validator.Error,
Err: fmt.Errorf("error validating deployment.yml"),
},
{
Resource: resource.Resource{
Path: "deployment3.yml",
Bytes: []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: "my-app"
`),
},
Status: validator.Invalid,
Err: fmt.Errorf("deployment3.yml is invalid"),
},
},
"<testsuites name=\"kubeconform\" time=\"\" tests=\"3\" failures=\"1\" disabled=\"0\" errors=\"2\">\n" +
" <testsuite name=\"deployment.yml\" id=\"1\" tests=\"1\" failures=\"0\" errors=\"1\" disabled=\"0\" skipped=\"0\">\n" +
" <testcase name=\"my-app\" classname=\"Deployment@apps/v1\" time=\"\">\n" +
" <error message=\"error validating deployment.yml\" type=\"\"></error>\n" +
" </testcase>\n" +
" </testsuite>\n" +
" <testsuite name=\"deployment2.yml\" id=\"2\" tests=\"1\" failures=\"0\" errors=\"1\" disabled=\"0\" skipped=\"0\">\n" +
" <testcase name=\"my-app\" classname=\"Deployment@apps/v1\" time=\"\">\n" +
" <error message=\"error validating deployment.yml\" type=\"\"></error>\n" +
" </testcase>\n" +
" </testsuite>\n" +
" <testsuite name=\"deployment3.yml\" id=\"3\" tests=\"1\" failures=\"1\" errors=\"0\" disabled=\"0\" skipped=\"0\">\n" +
" <testcase name=\"my-app\" classname=\"Deployment@apps/v1\" time=\"\">\n" +
" <failure message=\"deployment3.yml is invalid\" type=\"\"></failure>\n" +
" </testcase>\n" +
" </testsuite>\n" +
"</testsuites>\n",
},
} { } {
w := new(bytes.Buffer) w := new(bytes.Buffer)
o := junitOutput(w, testCase.withSummary, testCase.isStdin, testCase.verbose) o := junitOutput(w, testCase.withSummary, testCase.isStdin, testCase.verbose)