Skip to content

Commit a656573

Browse files
committed
feat: ctrf for custom reporter
1 parent 8870365 commit a656573

File tree

10 files changed

+649
-128
lines changed

10 files changed

+649
-128
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
.idea
3+
*.iml

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
# Go JSON Reporter
1+
# Go CTRF JSON format support
2+
3+
## Go JSON Reporter
4+
25

36
A Go JSON test reporter to create test reports that follow the CTRF standard.
47

@@ -115,3 +118,26 @@ When running go-ctrf-json-reporter results in a "command not found" error this u
115118
## Support Us
116119

117120
If you find this project useful, consider giving it a GitHub star ⭐ It means a lot to us.
121+
122+
123+
## Generate a CTRF JSON report in your own testing tool written in go
124+
125+
If you are writting your own testing tool and wish to generate a CTRF JSON report, you can use the `ctrf` package.
126+
127+
```go
128+
import (
129+
"github.com/ctrf-io/go-ctrf-json-reporter/ctrf"
130+
)
131+
132+
func runTests(destinationReportFile string) error {
133+
env := ctrf.Environment{
134+
// add your environment details here
135+
}
136+
report := ctrf.NewReport("my-awesome-testing-tool", &env)
137+
138+
// run your tests and populate the report object here
139+
140+
return report.WriteFile(destinationReportFile)
141+
}
142+
143+
```

cmd/go-ctrf-json-reporter/main.go

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,71 @@
11
package main
22

33
import (
4-
"flag"
5-
"fmt"
6-
"github.com/ctrf-io/go-ctrf-json-reporter/reporter"
7-
"os"
4+
"flag"
5+
"fmt"
6+
"github.com/ctrf-io/go-ctrf-json-reporter/ctrf"
7+
"github.com/ctrf-io/go-ctrf-json-reporter/reporter"
8+
"os"
89
)
910

1011
func main() {
11-
var outputFile string
12-
var verbose bool
13-
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
14-
flag.BoolVar(&verbose, "v", false, "Enable verbose output (shorthand)")
15-
flag.StringVar(&outputFile, "output", "ctrf-report.json", "The output file for the test results")
16-
flag.StringVar(&outputFile, "o", "ctrf-report.json", "The output file for the test results (shorthand)")
12+
var outputFile string
13+
var verbose bool
14+
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
15+
flag.BoolVar(&verbose, "v", false, "Enable verbose output (shorthand)")
16+
flag.StringVar(&outputFile, "output", "ctrf-report.json", "The output file for the test results")
17+
flag.StringVar(&outputFile, "o", "ctrf-report.json", "The output file for the test results (shorthand)")
1718

18-
var tempAppName, tempAppVersion, tempOSPlatform, tempOSRelease, tempOSVersion, tempBuildName, tempBuildNumber string
19+
var tempAppName, tempAppVersion, tempOSPlatform, tempOSRelease, tempOSVersion, tempBuildName, tempBuildNumber string
1920

20-
flag.StringVar(&tempAppName, "appName", "", "The name of the application being tested.")
21-
flag.StringVar(&tempAppVersion, "appVersion", "", "The version of the application being tested.")
22-
flag.StringVar(&tempOSPlatform, "osPlatform", "", "The operating system platform (e.g., Windows, Linux).")
23-
flag.StringVar(&tempOSRelease, "osRelease", "", "The release version of the operating system.")
24-
flag.StringVar(&tempOSVersion, "osVersion", "", "The version number of the operating system.")
25-
flag.StringVar(&tempBuildName, "buildName", "", "The name of the build (e.g., feature branch name).")
26-
flag.StringVar(&tempBuildNumber, "buildNumber", "", "The build number or identifier.")
21+
flag.StringVar(&tempAppName, "appName", "", "The name of the application being tested.")
22+
flag.StringVar(&tempAppVersion, "appVersion", "", "The version of the application being tested.")
23+
flag.StringVar(&tempOSPlatform, "osPlatform", "", "The operating system platform (e.g., Windows, Linux).")
24+
flag.StringVar(&tempOSRelease, "osRelease", "", "The release version of the operating system.")
25+
flag.StringVar(&tempOSVersion, "osVersion", "", "The version number of the operating system.")
26+
flag.StringVar(&tempBuildName, "buildName", "", "The name of the build (e.g., feature branch name).")
27+
flag.StringVar(&tempBuildNumber, "buildNumber", "", "The build number or identifier.")
2728

28-
flag.Parse()
29+
flag.Parse()
2930

30-
var env *reporter.Environment
31+
var env *ctrf.Environment
3132

32-
if tempAppName != "" || tempAppVersion != "" || tempOSPlatform != "" ||
33-
tempOSRelease != "" || tempOSVersion != "" || tempBuildName != "" || tempBuildNumber != "" {
34-
env = &reporter.Environment{}
33+
if tempAppName != "" || tempAppVersion != "" || tempOSPlatform != "" ||
34+
tempOSRelease != "" || tempOSVersion != "" || tempBuildName != "" || tempBuildNumber != "" {
35+
env = &ctrf.Environment{}
3536

36-
if tempAppName != "" {
37-
env.AppName = &tempAppName
38-
}
39-
if tempAppVersion != "" {
40-
env.AppVersion = &tempAppVersion
41-
}
42-
if tempOSPlatform != "" {
43-
env.OSPlatform = &tempOSPlatform
44-
}
45-
if tempOSRelease != "" {
46-
env.OSRelease = &tempOSRelease
47-
}
48-
if tempOSVersion != "" {
49-
env.OSVersion = &tempOSVersion
50-
}
51-
if tempBuildName != "" {
52-
env.BuildName = &tempBuildName
53-
}
54-
if tempBuildNumber != "" {
55-
env.BuildNumber = &tempBuildNumber
56-
}
57-
}
37+
if tempAppName != "" {
38+
env.AppName = tempAppName
39+
}
40+
if tempAppVersion != "" {
41+
env.AppVersion = tempAppVersion
42+
}
43+
if tempOSPlatform != "" {
44+
env.OSPlatform = tempOSPlatform
45+
}
46+
if tempOSRelease != "" {
47+
env.OSRelease = tempOSRelease
48+
}
49+
if tempOSVersion != "" {
50+
env.OSVersion = tempOSVersion
51+
}
52+
if tempBuildName != "" {
53+
env.BuildName = tempBuildName
54+
}
55+
if tempBuildNumber != "" {
56+
env.BuildNumber = tempBuildNumber
57+
}
58+
}
5859

59-
report, err := reporter.ParseTestResults(os.Stdin, verbose, env)
60-
if err != nil {
61-
fmt.Fprintf(os.Stderr, "Error parsing test results: %v\n", err)
62-
os.Exit(1)
63-
}
60+
report, err := reporter.ParseTestResults(os.Stdin, verbose, env)
61+
if err != nil {
62+
_, _ = fmt.Fprintf(os.Stderr, "Error parsing test results: %v\n", err)
63+
os.Exit(1)
64+
}
6465

65-
err = reporter.WriteReportToFile(outputFile, report)
66-
if err != nil {
67-
fmt.Fprintf(os.Stderr, "Error writing the report to file: %v\n", err)
68-
os.Exit(1)
69-
}
66+
err = reporter.WriteReportToFile(outputFile, report)
67+
if err != nil {
68+
_, _ = fmt.Fprintf(os.Stderr, "Error writing the report to file: %v\n", err)
69+
os.Exit(1)
70+
}
7071
}

ctrf/ctrf.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package ctrf
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strings"
10+
)
11+
12+
type Report struct {
13+
Results *Results `json:"results"`
14+
}
15+
16+
func NewReport(toolName string, env *Environment) *Report {
17+
return &Report{
18+
Results: &Results{
19+
Tool: &Tool{Name: toolName},
20+
Environment: env,
21+
Summary: &Summary{},
22+
},
23+
}
24+
}
25+
26+
func (report *Report) ToJson() (string, error) {
27+
stringBuilder := &strings.Builder{}
28+
29+
err := report.Write(stringBuilder, false)
30+
if err != nil {
31+
return "", err
32+
}
33+
34+
return stringBuilder.String(), nil
35+
}
36+
37+
func (report *Report) ToJsonPretty() (string, error) {
38+
stringBuilder := &strings.Builder{}
39+
40+
err := report.Write(stringBuilder, true)
41+
if err != nil {
42+
return "", err
43+
}
44+
45+
return stringBuilder.String(), nil
46+
}
47+
48+
func (report *Report) Write(w io.Writer, pretty bool) error {
49+
if len(report.Validate()) > 0 {
50+
return errors.New("report is invalid")
51+
}
52+
encoder := json.NewEncoder(w)
53+
if pretty {
54+
encoder.SetIndent("", " ")
55+
}
56+
err := encoder.Encode(report)
57+
if err != nil {
58+
return fmt.Errorf("error writing ctrf json report: %v", err)
59+
}
60+
return nil
61+
}
62+
63+
func (report *Report) WriteFile(filePath string) error {
64+
file, err := os.Create(filePath)
65+
if err != nil {
66+
return fmt.Errorf("error writing ctrf json report: %v", err)
67+
}
68+
defer func(file *os.File) {
69+
_ = file.Close()
70+
}(file)
71+
72+
return report.Write(file, true)
73+
}
74+
75+
func (report *Report) Validate() []error {
76+
results := report.Results
77+
if results == nil {
78+
return singleError("missing property 'results'")
79+
}
80+
81+
var errs []error
82+
if results.Tool == nil {
83+
errs = append(errs, errors.New("missing property 'results.tool'"))
84+
} else {
85+
errs = append(errs, results.Tool.Validate()...)
86+
}
87+
if results.Summary == nil {
88+
errs = append(errs, errors.New("missing property 'results.summary'"))
89+
} else {
90+
errs = append(errs, results.Summary.Validate()...)
91+
}
92+
if results.Tests == nil {
93+
errs = append(errs, errors.New("missing property 'results.tests'"))
94+
}
95+
return errs
96+
}
97+
98+
func singleError(errorMessage string) []error {
99+
return []error{errors.New(errorMessage)}
100+
}
101+
102+
type Results struct {
103+
Tool *Tool `json:"tool"`
104+
Summary *Summary `json:"summary"`
105+
Tests []*TestResult `json:"tests"`
106+
Environment *Environment `json:"environment,omitempty"`
107+
Extra interface{} `json:"extra,omitempty"`
108+
}
109+
110+
type Tool struct {
111+
Name string `json:"name"`
112+
Version string `json:"version,omitempty"`
113+
Extra interface{} `json:"extra,omitempty"`
114+
}
115+
116+
func (tool *Tool) Validate() []error {
117+
if tool.Name == "" {
118+
return singleError("missing property 'results.tool.name'")
119+
}
120+
return nil
121+
}
122+
123+
type Summary struct {
124+
Tests int `json:"tests"`
125+
Passed int `json:"passed"`
126+
Failed int `json:"failed"`
127+
Pending int `json:"pending"`
128+
Skipped int `json:"skipped"`
129+
Other int `json:"other"`
130+
Suites int `json:"suites,omitempty"`
131+
Start int64 `json:"start"`
132+
Stop int64 `json:"stop"`
133+
Extra interface{} `json:"extra,omitempty"`
134+
}
135+
136+
func (summary *Summary) Validate() []error {
137+
var errs []error
138+
if summary.Tests < 0 {
139+
errs = append(errs, errors.New("invalid property 'results.summary.tests'"))
140+
}
141+
if summary.Passed < 0 {
142+
errs = append(errs, errors.New("invalid property 'results.summary.passed'"))
143+
}
144+
if summary.Failed < 0 {
145+
errs = append(errs, errors.New("invalid property 'results.summary.failed'"))
146+
}
147+
if summary.Pending < 0 {
148+
errs = append(errs, errors.New("invalid property 'results.summary.pending'"))
149+
}
150+
if summary.Skipped < 0 {
151+
errs = append(errs, errors.New("invalid property 'results.summary.skipped'"))
152+
}
153+
if summary.Other < 0 {
154+
errs = append(errs, errors.New("invalid property 'results.summary.other'"))
155+
}
156+
if summary.Start < 0 {
157+
errs = append(errs, errors.New("invalid property 'results.summary.start'"))
158+
}
159+
if summary.Stop < 0 {
160+
errs = append(errs, errors.New("invalid property 'results.summary.stop'"))
161+
}
162+
if summary.Suites < 0 {
163+
errs = append(errs, errors.New("invalid property 'results.summary.suites'"))
164+
}
165+
if summary.Start > summary.Stop {
166+
errs = append(errs, errors.New("invalid summary timestamps: start can't be greater than stop"))
167+
}
168+
testsSum := summary.Passed + summary.Failed + summary.Pending + summary.Skipped + summary.Other
169+
if summary.Tests != testsSum {
170+
errs = append(errs, fmt.Errorf("invalid summary counts: tests (%d) must be the sum of passed, failed, pending, skipped, and other (%d)", summary.Tests, testsSum))
171+
172+
}
173+
return errs
174+
}
175+
176+
type TestStatus string
177+
178+
const (
179+
TestPassed TestStatus = "passed"
180+
TestFailed TestStatus = "failed"
181+
TestSkipped TestStatus = "skipped"
182+
TestPending TestStatus = "pending"
183+
TestOther TestStatus = "other"
184+
)
185+
186+
type TestResult struct {
187+
Name string `json:"name"`
188+
Status TestStatus `json:"status"`
189+
Duration float64 `json:"duration"`
190+
Start int64 `json:"start,omitempty"`
191+
Stop int64 `json:"stop,omitempty"`
192+
Suite string `json:"suite,omitempty"`
193+
Message string `json:"message,omitempty"`
194+
Trace string `json:"trace,omitempty"`
195+
RawStatus string `json:"rawStatus,omitempty"`
196+
Tags []string `json:"tags,omitempty"`
197+
Type string `json:"type,omitempty"`
198+
Filepath string `json:"filepath,omitempty"`
199+
Retry int `json:"retry,omitempty"`
200+
Flake bool `json:"flake,omitempty"`
201+
Browser string `json:"browser,omitempty"`
202+
Device string `json:"device,omitempty"`
203+
Screenshot string `json:"screenshot,omitempty"`
204+
Parameters interface{} `json:"parameters,omitempty"`
205+
Steps []interface{} `json:"steps,omitempty"`
206+
Extra interface{} `json:"extra,omitempty"`
207+
}
208+
209+
type Environment struct {
210+
AppName string `json:"appName,omitempty"`
211+
AppVersion string `json:"appVersion,omitempty"`
212+
OSPlatform string `json:"osPlatform,omitempty"`
213+
OSRelease string `json:"osRelease,omitempty"`
214+
OSVersion string `json:"osVersion,omitempty"`
215+
BuildName string `json:"buildName,omitempty"`
216+
BuildNumber string `json:"buildNumber,omitempty"`
217+
Extra interface{} `json:"extra,omitempty"`
218+
}

0 commit comments

Comments
 (0)