@@ -9,6 +9,7 @@ package main_test
9
9
10
10
import (
11
11
"bytes"
12
+ "context"
12
13
"fmt"
13
14
"internal/testenv"
14
15
"io/ioutil"
@@ -55,21 +56,28 @@ func TestScript(t *testing.T) {
55
56
56
57
// A testScript holds execution state for a single test script.
57
58
type testScript struct {
58
- t * testing.T
59
- workdir string // temporary work dir ($WORK)
60
- log bytes.Buffer // test execution log (printed at end of test)
61
- mark int // offset of next log truncation
62
- cd string // current directory during test execution; initially $WORK/gopath/src
63
- name string // short name of test ("foo")
64
- file string // full file name ("testdata/script/foo.txt")
65
- lineno int // line number currently executing
66
- line string // line currently executing
67
- env []string // environment list (for os/exec)
68
- envMap map [string ]string // environment mapping (matches env)
69
- stdout string // standard output from last 'go' command; for 'stdout' command
70
- stderr string // standard error from last 'go' command; for 'stderr' command
71
- stopped bool // test wants to stop early
72
- start time.Time // time phase started
59
+ t * testing.T
60
+ workdir string // temporary work dir ($WORK)
61
+ log bytes.Buffer // test execution log (printed at end of test)
62
+ mark int // offset of next log truncation
63
+ cd string // current directory during test execution; initially $WORK/gopath/src
64
+ name string // short name of test ("foo")
65
+ file string // full file name ("testdata/script/foo.txt")
66
+ lineno int // line number currently executing
67
+ line string // line currently executing
68
+ env []string // environment list (for os/exec)
69
+ envMap map [string ]string // environment mapping (matches env)
70
+ stdout string // standard output from last 'go' command; for 'stdout' command
71
+ stderr string // standard error from last 'go' command; for 'stderr' command
72
+ stopped bool // test wants to stop early
73
+ start time.Time // time phase started
74
+ background []backgroundCmd // backgrounded 'exec' and 'go' commands
75
+ }
76
+
77
+ type backgroundCmd struct {
78
+ cmd * exec.Cmd
79
+ wait <- chan struct {}
80
+ neg bool // if true, cmd should fail
73
81
}
74
82
75
83
var extraEnvKeys = []string {
@@ -146,6 +154,17 @@ func (ts *testScript) run() {
146
154
}
147
155
148
156
defer func () {
157
+ // On a normal exit from the test loop, background processes are cleaned up
158
+ // before we print PASS. If we return early (e.g., due to a test failure),
159
+ // don't print anything about the processes that were still running.
160
+ for _ , bg := range ts .background {
161
+ interruptProcess (bg .cmd .Process )
162
+ }
163
+ for _ , bg := range ts .background {
164
+ <- bg .wait
165
+ }
166
+ ts .background = nil
167
+
149
168
markTime ()
150
169
// Flush testScript log to testing.T log.
151
170
ts .t .Log ("\n " + ts .abbrev (ts .log .String ()))
@@ -284,14 +303,23 @@ Script:
284
303
285
304
// Command can ask script to stop early.
286
305
if ts .stopped {
287
- return
306
+ // Break instead of returning, so that we check the status of any
307
+ // background processes and print PASS.
308
+ break
288
309
}
289
310
}
290
311
312
+ for _ , bg := range ts .background {
313
+ interruptProcess (bg .cmd .Process )
314
+ }
315
+ ts .cmdWait (false , nil )
316
+
291
317
// Final phase ended.
292
318
rewind ()
293
319
markTime ()
294
- fmt .Fprintf (& ts .log , "PASS\n " )
320
+ if ! ts .stopped {
321
+ fmt .Fprintf (& ts .log , "PASS\n " )
322
+ }
295
323
}
296
324
297
325
// scriptCmds are the script command implementations.
@@ -317,6 +345,7 @@ var scriptCmds = map[string]func(*testScript, bool, []string){
317
345
"stdout" : (* testScript ).cmdStdout ,
318
346
"stop" : (* testScript ).cmdStop ,
319
347
"symlink" : (* testScript ).cmdSymlink ,
348
+ "wait" : (* testScript ).cmdWait ,
320
349
}
321
350
322
351
// addcrlf adds CRLF line endings to the named files.
@@ -451,26 +480,43 @@ func (ts *testScript) cmdEnv(neg bool, args []string) {
451
480
452
481
// exec runs the given command.
453
482
func (ts * testScript ) cmdExec (neg bool , args []string ) {
454
- if len (args ) < 1 {
455
- ts .fatalf ("usage: exec program [args...]" )
483
+ if len (args ) < 1 || ( len ( args ) == 1 && args [ 0 ] == "&" ) {
484
+ ts .fatalf ("usage: exec program [args...] [&] " )
456
485
}
486
+
457
487
var err error
458
- ts .stdout , ts .stderr , err = ts .exec (args [0 ], args [1 :]... )
459
- if ts .stdout != "" {
460
- fmt .Fprintf (& ts .log , "[stdout]\n %s" , ts .stdout )
461
- }
462
- if ts .stderr != "" {
463
- fmt .Fprintf (& ts .log , "[stderr]\n %s" , ts .stderr )
488
+ if len (args ) > 0 && args [len (args )- 1 ] == "&" {
489
+ var cmd * exec.Cmd
490
+ cmd , err = ts .execBackground (args [0 ], args [1 :len (args )- 1 ]... )
491
+ if err == nil {
492
+ wait := make (chan struct {})
493
+ go func () {
494
+ ctxWait (testCtx , cmd )
495
+ close (wait )
496
+ }()
497
+ ts .background = append (ts .background , backgroundCmd {cmd , wait , neg })
498
+ }
499
+ ts .stdout , ts .stderr = "" , ""
500
+ } else {
501
+ ts .stdout , ts .stderr , err = ts .exec (args [0 ], args [1 :]... )
502
+ if ts .stdout != "" {
503
+ fmt .Fprintf (& ts .log , "[stdout]\n %s" , ts .stdout )
504
+ }
505
+ if ts .stderr != "" {
506
+ fmt .Fprintf (& ts .log , "[stderr]\n %s" , ts .stderr )
507
+ }
508
+ if err == nil && neg {
509
+ ts .fatalf ("unexpected command success" )
510
+ }
464
511
}
512
+
465
513
if err != nil {
466
514
fmt .Fprintf (& ts .log , "[%v]\n " , err )
467
- if ! neg {
515
+ if testCtx .Err () != nil {
516
+ ts .fatalf ("test timed out while running command" )
517
+ } else if ! neg {
468
518
ts .fatalf ("unexpected command failure" )
469
519
}
470
- } else {
471
- if neg {
472
- ts .fatalf ("unexpected command success" )
473
- }
474
520
}
475
521
}
476
522
@@ -545,6 +591,14 @@ func (ts *testScript) cmdSkip(neg bool, args []string) {
545
591
if neg {
546
592
ts .fatalf ("unsupported: ! skip" )
547
593
}
594
+
595
+ // Before we mark the test as skipped, shut down any background processes and
596
+ // make sure they have returned the correct status.
597
+ for _ , bg := range ts .background {
598
+ interruptProcess (bg .cmd .Process )
599
+ }
600
+ ts .cmdWait (false , nil )
601
+
548
602
if len (args ) == 1 {
549
603
ts .t .Skip (args [0 ])
550
604
}
@@ -687,6 +741,52 @@ func (ts *testScript) cmdSymlink(neg bool, args []string) {
687
741
ts .check (os .Symlink (args [2 ], ts .mkabs (args [0 ])))
688
742
}
689
743
744
+ // wait waits for background commands to exit, setting stderr and stdout to their result.
745
+ func (ts * testScript ) cmdWait (neg bool , args []string ) {
746
+ if neg {
747
+ ts .fatalf ("unsupported: ! wait" )
748
+ }
749
+ if len (args ) > 0 {
750
+ ts .fatalf ("usage: wait" )
751
+ }
752
+
753
+ var stdouts , stderrs []string
754
+ for _ , bg := range ts .background {
755
+ <- bg .wait
756
+
757
+ args := append ([]string {filepath .Base (bg .cmd .Args [0 ])}, bg .cmd .Args [1 :]... )
758
+ fmt .Fprintf (& ts .log , "[background] %s: %v\n " , strings .Join (args , " " ), bg .cmd .ProcessState )
759
+
760
+ cmdStdout := bg .cmd .Stdout .(* strings.Builder ).String ()
761
+ if cmdStdout != "" {
762
+ fmt .Fprintf (& ts .log , "[stdout]\n %s" , cmdStdout )
763
+ stdouts = append (stdouts , cmdStdout )
764
+ }
765
+
766
+ cmdStderr := bg .cmd .Stderr .(* strings.Builder ).String ()
767
+ if cmdStderr != "" {
768
+ fmt .Fprintf (& ts .log , "[stderr]\n %s" , cmdStderr )
769
+ stderrs = append (stderrs , cmdStderr )
770
+ }
771
+
772
+ if bg .cmd .ProcessState .Success () {
773
+ if bg .neg {
774
+ ts .fatalf ("unexpected command success" )
775
+ }
776
+ } else {
777
+ if testCtx .Err () != nil {
778
+ ts .fatalf ("test timed out while running command" )
779
+ } else if ! bg .neg {
780
+ ts .fatalf ("unexpected command failure" )
781
+ }
782
+ }
783
+ }
784
+
785
+ ts .stdout = strings .Join (stdouts , "" )
786
+ ts .stderr = strings .Join (stderrs , "" )
787
+ ts .background = nil
788
+ }
789
+
690
790
// Helpers for command implementations.
691
791
692
792
// abbrev abbreviates the actual work directory in the string s to the literal string "$WORK".
@@ -716,10 +816,51 @@ func (ts *testScript) exec(command string, args ...string) (stdout, stderr strin
716
816
var stdoutBuf , stderrBuf strings.Builder
717
817
cmd .Stdout = & stdoutBuf
718
818
cmd .Stderr = & stderrBuf
719
- err = cmd .Run ()
819
+ if err = cmd .Start (); err == nil {
820
+ err = ctxWait (testCtx , cmd )
821
+ }
720
822
return stdoutBuf .String (), stderrBuf .String (), err
721
823
}
722
824
825
+ // execBackground starts the given command line (an actual subprocess, not simulated)
826
+ // in ts.cd with environment ts.env.
827
+ func (ts * testScript ) execBackground (command string , args ... string ) (* exec.Cmd , error ) {
828
+ cmd := exec .Command (command , args ... )
829
+ cmd .Dir = ts .cd
830
+ cmd .Env = append (ts .env , "PWD=" + ts .cd )
831
+ var stdoutBuf , stderrBuf strings.Builder
832
+ cmd .Stdout = & stdoutBuf
833
+ cmd .Stderr = & stderrBuf
834
+ return cmd , cmd .Start ()
835
+ }
836
+
837
+ // ctxWait is like cmd.Wait, but terminates cmd with os.Interrupt if ctx becomes done.
838
+ //
839
+ // This differs from exec.CommandContext in that it prefers os.Interrupt over os.Kill.
840
+ // (See https://golang.org/issue/21135.)
841
+ func ctxWait (ctx context.Context , cmd * exec.Cmd ) error {
842
+ errc := make (chan error , 1 )
843
+ go func () { errc <- cmd .Wait () }()
844
+
845
+ select {
846
+ case err := <- errc :
847
+ return err
848
+ case <- ctx .Done ():
849
+ interruptProcess (cmd .Process )
850
+ return <- errc
851
+ }
852
+ }
853
+
854
+ // interruptProcess sends os.Interrupt to p if supported, or os.Kill otherwise.
855
+ func interruptProcess (p * os.Process ) {
856
+ if err := p .Signal (os .Interrupt ); err != nil {
857
+ // Per https://golang.org/pkg/os/#Signal, “Interrupt is not implemented on
858
+ // Windows; using it with os.Process.Signal will return an error.”
859
+ // Fall back to Kill instead.
860
+ p .Kill ()
861
+ }
862
+ }
863
+
723
864
// expand applies environment variable expansion to the string s.
724
865
func (ts * testScript ) expand (s string ) string {
725
866
return os .Expand (s , func (key string ) string { return ts .envMap [key ] })
0 commit comments