expect.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. "io"
  19. "os"
  20. "os/exec"
  21. "strings"
  22. "sync"
  23. "github.com/kr/pty"
  24. )
  25. type ExpectProcess struct {
  26. cmd *exec.Cmd
  27. fpty *os.File
  28. wg sync.WaitGroup
  29. ptyMu sync.Mutex // protects accessing fpty
  30. cond *sync.Cond // for broadcasting updates are avaiable
  31. mu sync.Mutex // protects lines and err
  32. lines []string
  33. err error
  34. }
  35. // NewExpect creates a new process for expect testing.
  36. func NewExpect(name string, arg ...string) (ep *ExpectProcess, err error) {
  37. ep = &ExpectProcess{cmd: exec.Command(name, arg...)}
  38. ep.cond = sync.NewCond(&ep.mu)
  39. ep.cmd.Stderr = ep.cmd.Stdout
  40. ep.cmd.Stdin = nil
  41. if ep.fpty, err = pty.Start(ep.cmd); err != nil {
  42. return nil, err
  43. }
  44. ep.wg.Add(1)
  45. go ep.read()
  46. return ep, nil
  47. }
  48. func (ep *ExpectProcess) read() {
  49. defer ep.wg.Done()
  50. r := bufio.NewReader(ep.fpty)
  51. for ep.err == nil {
  52. ep.ptyMu.Lock()
  53. l, rerr := r.ReadString('\n')
  54. ep.ptyMu.Unlock()
  55. ep.mu.Lock()
  56. ep.err = rerr
  57. if l != "" {
  58. ep.lines = append(ep.lines, l)
  59. if len(ep.lines) == 1 {
  60. ep.cond.Signal()
  61. }
  62. }
  63. ep.mu.Unlock()
  64. }
  65. ep.cond.Signal()
  66. }
  67. // Expect returns the first line containing the given string.
  68. func (ep *ExpectProcess) Expect(s string) (string, error) {
  69. ep.mu.Lock()
  70. for {
  71. for len(ep.lines) == 0 && ep.err == nil {
  72. ep.cond.Wait()
  73. }
  74. if len(ep.lines) == 0 {
  75. break
  76. }
  77. l := ep.lines[0]
  78. ep.lines = ep.lines[1:]
  79. if strings.Contains(l, s) {
  80. ep.mu.Unlock()
  81. return l, nil
  82. }
  83. }
  84. ep.mu.Unlock()
  85. return "", ep.err
  86. }
  87. // Close waits for the expect process to close
  88. func (ep *ExpectProcess) Close() error {
  89. if ep.cmd == nil {
  90. return nil
  91. }
  92. ep.cmd.Process.Kill()
  93. ep.ptyMu.Lock()
  94. ep.fpty.Close()
  95. ep.ptyMu.Unlock()
  96. err := ep.cmd.Wait()
  97. ep.wg.Wait()
  98. if err != nil && strings.Contains(err.Error(), "signal:") {
  99. // ignore signal errors; expected from pty
  100. err = nil
  101. }
  102. ep.cmd = nil
  103. return err
  104. }
  105. func (ep *ExpectProcess) Send(command string) error {
  106. _, err := io.WriteString(ep.fpty, command)
  107. return err
  108. }