| /* |
| Copyright 2017 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package testingexec |
| |
| import ( |
| "context" |
| "fmt" |
| "io" |
| |
| "k8s.io/utils/exec" |
| ) |
| |
| // FakeExec is a simple scripted Interface type. |
| type FakeExec struct { |
| CommandScript []FakeCommandAction |
| CommandCalls int |
| LookPathFunc func(string) (string, error) |
| // ExactOrder enforces that commands are called in the order they are scripted, |
| // and with the exact same arguments |
| ExactOrder bool |
| // DisableScripts removes the requirement that a slice of FakeCommandAction be |
| // populated before calling Command(). This makes the fakeexec (and subsequent |
| // calls to Run() or CombinedOutput() always return success and there is no |
| // ability to set their output. |
| DisableScripts bool |
| } |
| |
| var _ exec.Interface = &FakeExec{} |
| |
| // FakeCommandAction is the function to be executed |
| type FakeCommandAction func(cmd string, args ...string) exec.Cmd |
| |
| // Command is to track the commands that are executed |
| func (fake *FakeExec) Command(cmd string, args ...string) exec.Cmd { |
| if fake.DisableScripts { |
| fakeCmd := &FakeCmd{DisableScripts: true} |
| return InitFakeCmd(fakeCmd, cmd, args...) |
| } |
| if fake.CommandCalls > len(fake.CommandScript)-1 { |
| panic(fmt.Sprintf("ran out of Command() actions. Could not handle command [%d]: %s args: %v", fake.CommandCalls, cmd, args)) |
| } |
| i := fake.CommandCalls |
| fake.CommandCalls++ |
| fakeCmd := fake.CommandScript[i](cmd, args...) |
| if fake.ExactOrder { |
| argv := append([]string{cmd}, args...) |
| fc := fakeCmd.(*FakeCmd) |
| if cmd != fc.Argv[0] { |
| panic(fmt.Sprintf("received command: %s, expected: %s", cmd, fc.Argv[0])) |
| } |
| if len(argv) != len(fc.Argv) { |
| panic(fmt.Sprintf("command (%s) received with extra/missing arguments. Expected %v, Received %v", cmd, fc.Argv, argv)) |
| } |
| for i, a := range argv[1:] { |
| if a != fc.Argv[i+1] { |
| panic(fmt.Sprintf("command (%s) called with unexpected argument. Expected %s, Received %s", cmd, fc.Argv[i+1], a)) |
| } |
| } |
| } |
| return fakeCmd |
| } |
| |
| // CommandContext wraps arguments into exec.Cmd |
| func (fake *FakeExec) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd { |
| return fake.Command(cmd, args...) |
| } |
| |
| // LookPath is for finding the path of a file |
| func (fake *FakeExec) LookPath(file string) (string, error) { |
| return fake.LookPathFunc(file) |
| } |
| |
| // FakeCmd is a simple scripted Cmd type. |
| type FakeCmd struct { |
| Argv []string |
| CombinedOutputScript []FakeAction |
| CombinedOutputCalls int |
| CombinedOutputLog [][]string |
| OutputScript []FakeAction |
| OutputCalls int |
| OutputLog [][]string |
| RunScript []FakeAction |
| RunCalls int |
| RunLog [][]string |
| Dirs []string |
| Stdin io.Reader |
| Stdout io.Writer |
| Stderr io.Writer |
| Env []string |
| StdoutPipeResponse FakeStdIOPipeResponse |
| StderrPipeResponse FakeStdIOPipeResponse |
| WaitResponse error |
| StartResponse error |
| DisableScripts bool |
| } |
| |
| var _ exec.Cmd = &FakeCmd{} |
| |
| // InitFakeCmd is for creating a fake exec.Cmd |
| func InitFakeCmd(fake *FakeCmd, cmd string, args ...string) exec.Cmd { |
| fake.Argv = append([]string{cmd}, args...) |
| return fake |
| } |
| |
| // FakeStdIOPipeResponse holds responses to use as fakes for the StdoutPipe and |
| // StderrPipe method calls |
| type FakeStdIOPipeResponse struct { |
| ReadCloser io.ReadCloser |
| Error error |
| } |
| |
| // FakeAction is a function type |
| type FakeAction func() ([]byte, []byte, error) |
| |
| // SetDir sets the directory |
| func (fake *FakeCmd) SetDir(dir string) { |
| fake.Dirs = append(fake.Dirs, dir) |
| } |
| |
| // SetStdin sets the stdin |
| func (fake *FakeCmd) SetStdin(in io.Reader) { |
| fake.Stdin = in |
| } |
| |
| // SetStdout sets the stdout |
| func (fake *FakeCmd) SetStdout(out io.Writer) { |
| fake.Stdout = out |
| } |
| |
| // SetStderr sets the stderr |
| func (fake *FakeCmd) SetStderr(out io.Writer) { |
| fake.Stderr = out |
| } |
| |
| // SetEnv sets the environment variables |
| func (fake *FakeCmd) SetEnv(env []string) { |
| fake.Env = env |
| } |
| |
| // StdoutPipe returns an injected ReadCloser & error (via StdoutPipeResponse) |
| // to be able to inject an output stream on Stdout |
| func (fake *FakeCmd) StdoutPipe() (io.ReadCloser, error) { |
| return fake.StdoutPipeResponse.ReadCloser, fake.StdoutPipeResponse.Error |
| } |
| |
| // StderrPipe returns an injected ReadCloser & error (via StderrPipeResponse) |
| // to be able to inject an output stream on Stderr |
| func (fake *FakeCmd) StderrPipe() (io.ReadCloser, error) { |
| return fake.StderrPipeResponse.ReadCloser, fake.StderrPipeResponse.Error |
| } |
| |
| // Start mimicks starting the process (in the background) and returns the |
| // injected StartResponse |
| func (fake *FakeCmd) Start() error { |
| return fake.StartResponse |
| } |
| |
| // Wait mimicks waiting for the process to exit returns the |
| // injected WaitResponse |
| func (fake *FakeCmd) Wait() error { |
| return fake.WaitResponse |
| } |
| |
| // Run runs the command |
| func (fake *FakeCmd) Run() error { |
| if fake.DisableScripts { |
| return nil |
| } |
| if fake.RunCalls > len(fake.RunScript)-1 { |
| panic("ran out of Run() actions") |
| } |
| if fake.RunLog == nil { |
| fake.RunLog = [][]string{} |
| } |
| i := fake.RunCalls |
| fake.RunLog = append(fake.RunLog, append([]string{}, fake.Argv...)) |
| fake.RunCalls++ |
| stdout, stderr, err := fake.RunScript[i]() |
| if stdout != nil { |
| fake.Stdout.Write(stdout) |
| } |
| if stderr != nil { |
| fake.Stderr.Write(stderr) |
| } |
| return err |
| } |
| |
| // CombinedOutput returns the output from the command |
| func (fake *FakeCmd) CombinedOutput() ([]byte, error) { |
| if fake.DisableScripts { |
| return []byte{}, nil |
| } |
| if fake.CombinedOutputCalls > len(fake.CombinedOutputScript)-1 { |
| panic("ran out of CombinedOutput() actions") |
| } |
| if fake.CombinedOutputLog == nil { |
| fake.CombinedOutputLog = [][]string{} |
| } |
| i := fake.CombinedOutputCalls |
| fake.CombinedOutputLog = append(fake.CombinedOutputLog, append([]string{}, fake.Argv...)) |
| fake.CombinedOutputCalls++ |
| stdout, _, err := fake.CombinedOutputScript[i]() |
| return stdout, err |
| } |
| |
| // Output is the response from the command |
| func (fake *FakeCmd) Output() ([]byte, error) { |
| if fake.DisableScripts { |
| return []byte{}, nil |
| } |
| if fake.OutputCalls > len(fake.OutputScript)-1 { |
| panic("ran out of Output() actions") |
| } |
| if fake.OutputLog == nil { |
| fake.OutputLog = [][]string{} |
| } |
| i := fake.OutputCalls |
| fake.OutputLog = append(fake.OutputLog, append([]string{}, fake.Argv...)) |
| fake.OutputCalls++ |
| stdout, _, err := fake.OutputScript[i]() |
| return stdout, err |
| } |
| |
| // Stop is to stop the process |
| func (fake *FakeCmd) Stop() { |
| // no-op |
| } |
| |
| // FakeExitError is a simple fake ExitError type. |
| type FakeExitError struct { |
| Status int |
| } |
| |
| var _ exec.ExitError = FakeExitError{} |
| |
| func (fake FakeExitError) String() string { |
| return fmt.Sprintf("exit %d", fake.Status) |
| } |
| |
| func (fake FakeExitError) Error() string { |
| return fake.String() |
| } |
| |
| // Exited always returns true |
| func (fake FakeExitError) Exited() bool { |
| return true |
| } |
| |
| // ExitStatus returns the fake status |
| func (fake FakeExitError) ExitStatus() int { |
| return fake.Status |
| } |