8
8
"bufio"
9
9
"bytes"
10
10
"fmt"
11
+ "io"
11
12
"os"
12
13
"os/exec"
13
14
"path/filepath"
@@ -26,30 +27,31 @@ import (
26
27
//
27
28
// NOTE: If you make changes here, update doc.go.
28
29
var scriptCmds = map [string ]func (* TestScript , bool , []string ){
29
- "cd" : (* TestScript ).cmdCd ,
30
- "chmod" : (* TestScript ).cmdChmod ,
31
- "cmp" : (* TestScript ).cmdCmp ,
32
- "cmpenv" : (* TestScript ).cmdCmpenv ,
33
- "cp" : (* TestScript ).cmdCp ,
34
- "env" : (* TestScript ).cmdEnv ,
35
- "exec" : (* TestScript ).cmdExec ,
36
- "exists" : (* TestScript ).cmdExists ,
37
- "grep" : (* TestScript ).cmdGrep ,
38
- "kill" : (* TestScript ).cmdKill ,
39
- "mkdir" : (* TestScript ).cmdMkdir ,
40
- "mv" : (* TestScript ).cmdMv ,
41
- "rm" : (* TestScript ).cmdRm ,
42
- "skip" : (* TestScript ).cmdSkip ,
43
- "stderr" : (* TestScript ).cmdStderr ,
44
- "stdin" : (* TestScript ).cmdStdin ,
45
- "stdout" : (* TestScript ).cmdStdout ,
46
- "ttyin" : (* TestScript ).cmdTtyin ,
47
- "ttyout" : (* TestScript ).cmdTtyout ,
48
- "stop" : (* TestScript ).cmdStop ,
49
- "symlink" : (* TestScript ).cmdSymlink ,
50
- "unix2dos" : (* TestScript ).cmdUNIX2DOS ,
51
- "unquote" : (* TestScript ).cmdUnquote ,
52
- "wait" : (* TestScript ).cmdWait ,
30
+ "cd" : (* TestScript ).cmdCd ,
31
+ "chmod" : (* TestScript ).cmdChmod ,
32
+ "cmp" : (* TestScript ).cmdCmp ,
33
+ "cmpenv" : (* TestScript ).cmdCmpenv ,
34
+ "cp" : (* TestScript ).cmdCp ,
35
+ "env" : (* TestScript ).cmdEnv ,
36
+ "exec" : (* TestScript ).cmdExec ,
37
+ "exists" : (* TestScript ).cmdExists ,
38
+ "grep" : (* TestScript ).cmdGrep ,
39
+ "kill" : (* TestScript ).cmdKill ,
40
+ "mkdir" : (* TestScript ).cmdMkdir ,
41
+ "mv" : (* TestScript ).cmdMv ,
42
+ "rm" : (* TestScript ).cmdRm ,
43
+ "skip" : (* TestScript ).cmdSkip ,
44
+ "stderr" : (* TestScript ).cmdStderr ,
45
+ "stdin" : (* TestScript ).cmdStdin ,
46
+ "stdout" : (* TestScript ).cmdStdout ,
47
+ "ttyin" : (* TestScript ).cmdTtyin ,
48
+ "ttyout" : (* TestScript ).cmdTtyout ,
49
+ "stop" : (* TestScript ).cmdStop ,
50
+ "symlink" : (* TestScript ).cmdSymlink ,
51
+ "unix2dos" : (* TestScript ).cmdUNIX2DOS ,
52
+ "unquote" : (* TestScript ).cmdUnquote ,
53
+ "wait" : (* TestScript ).cmdWait ,
54
+ "waitmatch" : (* TestScript ).cmdWaitMatch ,
53
55
}
54
56
55
57
// cd changes to a different directory.
@@ -237,14 +239,26 @@ func (ts *TestScript) cmdExec(neg bool, args []string) {
237
239
ts .Fatalf ("duplicate background process name %q" , bgName )
238
240
}
239
241
var cmd * exec.Cmd
240
- cmd , err = ts .execBackground (args [0 ], args [1 :len (args )- 1 ]... )
242
+ var outreader io.ReadCloser
243
+ cmd , outreader , err = ts .execBackground (args [0 ], args [1 :len (args )- 1 ]... )
241
244
if err == nil {
242
245
wait := make (chan struct {})
243
246
go func () {
244
247
waitOrStop (ts .ctxt , cmd , - 1 )
245
248
close (wait )
246
249
}()
247
- ts .background = append (ts .background , backgroundCmd {bgName , cmd , wait , neg })
250
+ outbuf := new (strings.Builder )
251
+ ts .background = append (ts .background , backgroundCmd {
252
+ name : bgName ,
253
+ cmd : cmd ,
254
+ stdoutBuffer : outbuf ,
255
+ stdoutReader : struct {
256
+ io.Reader
257
+ io.Closer
258
+ }{io .TeeReader (outreader , outbuf ), outreader },
259
+ waitc : wait ,
260
+ neg : neg ,
261
+ })
248
262
}
249
263
ts .stdout , ts .stderr = "" , ""
250
264
} else {
@@ -567,9 +581,7 @@ func (ts *TestScript) waitBackgroundOne(bgName string) {
567
581
if bg == nil {
568
582
ts .Fatalf ("unknown background process %q" , bgName )
569
583
}
570
- <- bg .wait
571
- ts .stdout = bg .cmd .Stdout .(* strings.Builder ).String ()
572
- ts .stderr = bg .cmd .Stderr .(* strings.Builder ).String ()
584
+ ts .stdout , ts .stderr = bg .wait ()
573
585
if ts .stdout != "" {
574
586
fmt .Fprintf (& ts .log , "[stdout]\n %s" , ts .stdout )
575
587
}
@@ -614,18 +626,15 @@ func (ts *TestScript) findBackground(bgName string) *backgroundCmd {
614
626
func (ts * TestScript ) waitBackground (checkStatus bool ) {
615
627
var stdouts , stderrs []string
616
628
for _ , bg := range ts .background {
617
- <- bg .wait
629
+ cmdStdout , cmdStderr := bg .wait ()
618
630
619
631
args := append ([]string {filepath .Base (bg .cmd .Args [0 ])}, bg .cmd .Args [1 :]... )
620
632
fmt .Fprintf (& ts .log , "[background] %s: %v\n " , strings .Join (args , " " ), bg .cmd .ProcessState )
621
633
622
- cmdStdout := bg .cmd .Stdout .(* strings.Builder ).String ()
623
634
if cmdStdout != "" {
624
635
fmt .Fprintf (& ts .log , "[stdout]\n %s" , cmdStdout )
625
636
stdouts = append (stdouts , cmdStdout )
626
637
}
627
-
628
- cmdStderr := bg .cmd .Stderr .(* strings.Builder ).String ()
629
638
if cmdStderr != "" {
630
639
fmt .Fprintf (& ts .log , "[stderr]\n %s" , cmdStderr )
631
640
stderrs = append (stderrs , cmdStderr )
@@ -652,6 +661,69 @@ func (ts *TestScript) waitBackground(checkStatus bool) {
652
661
ts .background = nil
653
662
}
654
663
664
+ // cmdWaitMatch waits until a background command prints a line to standard output
665
+ // which matches the given regular expression. Once a match is found, the given
666
+ // environment variable names are set to the subexpressions of the match.
667
+ func (ts * TestScript ) cmdWaitMatch (neg bool , args []string ) {
668
+ if len (args ) < 1 {
669
+ ts .Fatalf ("usage: waitmatch name regexp [env-var...]" )
670
+ }
671
+ if neg {
672
+ ts .Fatalf ("unsupported: ! waitmatch" )
673
+ }
674
+ bg := ts .findBackground (args [0 ])
675
+ if bg == nil {
676
+ ts .Fatalf ("unknown background process %q" , args [0 ])
677
+ }
678
+ rx , err := regexp .Compile (args [1 ])
679
+ ts .Check (err )
680
+ envs := args [2 :]
681
+ if n := rx .NumSubexp (); n < len (envs ) {
682
+ ts .Fatalf ("cannot extract %d subexpressions into %d env vars" , n , len (envs ))
683
+ }
684
+ for {
685
+ line , err := readLine (bg .stdoutReader )
686
+ ts .Check (err )
687
+ m := rx .FindSubmatch (line )
688
+ if m != nil {
689
+ subm := m [1 :]
690
+ for i , env := range envs {
691
+ ts .Setenv (env , string (subm [i ]))
692
+ }
693
+ }
694
+ if err == io .EOF {
695
+ if m == nil {
696
+ ts .Fatalf ("reached EOF without matching any line" )
697
+ }
698
+ return
699
+ } else {
700
+ ts .Check (err )
701
+ }
702
+ if m != nil {
703
+ break
704
+ }
705
+ }
706
+ }
707
+
708
+ // readLine consumes enough bytes to read a line.
709
+ func readLine (r io.Reader ) ([]byte , error ) {
710
+ var line []byte
711
+ for {
712
+ var buf [1 ]byte
713
+ n , err := r .Read (buf [:])
714
+ if n > 0 {
715
+ b := buf [0 ]
716
+ if b == '\n' {
717
+ return line , nil
718
+ }
719
+ line = append (line , b )
720
+ }
721
+ if err != nil {
722
+ return line , err
723
+ }
724
+ }
725
+ }
726
+
655
727
// scriptMatch implements both stdout and stderr.
656
728
func scriptMatch (ts * TestScript , neg bool , args []string , text , name string ) {
657
729
n := 0
0 commit comments