tests: migrate assertions to be more modern#48915
Conversation
|
I'll work on the |
|
Looks like potentially flaky test; I see this one was mentioned in #48609 as being flaky (and possibly to be fixed with that one?) cc @robmry FYI |
thaJeztah
left a comment
There was a problem hiding this comment.
Thanks for working on this! I think I had some branches stashed with some of these changes, but never got to complete them 🤗
changes so far LGTM (assuming CI passes)
I see you left some TODO's, notably;
Fix all of the assertions that have help messages
I'd be fine merging this one as-is, and work on remaining ones as follow-up(s); perhaps some of those changes (if we can't come with a "smart" solution to find/replace) would be just a matter of some elbow-grease to go through them? (should be something we'd only have to do once to bring it up to speed).
| assert.Assert(c, strings.Contains(strings.TrimSpace(out), id)) | ||
| assert.Assert(c, is.Contains(strings.TrimSpace(out), id)) |
There was a problem hiding this comment.
Not necessarily for this PR, unless you need to make more changes; looks like these strings.Trimspace() calls here (and couple of other places below?) are redundant, as we're checking if it contains id
| @@ -2282,7 +2283,7 @@ func (s *DockerCLIRunSuite) TestRunAllowPortRangeThroughExpose(c *testing.T) { | |||
| func (s *DockerCLIRunSuite) TestRunExposePort(c *testing.T) { | |||
| out, _, err := dockerCmdWithError("run", "--expose", "80000", "busybox") | |||
| assert.Assert(c, err != nil, "--expose with an invalid port should error out") | |||
There was a problem hiding this comment.
Note to self: these blanket "any error should do" cases keep catching my eye. I may have a go at finding these to see if we can be more specific (admitted, the output check below should probably prevent catching random unrelated errors, but still)
There was a problem hiding this comment.
Yeah these checks really should be ErrorsIs or at the very least ErrorContains but unfortunately that migration can't be easily automated. Luckily there's fewer than 100 of them, so we could migrate them in one patch:
% git grep "assert.\(Assert\|Check\)(.*err.* != nil" | wc -l
87
| assert.Assert(c, strings.Contains(result.Stdout(), "foo=abc")) | ||
| assert.Assert(c, is.Contains(result.Stdout(), "foo=abc")) | ||
| result = cli.DockerCmd(c, "run", "--rm", imgName, "cat", "/out") | ||
| assert.Assert(c, !strings.Contains(result.Stdout(), "foo")) |
There was a problem hiding this comment.
Still looking for more elegant solutions to these (negative matches); we could probably consider putting result.Stdout() as part of a custom failure message to help (definitely not needed for this PR though)
edit: ah; I see you mentioned this in your PR description as well
Fix inverse comparisons. integration-cli/checker doesn't help with this entirely
There was a problem hiding this comment.
Yeah, the trick is that we need to create a custom type that wraps assert.BoolOrComparison which then inverts Success as well as providing a wrapped FormatMessage that describes that the comparison was inverted. I suspect the wrapped description is going to be quite janky (it would probably look like the suggestion that the author of gotest.tools proposed in gotestyourself/gotest.tools#147 (comment)). Unfortunately, FormatMessage is technically internal to gotest.tools so it could break in the future (but the author suggested doing it this way, so I'm not sure).
Since it seems you'd be happy with a patch like that, I can try to cook one up.
There was a problem hiding this comment.
Yeah, a custom helper could work still, but perhaps we could still look if we could contribute a patch to gotest.tools that would be acceptable to their maintainers 🤔
For the formatting, we could still go the manual way;
assert.Assert(c, !strings.Contains(result.Stdout(), "foo"), "Expected: %q to contain %q", result.Stdout(), "foo")Which (if all of these are in the same (integration-cli) package could be a local helper I guess;
func assertNotContains(t testing.T, val, expected string) {
t.Helper()
assert.Assert(t, !strings.Contains(val, expected), "Expected %q not to contain %q", val, expected)
}There was a problem hiding this comment.
That would be the simplest option if we want to avoid over-complicating things. I'll go with that version now and we can do the more complicated version in the future...
|
@cyphar let me know if you want to add more changes to this PR before merging, or if you're good with bring the current set of changes in already. I think the current set of changes are already an improvement, so (from my perspective) the remaining ones are not a blocker for bringing those in already. |
|
I'd like to fix the |
|
A proper |
140632d to
1968727
Compare
Migrated using
find . -type f -name "*_test.go" |
xargs gofmt -w \
-r "assert.Check(t, strings.Contains(a, b)) -> assert.Check(t, is.Contains(a, b))"
find . -type f -name "*_test.go" |
xargs gofmt -w \
-r "assert.Assert(t, strings.Contains(a, b)) -> assert.Assert(t, is.Contains(a, b))"
Using a boolean in assert.Assert or assert.Check results in error
messages that don't contain the actual problematic string, and when
running the integration suite on an actual machine (where the source
code parsing doesn't work) this makes it almost impossible to figure out
what the actual error is.
Signed-off-by: Aleksa Sarai <[email protected]>
Unfortunately, gofmt doesn't know about types so it was necessary to
find all of the err == nil statements through trial and error. Note that
there is no is.NilError, so for assert.Check(t, err == nil) we need to
switch to just doing assert.Check(t, err). If err is an error type, this
is equivalent (and there isn't another trivial way of doing it). Here
are the full set of rules used:
Generic "err == nil":
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, err == nil) -> assert.NilError(t, err)"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, err == nil) -> assert.Check(t, err)"
Generic, but with a different variable name:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, sr.err == nil) -> assert.NilError(t, sr.err)"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, sr.err == nil) -> assert.Check(t, sr.err)"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, err2 == nil) -> assert.NilError(t, err2)"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, err2 == nil) -> assert.Check(t, err2)"
JSON-related error assertions:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, json.Unmarshal(a, b) == nil) -> assert.NilError(t, json.Unmarshal(a, b))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, json.Unmarshal(a, b) == nil) -> assert.Check(t, json.Unmarshal(a, b))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, json.NewDecoder(a).Decode(b) == nil) -> assert.NilError(t, json.NewDecoder(a).Decode(b))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, json.NewDecoder(a).Decode(b) == nil) -> assert.Check(t, json.NewDecoder(a).Decode(b))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, json.NewEncoder(a).Encode(b) == nil) -> assert.NilError(t, json.NewEncoder(a).Encode(b))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, json.NewEncoder(a).Encode(b) == nil) -> assert.Check(t, json.NewEncoder(a).Encode(b))"
Process-related error assertions:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, a.Start() == nil) -> assert.NilError(t, a.Start())"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, a.Start() == nil) -> assert.Check(t, a.Start())"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, a.Kill() == nil) -> assert.NilError(t, a.Kill())"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, a.Kill() == nil) -> assert.Check(t, a.Kill())"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, a.Signal(b) == nil) -> assert.NilError(t, a.Signal(b))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, a.Signal(b) == nil) -> assert.Check(t, a.Signal(b))"
waitInspect:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, waitInspect(a, b, c, d) == nil) -> assert.NilError(t, waitInspect(a, b, c, d))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, waitInspect(a, b, c, d) == nil) -> assert.Check(t, waitInspect(a, b, c, d))"
File closing error assertions:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, a.Close() == nil) -> assert.NilError(t, a.Close())"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, a.Close() == nil) -> assert.Check(t, a.Close())"
mount.MakeRShared:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, mount.MakeRShared(a) == nil) -> assert.NilError(t, mount.MakeRShared(a))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, mount.MakeRShared(a) == nil) -> assert.Check(t, mount.MakeRShared(a))"
daemon.SwarmLeave:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, d.SwarmLeave(a, b, c) == nil) -> assert.NilError(t, d.SwarmLeave(a, b, c))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, d.SwarmLeave(a, b, c) == nil) -> assert.Check(t, d.SwarmLeave(a, b, c))"
os.MkdirAll:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, os.MkdirAll(a, b) == nil) -> assert.NilError(t, os.MkdirAll(a, b))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, os.MkdirAll(a, b) == nil) -> assert.Check(t, os.MkdirAll(a, b))"
Signed-off-by: Aleksa Sarai <[email protected]>
If a values is non-nil when we don't expect it, it would be quite
helpful to get an error message explaining what happened.
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Assert(t, a == nil) -> assert.Assert(t, is.Nil(a))"
find . -type f -name "*_test.go" | \
xargs gofmt -w -r "assert.Check(t, a == nil) -> assert.Check(t, is.Nil(a))"
Signed-off-by: Aleksa Sarai <[email protected]>
If we have an error type that we're checking a substring against, we
should really be checking using ErrorContains to indicate the right
semantics to assert.
Mostly done using these transforms:
find . -type f -name "*_test.go" | \
xargs gofmt -w -r 'assert.Assert(t, is.ErrorContains(e, s)) -> assert.ErrorContains(t, e, s)'
find . -type f -name "*_test.go" | \
xargs gofmt -w -r 'assert.Assert(t, is.Contains(err.Error(), s)) -> assert.ErrorContains(t, err, s)'
find . -type f -name "*_test.go" | \
xargs gofmt -w -r 'assert.Check(t, is.Contains(err.Error(), s)) -> assert.Check(t, is.ErrorContains(err, s))'
As well as some small fixups to helpers that were doing
strings.Contains explicitly.
Signed-off-by: Aleksa Sarai <[email protected]>
There were a handful of direct checks against errors.Is that can be translated to assert.ErrorIs without too much thought. Unfortunately there are a load of other examples where ErrorIs probably makes sense but would require testing whether this subtly breaks the test. These transformations were done by hand. Signed-off-by: Aleksa Sarai <[email protected]>
1968727 to
557e4ed
Compare
|
@thaJeztah I think this should be okay for now. |
| } else { | ||
| _, err := os.Stat(filepath.Join(r, "sub/subfile")) | ||
| assert.Assert(t, is.ErrorContains(err, "")) | ||
| assert.ErrorContains(t, err, "") |
There was a problem hiding this comment.
For others; I was wondering if there were some of these we kept in the assert.Assert(.. is...) form if they were considered for "this could be a assert.Check instead to get more assertions on failure, but I don't think that's a strong motivator to keep that format.
|
Once I finish doing the rest of the mitrations, it might make sense to add these as CI lints (because most of these are automated we can even provide a diff). That depends on whether I can get |
Quite a lot of tests (especially in
integration-cli) use boolean assertions which don't provide any useful information about why a comparison failed. This is already kind of annoying when debugging a failing test, but when running the integration suite on an actual machine (where the source code parsing doesn't work) this makes it almost impossible to figure out what the actual error is.A lot of this is due to limitations of
gotest.tools(see gotestyourself/gotest.tools#147 for instance) but we can work around most of these issues.NOTE: I've only tackled the "easy" cases for now. All of the transformations were done using
gofmt -rorgolang.org/x/tools/cmd/eg, which should make it easier to verify that the tests weren't subtly broken by these changes.Future work:
gofmtcannot handle these automatically, and we would need to rungofmtfor every number of arguments used. We could usesedbut this could lead to bugs, andegseems to be able to do this but it is incredibly poorly documented.assert.Assert(t, errors.Is(...))toassert.ErrorIsoris.ErrorIs.assert.Assert(t, is.Contains(err.Error(), ...)toassert.ErrorContains.integration-cli/checkerdoesn't help with this entirely (see Issues implementing a "Not" comparison gotestyourself/gotest.tools#147) -- we will instead need to create a newcmpextpackage that will let us create a generic inverter that has useful error messages. This will then let us tackle these issues:assert.Assert(t, a != nil)assert.Assert(t, !strings.Contains(...))