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