From 2d9159821a4a5c148d1ac1bc7c418a181c272236 Mon Sep 17 00:00:00 2001 From: Ed Santiago Date: Thu, 4 Apr 2024 08:26:03 -0600 Subject: [PATCH] e2e: redefine ExitWithError() to require exit code ...and an optional error-message string, to be checked against stderr. This is a starting point and baby-steps progress toward #18188. There are 249 ExitWithError() checks in test/e2e. It will take weeks to fix them all. This commit enables new functionality: Expect(ExitWithError(125, "expected substring")) ...while also allowing the current empty-args form. Once all 249 empty-args uses are modernized, the matcher code will be cleaned up. I expect it will take several months of light effort to get all e2e tests transitioned to the new form. I am choosing to do so in pieces, for (relative) ease of review. This PR: 1) makes the initial changes described above; and 2) updates a small subset of e2e _test.go files such that: a) ExitWithError() is given an exit code and error string; and b) Exit(Nonzero) is changed to ExitWithError(Nonzero, "string") (when possible) Signed-off-by: Ed Santiago --- test/README.md | 2 +- test/e2e/attach_test.go | 9 ++-- test/e2e/checkpoint_image_test.go | 3 +- test/e2e/checkpoint_test.go | 45 ++++++---------- test/e2e/commit_test.go | 8 ++- test/e2e/containers_conf_test.go | 18 +++---- test/e2e/cp_test.go | 2 +- test/e2e/create_staticip_test.go | 15 ++---- test/e2e/create_test.go | 66 ++++++++++------------- test/e2e/exec_test.go | 46 ++++++++++------ test/e2e/export_test.go | 2 +- test/e2e/farm_test.go | 8 +-- test/e2e/generate_kube_test.go | 27 +++++----- test/e2e/generate_spec_test.go | 2 +- test/e2e/generate_systemd_test.go | 19 ++----- test/e2e/healthcheck_run_test.go | 10 ++-- test/utils/matchers.go | 87 ++++++++++++++++++++++--------- 17 files changed, 183 insertions(+), 186 deletions(-) diff --git a/test/README.md b/test/README.md index 98b447e968..9bc8406d78 100644 --- a/test/README.md +++ b/test/README.md @@ -110,7 +110,7 @@ file itself. Consider the following actual test: It("podman inspect bogus pod", func() { session := podmanTest.Podman([]string{"pod", "inspect", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, "no such pod foobar")) }) ``` diff --git a/test/e2e/attach_test.go b/test/e2e/attach_test.go index f81614fdd5..1f22559023 100644 --- a/test/e2e/attach_test.go +++ b/test/e2e/attach_test.go @@ -7,7 +7,6 @@ import ( . "github.com/containers/podman/v5/test/utils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gexec" ) var _ = Describe("Podman attach", func() { @@ -15,7 +14,7 @@ var _ = Describe("Podman attach", func() { It("podman attach to bogus container", func() { session := podmanTest.Podman([]string{"attach", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) + Expect(session).Should(ExitWithError(125, `no container with name or ID "foobar" found: no such container`)) }) It("podman attach to non-running container", func() { @@ -25,7 +24,7 @@ var _ = Describe("Podman attach", func() { results := podmanTest.Podman([]string{"attach", "test1"}) results.WaitWithDefaultTimeout() - Expect(results).Should(Exit(125)) + Expect(results).Should(ExitWithError(125, "you can only attach to running containers")) }) It("podman container attach to non-running container", func() { @@ -36,7 +35,7 @@ var _ = Describe("Podman attach", func() { results := podmanTest.Podman([]string{"container", "attach", "test1"}) results.WaitWithDefaultTimeout() - Expect(results).Should(Exit(125)) + Expect(results).Should(ExitWithError(125, "you can only attach to running containers")) }) It("podman attach to multiple containers", func() { @@ -50,7 +49,7 @@ var _ = Describe("Podman attach", func() { results := podmanTest.Podman([]string{"attach", "test1", "test2"}) results.WaitWithDefaultTimeout() - Expect(results).Should(Exit(125)) + Expect(results).Should(ExitWithError(125, " attach` accepts at most one argument")) }) It("podman attach to a running container", func() { diff --git a/test/e2e/checkpoint_image_test.go b/test/e2e/checkpoint_image_test.go index 8a4a10b0ea..093c873092 100644 --- a/test/e2e/checkpoint_image_test.go +++ b/test/e2e/checkpoint_image_test.go @@ -35,8 +35,7 @@ var _ = Describe("Podman checkpoint", func() { checkpointImage := "foobar-checkpoint" session := podmanTest.Podman([]string{"container", "checkpoint", "--create-image", checkpointImage, "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) - Expect(session.ErrorToString()).To(ContainSubstring("no container with name or ID \"foobar\" found")) + Expect(session).To(ExitWithError(125, `no container with name or ID "foobar" found: no such container`)) }) It("podman checkpoint --create-image with running container", func() { diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index dc1a47a1d1..99e6fa0e96 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -64,15 +64,13 @@ var _ = Describe("Podman checkpoint", func() { It("podman checkpoint bogus container", func() { session := podmanTest.Podman([]string{"container", "checkpoint", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("no such container")) + Expect(session).Should(ExitWithError(125, "no such container")) }) It("podman restore bogus container", func() { session := podmanTest.Podman([]string{"container", "restore", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("no such container or image")) + Expect(session).Should(ExitWithError(125, "no such container or image")) }) It("podman checkpoint a running container by id", func() { @@ -228,7 +226,7 @@ var _ = Describe("Podman checkpoint", func() { result = podmanTest.Podman([]string{"pause", cid}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) + Expect(result).Should(ExitWithError(125, `"exited" is not running, can't pause: container state improper`)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) @@ -239,7 +237,7 @@ var _ = Describe("Podman checkpoint", func() { result = podmanTest.Podman([]string{"rm", cid}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(2)) + Expect(result).Should(ExitWithError(2, " as it is running - running or paused containers cannot be removed without force: container state improper")) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) result = podmanTest.Podman([]string{"rm", "-t", "1", "-f", cid}) @@ -354,7 +352,9 @@ var _ = Describe("Podman checkpoint", func() { result := podmanTest.Podman([]string{"container", "checkpoint", cid}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) + // FIXME: criu emits an error message, but podman never sees it: + // "CRIU checkpointing failed -52. Please check CRIU logfile /...." + Expect(result).Should(ExitWithError(125, "failed: exit status 1")) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) @@ -370,7 +370,8 @@ var _ = Describe("Podman checkpoint", func() { result = podmanTest.Podman([]string{"container", "restore", cid}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) + // FIXME: CRIU failure message not seen by podman (same as above) + Expect(result).Should(ExitWithError(125)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) @@ -618,8 +619,7 @@ var _ = Describe("Podman checkpoint", func() { result = podmanTest.Podman([]string{"container", "checkpoint", cid, "-e", fileName, "-c", "non-existing"}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) - Expect(result.ErrorToString()).To(ContainSubstring("not supported")) + Expect(result).Should(ExitWithError(125, `selected compression algorithm ("non-existing") not supported. Please select one from`)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) Expect(podmanTest.NumberOfContainers()).To(Equal(1)) @@ -731,8 +731,7 @@ var _ = Describe("Podman checkpoint", func() { // Verify the changes to the container's root file-system result = podmanTest.Podman([]string{"exec", cid, "cat", "/test.output"}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(1)) - Expect(result.ErrorToString()).To(ContainSubstring("cat: can't open '/test.output': No such file or directory")) + Expect(result).Should(ExitWithError(1, "cat: can't open '/test.output': No such file or directory")) // Remove exported checkpoint os.Remove(fileName) @@ -773,8 +772,7 @@ var _ = Describe("Podman checkpoint", func() { // Verify the changes to the container's root file-system result = podmanTest.Podman([]string{"exec", cid, "cat", "/test.output"}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(1)) - Expect(result.ErrorToString()).To(ContainSubstring("cat: can't open '/test.output': No such file or directory")) + Expect(result).Should(ExitWithError(1, "cat: can't open '/test.output': No such file or directory")) // Remove exported checkpoint os.Remove(fileName) @@ -833,8 +831,7 @@ var _ = Describe("Podman checkpoint", func() { // Checkpoint the container - this should fail as it was started with --rm result := podmanTest.Podman([]string{"container", "checkpoint", cid}) result.WaitWithDefaultTimeout() - Expect(result).To(ExitWithError()) - Expect(result.ErrorToString()).To(ContainSubstring("cannot checkpoint containers that have been started with '--rm'")) + Expect(result).To(ExitWithError(125, "cannot checkpoint containers that have been started with '--rm'")) // Checkpointing with --export should still work fileName := filepath.Join(podmanTest.TempDir, "/checkpoint-"+cid+".tar.gz") @@ -922,10 +919,7 @@ var _ = Describe("Podman checkpoint", func() { // Restore container should fail because named volume still exists result = podmanTest.Podman([]string{"container", "restore", "-i", checkpointFileName}) result.WaitWithDefaultTimeout() - Expect(result).To(ExitWithError()) - Expect(result.ErrorToString()).To(ContainSubstring( - "volume with name my-test-vol already exists. Use --ignore-volumes to not restore content of volumes", - )) + Expect(result).To(ExitWithError(125, "volume with name my-test-vol already exists. Use --ignore-volumes to not restore content of volumes")) // Remove named volume session = podmanTest.Podman([]string{"volume", "rm", "my-test-vol"}) @@ -1205,8 +1199,7 @@ var _ = Describe("Podman checkpoint", func() { fileName, }) result.WaitWithDefaultTimeout() - Expect(result).To(Exit(125)) - Expect(result.ErrorToString()).To(ContainSubstring("does not share the")) + Expect(result).To(ExitWithError(125, "does not share the ")) // Remove the pod and create a new pod result = podmanTest.Podman([]string{ @@ -1458,8 +1451,7 @@ var _ = Describe("Podman checkpoint", func() { // Checkpoint is expected to fail without --file-locks result := podmanTest.Podman([]string{"container", "checkpoint", "test_name"}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) - Expect(result.ErrorToString()).To(ContainSubstring("failed: exit status 1")) + Expect(result).Should(ExitWithError(125, "failed: exit status 1")) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) // Checkpoint is expected to succeed with --file-locks @@ -1703,10 +1695,7 @@ var _ = Describe("Podman checkpoint", func() { }) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) - Expect(result.ErrorToString()).To( - ContainSubstring("and cannot be restored with runtime"), - ) + Expect(result).Should(ExitWithError(125, "and cannot be restored with runtime")) result = podmanTest.Podman([]string{ "--runtime", diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index cf1081fb89..4ab55303b7 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -20,8 +20,7 @@ var _ = Describe("Podman commit", func() { session := podmanTest.Podman([]string{"commit", "test1", "--change", "BOGUS=foo", "foobar.com/test1-image:latest"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(HaveSuffix(`applying changes: processing change "BOGUS foo": did not understand change instruction "BOGUS foo"`)) + Expect(session).Should(ExitWithError(125, `applying changes: processing change "BOGUS foo": did not understand change instruction "BOGUS foo"`)) session = podmanTest.Podman([]string{"commit", "test1", "foobar.com/test1-image:latest"}) session.WaitWithDefaultTimeout() @@ -47,8 +46,7 @@ var _ = Describe("Podman commit", func() { // commit second time with --quiet, should not write to stderr session = podmanTest.Podman([]string{"commit", "--quiet", "bogus", "foobar.com/test1-image:latest"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(Equal("Error: no container with name or ID \"bogus\" found: no such container")) + Expect(session).Should(ExitWithError(125, `no container with name or ID "bogus" found: no such container`)) }) It("podman commit single letter container", func() { @@ -346,7 +344,7 @@ var _ = Describe("Podman commit", func() { session = podmanTest.Podman([]string{"run", "foobar.com/test1-image:latest", "cat", "/run/secrets/mysecret"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(1, "can't open '/run/secrets/mysecret': No such file or directory")) }) diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go index 56d7f7eb84..129c889bcf 100644 --- a/test/e2e/containers_conf_test.go +++ b/test/e2e/containers_conf_test.go @@ -441,8 +441,7 @@ var _ = Describe("Verify podman containers.conf usage", func() { It("--add-host and no-hosts=true fails", func() { session := podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) - Expect(session.ErrorToString()).To(ContainSubstring("--no-hosts and --add-host cannot be set together")) + Expect(session).To(ExitWithError(125, "--no-hosts and --add-host cannot be set together")) session = podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", "--no-hosts=false", ALPINE, "top"}) session.WaitWithDefaultTimeout() @@ -533,8 +532,7 @@ var _ = Describe("Verify podman containers.conf usage", func() { if !IsRemote() { session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("invalid image_copy_tmp_dir value \"storage1\" (relative paths are not accepted)")) + Expect(session).Should(ExitWithError(125, `invalid image_copy_tmp_dir value "storage1" (relative paths are not accepted)`)) os.Setenv("TMPDIR", "/hoge") session = podmanTest.Podman([]string{"info", "--format", "{{.Store.ImageCopyTmpDir}}"}) @@ -573,18 +571,15 @@ var _ = Describe("Verify podman containers.conf usage", func() { result := podmanTest.Podman([]string{"pod", "create", "--infra-image", infra2}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) - Expect(result.ErrorToString()).To(ContainSubstring(error2String)) + Expect(result).Should(ExitWithError(125, error2String)) result = podmanTest.Podman([]string{"pod", "create"}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) - Expect(result.ErrorToString()).To(ContainSubstring(errorString)) + Expect(result).Should(ExitWithError(125, errorString)) result = podmanTest.Podman([]string{"create", "--pod", "new:pod1", ALPINE}) result.WaitWithDefaultTimeout() - Expect(result).Should(Exit(125)) - Expect(result.ErrorToString()).To(ContainSubstring(errorString)) + Expect(result).Should(ExitWithError(125, errorString)) }) It("set .engine.remote=true", func() { @@ -679,8 +674,7 @@ var _ = Describe("Verify podman containers.conf usage", func() { podman.WaitWithDefaultTimeout() if mode == "invalid" { - Expect(podman).Should(Exit(125)) - Expect(podman.ErrorToString()).Should(ContainSubstring("invalid default_rootless_network_cmd option \"invalid\"")) + Expect(podman).Should(ExitWithError(125, `invalid default_rootless_network_cmd option "invalid"`)) continue } Expect(podman).Should(ExitCleanly()) diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index f63c778e13..4a2c7a5108 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -40,7 +40,7 @@ var _ = Describe("Podman cp", func() { // Cannot copy to a nonexistent path (note the trailing "/"). session = podmanTest.Podman([]string{"cp", srcFile.Name(), name + ":foo/"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, `"foo/" could not be found on container`)) // The file will now be created (and written to). session = podmanTest.Podman([]string{"cp", srcFile.Name(), name + ":foo"}) diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index df5aefc85f..fba85cbf72 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -1,12 +1,12 @@ package integration import ( + "fmt" "time" . "github.com/containers/podman/v5/test/utils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gexec" ) var _ = Describe("Podman create with --ip flag", func() { @@ -14,7 +14,7 @@ var _ = Describe("Podman create with --ip flag", func() { It("Podman create --ip with garbage address", func() { result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "114232346", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result).To(ExitWithError()) + Expect(result).To(ExitWithError(125, `"114232346" is not an ip address`)) }) It("Podman create --ip with non-allocatable IP", func() { @@ -25,7 +25,7 @@ var _ = Describe("Podman create with --ip flag", func() { result = podmanTest.Podman([]string{"start", "test"}) result.WaitWithDefaultTimeout() - Expect(result).To(ExitWithError()) + Expect(result).To(ExitWithError(125, "requested static ip 203.0.113.124 not in any subnet on network podman")) }) It("Podman create with specified static IP has correct IP", func() { @@ -34,7 +34,7 @@ var _ = Describe("Podman create with --ip flag", func() { result.WaitWithDefaultTimeout() // Rootless static ip assignment without network should error if isRootless() { - Expect(result).Should(Exit(125)) + Expect(result).Should(ExitWithError(125, "invalid config provided: networks and static ip/mac address can only be used with Bridge mode networking")) } else { Expect(result).Should(ExitCleanly()) @@ -74,11 +74,6 @@ var _ = Describe("Podman create with --ip flag", func() { // test1 container is running with the given IP. result = podmanTest.Podman([]string{"start", "-a", "test2"}) result.WaitWithDefaultTimeout() - Expect(result).To(ExitWithError()) - if podmanTest.NetworkBackend == CNI { - Expect(result.ErrorToString()).To(ContainSubstring("requested IP address %s is not available", ip)) - } else if podmanTest.NetworkBackend == Netavark { - Expect(result.ErrorToString()).To(ContainSubstring("requested ip address %s is already allocated", ip)) - } + Expect(result).To(ExitWithError(125, fmt.Sprintf("requested ip address %s is already allocated", ip))) }) }) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 66befee34a..74087dc2f4 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -60,9 +60,8 @@ var _ = Describe("Podman create", func() { create := podmanTest.Podman([]string{"container", "create", pushedImage}) create.WaitWithDefaultTimeout() - Expect(create).Should(Exit(125)) + Expect(create).Should(ExitWithError(125, "http: server gave HTTP response to HTTPS client")) Expect(create.ErrorToString()).To(ContainSubstring("pinging container registry localhost:" + port)) - Expect(create.ErrorToString()).To(ContainSubstring("http: server gave HTTP response to HTTPS client")) create = podmanTest.Podman([]string{"create", "--tls-verify=false", pushedImage, "echo", "got here"}) create.WaitWithDefaultTimeout() @@ -85,7 +84,7 @@ var _ = Describe("Podman create", func() { session = podmanTest.Podman([]string{"create", "--name=foo", ALPINE, "ls"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) + Expect(session).Should(ExitWithError(125, `creating container storage: the container name "foo" is already in use by`)) }) It("podman create adds rdt-class", func() { @@ -227,7 +226,7 @@ var _ = Describe("Podman create", func() { // if used together. session := podmanTest.Podman([]string{"create", "--pod", "foo", "--pod-id-file", "bar", ALPINE, "ls"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) + Expect(session).Should(ExitWithError(125, "cannot specify both --pod and --pod-id-file")) tmpDir := GinkgoT().TempDir() @@ -273,7 +272,7 @@ var _ = Describe("Podman create", func() { It("podman create --pull", func() { session := podmanTest.Podman([]string{"create", "--pull", "never", "--name=foo", "testimage:00000000"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, "testimage:00000000: image not known")) session = podmanTest.Podman([]string{"create", "--pull", "always", "--name=foo", "testimage:00000000"}) session.WaitWithDefaultTimeout() @@ -342,23 +341,23 @@ var _ = Describe("Podman create", func() { bogus := filepath.Join(podmanTest.TempDir, "bogus.conf") session := podmanTest.Podman([]string{"create", "--authfile", bogus, "--name=foo", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, "credential file is not accessible: ")) Expect(session.ErrorToString()).To(ContainSubstring("no such file or directory")) }) It("podman create --signature-policy", func() { session := podmanTest.Podman([]string{"create", "--pull=always", "--signature-policy", "/no/such/file", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) - - session = podmanTest.Podman([]string{"create", "-q", "--pull=always", "--signature-policy", "/etc/containers/policy.json", ALPINE}) - session.WaitWithDefaultTimeout() if IsRemote() { - Expect(session).To(ExitWithError()) - Expect(session.ErrorToString()).To(ContainSubstring("unknown flag")) + Expect(session).To(ExitWithError(125, "unknown flag: --signature-policy")) + return } else { - Expect(session).Should(ExitCleanly()) + Expect(session).To(ExitWithError(125, "open /no/such/file: no such file or directory")) } + + session = podmanTest.Podman([]string{"create", "-q", "--pull=always", "--signature-policy", "/etc/containers/policy.json", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) }) It("podman create with unset label", func() { @@ -410,7 +409,7 @@ var _ = Describe("Podman create", func() { It("podman create with --restart-policy=always:5 fails", func() { session := podmanTest.Podman([]string{"create", "-t", "--restart", "always:5", ALPINE, "/bin/sh"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, "restart policy retries can only be specified with on-failure restart policy")) }) It("podman create with --restart-policy unless-stopped", func() { @@ -462,7 +461,7 @@ var _ = Describe("Podman create", func() { // Make sure we error out with --name. session := podmanTest.Podman([]string{"create", "--replace", ALPINE, "/bin/sh"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) + Expect(session).Should(ExitWithError(125, "cannot replace container without --name being set")) // Create and replace 5 times in a row the "same" container. ctrName := "testCtr" @@ -489,15 +488,11 @@ var _ = Describe("Podman create", func() { It("podman create --tz", func() { session := podmanTest.Podman([]string{"create", "--tz", "foo", "--name", "bad", ALPINE, "date"}) session.WaitWithDefaultTimeout() - Expect(session).To(Exit(125)) - Expect(session.ErrorToString()).To( - Equal("Error: running container create option: finding timezone: unknown time zone foo")) + Expect(session).To(ExitWithError(125, "running container create option: finding timezone: unknown time zone foo")) session = podmanTest.Podman([]string{"create", "--tz", "America", "--name", "dir", ALPINE, "date"}) session.WaitWithDefaultTimeout() - Expect(session).To(Exit(125)) - Expect(session.ErrorToString()).To( - Equal("Error: running container create option: finding timezone: is a directory")) + Expect(session).To(ExitWithError(125, "running container create option: finding timezone: is a directory")) session = podmanTest.Podman([]string{"create", "--tz", "Pacific/Honolulu", "--name", "zone", ALPINE, "date"}) session.WaitWithDefaultTimeout() @@ -555,8 +550,7 @@ var _ = Describe("Podman create", func() { session = podmanTest.Podman([]string{"create", "--umask", "9999", "--name", "bad", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) - Expect(session.ErrorToString()).To(ContainSubstring("invalid umask")) + Expect(session).To(ExitWithError(125, "invalid umask string 9999: invalid argument")) }) It("create container in pod with IP should fail", func() { @@ -568,7 +562,7 @@ var _ = Describe("Podman create", func() { session := podmanTest.Podman([]string{"create", "--pod", name, "--ip", "192.168.1.2", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, "invalid config provided: networks must be defined when the pod is created: network cannot be configured when it is shared with a pod")) }) It("create container in pod with mac should fail", func() { @@ -580,7 +574,7 @@ var _ = Describe("Podman create", func() { session := podmanTest.Podman([]string{"create", "--pod", name, "--mac-address", "52:54:00:6d:2f:82", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, "invalid config provided: networks must be defined when the pod is created: network cannot be configured when it is shared with a pod")) }) It("create container in pod with network should not fail", func() { @@ -608,7 +602,7 @@ var _ = Describe("Podman create", func() { session := podmanTest.Podman([]string{"create", "--pod", name, "-p", "8086:80", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, "invalid config provided: published or exposed ports must be defined when the pod is created: network cannot be configured when it is shared with a pod")) }) It("create container in pod publish ports should fail", func() { @@ -619,7 +613,7 @@ var _ = Describe("Podman create", func() { session := podmanTest.Podman([]string{"create", "--pod", name, "-P", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, "invalid config provided: published or exposed ports must be defined when the pod is created: network cannot be configured when it is shared with a pod")) }) It("create use local store image if input image contains a manifest list", func() { @@ -643,32 +637,26 @@ var _ = Describe("Podman create", func() { It("podman create -d should fail, can not detach create containers", func() { session := podmanTest.Podman([]string{"create", "-d", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("unknown shorthand flag")) + Expect(session).Should(ExitWithError(125, "unknown shorthand flag: 'd' in -d")) session = podmanTest.Podman([]string{"create", "--detach", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("unknown flag")) + Expect(session).Should(ExitWithError(125, "unknown flag")) + Expect(session.ErrorToString()).To(ContainSubstring("unknown flag: --detach")) session = podmanTest.Podman([]string{"create", "--detach-keys", "ctrl-x", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("unknown flag")) + Expect(session).Should(ExitWithError(125, "unknown flag: --detach-keys")) }) It("podman create --platform", func() { session := podmanTest.Podman([]string{"create", "--platform=linux/bogus", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - expectedError := "no image found in manifest list for architecture bogus" - Expect(session.ErrorToString()).To(ContainSubstring(expectedError)) + Expect(session).Should(ExitWithError(125, "no image found in manifest list for architecture bogus")) session = podmanTest.Podman([]string{"create", "--platform=linux/arm64", "--os", "windows", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - expectedError = "--platform option can not be specified with --arch or --os" - Expect(session.ErrorToString()).To(ContainSubstring(expectedError)) + Expect(session).Should(ExitWithError(125, "--platform option can not be specified with --arch or --os")) session = podmanTest.Podman([]string{"create", "-q", "--platform=linux/arm64", ALPINE}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 43a9490abc..efb9e13acc 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -17,13 +17,7 @@ var _ = Describe("Podman exec", func() { It("podman exec into bogus container", func() { session := podmanTest.Podman([]string{"exec", "foobar", "ls"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - }) - - It("podman exec without command", func() { - session := podmanTest.Podman([]string{"exec", "foobar"}) - session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) + Expect(session).Should(ExitWithError(125, `no container with name or ID "foobar" found: no such container`)) }) It("podman exec simple command", func() { @@ -34,6 +28,15 @@ var _ = Describe("Podman exec", func() { session := podmanTest.Podman([]string{"exec", "test1", "ls"}) session.WaitWithDefaultTimeout() Expect(session).Should(ExitCleanly()) + + // With no command + session = podmanTest.Podman([]string{"exec", "test1"}) + session.WaitWithDefaultTimeout() + expectedStatus := 255 + if IsRemote() { + expectedStatus = 125 + } + Expect(session).Should(ExitWithError(expectedStatus, "must provide a non-empty command to start an exec session: invalid argument")) }) It("podman container exec simple command", func() { @@ -399,13 +402,17 @@ var _ = Describe("Podman exec", func() { setup.WaitWithDefaultTimeout() Expect(setup).Should(ExitCleanly()) + expect := "chdir to `/missing`: No such file or directory" + if podmanTest.OCIRuntime == "runc" { + expect = "chdir to cwd" + } session := podmanTest.Podman([]string{"exec", "--workdir", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(127, expect)) session = podmanTest.Podman([]string{"exec", "-w", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(127, expect)) }) It("podman exec cannot be invoked", func() { @@ -416,13 +423,19 @@ var _ = Describe("Podman exec", func() { session := podmanTest.Podman([]string{"exec", "test1", "/etc"}) session.WaitWithDefaultTimeout() + // crun (and, we hope, any other future runtimes) + expectedStatus := 126 + expectedMessage := "open executable: Operation not permitted: OCI permission denied" + + // ...but it's much more complicated under runc (#19552) if podmanTest.OCIRuntime == "runc" { - // #19552 and others: some versions of runc exit 255. - Expect(session).Should(ExitWithError()) - } else { - // crun (and, we hope, any other future runtimes) - Expect(session).Should(Exit(126)) + expectedMessage = `exec failed: unable to start container process: exec: "/etc": is a directory` + expectedStatus = 255 + if IsRemote() { + expectedStatus = 125 + } } + Expect(session).Should(ExitWithError(expectedStatus, expectedMessage)) }) It("podman exec command not found", func() { @@ -432,7 +445,7 @@ var _ = Describe("Podman exec", func() { session := podmanTest.Podman([]string{"exec", "test1", "notthere"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(127)) + Expect(session).Should(ExitWithError(127, "OCI runtime attempted to invoke a command that was not found")) }) It("podman exec preserve fds sanity check", func() { @@ -559,8 +572,7 @@ RUN useradd -u 1000 auser`, fedoraMinimal) SkipIfRemote("not supported for --wait") session := podmanTest.Podman([]string{"exec", "--wait", "2", "1234"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(Equal("Error: timed out waiting for container: 1234")) + Expect(session).Should(ExitWithError(125, "timed out waiting for container: 1234")) }) It("podman exec --wait 5 seconds for started container", func() { diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index 36ad540df4..efebd9f8ed 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -48,6 +48,6 @@ var _ = Describe("Podman export", func() { outfile := filepath.Join(podmanTest.TempDir, "container:with:colon.tar") result := podmanTest.Podman([]string{"export", "-o", outfile, cid}) result.WaitWithDefaultTimeout() - Expect(result).To(ExitWithError()) + Expect(result).To(ExitWithError(125, "invalid filename (should not contain ':')")) }) }) diff --git a/test/e2e/farm_test.go b/test/e2e/farm_test.go index b676a92499..ede6d2d94d 100644 --- a/test/e2e/farm_test.go +++ b/test/e2e/farm_test.go @@ -180,13 +180,13 @@ farm2 [QA] false true cmd = []string{"farm", "update", "--add", "no-node", "farm1"} session = podmanTest.Podman(cmd) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, `cannot add to farm, "no-node" is not a system connection`)) // update farm2 to remove node not in farm connections from it cmd = []string{"farm", "update", "--remove", "QB", "farm2"} session = podmanTest.Podman(cmd) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, `cannot remove from farm, "QB" is not a connection in the farm`)) // check again to ensure that nothing has changed session = podmanTest.Podman(farmListCmd) @@ -209,13 +209,13 @@ farm2 [QA] false true cmd = []string{"farm", "update", "--add", "no-node", "non-existent"} session = podmanTest.Podman(cmd) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, `cannot update farm, "non-existent" farm doesn't exist`)) // update non-existent farm to default cmd = []string{"farm", "update", "--default", "non-existent"} session = podmanTest.Podman(cmd) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, `cannot update farm, "non-existent" farm doesn't exist`)) session = podmanTest.Podman(farmListCmd) session.WaitWithDefaultTimeout() diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 106b6e6a4f..bb8800a8db 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -15,22 +15,21 @@ import ( . "github.com/containers/podman/v5/test/utils" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gexec" "sigs.k8s.io/yaml" ) var _ = Describe("Podman kube generate", func() { It("pod on bogus object", func() { - session := podmanTest.Podman([]string{"generate", "kube", "foobar"}) + session := podmanTest.Podman([]string{"generate", "kube", "foobarpod"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, `name or ID "foobarpod" not found`)) }) It("service on bogus object", func() { - session := podmanTest.Podman([]string{"kube", "generate", "-s", "foobar"}) + session := podmanTest.Podman([]string{"kube", "generate", "-s", "foobarservice"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, `name or ID "foobarservice" not found`)) }) It("on container", func() { @@ -390,7 +389,7 @@ var _ = Describe("Podman kube generate", func() { ctrSession := podmanTest.Podman([]string{"create", "--name", "testCtr", "--pod", podName, "-p", "9000:8000", CITEST_IMAGE, "top"}) ctrSession.WaitWithDefaultTimeout() - Expect(ctrSession).Should(Exit(125)) + Expect(ctrSession).Should(ExitWithError(125, "invalid config provided: published or exposed ports must be defined when the pod is created: network cannot be configured when it is shared with a pod")) // Ports without Net sharing should work with ports being set for each container in the generated kube yaml podName = "testNet" @@ -431,7 +430,7 @@ var _ = Describe("Podman kube generate", func() { ctrSession := podmanTest.Podman([]string{"create", "--name", "testCtr", "--pod", podName, "--hostname", "test-hostname", CITEST_IMAGE, "top"}) ctrSession.WaitWithDefaultTimeout() - Expect(ctrSession).Should(Exit(125)) + Expect(ctrSession).Should(ExitWithError(125, "invalid config provided: cannot set hostname when joining the pod UTS namespace: invalid configuration")) // Hostname without uts sharing should work, but generated kube yaml will have pod hostname // set to the hostname of the first container @@ -955,7 +954,7 @@ var _ = Describe("Podman kube generate", func() { kube := podmanTest.Podman([]string{"kube", "generate", "top"}) kube.WaitWithDefaultTimeout() - Expect(kube).To(ExitWithError()) + Expect(kube).To(ExitWithError(125, " is associated with pod ")) }) It("with multiple containers", func() { @@ -983,7 +982,7 @@ var _ = Describe("Podman kube generate", func() { kube := podmanTest.Podman([]string{"kube", "generate", "top1", "top2"}) kube.WaitWithDefaultTimeout() - Expect(kube).To(ExitWithError()) + Expect(kube).To(ExitWithError(125, " is associated with pod ")) }) It("on a container with dns options", func() { @@ -1522,7 +1521,7 @@ USER test1` kube := podmanTest.Podman([]string{"kube", "generate", "--type", "pod", "--replicas", "3", ctrName}) kube.WaitWithDefaultTimeout() - Expect(kube).Should(Exit(125)) + Expect(kube).Should(ExitWithError(125, "--replicas can only be set when --type is set to deployment")) }) It("on pod with --type=deployment and --restart=no should fail", func() { @@ -1537,7 +1536,7 @@ USER test1` kube := podmanTest.Podman([]string{"kube", "generate", "--type", "deployment", podName}) kube.WaitWithDefaultTimeout() - Expect(kube).Should(Exit(125)) + Expect(kube).Should(ExitWithError(125, "k8s Deployments can only have restartPolicy set to Always")) }) It("on pod with invalid name", func() { @@ -1877,8 +1876,7 @@ EXPOSE 2004-2005/tcp`, CITEST_IMAGE) kube := podmanTest.Podman([]string{"kube", "generate", "--type", "daemonset", "--replicas", "3", ctrName}) kube.WaitWithDefaultTimeout() - Expect(kube).Should(Exit(125)) - Expect(kube.ErrorToString()).To(ContainSubstring("--replicas can only be set when --type is set to deployment")) + Expect(kube).Should(ExitWithError(125, "--replicas can only be set when --type is set to deployment")) }) It("on pod with --type=daemonset and --restart=no should fail", func() { @@ -1893,7 +1891,6 @@ EXPOSE 2004-2005/tcp`, CITEST_IMAGE) kube := podmanTest.Podman([]string{"kube", "generate", "--type", "daemonset", podName}) kube.WaitWithDefaultTimeout() - Expect(kube).Should(Exit(125)) - Expect(kube.ErrorToString()).To(ContainSubstring("k8s DaemonSets can only have restartPolicy set to Always")) + Expect(kube).Should(ExitWithError(125, "k8s DaemonSets can only have restartPolicy set to Always")) }) }) diff --git a/test/e2e/generate_spec_test.go b/test/e2e/generate_spec_test.go index 8fa378393e..b4641ae57c 100644 --- a/test/e2e/generate_spec_test.go +++ b/test/e2e/generate_spec_test.go @@ -18,7 +18,7 @@ var _ = Describe("Podman generate spec", func() { It("podman generate spec bogus should fail", func() { session := podmanTest.Podman([]string{"generate", "spec", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).Should(ExitWithError()) + Expect(session).Should(ExitWithError(125, "could not find a pod or container with the id foobar")) }) It("podman generate spec basic usage", func() { diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 824cf65132..bdf82e89dd 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -15,19 +15,13 @@ var _ = Describe("Podman generate systemd", func() { It("podman generate systemd on bogus container/pod", func() { session := podmanTest.Podman([]string{"generate", "systemd", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) - }) - - It("podman generate systemd bad restart policy", func() { - session := podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "never", "foobar"}) - session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, `foobar does not refer to a container or pod: no pod with name or ID foobar found: no such pod: no container with name or ID "foobar" found: no such container`)) }) It("podman generate systemd bad timeout value", func() { session := podmanTest.Podman([]string{"generate", "systemd", "--time", "-1", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, `invalid argument "-1" for "-t, --time" flag: strconv.ParseUint: parsing "-1": invalid syntax`)) }) It("podman generate systemd bad restart-policy value", func() { @@ -37,8 +31,7 @@ var _ = Describe("Podman generate systemd", func() { session = podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "bogus", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) - Expect(session.ErrorToString()).To(ContainSubstring("bogus is not a valid restart policy")) + Expect(session).To(ExitWithError(125, "bogus is not a valid restart policy")) }) It("podman generate systemd with --no-header=true", func() { @@ -628,8 +621,7 @@ var _ = Describe("Podman generate systemd", func() { session = podmanTest.Podman([]string{"generate", "systemd", "--env", "=bar", "-e", "hoge=fuga", "test"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("invalid variable")) + Expect(session).Should(ExitWithError(125, "invalid variable")) // Use -e/--env option with --new option session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "hoge=fuga", "--new", "test"}) @@ -640,8 +632,7 @@ var _ = Describe("Podman generate systemd", func() { session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "=fuga", "--new", "test"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(125)) - Expect(session.ErrorToString()).To(ContainSubstring("invalid variable")) + Expect(session).Should(ExitWithError(125, "invalid variable")) // Escape systemd arguments session = podmanTest.Podman([]string{"generate", "systemd", "--env", "BAR=my test", "-e", "USER=%a", "test"}) diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index 1c44c35ad3..6aa08703bc 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -18,7 +18,7 @@ var _ = Describe("Podman healthcheck run", func() { It("podman healthcheck run bogus container", func() { session := podmanTest.Podman([]string{"healthcheck", "run", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).To(ExitWithError()) + Expect(session).To(ExitWithError(125, `unable to look up foobar to perform a health check: no container with name or ID "foobar" found: no such container`)) }) It("podman disable healthcheck with --no-healthcheck on valid container", func() { @@ -27,7 +27,7 @@ var _ = Describe("Podman healthcheck run", func() { Expect(session).Should(ExitCleanly()) hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) hc.WaitWithDefaultTimeout() - Expect(hc).Should(Exit(125)) + Expect(hc).Should(ExitWithError(125, "has no defined healthcheck")) }) It("podman disable healthcheck with --no-healthcheck must not show starting on status", func() { @@ -80,7 +80,7 @@ var _ = Describe("Podman healthcheck run", func() { Expect(session).Should(ExitCleanly()) hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) hc.WaitWithDefaultTimeout() - Expect(hc).Should(Exit(125)) + Expect(hc).Should(ExitWithError(125, "has no defined healthcheck")) }) It("podman healthcheck on valid container", func() { @@ -126,7 +126,7 @@ var _ = Describe("Podman healthcheck run", func() { hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) hc.WaitWithDefaultTimeout() - Expect(hc).Should(Exit(125)) + Expect(hc).Should(ExitWithError(125, "is not running")) }) It("podman healthcheck on container without healthcheck", func() { @@ -136,7 +136,7 @@ var _ = Describe("Podman healthcheck run", func() { hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) hc.WaitWithDefaultTimeout() - Expect(hc).Should(Exit(125)) + Expect(hc).Should(ExitWithError(125, "has no defined healthcheck")) }) It("podman healthcheck should be starting", func() { diff --git a/test/utils/matchers.go b/test/utils/matchers.go index 26050d1835..1c7abd1623 100644 --- a/test/utils/matchers.go +++ b/test/utils/matchers.go @@ -3,57 +3,97 @@ package utils import ( "encoding/json" "fmt" + "strings" "github.com/onsi/gomega/format" "github.com/onsi/gomega/gexec" "github.com/onsi/gomega/types" ) +type podmanSession interface { + ExitCode() int + ErrorToString() string +} + type ExitMatcher struct { types.GomegaMatcher - Expected int - Actual int + ExpectedExitCode int + ExitCode int + ExpectedStderr string + msg string } // ExitWithError matches when assertion is > argument. Default 0 // Modeled after the gomega Exit() matcher and also operates on sessions. -func ExitWithError(optionalExitCode ...int) *ExitMatcher { +func ExitWithError(expectations ...interface{}) *ExitMatcher { exitCode := 0 - if len(optionalExitCode) > 0 { - exitCode = optionalExitCode[0] + expectStderr := "" + // FIXME: once all ExitWithError()s have been migrated to new form, + // change interface to (int, ...string) + if len(expectations) > 0 { + var ok bool + exitCode, ok = expectations[0].(int) + if !ok { + panic("ExitWithError(): first arg, if present, must be an int") + } + + if len(expectations) > 1 { + expectStderr, ok = expectations[1].(string) + if !ok { + panic("ExitWithError(): second arg, if present, must be a string") + } + } } - return &ExitMatcher{Expected: exitCode} + + return &ExitMatcher{ExpectedExitCode: exitCode, ExpectedStderr: expectStderr} } // Match follows gexec.Matcher interface. func (matcher *ExitMatcher) Match(actual interface{}) (success bool, err error) { - exiter, ok := actual.(gexec.Exiter) + session, ok := actual.(podmanSession) if !ok { return false, fmt.Errorf("ExitWithError must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n#{format.Object(actual, 1)}") } - matcher.Actual = exiter.ExitCode() - if matcher.Actual == -1 { + matcher.ExitCode = session.ExitCode() + if matcher.ExitCode == -1 { + matcher.msg = "Expected process to exit. It did not." return false, nil } - return matcher.Actual > matcher.Expected, nil + + // FIXME: temporary until all ExitWithError()s are migrated + // to new mandatory-int form. + if matcher.ExpectedExitCode == 0 { + if matcher.ExitCode == 0 { + matcher.msg = "Expected process to exit nonzero. It did not." + return false, nil + } + return true, nil + } + + // Check exit code first. If it's not what we want, there's no point + // in checking error substrings + if matcher.ExitCode != matcher.ExpectedExitCode { + matcher.msg = fmt.Sprintf("Command exited with status %d (expected %d)", matcher.ExitCode, matcher.ExpectedExitCode) + return false, nil + } + + if matcher.ExpectedStderr != "" { + if !strings.Contains(session.ErrorToString(), matcher.ExpectedStderr) { + matcher.msg = fmt.Sprintf("Command exited %d as expected, but did not emit '%s'", matcher.ExitCode, matcher.ExpectedStderr) + return false, nil + } + } + + return true, nil } func (matcher *ExitMatcher) FailureMessage(_ interface{}) (message string) { - if matcher.Actual == -1 { - return "Expected process to exit. It did not." - } - return format.Message(matcher.Actual, "to be greater than exit code: ", matcher.Expected) + return matcher.msg } func (matcher *ExitMatcher) NegatedFailureMessage(_ interface{}) (message string) { - switch { - case matcher.Actual == -1: - return "you really shouldn't be able to see this!" - case matcher.Expected == -1: - return "Expected process not to exit. It did." - } - return format.Message(matcher.Actual, "is less than or equal to exit code: ", matcher.Expected) + panic("There is no conceivable reason to call Not(ExitWithError) !") } func (matcher *ExitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { @@ -73,11 +113,6 @@ type exitCleanlyMatcher struct { msg string } -type podmanSession interface { - ExitCode() int - ErrorToString() string -} - func (matcher *exitCleanlyMatcher) Match(actual interface{}) (success bool, err error) { session, ok := actual.(podmanSession) if !ok {