18
18
package main
19
19
20
20
import (
21
+ "bytes"
21
22
"flag"
22
23
"fmt"
23
24
"os"
24
25
"os/exec"
25
26
"path/filepath"
26
27
"regexp"
27
28
"runtime"
29
+ "sync/atomic"
28
30
"syscall"
29
31
"time"
30
32
)
31
33
32
34
var (
33
- flagP = flag .Int ("p" , runtime .NumCPU (), "run `N` processes in parallel" )
34
- flagTimeout = flag .Duration ("timeout" , 10 * time .Minute , "timeout each process after `duration`" )
35
- flagKill = flag .Bool ("kill" , true , "kill timed out processes if true, otherwise just print pid (to attach with gdb)" )
35
+ flagCount = flag .Int ("count" , 0 , "stop after `N` runs (default never stop)" )
36
36
flagFailure = flag .String ("failure" , "" , "fail only if output matches `regexp`" )
37
37
flagIgnore = flag .String ("ignore" , "" , "ignore failure if output matches `regexp`" )
38
+ flagKill = flag .Bool ("kill" , true , "kill timed out processes if true, otherwise just print pid (to attach with gdb)" )
38
39
flagOutput = flag .String ("o" , defaultPrefix (), "output failure logs to `path` plus a unique suffix" )
40
+ flagP = flag .Int ("p" , runtime .NumCPU (), "run `N` processes in parallel" )
41
+ flagTimeout = flag .Duration ("timeout" , 10 * time .Minute , "timeout each process after `duration`" )
39
42
)
40
43
41
44
func init () {
@@ -78,12 +81,22 @@ func main() {
78
81
}
79
82
}
80
83
res := make (chan []byte )
84
+ var started atomic.Int64
81
85
for i := 0 ; i < * flagP ; i ++ {
82
86
go func () {
83
87
for {
88
+ // Note: Must started.Add(1) even if not using -count,
89
+ // because it enables the '%d active' print below.
90
+ if started .Add (1 ) > int64 (* flagCount ) && * flagCount > 0 {
91
+ break
92
+ }
84
93
cmd := exec .Command (flag .Args ()[0 ], flag .Args ()[1 :]... )
94
+ var buf bytes.Buffer
95
+ cmd .Stdout = & buf
96
+ cmd .Stderr = & buf
97
+ err := cmd .Start () // make cmd.Process valid for timeout goroutine
85
98
done := make (chan bool )
86
- if * flagTimeout > 0 {
99
+ if err == nil && * flagTimeout > 0 {
87
100
go func () {
88
101
select {
89
102
case <- done :
@@ -103,7 +116,10 @@ func main() {
103
116
cmd .Process .Kill ()
104
117
}()
105
118
}
106
- out , err := cmd .CombinedOutput ()
119
+ if err == nil {
120
+ err = cmd .Wait ()
121
+ }
122
+ out := buf .Bytes ()
107
123
close (done )
108
124
if err != nil && (failureRe == nil || failureRe .Match (out )) && (ignoreRe == nil || ! ignoreRe .Match (out )) {
109
125
out = append (out , fmt .Sprintf ("\n \n ERROR: %v\n " , err )... )
@@ -117,35 +133,56 @@ func main() {
117
133
runs , fails := 0 , 0
118
134
start := time .Now ()
119
135
ticker := time .NewTicker (5 * time .Second ).C
136
+ status := func (context string ) {
137
+ elapsed := time .Since (start ).Truncate (time .Second )
138
+ var pct string
139
+ if fails > 0 {
140
+ pct = fmt .Sprintf (" (%0.2f%%)" , 100.0 * float64 (fails )/ float64 (runs ))
141
+ }
142
+ var active string
143
+ n := started .Load () - int64 (runs )
144
+ if * flagCount > 0 {
145
+ // started counts past *flagCount at end; do not count those
146
+ // TODO: n = min(n, int64(*flagCount-runs))
147
+ if x := int64 (* flagCount - runs ); n > x {
148
+ n = x
149
+ }
150
+ }
151
+ if n > 0 {
152
+ active = fmt .Sprintf (", %d active" , n )
153
+ }
154
+ fmt .Printf ("%v: %v runs %s, %v failures%s%s\n " , elapsed , runs , context , fails , pct , active )
155
+ }
120
156
for {
121
157
select {
122
158
case out := <- res :
123
159
runs ++
124
- if len (out ) == 0 {
125
- continue
126
- }
127
- fails ++
128
- dir , path := filepath .Split (* flagOutput )
129
- f , err := os .CreateTemp (dir , path )
130
- if err != nil {
131
- fmt .Printf ("failed to create temp file: %v\n " , err )
132
- os .Exit (1 )
160
+ if len (out ) > 0 {
161
+ fails ++
162
+ dir , path := filepath .Split (* flagOutput )
163
+ f , err := os .CreateTemp (dir , path )
164
+ if err != nil {
165
+ fmt .Printf ("failed to create temp file: %v\n " , err )
166
+ os .Exit (1 )
167
+ }
168
+ f .Write (out )
169
+ f .Close ()
170
+ if len (out ) > 2 << 10 {
171
+ out := out [:2 << 10 ]
172
+ fmt .Printf ("\n %s\n %s\n …\n " , f .Name (), out )
173
+ } else {
174
+ fmt .Printf ("\n %s\n %s\n " , f .Name (), out )
175
+ }
133
176
}
134
- f .Write (out )
135
- f .Close ()
136
- if len (out ) > 2 << 10 {
137
- out := out [:2 << 10 ]
138
- fmt .Printf ("\n %s\n %s\n …\n " , f .Name (), out )
139
- } else {
140
- fmt .Printf ("\n %s\n %s\n " , f .Name (), out )
177
+ if * flagCount > 0 && runs >= * flagCount {
178
+ status ("total" )
179
+ if fails > 0 {
180
+ os .Exit (1 )
181
+ }
182
+ os .Exit (0 )
141
183
}
142
184
case <- ticker :
143
- elapsed := time .Since (start ).Truncate (time .Second )
144
- var pct string
145
- if fails > 0 {
146
- pct = fmt .Sprintf (" (%0.2f%%)" , 100.0 * float64 (fails )/ float64 (runs ))
147
- }
148
- fmt .Printf ("%v: %v runs so far, %v failures%s\n " , elapsed , runs , fails , pct )
185
+ status ("so far" )
149
186
}
150
187
}
151
188
}
0 commit comments