expect.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // Copyright 2016 CoreOS, Inc.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package expect implements a small expect-style interface
  15. package expect
  16. import (
  17. "bufio"
  18. "os"
  19. "os/exec"
  20. "strings"
  21. "sync"
  22. "github.com/kr/pty"
  23. )
  24. type ExpectProcess struct {
  25. cmd *exec.Cmd
  26. fpty *os.File
  27. wg sync.WaitGroup
  28. ptyMu sync.Mutex // protects accessing fpty
  29. cond *sync.Cond // for broadcasting updates are avaiable
  30. mu sync.Mutex // protects lines and err
  31. lines []string
  32. err error
  33. }
  34. // NewExpect creates a new process for expect testing.
  35. func NewExpect(name string, arg ...string) (ep *ExpectProcess, err error) {
  36. ep = &ExpectProcess{cmd: exec.Command(name, arg...)}
  37. ep.cond = sync.NewCond(&ep.mu)
  38. ep.cmd.Stderr = ep.cmd.Stdout
  39. ep.cmd.Stdin = nil
  40. if ep.fpty, err = pty.Start(ep.cmd); err != nil {
  41. return nil, err
  42. }
  43. ep.wg.Add(1)
  44. go ep.read()
  45. return ep, nil
  46. }
  47. func (ep *ExpectProcess) read() {
  48. defer ep.wg.Done()
  49. r := bufio.NewReader(ep.fpty)
  50. for ep.err == nil {
  51. ep.ptyMu.Lock()
  52. l, rerr := r.ReadString('\n')
  53. ep.ptyMu.Unlock()
  54. ep.mu.Lock()
  55. ep.err = rerr
  56. if l != "" {
  57. ep.lines = append(ep.lines, l)
  58. if len(ep.lines) == 1 {
  59. ep.cond.Signal()
  60. }
  61. }
  62. ep.mu.Unlock()
  63. }
  64. ep.cond.Signal()
  65. }
  66. // Expect returns the first line containing the given string.
  67. func (ep *ExpectProcess) Expect(s string) (string, error) {
  68. ep.mu.Lock()
  69. for {
  70. for len(ep.lines) == 0 && ep.err == nil {
  71. ep.cond.Wait()
  72. }
  73. if len(ep.lines) == 0 {
  74. break
  75. }
  76. l := ep.lines[0]
  77. ep.lines = ep.lines[1:]
  78. if strings.Contains(l, s) {
  79. ep.mu.Unlock()
  80. return l, nil
  81. }
  82. }
  83. ep.mu.Unlock()
  84. return "", ep.err
  85. }
  86. // Close waits for the expect process to close
  87. func (ep *ExpectProcess) Close() error {
  88. if ep.cmd == nil {
  89. return nil
  90. }
  91. ep.cmd.Process.Kill()
  92. ep.ptyMu.Lock()
  93. ep.fpty.Close()
  94. ep.ptyMu.Unlock()
  95. err := ep.cmd.Wait()
  96. ep.wg.Wait()
  97. if err != nil && strings.Contains(err.Error(), "signal:") {
  98. // ignore signal errors; expected from pty
  99. err = nil
  100. }
  101. ep.cmd = nil
  102. return err
  103. }