@@ -14,6 +14,43 @@ import (
14
14
"github.com/mattn/go-isatty"
15
15
)
16
16
17
+ const Usage = `gotestdox is a command-line tool for turning Go test names into readable sentences.
18
+
19
+ Usage:
20
+
21
+ gotestdox [ARGS]
22
+
23
+ This will run 'go test -json [ARGS]' in the current directory and format the results in a readable
24
+ way. You can use any arguments that 'go test -json' accepts, including a list of packages, for
25
+ example.
26
+
27
+ If the standard input is not an interactive terminal, gotestdox will assume you want to pipe JSON
28
+ data into it. For example:
29
+
30
+ go test -json |gotestdox
31
+
32
+ See https://github.com/bitfield/gotestdox for more information.`
33
+
34
+ // Main runs the command-line interface for gotestdox. The exit status for the
35
+ // binary is 0 if the tests passed, or 1 if the tests failed, or there was some
36
+ // error.
37
+ func Main () int {
38
+ if len (os .Args ) > 1 && os .Args [1 ] == "-h" {
39
+ fmt .Println (Usage )
40
+ return 0
41
+ }
42
+ td := NewTestDoxer ()
43
+ if isatty .IsTerminal (os .Stdin .Fd ()) {
44
+ td .ExecGoTest (os .Args [1 :])
45
+ } else {
46
+ td .Filter ()
47
+ }
48
+ if ! td .OK {
49
+ return 1
50
+ }
51
+ return 0
52
+ }
53
+
17
54
// TestDoxer holds the state and config associated with a particular invocation
18
55
// of 'go test'.
19
56
type TestDoxer struct {
@@ -72,6 +109,7 @@ func (td *TestDoxer) ExecGoTest(userArgs []string) {
72
109
func (td * TestDoxer ) Filter () {
73
110
td .OK = true
74
111
results := map [string ][]Event {}
112
+ outputs := map [string ][]string {}
75
113
scanner := bufio .NewScanner (td .Stdin )
76
114
for scanner .Scan () {
77
115
event , err := ParseJSON (scanner .Text ())
@@ -80,27 +118,46 @@ func (td *TestDoxer) Filter() {
80
118
fmt .Fprintln (td .Stderr , err )
81
119
return
82
120
}
83
- if event .Action == "fail" {
84
- td .OK = false
85
- }
86
- if event .IsPackageResult () {
121
+ switch {
122
+ case event .IsPackageResult ():
87
123
fmt .Fprintf (td .Stdout , "%s:\n " , event .Package )
88
124
tests := results [event .Package ]
89
125
sort .Slice (tests , func (i , j int ) bool {
90
126
return tests [i ].Sentence < tests [j ].Sentence
91
127
})
92
128
for _ , r := range tests {
93
129
fmt .Fprintln (td .Stdout , r .String ())
130
+ if r .Action == "fail" {
131
+ for _ , line := range outputs [r .Test ] {
132
+ fmt .Fprint (td .Stdout , line )
133
+ }
134
+ }
94
135
}
95
136
fmt .Fprintln (td .Stdout )
96
- }
97
- if event .Relevant () {
137
+ case event .IsOutput ():
138
+ outputs [event .Test ] = append (outputs [event .Test ], event .Output )
139
+ case event .IsTestResult ():
98
140
event .Sentence = Prettify (event .Test )
99
141
results [event .Package ] = append (results [event .Package ], event )
142
+ if event .Action == "fail" {
143
+ td .OK = false
144
+ }
100
145
}
101
146
}
102
147
}
103
148
149
+ // ParseJSON takes a string representing a single JSON test record as emitted
150
+ // by 'go test -json', and attempts to parse it into an [Event], returning any
151
+ // parsing error encountered.
152
+ func ParseJSON (line string ) (Event , error ) {
153
+ event := Event {}
154
+ err := json .Unmarshal ([]byte (line ), & event )
155
+ if err != nil {
156
+ return Event {}, fmt .Errorf ("parsing JSON: %w\n input: %s" , err , line )
157
+ }
158
+ return event , nil
159
+ }
160
+
104
161
// Event represents a Go test event as recorded by the 'go test -json' command.
105
162
// It does not attempt to unmarshal all the data, only those fields it needs to
106
163
// know about. It is based on the (unexported) 'event' struct used by Go's
@@ -110,6 +167,7 @@ type Event struct {
110
167
Package string
111
168
Test string
112
169
Sentence string
170
+ Output string
113
171
Elapsed float64
114
172
}
115
173
@@ -132,11 +190,11 @@ func (e Event) String() string {
132
190
return fmt .Sprintf (" %s %s (%.2fs)" , status , e .Sentence , e .Elapsed )
133
191
}
134
192
135
- // Relevant determines whether or not the test event is one that we are
193
+ // IsTestResult determines whether or not the test event is one that we are
136
194
// interested in (namely, a pass or fail event on a test). Events on non-tests
137
195
// (for example, examples) are ignored, and all events on tests other than pass
138
196
// or fail events (for example, run or pause events) are also ignored.
139
- func (e Event ) Relevant () bool {
197
+ func (e Event ) IsTestResult () bool {
140
198
// Events on non-tests are irrelevant
141
199
if ! strings .HasPrefix (e .Test , "Test" ) {
142
200
return false
@@ -160,30 +218,18 @@ func (e Event) IsPackageResult() bool {
160
218
return false
161
219
}
162
220
163
- // ParseJSON takes a string representing a single JSON test record as emitted
164
- // by 'go test -json', and attempts to parse it into an [Event], returning any
165
- // parsing error encountered.
166
- func ParseJSON (line string ) (Event , error ) {
167
- event := Event {}
168
- err := json .Unmarshal ([]byte (line ), & event )
169
- if err != nil {
170
- return Event {}, fmt .Errorf ("parsing JSON: %w\n input: %s" , err , line )
221
+ // IsOutput determines whether or not the event is a test output (for example
222
+ // from [testing.T.Error]), excluding status messages automatically generated
223
+ // by 'go test' such as "--- FAIL: ..." or "=== RUN / PAUSE / CONT".
224
+ func (e Event ) IsOutput () bool {
225
+ if e .Action != "output" {
226
+ return false
171
227
}
172
- return event , nil
173
- }
174
-
175
- // Main runs the command-line interface for gotestdox. The exit status for the
176
- // binary is 0 if the tests passed, or 1 if the tests failed, or there was some
177
- // error.
178
- func Main () int {
179
- td := NewTestDoxer ()
180
- if isatty .IsTerminal (os .Stdin .Fd ()) {
181
- td .ExecGoTest (os .Args [1 :])
182
- } else {
183
- td .Filter ()
228
+ if strings .HasPrefix (e .Output , "---" ) {
229
+ return false
184
230
}
185
- if ! td . OK {
186
- return 1
231
+ if strings . HasPrefix ( e . Output , "===" ) {
232
+ return false
187
233
}
188
- return 0
234
+ return true
189
235
}
0 commit comments