@@ -6,11 +6,14 @@ package pytest
6
6
7
7
import (
8
8
"bytes"
9
+ "flag"
10
+ "fmt"
9
11
"io"
10
12
"os"
11
13
"path"
12
14
"path/filepath"
13
15
"strings"
16
+ "sync/atomic"
14
17
"testing"
15
18
16
19
"github.com/go-python/gpython/compile"
@@ -20,6 +23,8 @@ import (
20
23
_ "github.com/go-python/gpython/stdlib"
21
24
)
22
25
26
+ var RegenTestData = flag .Bool ("regen" , false , "Regenerate golden files from current testdata." )
27
+
23
28
var gContext = py .NewContext (py .DefaultContextOpts ())
24
29
25
30
// Compile the program in the file prog to code in the module that is returned
@@ -132,58 +137,148 @@ func RunBenchmarks(b *testing.B, testDir string) {
132
137
133
138
// RunScript runs the provided path to a script.
134
139
// RunScript captures the stdout and stderr while executing the script
135
- // and compares it to a golden file:
140
+ // and compares it to a golden file, blocking until completion.
136
141
//
137
142
// RunScript("./testdata/foo.py")
138
143
//
139
144
// will compare the output with "./testdata/foo_golden.txt".
140
145
func RunScript (t * testing.T , fname string ) {
146
+
147
+ RunTestTasks (t , []* Task {
148
+ {
149
+ PyFile : fname ,
150
+ },
151
+ })
152
+ }
153
+
154
+ // RunTestTasks runs each given task in a newly created py.Context concurrently.
155
+ // If a fatal error is encountered, the given testing.T is signaled.
156
+ func RunTestTasks (t * testing.T , tasks []* Task ) {
157
+ onCompleted := make (chan * Task )
158
+
159
+ numTasks := len (tasks )
160
+ for ti := 0 ; ti < numTasks ; ti ++ {
161
+ task := tasks [ti ]
162
+ go func () {
163
+ err := task .run ()
164
+ task .Err = err
165
+ onCompleted <- task
166
+ }()
167
+ }
168
+
169
+ tasks = tasks [:0 ]
170
+ for ti := 0 ; ti < numTasks ; ti ++ {
171
+ task := <- onCompleted
172
+ if task .Err != nil {
173
+ t .Error (task .Err )
174
+ }
175
+ tasks = append (tasks , task )
176
+ }
177
+ }
178
+
179
+ var (
180
+ taskCounter int32
181
+ )
182
+
183
+ type Task struct {
184
+ num int32 // Assigned when this task is run
185
+ ID string // unique key identifying this task. If empty, autogenerated from the basename of PyFile
186
+ PyFile string // If set, this file pathname is executed in a newly created ctx
187
+ PyTask func (ctx py.Context ) error // If set, a new created ctx is created and this blocks until completion
188
+ GoldFile string // Filename containing the "gold standard" stdout+stderr. If empty, autogenerated from PyFile or ID
189
+ Err error // Non-nil if a fatal error is encountered with this task
190
+ }
191
+
192
+ func (task * Task ) run () error {
193
+ fileBase := ""
194
+
141
195
opts := py .DefaultContextOpts ()
142
- opts .SysArgs = []string {fname }
196
+ if task .PyFile != "" {
197
+ opts .SysArgs = []string {task .PyFile }
198
+ if task .ID == "" {
199
+ ext := filepath .Ext (task .PyFile )
200
+ fileBase = task .PyFile [0 : len (task .PyFile )- len (ext )]
201
+ }
202
+ }
203
+
204
+ task .num = atomic .AddInt32 (& taskCounter , 1 )
205
+ if task .ID == "" {
206
+ if fileBase == "" {
207
+ task .ID = fmt .Sprintf ("task-%04d" , atomic .AddInt32 (& taskCounter , 1 ))
208
+ } else {
209
+ task .ID = strings .TrimPrefix (fileBase , "./" )
210
+ }
211
+ }
212
+
213
+ if task .GoldFile == "" {
214
+ task .GoldFile = fileBase + "_golden.txt"
215
+ }
216
+
143
217
ctx := py .NewContext (opts )
144
218
defer ctx .Close ()
145
219
146
220
sys := ctx .Store ().MustGetModule ("sys" )
147
221
tmp , err := os .MkdirTemp ("" , "gpython-pytest-" )
148
222
if err != nil {
149
- t . Fatal ( err )
223
+ return err
150
224
}
151
225
defer os .RemoveAll (tmp )
152
226
153
227
out , err := os .Create (filepath .Join (tmp , "combined" ))
154
228
if err != nil {
155
- t . Fatalf ("could not create stdout/ stderr: %+v " , err )
229
+ return fmt . Errorf ("could not create stdout+ stderr output file : %w " , err )
156
230
}
157
231
defer out .Close ()
158
232
159
233
sys .Globals ["stdout" ] = & py.File {File : out , FileMode : py .FileWrite }
160
234
sys .Globals ["stderr" ] = & py.File {File : out , FileMode : py .FileWrite }
161
235
162
- _ , err = py .RunFile (ctx , fname , py.CompileOpts {}, nil )
163
- if err != nil {
164
- t .Fatalf ("could not run script %q: %+v" , fname , err )
236
+ if task .PyFile != "" {
237
+ _ , err := py .RunFile (ctx , task .PyFile , py.CompileOpts {}, nil )
238
+ if err != nil {
239
+ return fmt .Errorf ("could not run target script %q: %w" , task .PyFile , err )
240
+ }
165
241
}
166
242
243
+ if task .PyTask != nil {
244
+ err := task .PyTask (ctx )
245
+ if err != nil {
246
+ return fmt .Errorf ("PyTask %q failed: %w" , task .ID , err )
247
+ }
248
+ }
249
+
250
+ // Close the ctx explicitly as it may legitimately generate output
251
+ ctx .Close ()
252
+ <- ctx .Done ()
253
+
167
254
err = out .Close ()
168
255
if err != nil {
169
- t . Fatalf ("could not close stdout/stderr : %+v " , err )
256
+ return fmt . Errorf ("could not close output file : %w " , err )
170
257
}
171
258
172
259
got , err := os .ReadFile (out .Name ())
173
260
if err != nil {
174
- t . Fatalf ("could not read script output: %+v " , err )
261
+ return fmt . Errorf ("could not read script output file : %w " , err )
175
262
}
176
263
177
- ref := fname [:len (fname )- len (".py" )] + "_golden.txt"
178
- want , err := os .ReadFile (ref )
264
+ if * RegenTestData {
265
+ err := os .WriteFile (task .GoldFile , got , 0644 )
266
+ if err != nil {
267
+ return fmt .Errorf ("could not write golden output %q: %w" , task .GoldFile , err )
268
+ }
269
+ }
270
+
271
+ want , err := os .ReadFile (task .GoldFile )
179
272
if err != nil {
180
- t . Fatalf ("could not read golden output %q: %+v " , ref , err )
273
+ return fmt . Errorf ("could not read golden output %q: %w " , task . GoldFile , err )
181
274
}
182
275
183
276
diff := cmp .Diff (string (want ), string (got ))
184
277
if ! bytes .Equal (got , want ) {
185
- out := fname [: len ( fname ) - len ( ".py" )] + ".txt"
278
+ out := fileBase + ".txt"
186
279
_ = os .WriteFile (out , got , 0644 )
187
- t . Fatalf ("output differ: -- (-ref +got)\n %s" , diff )
280
+ return fmt . Errorf ("output differ: -- (-ref +got)\n %s" , diff )
188
281
}
282
+
283
+ return nil
189
284
}
0 commit comments