Skip to content

Commit bda4cb9

Browse files
committed
JUnit Result Writer
1 parent 7993a48 commit bda4cb9

File tree

9 files changed

+297
-18
lines changed

9 files changed

+297
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
junit*.xml
12
/framework-tests
23
/example-tests

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ unit:
2828
go test ./...
2929

3030
integration: build
31-
./framework-tests run-suite framework
31+
ifeq ($(OPENSHIFT_CI), true)
32+
./framework-tests run-suite openshift-tests-extension/framework --junit-path $(ARTIFACT_DIR)/junit_$(shell date +%Y%m%d-%H%M%S).xml
33+
else
34+
./framework-tests run-suite openshift-tests-extension/framework
35+
endif
3236

3337
lint:
3438
./hack/go-lint.sh run ./...

cmd/framework-tests/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func main() {
2020
registry := e.NewRegistry()
2121
ext := e.NewExtension("openshift", "framework", "default")
2222
ext.AddSuite(e.Suite{
23-
Name: "framework",
23+
Name: "openshift-tests-extension/framework",
2424
})
2525

2626
// If using Ginkgo, build test specs automatically

pkg/cmd/cmdrun/runsuite.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ func NewRunSuiteCommand(registry *extension.Registry) *cobra.Command {
1717
componentFlags *flags.ComponentFlags
1818
outputFlags *flags.OutputFlags
1919
concurrencyFlags *flags.ConcurrencyFlags
20+
junitPath string
2021
}{
2122
componentFlags: flags.NewComponentFlags(),
2223
outputFlags: flags.NewOutputFlags(),
2324
concurrencyFlags: flags.NewConcurrencyFlags(),
25+
junitPath: "",
2426
}
2527

2628
cmd := &cobra.Command{
@@ -35,29 +37,47 @@ func NewRunSuiteCommand(registry *extension.Registry) *cobra.Command {
3537
if len(args) != 1 {
3638
return fmt.Errorf("must specify one suite name")
3739
}
38-
39-
w, err := extensiontests.NewResultWriter(os.Stdout, extensiontests.ResultFormat(opts.outputFlags.Output))
40+
suite, err := ext.GetSuite(args[0])
4041
if err != nil {
41-
return err
42+
return errors.Wrapf(err, "couldn't find suite: %s", args[0])
4243
}
43-
defer w.Flush()
4444

45-
suite, err := ext.GetSuite(args[0])
45+
compositeWriter := extensiontests.NewCompositeResultWriter()
46+
defer func() {
47+
if err = compositeWriter.Flush(); err != nil {
48+
fmt.Fprintf(os.Stderr, "failed to write results: %v\n", err)
49+
}
50+
}()
51+
52+
// JUnit writer if needed
53+
if opts.junitPath != "" {
54+
junitWriter, err := extensiontests.NewJUnitResultWriter(opts.junitPath, suite.Name)
55+
if err != nil {
56+
return errors.Wrap(err, "couldn't create junit writer")
57+
}
58+
compositeWriter.AddWriter(junitWriter)
59+
}
60+
61+
// JSON writer
62+
jsonWriter, err := extensiontests.NewJSONResultWriter(os.Stdout,
63+
extensiontests.ResultFormat(opts.outputFlags.Output))
4664
if err != nil {
47-
return errors.Wrapf(err, "couldn't find suite: %s", args[0])
65+
return err
4866
}
67+
compositeWriter.AddWriter(jsonWriter)
4968

5069
specs, err := ext.GetSpecs().Filter(suite.Qualifiers)
5170
if err != nil {
5271
return errors.Wrap(err, "couldn't filter specs")
5372
}
5473

55-
return specs.Run(w, opts.concurrencyFlags.MaxConcurency)
74+
return specs.Run(compositeWriter, opts.concurrencyFlags.MaxConcurency)
5675
},
5776
}
5877
opts.componentFlags.BindFlags(cmd.Flags())
5978
opts.outputFlags.BindFlags(cmd.Flags())
6079
opts.concurrencyFlags.BindFlags(cmd.Flags())
80+
cmd.Flags().StringVarP(&opts.junitPath, "junit-path", "j", opts.junitPath, "write results to junit XML")
6181

6282
return cmd
6383
}

pkg/cmd/cmdrun/runtest.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func NewRunTestCommand(registry *extension.Registry) *cobra.Command {
6363
return err
6464
}
6565

66-
w, err := extensiontests.NewResultWriter(os.Stdout, extensiontests.ResultFormat(opts.outputFlags.Output))
66+
w, err := extensiontests.NewJSONResultWriter(os.Stdout, extensiontests.ResultFormat(opts.outputFlags.Output))
6767
if err != nil {
6868
return err
6969
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,54 @@
11
package extensiontests
22

3+
import (
4+
"strings"
5+
6+
"github.com/openshift-eng/openshift-tests-extension/pkg/junit"
7+
)
8+
39
func (results ExtensionTestResults) Walk(walkFn func(*ExtensionTestResult)) {
410
for i := range results {
511
walkFn(results[i])
612
}
713
}
14+
15+
func (result ExtensionTestResult) ToJUnit() *junit.TestCase {
16+
tc := &junit.TestCase{
17+
Name: result.Name,
18+
Duration: float64(result.Duration) / 1000.0,
19+
}
20+
switch result.Result {
21+
case ResultFailed:
22+
tc.FailureOutput = &junit.FailureOutput{
23+
Message: result.Error,
24+
Output: result.Error,
25+
}
26+
case ResultSkipped:
27+
tc.SkipMessage = &junit.SkipMessage{
28+
Message: strings.Join(result.Details, "\n"),
29+
}
30+
case ResultPassed:
31+
tc.SystemOut = result.Output
32+
}
33+
34+
return tc
35+
}
36+
37+
func (results ExtensionTestResults) ToJUnit(suiteName string) junit.TestSuite {
38+
suite := junit.TestSuite{
39+
Name: suiteName,
40+
}
41+
for _, result := range results {
42+
suite.NumTests++
43+
switch result.Result {
44+
case ResultFailed:
45+
suite.NumFailed++
46+
case ResultSkipped:
47+
suite.NumSkipped++
48+
}
49+
50+
suite.TestCases = append(suite.TestCases, result.ToJUnit())
51+
}
52+
53+
return suite
54+
}

pkg/extension/extensiontests/result_writer.go

Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,136 @@ package extensiontests
22

33
import (
44
"encoding/json"
5+
"encoding/xml"
56
"fmt"
67
"io"
8+
"os"
9+
"sync"
10+
11+
"github.com/openshift-eng/openshift-tests-extension/pkg/junit"
712
)
813

14+
// ResultWriter is an interface for recording ExtensionTestResults in a particular format.
15+
// Implementations must be threadsafe.
16+
type ResultWriter interface {
17+
Write(*ExtensionTestResult)
18+
Flush() error
19+
}
20+
21+
type CompositeResultWriter struct {
22+
writers []ResultWriter
23+
}
24+
25+
func NewCompositeResultWriter(writers ...ResultWriter) *CompositeResultWriter {
26+
return &CompositeResultWriter{
27+
writers: writers,
28+
}
29+
}
30+
31+
func (w *CompositeResultWriter) AddWriter(writer ResultWriter) {
32+
w.writers = append(w.writers, writer)
33+
}
34+
35+
func (w *CompositeResultWriter) Write(res *ExtensionTestResult) {
36+
for _, writer := range w.writers {
37+
writer.Write(res)
38+
}
39+
}
40+
41+
func (w *CompositeResultWriter) Flush() error {
42+
var errs []error
43+
for _, writer := range w.writers {
44+
if err := writer.Flush(); err != nil {
45+
errs = append(errs, err)
46+
}
47+
}
48+
49+
if len(errs) > 0 {
50+
return fmt.Errorf("encountered errors from writers: %v", errs)
51+
}
52+
53+
return nil
54+
}
55+
56+
type JUnitResultWriter struct {
57+
lock sync.Mutex
58+
testSuite *junit.TestSuite
59+
out *os.File
60+
suiteName string
61+
path string
62+
results ExtensionTestResults
63+
}
64+
65+
func NewJUnitResultWriter(path, suiteName string) (ResultWriter, error) {
66+
file, err := os.Create(path)
67+
if err != nil {
68+
return nil, err
69+
}
70+
71+
return &JUnitResultWriter{
72+
testSuite: &junit.TestSuite{
73+
Name: suiteName,
74+
},
75+
out: file,
76+
suiteName: suiteName,
77+
path: path,
78+
}, nil
79+
}
80+
81+
func (w *JUnitResultWriter) Write(res *ExtensionTestResult) {
82+
w.lock.Lock()
83+
defer w.lock.Unlock()
84+
w.results = append(w.results, res)
85+
}
86+
87+
func (w *JUnitResultWriter) Flush() error {
88+
w.lock.Lock()
89+
defer w.lock.Unlock()
90+
data, err := xml.Marshal(w.results.ToJUnit(w.suiteName))
91+
if err != nil {
92+
panic(err)
93+
}
94+
if _, err := w.out.Write(data); err != nil {
95+
return err
96+
}
97+
if err := w.out.Close(); err != nil {
98+
return err
99+
}
100+
101+
return nil
102+
}
103+
9104
type ResultFormat string
10105

11106
var (
12107
JSON ResultFormat = "json"
13108
JSONL ResultFormat = "jsonl"
14109
)
15110

16-
type ResultWriter struct {
111+
type JSONResultWriter struct {
112+
lock sync.Mutex
17113
out io.Writer
18114
format ResultFormat
19115
results ExtensionTestResults
20116
}
21117

22-
func NewResultWriter(out io.Writer, format ResultFormat) (*ResultWriter, error) {
118+
func NewJSONResultWriter(out io.Writer, format ResultFormat) (*JSONResultWriter, error) {
23119
switch format {
24120
case JSON, JSONL:
25121
// do nothing
26122
default:
27123
return nil, fmt.Errorf("unsupported result format: %s", format)
28124
}
29125

30-
return &ResultWriter{
126+
return &JSONResultWriter{
31127
out: out,
32128
format: format,
33129
}, nil
34130
}
35131

36-
func (w *ResultWriter) Write(result *ExtensionTestResult) {
132+
func (w *JSONResultWriter) Write(result *ExtensionTestResult) {
133+
w.lock.Lock()
134+
defer w.lock.Unlock()
37135
switch w.format {
38136
case JSONL:
39137
// JSONL gets written to out as we get the items
@@ -47,15 +145,20 @@ func (w *ResultWriter) Write(result *ExtensionTestResult) {
47145
}
48146
}
49147

50-
func (w *ResultWriter) Flush() {
148+
func (w *JSONResultWriter) Flush() error {
149+
w.lock.Lock()
150+
defer w.lock.Unlock()
51151
switch w.format {
52152
case JSONL:
53153
// we already wrote it out
54154
case JSON:
55155
data, err := json.MarshalIndent(w.results, "", " ")
56156
if err != nil {
57-
panic(err)
157+
return err
58158
}
59-
fmt.Fprintf(w.out, "%s\n", string(data))
159+
_, err = w.out.Write(data)
160+
return err
60161
}
162+
163+
return nil
61164
}

pkg/extension/extensiontests/spec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (specs ExtensionTestSpecs) Names() []string {
3939
return names
4040
}
4141

42-
func (specs ExtensionTestSpecs) Run(w *ResultWriter, maxConcurrent int) error {
42+
func (specs ExtensionTestSpecs) Run(w ResultWriter, maxConcurrent int) error {
4343
queue := make(chan *ExtensionTestSpec)
4444
failures := atomic.Int64{}
4545

0 commit comments

Comments
 (0)