fake_exec.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package testingexec
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "k8s.io/utils/exec"
  19. )
  20. // FakeExec is a simple scripted Interface type.
  21. type FakeExec struct {
  22. CommandScript []FakeCommandAction
  23. CommandCalls int
  24. LookPathFunc func(string) (string, error)
  25. // ExactOrder enforces that commands are called in the order they are scripted,
  26. // and with the exact same arguments
  27. ExactOrder bool
  28. // DisableScripts removes the requirement that a slice of FakeCommandAction be
  29. // propulated before calling Command(). This makes the fakeexec (and subsequent
  30. // calls to Run() or CombinedOutput() always return success and there is no
  31. // ability to set their output.
  32. DisableScripts bool
  33. }
  34. var _ exec.Interface = &FakeExec{}
  35. // FakeCommandAction is the function to be executed
  36. type FakeCommandAction func(cmd string, args ...string) exec.Cmd
  37. // Command is to track the commands that are executed
  38. func (fake *FakeExec) Command(cmd string, args ...string) exec.Cmd {
  39. if fake.DisableScripts {
  40. fakeCmd := &FakeCmd{DisableScripts: true}
  41. return InitFakeCmd(fakeCmd, cmd, args...)
  42. }
  43. if fake.CommandCalls > len(fake.CommandScript)-1 {
  44. panic(fmt.Sprintf("ran out of Command() actions. Could not handle command [%d]: %s args: %v", fake.CommandCalls, cmd, args))
  45. }
  46. i := fake.CommandCalls
  47. fake.CommandCalls++
  48. fakeCmd := fake.CommandScript[i](cmd, args...)
  49. if fake.ExactOrder {
  50. argv := append([]string{cmd}, args...)
  51. fc := fakeCmd.(*FakeCmd)
  52. if cmd != fc.Argv[0] {
  53. panic(fmt.Sprintf("received command: %s, expected: %s", cmd, fc.Argv[0]))
  54. }
  55. if len(argv) != len(fc.Argv) {
  56. panic(fmt.Sprintf("command (%s) received with extra/missing arguments. Expected %v, Received %v", cmd, fc.Argv, argv))
  57. }
  58. for i, a := range argv[1:] {
  59. if a != fc.Argv[i+1] {
  60. panic(fmt.Sprintf("command (%s) called with unexpected argument. Expected %s, Received %s", cmd, fc.Argv[i+1], a))
  61. }
  62. }
  63. }
  64. return fakeCmd
  65. }
  66. // CommandContext wraps arguments into exec.Cmd
  67. func (fake *FakeExec) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
  68. return fake.Command(cmd, args...)
  69. }
  70. // LookPath is for finding the path of a file
  71. func (fake *FakeExec) LookPath(file string) (string, error) {
  72. return fake.LookPathFunc(file)
  73. }
  74. // FakeCmd is a simple scripted Cmd type.
  75. type FakeCmd struct {
  76. Argv []string
  77. CombinedOutputScript []FakeAction
  78. CombinedOutputCalls int
  79. CombinedOutputLog [][]string
  80. OutputScript []FakeAction
  81. OutputCalls int
  82. OutputLog [][]string
  83. RunScript []FakeAction
  84. RunCalls int
  85. RunLog [][]string
  86. Dirs []string
  87. Stdin io.Reader
  88. Stdout io.Writer
  89. Stderr io.Writer
  90. Env []string
  91. StdoutPipeResponse FakeStdIOPipeResponse
  92. StderrPipeResponse FakeStdIOPipeResponse
  93. WaitResponse error
  94. StartResponse error
  95. DisableScripts bool
  96. }
  97. var _ exec.Cmd = &FakeCmd{}
  98. // InitFakeCmd is for creating a fake exec.Cmd
  99. func InitFakeCmd(fake *FakeCmd, cmd string, args ...string) exec.Cmd {
  100. fake.Argv = append([]string{cmd}, args...)
  101. return fake
  102. }
  103. // FakeStdIOPipeResponse holds responses to use as fakes for the StdoutPipe and
  104. // StderrPipe method calls
  105. type FakeStdIOPipeResponse struct {
  106. ReadCloser io.ReadCloser
  107. Error error
  108. }
  109. // FakeAction is a function type
  110. type FakeAction func() ([]byte, []byte, error)
  111. // SetDir sets the directory
  112. func (fake *FakeCmd) SetDir(dir string) {
  113. fake.Dirs = append(fake.Dirs, dir)
  114. }
  115. // SetStdin sets the stdin
  116. func (fake *FakeCmd) SetStdin(in io.Reader) {
  117. fake.Stdin = in
  118. }
  119. // SetStdout sets the stdout
  120. func (fake *FakeCmd) SetStdout(out io.Writer) {
  121. fake.Stdout = out
  122. }
  123. // SetStderr sets the stderr
  124. func (fake *FakeCmd) SetStderr(out io.Writer) {
  125. fake.Stderr = out
  126. }
  127. // SetEnv sets the environment variables
  128. func (fake *FakeCmd) SetEnv(env []string) {
  129. fake.Env = env
  130. }
  131. // StdoutPipe returns an injected ReadCloser & error (via StdoutPipeResponse)
  132. // to be able to inject an output stream on Stdout
  133. func (fake *FakeCmd) StdoutPipe() (io.ReadCloser, error) {
  134. return fake.StdoutPipeResponse.ReadCloser, fake.StdoutPipeResponse.Error
  135. }
  136. // StderrPipe returns an injected ReadCloser & error (via StderrPipeResponse)
  137. // to be able to inject an output stream on Stderr
  138. func (fake *FakeCmd) StderrPipe() (io.ReadCloser, error) {
  139. return fake.StderrPipeResponse.ReadCloser, fake.StderrPipeResponse.Error
  140. }
  141. // Start mimicks starting the process (in the background) and returns the
  142. // injected StartResponse
  143. func (fake *FakeCmd) Start() error {
  144. return fake.StartResponse
  145. }
  146. // Wait mimicks waiting for the process to exit returns the
  147. // injected WaitResponse
  148. func (fake *FakeCmd) Wait() error {
  149. return fake.WaitResponse
  150. }
  151. // Run runs the command
  152. func (fake *FakeCmd) Run() error {
  153. if fake.DisableScripts {
  154. return nil
  155. }
  156. if fake.RunCalls > len(fake.RunScript)-1 {
  157. panic("ran out of Run() actions")
  158. }
  159. if fake.RunLog == nil {
  160. fake.RunLog = [][]string{}
  161. }
  162. i := fake.RunCalls
  163. fake.RunLog = append(fake.RunLog, append([]string{}, fake.Argv...))
  164. fake.RunCalls++
  165. stdout, stderr, err := fake.RunScript[i]()
  166. if stdout != nil {
  167. fake.Stdout.Write(stdout)
  168. }
  169. if stderr != nil {
  170. fake.Stderr.Write(stderr)
  171. }
  172. return err
  173. }
  174. // CombinedOutput returns the output from the command
  175. func (fake *FakeCmd) CombinedOutput() ([]byte, error) {
  176. if fake.DisableScripts {
  177. return []byte{}, nil
  178. }
  179. if fake.CombinedOutputCalls > len(fake.CombinedOutputScript)-1 {
  180. panic("ran out of CombinedOutput() actions")
  181. }
  182. if fake.CombinedOutputLog == nil {
  183. fake.CombinedOutputLog = [][]string{}
  184. }
  185. i := fake.CombinedOutputCalls
  186. fake.CombinedOutputLog = append(fake.CombinedOutputLog, append([]string{}, fake.Argv...))
  187. fake.CombinedOutputCalls++
  188. stdout, _, err := fake.CombinedOutputScript[i]()
  189. return stdout, err
  190. }
  191. // Output is the response from the command
  192. func (fake *FakeCmd) Output() ([]byte, error) {
  193. if fake.DisableScripts {
  194. return []byte{}, nil
  195. }
  196. if fake.OutputCalls > len(fake.OutputScript)-1 {
  197. panic("ran out of Output() actions")
  198. }
  199. if fake.OutputLog == nil {
  200. fake.OutputLog = [][]string{}
  201. }
  202. i := fake.OutputCalls
  203. fake.OutputLog = append(fake.OutputLog, append([]string{}, fake.Argv...))
  204. fake.OutputCalls++
  205. stdout, _, err := fake.OutputScript[i]()
  206. return stdout, err
  207. }
  208. // Stop is to stop the process
  209. func (fake *FakeCmd) Stop() {
  210. // no-op
  211. }
  212. // FakeExitError is a simple fake ExitError type.
  213. type FakeExitError struct {
  214. Status int
  215. }
  216. var _ exec.ExitError = FakeExitError{}
  217. func (fake FakeExitError) String() string {
  218. return fmt.Sprintf("exit %d", fake.Status)
  219. }
  220. func (fake FakeExitError) Error() string {
  221. return fake.String()
  222. }
  223. // Exited always returns true
  224. func (fake FakeExitError) Exited() bool {
  225. return true
  226. }
  227. // ExitStatus returns the fake status
  228. func (fake FakeExitError) ExitStatus() int {
  229. return fake.Status
  230. }