1
2
3
4
5 package ssa_test
6
7 import (
8 "bytes"
9 "flag"
10 "fmt"
11 "internal/testenv"
12 "io"
13 "io/ioutil"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "regexp"
18 "runtime"
19 "strconv"
20 "strings"
21 "testing"
22 "time"
23 )
24
25 var (
26 update = flag.Bool("u", false, "update test reference files")
27 verbose = flag.Bool("v", false, "print debugger interactions (very verbose)")
28 dryrun = flag.Bool("n", false, "just print the command line and first debugging bits")
29 useGdb = flag.Bool("g", false, "use Gdb instead of Delve (dlv), use gdb reference files")
30 force = flag.Bool("f", false, "force run under not linux-amd64; also do not use tempdir")
31 repeats = flag.Bool("r", false, "detect repeats in debug steps and don't ignore them")
32 inlines = flag.Bool("i", false, "do inlining for gdb (makes testing flaky till inlining info is correct)")
33 )
34
35 var (
36 hexRe = regexp.MustCompile("0x[a-zA-Z0-9]+")
37 numRe = regexp.MustCompile("-?[0-9]+")
38 stringRe = regexp.MustCompile("\"([^\\\"]|(\\.))*\"")
39 leadingDollarNumberRe = regexp.MustCompile("^[$][0-9]+")
40 optOutGdbRe = regexp.MustCompile("[<]optimized out[>]")
41 numberColonRe = regexp.MustCompile("^ *[0-9]+:")
42 )
43
44 var gdb = "gdb"
45 var debugger = "dlv"
46
47 var gogcflags = os.Getenv("GO_GCFLAGS")
48
49
50 var optimizedLibs = (!strings.Contains(gogcflags, "-N") && !strings.Contains(gogcflags, "-l"))
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 func TestNexting(t *testing.T) {
98 testenv.SkipFlaky(t, 37404)
99
100 skipReasons := ""
101 if testing.Short() {
102 skipReasons = "not run in short mode; "
103 }
104 testenv.MustHaveGoBuild(t)
105
106 if *useGdb && !*force && !(runtime.GOOS == "linux" && runtime.GOARCH == "amd64") {
107
108
109
110
111
112
113 skipReasons += "not run when testing gdb (-g) unless forced (-f) or linux-amd64; "
114 }
115
116 if !*useGdb && !*force && testenv.Builder() == "linux-386-longtest" {
117
118
119 skipReasons += "not run when testing delve on linux-386-longtest builder unless forced (-f); "
120 }
121
122 if *useGdb {
123 debugger = "gdb"
124 _, err := exec.LookPath(gdb)
125 if err != nil {
126 if runtime.GOOS != "darwin" {
127 skipReasons += "not run because gdb not on path; "
128 } else {
129
130 _, err = exec.LookPath("ggdb")
131 if err != nil {
132 skipReasons += "not run because gdb (and also ggdb) request by -g option not on path; "
133 } else {
134 gdb = "ggdb"
135 }
136 }
137 }
138 } else {
139 debugger = "dlv"
140 _, err := exec.LookPath("dlv")
141 if err != nil {
142 skipReasons += "not run because dlv not on path; "
143 }
144 }
145
146 if skipReasons != "" {
147 t.Skip(skipReasons[:len(skipReasons)-2])
148 }
149
150 optFlags := ""
151 dbgFlags := "-N -l"
152 if *useGdb && !*inlines {
153
154
155 optFlags += " -l"
156 }
157
158 moreargs := []string{}
159 if *useGdb && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
160
161
162 moreargs = append(moreargs, "-ldflags=-compressdwarf=false")
163 }
164
165 subTest(t, debugger+"-dbg", "hist", dbgFlags, moreargs...)
166 subTest(t, debugger+"-dbg", "scopes", dbgFlags, moreargs...)
167 subTest(t, debugger+"-dbg", "i22558", dbgFlags, moreargs...)
168
169 subTest(t, debugger+"-dbg-race", "i22600", dbgFlags, append(moreargs, "-race")...)
170
171 optSubTest(t, debugger+"-opt", "hist", optFlags, 1000, moreargs...)
172 optSubTest(t, debugger+"-opt", "scopes", optFlags, 1000, moreargs...)
173
174
175
176
177 skipSubTest(t, debugger+"-opt", "infloop", optFlags, 10, moreargs...)
178
179 }
180
181
182
183 func subTest(t *testing.T, tag string, basename string, gcflags string, moreargs ...string) {
184 t.Run(tag+"-"+basename, func(t *testing.T) {
185 if t.Name() == "TestNexting/gdb-dbg-i22558" {
186 testenv.SkipFlaky(t, 31263)
187 }
188 testNexting(t, basename, tag, gcflags, 1000, moreargs...)
189 })
190 }
191
192
193 func skipSubTest(t *testing.T, tag string, basename string, gcflags string, count int, moreargs ...string) {
194 t.Run(tag+"-"+basename, func(t *testing.T) {
195 if *force {
196 testNexting(t, basename, tag, gcflags, count, moreargs...)
197 } else {
198 t.Skip("skipping flaky test becaused not forced (-f)")
199 }
200 })
201 }
202
203
204
205 func optSubTest(t *testing.T, tag string, basename string, gcflags string, count int, moreargs ...string) {
206
207
208 t.Run(tag+"-"+basename, func(t *testing.T) {
209 if *force || optimizedLibs {
210 testNexting(t, basename, tag, gcflags, count, moreargs...)
211 } else {
212 t.Skip("skipping for unoptimized stdlib/runtime")
213 }
214 })
215 }
216
217 func testNexting(t *testing.T, base, tag, gcflags string, count int, moreArgs ...string) {
218
219
220
221
222
223 testbase := filepath.Join("testdata", base) + "." + tag
224 tmpbase := filepath.Join("testdata", "test-"+base+"."+tag)
225
226
227 if !*force {
228 tmpdir, err := ioutil.TempDir("", "debug_test")
229 if err != nil {
230 panic(fmt.Sprintf("Problem creating TempDir, error %v\n", err))
231 }
232 tmpbase = filepath.Join(tmpdir, "test-"+base+"."+tag)
233 if *verbose {
234 fmt.Printf("Tempdir is %s\n", tmpdir)
235 }
236 defer os.RemoveAll(tmpdir)
237 }
238 exe := tmpbase
239
240 runGoArgs := []string{"build", "-o", exe, "-gcflags=all=" + gcflags}
241 runGoArgs = append(runGoArgs, moreArgs...)
242 runGoArgs = append(runGoArgs, filepath.Join("testdata", base+".go"))
243
244 runGo(t, "", runGoArgs...)
245
246 nextlog := testbase + ".nexts"
247 tmplog := tmpbase + ".nexts"
248 var dbg dbgr
249 if *useGdb {
250 dbg = newGdb(tag, exe)
251 } else {
252 dbg = newDelve(tag, exe)
253 }
254 h1 := runDbgr(dbg, count)
255 if *dryrun {
256 fmt.Printf("# Tag for above is %s\n", dbg.tag())
257 return
258 }
259 if *update {
260 h1.write(nextlog)
261 } else {
262 h0 := &nextHist{}
263 h0.read(nextlog)
264 if !h0.equals(h1) {
265
266 h1.write(tmplog)
267 cmd := exec.Command("diff", "-u", nextlog, tmplog)
268 line := asCommandLine("", cmd)
269 bytes, err := cmd.CombinedOutput()
270 if err != nil && len(bytes) == 0 {
271 t.Fatalf("step/next histories differ, diff command %s failed with error=%v", line, err)
272 }
273 t.Fatalf("step/next histories differ, diff=\n%s", string(bytes))
274 }
275 }
276 }
277
278 type dbgr interface {
279 start()
280 stepnext(s string) bool
281 quit()
282 hist() *nextHist
283 tag() string
284 }
285
286 func runDbgr(dbg dbgr, maxNext int) *nextHist {
287 dbg.start()
288 if *dryrun {
289 return nil
290 }
291 for i := 0; i < maxNext; i++ {
292 if !dbg.stepnext("n") {
293 break
294 }
295 }
296 dbg.quit()
297 h := dbg.hist()
298 return h
299 }
300
301 func runGo(t *testing.T, dir string, args ...string) string {
302 var stdout, stderr bytes.Buffer
303 cmd := exec.Command(testenv.GoToolPath(t), args...)
304 cmd.Dir = dir
305 if *dryrun {
306 fmt.Printf("%s\n", asCommandLine("", cmd))
307 return ""
308 }
309 cmd.Stdout = &stdout
310 cmd.Stderr = &stderr
311
312 if err := cmd.Run(); err != nil {
313 t.Fatalf("error running cmd (%s): %v\nstdout:\n%sstderr:\n%s\n", asCommandLine("", cmd), err, stdout.String(), stderr.String())
314 }
315
316 if s := stderr.String(); s != "" {
317 t.Fatalf("Stderr = %s\nWant empty", s)
318 }
319
320 return stdout.String()
321 }
322
323
324 type tstring struct {
325 o string
326 e string
327 }
328
329 func (t tstring) String() string {
330 return t.o + t.e
331 }
332
333 type pos struct {
334 line uint32
335 file uint8
336 }
337
338 type nextHist struct {
339 f2i map[string]uint8
340 fs []string
341 ps []pos
342 texts []string
343 vars [][]string
344 }
345
346 func (h *nextHist) write(filename string) {
347 file, err := os.Create(filename)
348 if err != nil {
349 panic(fmt.Sprintf("Problem opening %s, error %v\n", filename, err))
350 }
351 defer file.Close()
352 var lastfile uint8
353 for i, x := range h.texts {
354 p := h.ps[i]
355 if lastfile != p.file {
356 fmt.Fprintf(file, " %s\n", h.fs[p.file-1])
357 lastfile = p.file
358 }
359 fmt.Fprintf(file, "%d:%s\n", p.line, x)
360
361 for _, y := range h.vars[i] {
362 y = strings.TrimSpace(y)
363 fmt.Fprintf(file, "%s\n", y)
364 }
365 }
366 file.Close()
367 }
368
369 func (h *nextHist) read(filename string) {
370 h.f2i = make(map[string]uint8)
371 bytes, err := ioutil.ReadFile(filename)
372 if err != nil {
373 panic(fmt.Sprintf("Problem reading %s, error %v\n", filename, err))
374 }
375 var lastfile string
376 lines := strings.Split(string(bytes), "\n")
377 for i, l := range lines {
378 if len(l) > 0 && l[0] != '#' {
379 if l[0] == ' ' {
380
381 lastfile = strings.TrimSpace(l)
382 } else if numberColonRe.MatchString(l) {
383
384 colonPos := strings.Index(l, ":")
385 if colonPos == -1 {
386 panic(fmt.Sprintf("Line %d (%s) in file %s expected to contain '<number>:' but does not.\n", i+1, l, filename))
387 }
388 h.add(lastfile, l[0:colonPos], l[colonPos+1:])
389 } else {
390 h.addVar(l)
391 }
392 }
393 }
394 }
395
396
397
398
399
400 func (h *nextHist) add(file, line, text string) bool {
401
402 if !*inlines && !strings.Contains(file, "/testdata/") {
403 return false
404 }
405 fi := h.f2i[file]
406 if fi == 0 {
407 h.fs = append(h.fs, file)
408 fi = uint8(len(h.fs))
409 h.f2i[file] = fi
410 }
411
412 line = strings.TrimSpace(line)
413 var li int
414 var err error
415 if line != "" {
416 li, err = strconv.Atoi(line)
417 if err != nil {
418 panic(fmt.Sprintf("Non-numeric line: %s, error %v\n", line, err))
419 }
420 }
421 l := len(h.ps)
422 p := pos{line: uint32(li), file: fi}
423
424 if l == 0 || *repeats || h.ps[l-1] != p {
425 h.ps = append(h.ps, p)
426 h.texts = append(h.texts, text)
427 h.vars = append(h.vars, []string{})
428 return true
429 }
430 return false
431 }
432
433 func (h *nextHist) addVar(text string) {
434 l := len(h.texts)
435 h.vars[l-1] = append(h.vars[l-1], text)
436 }
437
438 func invertMapSU8(hf2i map[string]uint8) map[uint8]string {
439 hi2f := make(map[uint8]string)
440 for hs, i := range hf2i {
441 hi2f[i] = hs
442 }
443 return hi2f
444 }
445
446 func (h *nextHist) equals(k *nextHist) bool {
447 if len(h.f2i) != len(k.f2i) {
448 return false
449 }
450 if len(h.ps) != len(k.ps) {
451 return false
452 }
453 hi2f := invertMapSU8(h.f2i)
454 ki2f := invertMapSU8(k.f2i)
455
456 for i, hs := range hi2f {
457 if hs != ki2f[i] {
458 return false
459 }
460 }
461
462 for i, x := range h.ps {
463 if k.ps[i] != x {
464 return false
465 }
466 }
467
468 for i, hv := range h.vars {
469 kv := k.vars[i]
470 if len(hv) != len(kv) {
471 return false
472 }
473 for j, hvt := range hv {
474 if hvt != kv[j] {
475 return false
476 }
477 }
478 }
479
480 return true
481 }
482
483
484
485
486 func canonFileName(f string) string {
487 i := strings.Index(f, "/src/")
488 if i != -1 {
489 f = f[i+1:]
490 }
491 return f
492 }
493
494
495
496 type delveState struct {
497 cmd *exec.Cmd
498 tagg string
499 *ioState
500 atLineRe *regexp.Regexp
501 funcFileLinePCre *regexp.Regexp
502 line string
503 file string
504 function string
505 }
506
507 func newDelve(tag, executable string, args ...string) dbgr {
508 cmd := exec.Command("dlv", "exec", executable)
509 cmd.Env = replaceEnv(cmd.Env, "TERM", "dumb")
510 if len(args) > 0 {
511 cmd.Args = append(cmd.Args, "--")
512 cmd.Args = append(cmd.Args, args...)
513 }
514 s := &delveState{tagg: tag, cmd: cmd}
515
516
517 s.atLineRe = regexp.MustCompile("\n=>[[:space:]]+[0-9]+:(.*)")
518 s.funcFileLinePCre = regexp.MustCompile("> ([^ ]+) ([^:]+):([0-9]+) .*[(]PC: (0x[a-z0-9]+)[)]\n")
519 s.ioState = newIoState(s.cmd)
520 return s
521 }
522
523 func (s *delveState) tag() string {
524 return s.tagg
525 }
526
527 func (s *delveState) stepnext(ss string) bool {
528 x := s.ioState.writeReadExpect(ss+"\n", "[(]dlv[)] ")
529 excerpts := s.atLineRe.FindStringSubmatch(x.o)
530 locations := s.funcFileLinePCre.FindStringSubmatch(x.o)
531 excerpt := ""
532 if len(excerpts) > 1 {
533 excerpt = excerpts[1]
534 }
535 if len(locations) > 0 {
536 fn := canonFileName(locations[2])
537 if *verbose {
538 if s.file != fn {
539 fmt.Printf("%s\n", locations[2])
540 }
541 fmt.Printf(" %s\n", locations[3])
542 }
543 s.line = locations[3]
544 s.file = fn
545 s.function = locations[1]
546 s.ioState.history.add(s.file, s.line, excerpt)
547
548
549 return true
550 }
551 if *verbose {
552 fmt.Printf("DID NOT MATCH EXPECTED NEXT OUTPUT\nO='%s'\nE='%s'\n", x.o, x.e)
553 }
554 return false
555 }
556
557 func (s *delveState) start() {
558 if *dryrun {
559 fmt.Printf("%s\n", asCommandLine("", s.cmd))
560 fmt.Printf("b main.test\n")
561 fmt.Printf("c\n")
562 return
563 }
564 err := s.cmd.Start()
565 if err != nil {
566 line := asCommandLine("", s.cmd)
567 panic(fmt.Sprintf("There was an error [start] running '%s', %v\n", line, err))
568 }
569 s.ioState.readExpecting(-1, 5000, "Type 'help' for list of commands.")
570 s.ioState.writeReadExpect("b main.test\n", "[(]dlv[)] ")
571 s.stepnext("c")
572 }
573
574 func (s *delveState) quit() {
575 expect("", s.ioState.writeRead("q\n"))
576 }
577
578
579
580 type gdbState struct {
581 cmd *exec.Cmd
582 tagg string
583 args []string
584 *ioState
585 atLineRe *regexp.Regexp
586 funcFileLinePCre *regexp.Regexp
587 line string
588 file string
589 function string
590 }
591
592 func newGdb(tag, executable string, args ...string) dbgr {
593
594 cmd := exec.Command(gdb, "-nx",
595 "-iex", fmt.Sprintf("add-auto-load-safe-path %s/src/runtime", runtime.GOROOT()),
596 "-ex", "set startup-with-shell off", executable)
597 cmd.Env = replaceEnv(cmd.Env, "TERM", "dumb")
598 s := &gdbState{tagg: tag, cmd: cmd, args: args}
599 s.atLineRe = regexp.MustCompile("(^|\n)([0-9]+)(.*)")
600 s.funcFileLinePCre = regexp.MustCompile(
601 "([^ ]+) [(][^)]*[)][ \\t\\n]+at ([^:]+):([0-9]+)")
602
603
604
605 s.ioState = newIoState(s.cmd)
606 return s
607 }
608
609 func (s *gdbState) tag() string {
610 return s.tagg
611 }
612
613 func (s *gdbState) start() {
614 run := "run"
615 for _, a := range s.args {
616 run += " " + a
617 }
618 if *dryrun {
619 fmt.Printf("%s\n", asCommandLine("", s.cmd))
620 fmt.Printf("tbreak main.test\n")
621 fmt.Printf("%s\n", run)
622 return
623 }
624 err := s.cmd.Start()
625 if err != nil {
626 line := asCommandLine("", s.cmd)
627 panic(fmt.Sprintf("There was an error [start] running '%s', %v\n", line, err))
628 }
629 s.ioState.readSimpleExpecting("[(]gdb[)] ")
630 x := s.ioState.writeReadExpect("b main.test\n", "[(]gdb[)] ")
631 expect("Breakpoint [0-9]+ at", x)
632 s.stepnext(run)
633 }
634
635 func (s *gdbState) stepnext(ss string) bool {
636 x := s.ioState.writeReadExpect(ss+"\n", "[(]gdb[)] ")
637 excerpts := s.atLineRe.FindStringSubmatch(x.o)
638 locations := s.funcFileLinePCre.FindStringSubmatch(x.o)
639 excerpt := ""
640 addedLine := false
641 if len(excerpts) == 0 && len(locations) == 0 {
642 if *verbose {
643 fmt.Printf("DID NOT MATCH %s", x.o)
644 }
645 return false
646 }
647 if len(excerpts) > 0 {
648 excerpt = excerpts[3]
649 }
650 if len(locations) > 0 {
651 fn := canonFileName(locations[2])
652 if *verbose {
653 if s.file != fn {
654 fmt.Printf("%s\n", locations[2])
655 }
656 fmt.Printf(" %s\n", locations[3])
657 }
658 s.line = locations[3]
659 s.file = fn
660 s.function = locations[1]
661 addedLine = s.ioState.history.add(s.file, s.line, excerpt)
662 }
663 if len(excerpts) > 0 {
664 if *verbose {
665 fmt.Printf(" %s\n", excerpts[2])
666 }
667 s.line = excerpts[2]
668 addedLine = s.ioState.history.add(s.file, s.line, excerpt)
669 }
670
671 if !addedLine {
672
673 return true
674 }
675
676 vars := varsToPrint(excerpt, "//"+s.tag()+"=(")
677 for _, v := range vars {
678 response := printVariableAndNormalize(v, func(v string) string {
679 return s.ioState.writeReadExpect("p "+v+"\n", "[(]gdb[)] ").String()
680 })
681 s.ioState.history.addVar(response)
682 }
683 return true
684 }
685
686
687
688
689 func printVariableAndNormalize(v string, printer func(v string) string) string {
690 slashIndex := strings.Index(v, "/")
691 substitutions := ""
692 if slashIndex != -1 {
693 substitutions = v[slashIndex:]
694 v = v[:slashIndex]
695 }
696 response := printer(v)
697
698 dollar := strings.Index(response, "$")
699 cr := strings.Index(response, "\n")
700
701 if dollar == -1 {
702 if cr == -1 {
703 response = strings.TrimSpace(response)
704 response = strings.Replace(response, "\n", "<BR>", -1)
705 return "$ Malformed response " + response
706 }
707 response = strings.TrimSpace(response[:cr])
708 return "$ " + response
709 }
710 if cr == -1 {
711 cr = len(response)
712 }
713
714
715 response = strings.TrimSpace(response[dollar:cr])
716 response = leadingDollarNumberRe.ReplaceAllString(response, v)
717
718
719 if strings.Contains(substitutions, "A") {
720 response = hexRe.ReplaceAllString(response, "<A>")
721 }
722 if strings.Contains(substitutions, "N") {
723 response = numRe.ReplaceAllString(response, "<N>")
724 }
725 if strings.Contains(substitutions, "S") {
726 response = stringRe.ReplaceAllString(response, "<S>")
727 }
728 if strings.Contains(substitutions, "O") {
729 response = optOutGdbRe.ReplaceAllString(response, "<Optimized out, as expected>")
730 }
731 return response
732 }
733
734
735
736
737
738 func varsToPrint(line, lookfor string) []string {
739 var vars []string
740 if strings.Contains(line, lookfor) {
741 x := line[strings.Index(line, lookfor)+len(lookfor):]
742 end := strings.Index(x, ")")
743 if end == -1 {
744 panic(fmt.Sprintf("Saw variable list begin %s in %s but no closing ')'", lookfor, line))
745 }
746 vars = strings.Split(x[:end], ",")
747 for i, y := range vars {
748 vars[i] = strings.TrimSpace(y)
749 }
750 }
751 return vars
752 }
753
754 func (s *gdbState) quit() {
755 response := s.ioState.writeRead("q\n")
756 if strings.Contains(response.o, "Quit anyway? (y or n)") {
757 defer func() {
758 if r := recover(); r != nil {
759 if s, ok := r.(string); !(ok && strings.Contains(s, "'Y\n'")) {
760
761 fmt.Printf("Expected a broken pipe panic, but saw the following panic instead")
762 panic(r)
763 }
764 }
765 }()
766 s.ioState.writeRead("Y\n")
767 }
768 }
769
770 type ioState struct {
771 stdout io.ReadCloser
772 stderr io.ReadCloser
773 stdin io.WriteCloser
774 outChan chan string
775 errChan chan string
776 last tstring
777 history *nextHist
778 }
779
780 func newIoState(cmd *exec.Cmd) *ioState {
781 var err error
782 s := &ioState{}
783 s.history = &nextHist{}
784 s.history.f2i = make(map[string]uint8)
785 s.stdout, err = cmd.StdoutPipe()
786 line := asCommandLine("", cmd)
787 if err != nil {
788 panic(fmt.Sprintf("There was an error [stdoutpipe] running '%s', %v\n", line, err))
789 }
790 s.stderr, err = cmd.StderrPipe()
791 if err != nil {
792 panic(fmt.Sprintf("There was an error [stdouterr] running '%s', %v\n", line, err))
793 }
794 s.stdin, err = cmd.StdinPipe()
795 if err != nil {
796 panic(fmt.Sprintf("There was an error [stdinpipe] running '%s', %v\n", line, err))
797 }
798
799 s.outChan = make(chan string, 1)
800 s.errChan = make(chan string, 1)
801 go func() {
802 buffer := make([]byte, 4096)
803 for {
804 n, err := s.stdout.Read(buffer)
805 if n > 0 {
806 s.outChan <- string(buffer[0:n])
807 }
808 if err == io.EOF || n == 0 {
809 break
810 }
811 if err != nil {
812 fmt.Printf("Saw an error forwarding stdout")
813 break
814 }
815 }
816 close(s.outChan)
817 s.stdout.Close()
818 }()
819
820 go func() {
821 buffer := make([]byte, 4096)
822 for {
823 n, err := s.stderr.Read(buffer)
824 if n > 0 {
825 s.errChan <- string(buffer[0:n])
826 }
827 if err == io.EOF || n == 0 {
828 break
829 }
830 if err != nil {
831 fmt.Printf("Saw an error forwarding stderr")
832 break
833 }
834 }
835 close(s.errChan)
836 s.stderr.Close()
837 }()
838 return s
839 }
840
841 func (s *ioState) hist() *nextHist {
842 return s.history
843 }
844
845
846
847 func (s *ioState) writeRead(ss string) tstring {
848 if *verbose {
849 fmt.Printf("=> %s", ss)
850 }
851 _, err := io.WriteString(s.stdin, ss)
852 if err != nil {
853 panic(fmt.Sprintf("There was an error writing '%s', %v\n", ss, err))
854 }
855 return s.readExpecting(-1, 500, "")
856 }
857
858
859
860 func (s *ioState) writeReadExpect(ss, expectRE string) tstring {
861 if *verbose {
862 fmt.Printf("=> %s", ss)
863 }
864 if expectRE == "" {
865 panic("expectRE should not be empty; use .* instead")
866 }
867 _, err := io.WriteString(s.stdin, ss)
868 if err != nil {
869 panic(fmt.Sprintf("There was an error writing '%s', %v\n", ss, err))
870 }
871 return s.readSimpleExpecting(expectRE)
872 }
873
874 func (s *ioState) readExpecting(millis, interlineTimeout int, expectedRE string) tstring {
875 timeout := time.Millisecond * time.Duration(millis)
876 interline := time.Millisecond * time.Duration(interlineTimeout)
877 s.last = tstring{}
878 var re *regexp.Regexp
879 if expectedRE != "" {
880 re = regexp.MustCompile(expectedRE)
881 }
882 loop:
883 for {
884 var timer <-chan time.Time
885 if timeout > 0 {
886 timer = time.After(timeout)
887 }
888 select {
889 case x, ok := <-s.outChan:
890 if !ok {
891 s.outChan = nil
892 }
893 s.last.o += x
894 case x, ok := <-s.errChan:
895 if !ok {
896 s.errChan = nil
897 }
898 s.last.e += x
899 case <-timer:
900 break loop
901 }
902 if re != nil {
903 if re.MatchString(s.last.o) {
904 break
905 }
906 if re.MatchString(s.last.e) {
907 break
908 }
909 }
910 timeout = interline
911 }
912 if *verbose {
913 fmt.Printf("<= %s%s", s.last.o, s.last.e)
914 }
915 return s.last
916 }
917
918 func (s *ioState) readSimpleExpecting(expectedRE string) tstring {
919 s.last = tstring{}
920 var re *regexp.Regexp
921 if expectedRE != "" {
922 re = regexp.MustCompile(expectedRE)
923 }
924 for {
925 select {
926 case x, ok := <-s.outChan:
927 if !ok {
928 s.outChan = nil
929 }
930 s.last.o += x
931 case x, ok := <-s.errChan:
932 if !ok {
933 s.errChan = nil
934 }
935 s.last.e += x
936 }
937 if re != nil {
938 if re.MatchString(s.last.o) {
939 break
940 }
941 if re.MatchString(s.last.e) {
942 break
943 }
944 }
945 }
946 if *verbose {
947 fmt.Printf("<= %s%s", s.last.o, s.last.e)
948 }
949 return s.last
950 }
951
952
953
954 func replaceEnv(env []string, ev string, evv string) []string {
955 if env == nil {
956 env = os.Environ()
957 }
958 evplus := ev + "="
959 var found bool
960 for i, v := range env {
961 if strings.HasPrefix(v, evplus) {
962 found = true
963 env[i] = evplus + evv
964 }
965 }
966 if !found {
967 env = append(env, evplus+evv)
968 }
969 return env
970 }
971
972
973
974 func asCommandLine(cwd string, cmd *exec.Cmd) string {
975 s := "("
976 if cmd.Dir != "" && cmd.Dir != cwd {
977 s += "cd" + escape(cmd.Dir) + ";"
978 }
979 for _, e := range cmd.Env {
980 if !strings.HasPrefix(e, "PATH=") &&
981 !strings.HasPrefix(e, "HOME=") &&
982 !strings.HasPrefix(e, "USER=") &&
983 !strings.HasPrefix(e, "SHELL=") {
984 s += escape(e)
985 }
986 }
987 for _, a := range cmd.Args {
988 s += escape(a)
989 }
990 s += " )"
991 return s
992 }
993
994
995 func escape(s string) string {
996 s = strings.Replace(s, "\\", "\\\\", -1)
997 s = strings.Replace(s, "'", "\\'", -1)
998
999 if strings.ContainsAny(s, "\\ ;#*&$~?!|[]()<>{}`") {
1000 s = " '" + s + "'"
1001 } else {
1002 s = " " + s
1003 }
1004 return s
1005 }
1006
1007 func expect(want string, got tstring) {
1008 if want != "" {
1009 match, err := regexp.MatchString(want, got.o)
1010 if err != nil {
1011 panic(fmt.Sprintf("Error for regexp %s, %v\n", want, err))
1012 }
1013 if match {
1014 return
1015 }
1016
1017 match, _ = regexp.MatchString(want, got.e)
1018 if match {
1019 return
1020 }
1021 fmt.Printf("EXPECTED '%s'\n GOT O='%s'\nAND E='%s'\n", want, got.o, got.e)
1022 }
1023 }
1024
View as plain text