-
Notifications
You must be signed in to change notification settings - Fork 201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add useful test wrapper functions to FakeExec #14
Conversation
|
||
// SetCombinedOutput provides the result of calling CombinedOutput() on a FakeCmd. | ||
// output can be either a []byte or a string (which will be converted to a []byte). | ||
func (fcmd *FakeCmd) SetCombinedOutput(output interface{}, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you absolutely want output
to be []byte
, why don't you take a []byte
type instead? It's not difficult to convert string
to []byte
for the caller anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Literally every existing caller in kubernetes wants to pass a string rather than a []byte, so if you don't want it to accept both then I'd change the API to just take a string instead, and say people can use the lower-level API if they need to use []byte
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I just don't really like the interface{}
. string is fine.
} | ||
|
||
// AssertExpectedCommands ensures that all of the commands added to fake via AddCommand were actually executed | ||
func (fake *FakeExec) AssertExpectedCommands() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should probably be with other FakeExec
function rather than here (if we keep it, see below)
Also, I'm really not a big fan of embedding testing.T
in that class in the first place, and all the variables are public anyway, so why not leave that code inside the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Convenience. That's all any of this is about; making FakeExec more complicated so that test programs can be more simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, then it should probably panic instead, and the name could be better
} | ||
|
||
// SetRunOutput provides the result of calling Run() on a FakeCmd. | ||
// stdout and stderr can be either []byte or string (which will be converted to []byte). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as below, pass a []byte
if that's what you want
return fcmd | ||
} | ||
|
||
func outputAsBytes(output interface{}) []byte { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See below, remove this
// t.Fatal.) cmdsInPath is a list of command names for which LookPath will return a match | ||
// ("/fake-bin/commandname"); if your code does not use LookPath you can pass nil. (If you | ||
// need more complex behavior, you can override LookPathFunc on the returned FakeExec.) | ||
func NewFakeExec(t *testing.T, cmdsInPath []string) *FakeExec { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you need more complex behavior, you can override LookPathFunc on the returned FakeExec.
Why would I call this function in the first place if I want to change LookPathFunc
, considering that's pretty much the only added value of this function?
I think my point is: the value of this function is to provide a default LookPathFunc
. Can you define a sort of LookPathFunc
function somewhere that one could use (decouple creation of FakeExec
from LookPath
selection):
func MakeFakeBinLookPathFunc(cmdsInPath []string) func(cmd string) (string, error) {
return func(cmd string) (string, error) {
for _, pathCmd := range cmdsInPath {
if pathCmd == cmd {
return "/fake-bin/" + cmd, nil
}
}
return "", &osexec.Error{Name: cmd, Err: osexec.ErrNotFound}
}
}
func MyTest(t *testing.T) {
fexec := myFakeExec{LookPathFunc: MakeFakeBinLookPathFunc([]string{ ... })}
...
}
Edit: I'm bad at naming things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would I call this function in the first place if I want to change LookPathFunc, considering that's pretty much the only added value of this function?
I guess I like having New methods rather than manually constructing somebody-else's-struct-type by hand. I had added func NewFakeExec(t *testing.T) *FakeExec
before adding the cmdsInPath
arg.
But yes, I could do it the way you suggested instead.
} | ||
|
||
type FakeCommandAction func(cmd string, args ...string) exec.Cmd | ||
|
||
func (fake *FakeExec) Command(cmd string, args ...string) exec.Cmd { | ||
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)) | ||
fake.fail("ran out of Command() actions at %s executing %v", getCaller(1), append([]string{cmd}, args...)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we accumulate the errors in a Errors
list inside the FakeExec
instead? So that we don't depend on testing.T
and we don't have to panic, and the test can check Errors
however it wants to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 prefer leaving the "fail" and the error reporting to the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, but the Command still has to do something when the code being tested calls CombinedOutput()
/Run()
on it. So you could have it return an error to the caller, but then you're testing how your code responds when it gets an error that doesn't correspond to anything it could actually get in the real world.
If you hit this point (or any of the other panic/fail points), then there is a bug in your test case, not in the code that it's testing. So the only reasonable behavior is to abort the test case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't disagree with that, but then it should just probably just panic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(If using testing.T
is absolutely unacceptable, then we can go back to using panic
... I just preferred the t.Fatalf
solution because it doesn't spew a gigantic pile of stack traces at you like panic
does.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(fwiw, wrote that last comment before seeing your response)
|
||
// AddCommand adds a Cmd to be returned by a future fake.Command(...) call. Call | ||
// SetCombinedOutput or SetRunOutput on the result to specify its result. | ||
func (fake *FakeExec) AddCommand(cmd string, args ...string) *FakeCmd { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that this makes things easier, but I'd decouple the command creation a little bit.
Can we have:
// Hides the logic of appending commands
func (fake *FakeExec) AddCommand(cmd *FakeCmd) { ... }
And something else to create the command
// Hides the pain of creating commands
func NewMatchingArgsCommand(cmd string, args ...string) *FakeCmd {
return func(cmd string, args ...string) exec.Cmd {
// Some of the logic you have in your command
}
}
Then it means that:
- I can add command easily, but they don't have to follow your logic
- I can invent new types of commands
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again +1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to add a command in a different way, you can just continue to use the existing FakeExec API. There is currently only 1 test program in all of kubernetes that wants to do something other than what the API above supports. (pkg/volume/git_repo/git_repo_test.go
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but I think this would make it much easier to compose and re-use the code
Also, this is complicated enough that we may want to test it? |
Yes, although ironically, the code using |
;-) |
Issues go stale after 90d of inactivity. Prevent issues from auto-closing with an If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or |
fejta-bot is coming after PRs now! |
Resistance is futile. Your issues and PRs will be assimilated. Their historical and technological distinctiveness will be added to our org. |
Stale issues rot after 30d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
@danwinship is this still alive? |
Rotten issues close after 30d of inactivity. Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
Migrated from kubernetes/kubernetes#46537...
The fake implementation of pkg/util/exec for test programs is kind of really annoying to use. This PR adds some wrapper functions to make it easier, AND to automatically check that the passed-in command was what you expected (which some tests were doing but most weren't).
So:
becomes
There's some discussion on the original kubernetes bug (which I'm going to rebase on top of this to cover only the "porting pkg/util to the new interface" part). The June 27 comment about it not passing "make verify" no longer seems to be a problem with the migration of fakeexec into a separate subdirectory.