From 2b0a71c4f19a2d69a881e041942c76d2b3d2be46 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 4 Aug 2016 12:17:37 -0400 Subject: [PATCH 1/3] Remove unnecessary json.Unmarshal wrapper. Signed-off-by: Daniel Nephin Upstream-commit: fb42e8477208badf0714a9d8ae20946a9b531dba Component: engine --- .../integration-cli/docker_cli_build_test.go | 14 +++++++------- .../integration-cli/docker_cli_inspect_test.go | 4 ++-- .../integration-cli/docker_cli_links_test.go | 5 +++-- .../engine/integration-cli/docker_cli_run_test.go | 5 +++-- components/engine/integration-cli/docker_utils.go | 2 +- components/engine/integration-cli/utils.go | 4 ---- components/engine/pkg/integration/utils.go | 10 ---------- components/engine/pkg/integration/utils_test.go | 15 --------------- 8 files changed, 16 insertions(+), 43 deletions(-) diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 32497d1e56..5201f731af 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/integration-cli/docker_cli_build_test.go @@ -240,7 +240,7 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { envResult := []string{} - if err = unmarshalJSON([]byte(res), &envResult); err != nil { + if err = json.Unmarshal([]byte(res), &envResult); err != nil { c.Fatal(err) } @@ -297,7 +297,7 @@ func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { res := inspectFieldJSON(c, name, "Config.Volumes") - if err = unmarshalJSON([]byte(res), &result); err != nil { + if err = json.Unmarshal([]byte(res), &result); err != nil { c.Fatal(err) } @@ -320,7 +320,7 @@ func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { res = inspectFieldJSON(c, name, "Config.Volumes") - if err = unmarshalJSON([]byte(res), &result); err != nil { + if err = json.Unmarshal([]byte(res), &result); err != nil { c.Fatal(err) } @@ -347,7 +347,7 @@ func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { res = inspectFieldJSON(c, name, "Config.Volumes") - if err = unmarshalJSON([]byte(res), &result); err != nil { + if err = json.Unmarshal([]byte(res), &result); err != nil { c.Fatal(err) } @@ -1704,7 +1704,7 @@ func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { } res := inspectFieldJSON(c, name, "Config.Volumes") - err = unmarshalJSON([]byte(res), &result) + err = json.Unmarshal([]byte(res), &result) if err != nil { c.Fatal(err) } @@ -1833,9 +1833,9 @@ func (s *DockerSuite) TestBuildWindowsAddCopyPathProcessing(c *check.C) { ADD wc2 c:/wc2 WORKDIR c:/ RUN sh -c "[ $(cat c:/wc1) = 'hellowc1' ]" - RUN sh -c "[ $(cat c:/wc2) = 'worldwc2' ]" + RUN sh -c "[ $(cat c:/wc2) = 'worldwc2' ]" - # Trailing slash on COPY/ADD, Windows-style path. + # Trailing slash on COPY/ADD, Windows-style path. WORKDIR /wd1 COPY wd1 c:/wd1/ WORKDIR /wd2 diff --git a/components/engine/integration-cli/docker_cli_inspect_test.go b/components/engine/integration-cli/docker_cli_inspect_test.go index 1bab86f419..6c61984d80 100644 --- a/components/engine/integration-cli/docker_cli_inspect_test.go +++ b/components/engine/integration-cli/docker_cli_inspect_test.go @@ -227,7 +227,7 @@ func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) { vol := inspectFieldJSON(c, "test", "Mounts") var mp []types.MountPoint - err := unmarshalJSON([]byte(vol), &mp) + err := json.Unmarshal([]byte(vol), &mp) c.Assert(err, checker.IsNil) // check that there is only one mountpoint @@ -253,7 +253,7 @@ func (s *DockerSuite) TestInspectNamedMountPoint(c *check.C) { vol := inspectFieldJSON(c, "test", "Mounts") var mp []types.MountPoint - err := unmarshalJSON([]byte(vol), &mp) + err := json.Unmarshal([]byte(vol), &mp) c.Assert(err, checker.IsNil) // check that there is only one mountpoint diff --git a/components/engine/integration-cli/docker_cli_links_test.go b/components/engine/integration-cli/docker_cli_links_test.go index d4bfc6a128..a5872d9e0c 100644 --- a/components/engine/integration-cli/docker_cli_links_test.go +++ b/components/engine/integration-cli/docker_cli_links_test.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "regexp" "strings" @@ -97,7 +98,7 @@ func (s *DockerSuite) TestLinksInspectLinksStarted(c *check.C) { dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "top") links := inspectFieldJSON(c, "testinspectlink", "HostConfig.Links") - err := unmarshalJSON([]byte(links), &result) + err := json.Unmarshal([]byte(links), &result) c.Assert(err, checker.IsNil) output := convertSliceOfStringsToMap(result) @@ -116,7 +117,7 @@ func (s *DockerSuite) TestLinksInspectLinksStopped(c *check.C) { dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true") links := inspectFieldJSON(c, "testinspectlink", "HostConfig.Links") - err := unmarshalJSON([]byte(links), &result) + err := json.Unmarshal([]byte(links), &result) c.Assert(err, checker.IsNil) output := convertSliceOfStringsToMap(result) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index e0820d572c..965bdb9644 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -3,6 +3,7 @@ package main import ( "bufio" "bytes" + "encoding/json" "fmt" "io/ioutil" "net" @@ -2397,7 +2398,7 @@ func (s *DockerSuite) TestRunAllowPortRangeThroughExpose(c *check.C) { id := strings.TrimSpace(out) portstr := inspectFieldJSON(c, id, "NetworkSettings.Ports") var ports nat.PortMap - if err := unmarshalJSON([]byte(portstr), &ports); err != nil { + if err := json.Unmarshal([]byte(portstr), &ports); err != nil { c.Fatal(err) } for port, binding := range ports { @@ -2827,7 +2828,7 @@ func (s *DockerSuite) TestRunAllowPortRangeThroughPublish(c *check.C) { portstr := inspectFieldJSON(c, id, "NetworkSettings.Ports") var ports nat.PortMap - err := unmarshalJSON([]byte(portstr), &ports) + err := json.Unmarshal([]byte(portstr), &ports) c.Assert(err, checker.IsNil, check.Commentf("failed to unmarshal: %v", portstr)) for port, binding := range ports { portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0]) diff --git a/components/engine/integration-cli/docker_utils.go b/components/engine/integration-cli/docker_utils.go index 1141cae2c1..238929c16c 100644 --- a/components/engine/integration-cli/docker_utils.go +++ b/components/engine/integration-cli/docker_utils.go @@ -867,7 +867,7 @@ var errMountNotFound = errors.New("mount point not found") func inspectMountPointJSON(j, destination string) (types.MountPoint, error) { var mp []types.MountPoint - if err := unmarshalJSON([]byte(j), &mp); err != nil { + if err := json.Unmarshal([]byte(j), &mp); err != nil { return types.MountPoint{}, err } diff --git a/components/engine/integration-cli/utils.go b/components/engine/integration-cli/utils.go index 149f1c4540..eac6379bcd 100644 --- a/components/engine/integration-cli/utils.go +++ b/components/engine/integration-cli/utils.go @@ -52,10 +52,6 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in return integration.RunCommandPipelineWithOutput(cmds...) } -func unmarshalJSON(data []byte, result interface{}) error { - return integration.UnmarshalJSON(data, result) -} - func convertSliceOfStringsToMap(input []string) map[string]struct{} { return integration.ConvertSliceOfStringsToMap(input) } diff --git a/components/engine/pkg/integration/utils.go b/components/engine/pkg/integration/utils.go index cfccc80143..a8293f64cf 100644 --- a/components/engine/pkg/integration/utils.go +++ b/components/engine/pkg/integration/utils.go @@ -3,7 +3,6 @@ package integration import ( "archive/tar" "bytes" - "encoding/json" "errors" "fmt" "io" @@ -209,15 +208,6 @@ func RunCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in return RunCommandWithOutput(cmds[len(cmds)-1]) } -// UnmarshalJSON deserialize a JSON in the given interface. -func UnmarshalJSON(data []byte, result interface{}) error { - if err := json.Unmarshal(data, result); err != nil { - return err - } - - return nil -} - // ConvertSliceOfStringsToMap converts a slices of string in a map // with the strings as key and an empty string as values. func ConvertSliceOfStringsToMap(input []string) map[string]struct{} { diff --git a/components/engine/pkg/integration/utils_test.go b/components/engine/pkg/integration/utils_test.go index b354ab932d..26d0e3d0c3 100644 --- a/components/engine/pkg/integration/utils_test.go +++ b/components/engine/pkg/integration/utils_test.go @@ -294,21 +294,6 @@ func TestRunCommandPipelineWithOutput(t *testing.T) { } } -// Simple simple test as it is just a passthrough for json.Unmarshal -func TestUnmarshalJSON(t *testing.T) { - emptyResult := struct{}{} - if err := UnmarshalJSON([]byte(""), &emptyResult); err == nil { - t.Fatalf("Expected an error, got nothing") - } - result := struct{ Name string }{} - if err := UnmarshalJSON([]byte(`{"name": "name"}`), &result); err != nil { - t.Fatal(err) - } - if result.Name != "name" { - t.Fatalf("Expected result.name to be 'name', was '%s'", result.Name) - } -} - func TestConvertSliceOfStringsToMap(t *testing.T) { input := []string{"a", "b"} actual := ConvertSliceOfStringsToMap(input) From eaa727c6e21f3666bbe60fbbe5c7c60016469dce Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 4 Aug 2016 12:57:34 -0400 Subject: [PATCH 2/3] Create a unified RunCommand function with Assert() Remove some run functions and replace them with the unified run command. Remove DockerCmdWithStdoutStderr Remove many duplicate runCommand functions. Also add dockerCmdWithResult() Allow Result.Assert() to ignore the error message if an exit status is expected. Fix race in DockerSuite.TestDockerInspectMultipleNetwork Fix flaky test DockerSuite.TestRunInteractiveWithRestartPolicy Signed-off-by: Daniel Nephin Upstream-commit: d7022f2b46589cb9d860219e1d8278351ba147c3 Component: engine --- .../engine/integration-cli/docker_api_test.go | 15 +- .../integration-cli/docker_cli_attach_test.go | 12 +- .../docker_cli_attach_unix_test.go | 7 +- .../integration-cli/docker_cli_build_test.go | 13 +- .../docker_cli_build_unix_test.go | 3 +- .../integration-cli/docker_cli_daemon_test.go | 26 +- .../integration-cli/docker_cli_events_test.go | 18 +- .../integration-cli/docker_cli_exec_test.go | 7 +- .../docker_cli_network_unix_test.go | 31 +- .../integration-cli/docker_cli_ps_test.go | 8 +- .../integration-cli/docker_cli_rename_test.go | 9 +- .../integration-cli/docker_cli_run_test.go | 33 +- .../integration-cli/docker_cli_volume_test.go | 13 +- .../docker_experimental_network_test.go | 20 +- .../engine/integration-cli/docker_utils.go | 114 +++-- components/engine/integration-cli/utils.go | 46 +- .../engine/pkg/integration/cmd/command.go | 266 ++++++++++++ .../pkg/integration/cmd/command_test.go | 104 +++++ .../engine/pkg/integration/dockerCmd_utils.go | 78 ---- .../pkg/integration/dockerCmd_utils_test.go | 405 ------------------ components/engine/pkg/integration/utils.go | 136 +----- .../engine/pkg/integration/utils_test.go | 198 --------- 22 files changed, 564 insertions(+), 998 deletions(-) create mode 100644 components/engine/pkg/integration/cmd/command.go create mode 100644 components/engine/pkg/integration/cmd/command_test.go delete mode 100644 components/engine/pkg/integration/dockerCmd_utils.go delete mode 100644 components/engine/pkg/integration/dockerCmd_utils_test.go diff --git a/components/engine/integration-cli/docker_api_test.go b/components/engine/integration-cli/docker_api_test.go index d09b8f193b..e42477a6e9 100644 --- a/components/engine/integration-cli/docker_api_test.go +++ b/components/engine/integration-cli/docker_api_test.go @@ -5,13 +5,13 @@ import ( "net/http" "net/http/httptest" "net/http/httputil" - "os/exec" "strconv" "strings" "time" "github.com/docker/docker/api" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/go-check/check" ) @@ -89,15 +89,12 @@ func (s *DockerSuite) TestApiDockerApiVersion(c *check.C) { defer server.Close() // Test using the env var first - cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "version") - cmd.Env = appendBaseEnv(false, "DOCKER_API_VERSION=xxx") - out, _, _ := runCommandWithOutput(cmd) - + result := icmd.RunCmd(icmd.Cmd{ + Command: binaryWithArgs([]string{"-H", server.URL[7:], "version"}), + Env: []string{"DOCKER_API_VERSION=xxx"}, + }) + result.Assert(c, icmd.Expected{Out: "API version: xxx", ExitCode: 1}) c.Assert(svrVersion, check.Equals, "/vxxx/version") - - if !strings.Contains(out, "API version: xxx") { - c.Fatalf("Out didn't have 'xxx' for the API version, had:\n%s", out) - } } func (s *DockerSuite) TestApiErrorJSON(c *check.C) { diff --git a/components/engine/integration-cli/docker_cli_attach_test.go b/components/engine/integration-cli/docker_cli_attach_test.go index b4d59e707a..6663f6aa6a 100644 --- a/components/engine/integration-cli/docker_cli_attach_test.go +++ b/components/engine/integration-cli/docker_cli_attach_test.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/go-check/check" ) @@ -161,7 +161,11 @@ func (s *DockerSuite) TestAttachPausedContainer(c *check.C) { defer unpauseAllContainers() dockerCmd(c, "run", "-d", "--name=test", "busybox", "top") dockerCmd(c, "pause", "test") - out, _, err := dockerCmdWithError("attach", "test") - c.Assert(err, checker.NotNil, check.Commentf(out)) - c.Assert(out, checker.Contains, "You cannot attach to a paused container, unpause it first") + + result := dockerCmdWithResult("attach", "test") + result.Assert(c, icmd.Expected{ + Error: "exit status 1", + ExitCode: 1, + Err: "You cannot attach to a paused container, unpause it first", + }) } diff --git a/components/engine/integration-cli/docker_cli_attach_unix_test.go b/components/engine/integration-cli/docker_cli_attach_unix_test.go index 7af761d7a3..2a07c79053 100644 --- a/components/engine/integration-cli/docker_cli_attach_unix_test.go +++ b/components/engine/integration-cli/docker_cli_attach_unix_test.go @@ -56,7 +56,6 @@ func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { } func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { - name := "detachtest" cpty, tty, err := pty.Open() @@ -80,7 +79,11 @@ func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { select { case err := <-errChan: - c.Assert(err, check.IsNil) + if err != nil { + buff := make([]byte, 200) + tty.Read(buff) + c.Fatalf("%s: %s", err, buff) + } case <-time.After(5 * time.Second): c.Fatal("timeout while detaching") } diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 5201f731af..86d4bf9c01 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/integration-cli/docker_cli_build_test.go @@ -20,6 +20,7 @@ import ( "github.com/docker/docker/builder/dockerfile/command" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/stringutils" "github.com/go-check/check" ) @@ -5063,13 +5064,11 @@ func (s *DockerSuite) TestBuildDockerfileOutsideContext(c *check.C) { filepath.Join(ctx, "dockerfile1"), filepath.Join(ctx, "dockerfile2"), } { - out, _, err := dockerCmdWithError("build", "-t", name, "--no-cache", "-f", dockerfilePath, ".") - if err == nil { - c.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out) - } - if !strings.Contains(out, "must be within the build context") && !strings.Contains(out, "Cannot locate Dockerfile") { - c.Fatalf("Unexpected error with %s. Out: %s", dockerfilePath, out) - } + result := dockerCmdWithResult("build", "-t", name, "--no-cache", "-f", dockerfilePath, ".") + result.Assert(c, icmd.Expected{ + Err: "must be within the build context", + ExitCode: 1, + }) deleteImages(name) } diff --git a/components/engine/integration-cli/docker_cli_build_unix_test.go b/components/engine/integration-cli/docker_cli_build_unix_test.go index 56ab66efae..0205a927dd 100644 --- a/components/engine/integration-cli/docker_cli_build_unix_test.go +++ b/components/engine/integration-cli/docker_cli_build_unix_test.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/docker/docker/pkg/integration" "github.com/docker/docker/pkg/integration/checker" "github.com/docker/go-units" "github.com/go-check/check" @@ -193,7 +194,7 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) { } // Get the exit status of `docker build`, check it exited because killed. - if err := buildCmd.Wait(); err != nil && !isKilled(err) { + if err := buildCmd.Wait(); err != nil && !integration.IsKilled(err) { c.Fatalf("wait failed during build run: %T %s", err, err) } diff --git a/components/engine/integration-cli/docker_cli_daemon_test.go b/components/engine/integration-cli/docker_cli_daemon_test.go index bf859c3e85..74c29495d0 100644 --- a/components/engine/integration-cli/docker_cli_daemon_test.go +++ b/components/engine/integration-cli/docker_cli_daemon_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/mount" "github.com/docker/go-units" "github.com/docker/libnetwork/iptables" @@ -908,9 +909,8 @@ func (s *DockerDaemonSuite) TestDaemonDefaultNetworkInvalidClusterConfig(c *chec c.Assert(err, checker.IsNil) // Start daemon with docker0 bridge - ifconfigCmd := exec.Command("ifconfig", defaultNetworkBridge) - _, err = runCommand(ifconfigCmd) - c.Assert(err, check.IsNil) + result := icmd.RunCommand("ifconfig", defaultNetworkBridge) + result.Assert(c, icmd.Expected{}) err = d.Restart(fmt.Sprintf("--cluster-store=%s", discoveryBackend)) c.Assert(err, checker.IsNil) @@ -2235,7 +2235,6 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithUnpausedRunningContainer(t *che pid, err := s.d.Cmd("inspect", "-f", "{{.State.Pid}}", cid) t.Assert(err, check.IsNil) - pid = strings.TrimSpace(pid) // pause the container if _, err := s.d.Cmd("pause", cid); err != nil { @@ -2248,19 +2247,18 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithUnpausedRunningContainer(t *che } // resume the container - runCmd := exec.Command(ctrBinary, "--address", "unix:///var/run/docker/libcontainerd/docker-containerd.sock", "containers", "resume", cid) - if out, ec, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf("Failed to run ctr, ExitCode: %d, err: '%v' output: '%s' cid: '%s'\n", ec, err, out, cid) - } + result := icmd.RunCommand( + ctrBinary, + "--address", "unix:///var/run/docker/libcontainerd/docker-containerd.sock", + "containers", "resume", cid) + result.Assert(t, icmd.Expected{}) // Give time to containerd to process the command if we don't // the resume event might be received after we do the inspect - pidCmd := exec.Command("kill", "-0", pid) - _, ec, _ := runCommandWithOutput(pidCmd) - for ec == 0 { - time.Sleep(1 * time.Second) - _, ec, _ = runCommandWithOutput(pidCmd) - } + waitAndAssert(t, defaultReconciliationTimeout, func(*check.C) (interface{}, check.CommentInterface) { + result := icmd.RunCommand("kill", "-0", strings.TrimSpace(pid)) + return result.ExitCode, nil + }, checker.Equals, 0) // restart the daemon if err := s.d.Start("--live-restore"); err != nil { diff --git a/components/engine/integration-cli/docker_cli_events_test.go b/components/engine/integration-cli/docker_cli_events_test.go index 260228f2cc..71864d9a57 100644 --- a/components/engine/integration-cli/docker_cli_events_test.go +++ b/components/engine/integration-cli/docker_cli_events_test.go @@ -13,6 +13,7 @@ import ( "github.com/docker/docker/daemon/events/testutils" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/go-check/check" ) @@ -57,11 +58,14 @@ func (s *DockerSuite) TestEventsUntag(c *check.C) { dockerCmd(c, "tag", image, "utest:tag2") dockerCmd(c, "rmi", "utest:tag1") dockerCmd(c, "rmi", "utest:tag2") - eventsCmd := exec.Command(dockerBinary, "events", "--since=1") - out, exitCode, _, err := runCommandWithOutputForDuration(eventsCmd, time.Duration(time.Millisecond*2500)) - c.Assert(err, checker.IsNil) - c.Assert(exitCode, checker.Equals, 0, check.Commentf("Failed to get events")) - events := strings.Split(out, "\n") + + result := icmd.RunCmd(icmd.Cmd{ + Command: []string{dockerBinary, "events", "--since=1"}, + Timeout: time.Millisecond * 2500, + }) + result.Assert(c, icmd.Expected{Timeout: true}) + + events := strings.Split(result.Stdout(), "\n") nEvents := len(events) // The last element after the split above will be an empty string, so we // get the two elements before the last, which are the untags we're @@ -275,8 +279,8 @@ func (s *DockerSuite) TestEventsImageLoad(c *check.C) { c.Assert(noImageID, checker.Equals, "", check.Commentf("Should not have any image")) dockerCmd(c, "load", "-i", "saveimg.tar") - cmd := exec.Command("rm", "-rf", "saveimg.tar") - runCommand(cmd) + result := icmd.RunCommand("rm", "-rf", "saveimg.tar") + result.Assert(c, icmd.Expected{}) out, _ = dockerCmd(c, "images", "-q", "--no-trunc", myImageName) imageID := strings.TrimSpace(out) diff --git a/components/engine/integration-cli/docker_cli_exec_test.go b/components/engine/integration-cli/docker_cli_exec_test.go index 77472d47b4..785a49094f 100644 --- a/components/engine/integration-cli/docker_cli_exec_test.go +++ b/components/engine/integration-cli/docker_cli_exec_test.go @@ -16,6 +16,7 @@ import ( "time" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/go-check/check" ) @@ -122,10 +123,8 @@ func (s *DockerSuite) TestExecEnv(c *check.C) { func (s *DockerSuite) TestExecExitStatus(c *check.C) { runSleepingContainer(c, "-d", "--name", "top") - // Test normal (non-detached) case first - cmd := exec.Command(dockerBinary, "exec", "top", "sh", "-c", "exit 23") - ec, _ := runCommand(cmd) - c.Assert(ec, checker.Equals, 23) + result := icmd.RunCommand(dockerBinary, "exec", "top", "sh", "-c", "exit 23") + result.Assert(c, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) } func (s *DockerSuite) TestExecPausedContainer(c *check.C) { diff --git a/components/engine/integration-cli/docker_cli_network_unix_test.go b/components/engine/integration-cli/docker_cli_network_unix_test.go index faf798782f..2899055ebc 100644 --- a/components/engine/integration-cli/docker_cli_network_unix_test.go +++ b/components/engine/integration-cli/docker_cli_network_unix_test.go @@ -15,6 +15,7 @@ import ( "time" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" "github.com/docker/engine-api/types" @@ -477,27 +478,33 @@ func (s *DockerSuite) TestDockerNetworkInspectWithID(c *check.C) { } func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) { - out, _ := dockerCmd(c, "network", "inspect", "host", "none") + result := dockerCmdWithResult("network", "inspect", "host", "none") + result.Assert(c, icmd.Expected{}) + networkResources := []types.NetworkResource{} - err := json.Unmarshal([]byte(out), &networkResources) + err := json.Unmarshal([]byte(result.Stdout()), &networkResources) c.Assert(err, check.IsNil) c.Assert(networkResources, checker.HasLen, 2) // Should print an error, return an exitCode 1 *but* should print the host network - out, exitCode, err := dockerCmdWithError("network", "inspect", "host", "nonexistent") - c.Assert(err, checker.NotNil) - c.Assert(exitCode, checker.Equals, 1) - c.Assert(out, checker.Contains, "Error: No such network: nonexistent") + result = dockerCmdWithResult("network", "inspect", "host", "nonexistent") + result.Assert(c, icmd.Expected{ + ExitCode: 1, + Err: "Error: No such network: nonexistent", + Out: "host", + }) + networkResources = []types.NetworkResource{} - inspectOut := strings.SplitN(out, "\nError: No such network: nonexistent\n", 2)[0] - err = json.Unmarshal([]byte(inspectOut), &networkResources) + err = json.Unmarshal([]byte(result.Stdout()), &networkResources) c.Assert(networkResources, checker.HasLen, 1) // Should print an error and return an exitCode, nothing else - out, exitCode, err = dockerCmdWithError("network", "inspect", "nonexistent") - c.Assert(err, checker.NotNil) - c.Assert(exitCode, checker.Equals, 1) - c.Assert(out, checker.Contains, "Error: No such network: nonexistent") + result = dockerCmdWithResult("network", "inspect", "nonexistent") + result.Assert(c, icmd.Expected{ + ExitCode: 1, + Err: "Error: No such network: nonexistent", + Out: "[]", + }) } func (s *DockerSuite) TestDockerInspectNetworkWithContainerName(c *check.C) { diff --git a/components/engine/integration-cli/docker_cli_ps_test.go b/components/engine/integration-cli/docker_cli_ps_test.go index 6e1756f06f..9c2b8a2389 100644 --- a/components/engine/integration-cli/docker_cli_ps_test.go +++ b/components/engine/integration-cli/docker_cli_ps_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/stringid" "github.com/go-check/check" ) @@ -204,8 +205,11 @@ func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) { containerOut = strings.TrimSpace(out) c.Assert(containerOut, checker.Equals, secondID) - out, _, _ = dockerCmdWithTimeout(time.Second*60, "ps", "-a", "-q", "--filter=status=rubbish") - c.Assert(out, checker.Contains, "Unrecognised filter value for status", check.Commentf("Expected error response due to invalid status filter output: %q", out)) + result := dockerCmdWithTimeout(time.Second*60, "ps", "-a", "-q", "--filter=status=rubbish") + result.Assert(c, icmd.Expected{ + ExitCode: 1, + Err: "Unrecognised filter value for status", + }) // Windows doesn't support pausing of containers if daemonPlatform != "windows" { diff --git a/components/engine/integration-cli/docker_cli_rename_test.go b/components/engine/integration-cli/docker_cli_rename_test.go index f1a16fae52..183cc3f868 100644 --- a/components/engine/integration-cli/docker_cli_rename_test.go +++ b/components/engine/integration-cli/docker_cli_rename_test.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/stringid" "github.com/go-check/check" ) @@ -61,9 +62,11 @@ func (s *DockerSuite) TestRenameCheckNames(c *check.C) { name := inspectField(c, newName, "Name") c.Assert(name, checker.Equals, "/"+newName, check.Commentf("Failed to rename container %s", name)) - name, err := inspectFieldWithError("first_name", "Name") - c.Assert(err, checker.NotNil, check.Commentf(name)) - c.Assert(err.Error(), checker.Contains, "No such container, image or task: first_name") + result := dockerCmdWithResult("inspect", "-f={{.Name}}", "first_name") + result.Assert(c, icmd.Expected{ + ExitCode: 1, + Err: "No such container, image or task: first_name", + }) } func (s *DockerSuite) TestRenameInvalidName(c *check.C) { diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 965bdb9644..00a8f0d82f 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringutils" @@ -1856,32 +1857,18 @@ func (s *DockerSuite) TestRunExitOnStdinClose(c *check.C) { // Test run -i --restart xxx doesn't hang func (s *DockerSuite) TestRunInteractiveWithRestartPolicy(c *check.C) { name := "test-inter-restart" - runCmd := exec.Command(dockerBinary, "run", "-i", "--name", name, "--restart=always", "busybox", "sh") - stdin, err := runCmd.StdinPipe() - c.Assert(err, checker.IsNil) - - err = runCmd.Start() - c.Assert(err, checker.IsNil) - c.Assert(waitRun(name), check.IsNil) - - _, err = stdin.Write([]byte("exit 11\n")) - c.Assert(err, checker.IsNil) - - finish := make(chan error) - go func() { - finish <- runCmd.Wait() - close(finish) + result := icmd.StartCmd(icmd.Cmd{ + Command: []string{dockerBinary, "run", "-i", "--name", name, "--restart=always", "busybox", "sh"}, + Stdin: bytes.NewBufferString("exit 11"), + }) + c.Assert(result.Error, checker.IsNil) + defer func() { + dockerCmdWithResult("stop", name).Assert(c, icmd.Expected{}) }() - delay := 10 * time.Second - select { - case <-finish: - case <-time.After(delay): - c.Fatal("run -i --restart hangs") - } - c.Assert(waitRun(name), check.IsNil) - dockerCmd(c, "stop", name) + result = icmd.WaitOnCmd(10*time.Second, result) + result.Assert(c, icmd.Expected{ExitCode: 11}) } // Test for #2267 diff --git a/components/engine/integration-cli/docker_cli_volume_test.go b/components/engine/integration-cli/docker_cli_volume_test.go index dc081763da..479d61a68a 100644 --- a/components/engine/integration-cli/docker_cli_volume_test.go +++ b/components/engine/integration-cli/docker_cli_volume_test.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/go-check/check" ) @@ -55,14 +56,18 @@ func (s *DockerSuite) TestVolumeCliInspectMulti(c *check.C) { dockerCmd(c, "volume", "create", "--name", "test2") dockerCmd(c, "volume", "create", "--name", "not-shown") - out, _, err := dockerCmdWithError("volume", "inspect", "--format='{{ .Name }}'", "test1", "test2", "doesntexist", "not-shown") - c.Assert(err, checker.NotNil) + result := dockerCmdWithResult("volume", "inspect", "--format={{ .Name }}", "test1", "test2", "doesntexist", "not-shown") + result.Assert(c, icmd.Expected{ + ExitCode: 1, + Err: "No such volume: doesntexist", + }) + + out := result.Stdout() outArr := strings.Split(strings.TrimSpace(out), "\n") - c.Assert(len(outArr), check.Equals, 3, check.Commentf("\n%s", out)) + c.Assert(len(outArr), check.Equals, 2, check.Commentf("\n%s", out)) c.Assert(out, checker.Contains, "test1") c.Assert(out, checker.Contains, "test2") - c.Assert(out, checker.Contains, "Error: No such volume: doesntexist") c.Assert(out, checker.Not(checker.Contains), "not-shown") } diff --git a/components/engine/integration-cli/docker_experimental_network_test.go b/components/engine/integration-cli/docker_experimental_network_test.go index f33dbd1c84..56aa9a7e0d 100644 --- a/components/engine/integration-cli/docker_experimental_network_test.go +++ b/components/engine/integration-cli/docker_experimental_network_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/docker/docker/pkg/integration/checker" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/parsers/kernel" "github.com/go-check/check" ) @@ -401,10 +402,11 @@ func (s *DockerSuite) TestDockerNetworkMacVlanBridgeInternalMode(c *check.C) { c.Assert(waitRun("second"), check.IsNil) // access outside of the network should fail - _, _, err := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") - c.Assert(err, check.NotNil) + result := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") + result.Assert(c, icmd.Expected{Timeout: true}) + // intra-network communications should succeed - _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") c.Assert(err, check.IsNil) } @@ -440,10 +442,10 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL2InternalMode(c *check.C) { c.Assert(waitRun("second"), check.IsNil) // access outside of the network should fail - _, _, err := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") - c.Assert(err, check.NotNil) + result := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") + c.Assert(result.Error, check.NotNil) // intra-network communications should succeed - _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") c.Assert(err, check.IsNil) } @@ -481,10 +483,10 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL3InternalMode(c *check.C) { c.Assert(waitRun("second"), check.IsNil) // access outside of the network should fail - _, _, err := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") - c.Assert(err, check.NotNil) + result := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") + c.Assert(result.Error, check.NotNil) // intra-network communications should succeed - _, _, err = dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") + _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") c.Assert(err, check.IsNil) } diff --git a/components/engine/integration-cli/docker_utils.go b/components/engine/integration-cli/docker_utils.go index 238929c16c..00e3d74f5b 100644 --- a/components/engine/integration-cli/docker_utils.go +++ b/components/engine/integration-cli/docker_utils.go @@ -24,7 +24,7 @@ import ( "github.com/docker/docker/opts" "github.com/docker/docker/pkg/httputils" - "github.com/docker/docker/pkg/integration" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" "github.com/docker/engine-api/types" @@ -229,15 +229,7 @@ func readBody(b io.ReadCloser) ([]byte, error) { } func deleteContainer(container string) error { - container = strings.TrimSpace(strings.Replace(container, "\n", " ", -1)) - rmArgs := strings.Split(fmt.Sprintf("rm -fv %v", container), " ") - exitCode, err := runCommand(exec.Command(dockerBinary, rmArgs...)) - // set error manually if not set - if exitCode != 0 && err == nil { - err = fmt.Errorf("failed to remove container: `docker rm` exit is non-zero") - } - - return err + return icmd.RunCommand(dockerBinary, "rm", "-fv", container).Error } func getAllContainers() (string, error) { @@ -398,13 +390,7 @@ func getSliceOfPausedContainers() ([]string, error) { } func unpauseContainer(container string) error { - unpauseCmd := exec.Command(dockerBinary, "unpause", container) - exitCode, err := runCommand(unpauseCmd) - if exitCode != 0 && err == nil { - err = fmt.Errorf("failed to unpause container") - } - - return err + return icmd.RunCommand(dockerBinary, "unpause", container).Error } func unpauseAllContainers() error { @@ -428,24 +414,12 @@ func unpauseAllContainers() error { } func deleteImages(images ...string) error { - args := []string{"rmi", "-f"} - args = append(args, images...) - rmiCmd := exec.Command(dockerBinary, args...) - exitCode, err := runCommand(rmiCmd) - // set error manually if not set - if exitCode != 0 && err == nil { - err = fmt.Errorf("failed to remove image: `docker rmi` exit is non-zero") - } - return err + args := []string{dockerBinary, "rmi", "-f"} + return icmd.RunCmd(icmd.Cmd{Command: append(args, images...)}).Error } func imageExists(image string) error { - inspectCmd := exec.Command(dockerBinary, "inspect", image) - exitCode, err := runCommand(inspectCmd) - if exitCode != 0 && err == nil { - err = fmt.Errorf("couldn't find image %q", image) - } - return err + return icmd.RunCommand(dockerBinary, "inspect", image).Error } func pullImageIfNotExist(image string) error { @@ -464,33 +438,49 @@ func dockerCmdWithError(args ...string) (string, int, error) { if err := validateArgs(args...); err != nil { return "", 0, err } - out, code, err := integration.DockerCmdWithError(dockerBinary, args...) - if err != nil { - err = fmt.Errorf("%v: %s", err, out) + result := icmd.RunCommand(dockerBinary, args...) + if result.Error != nil { + return result.Combined(), result.ExitCode, fmt.Errorf(result.Fails(icmd.Expected{})) } - return out, code, err + return result.Combined(), result.ExitCode, result.Error } func dockerCmdWithStdoutStderr(c *check.C, args ...string) (string, string, int) { if err := validateArgs(args...); err != nil { c.Fatalf(err.Error()) } - return integration.DockerCmdWithStdoutStderr(dockerBinary, c, args...) + + result := icmd.RunCommand(dockerBinary, args...) + // TODO: why is c ever nil? + if c != nil { + result.Assert(c, icmd.Expected{}) + } + return result.Stdout(), result.Stderr(), result.ExitCode } func dockerCmd(c *check.C, args ...string) (string, int) { if err := validateArgs(args...); err != nil { c.Fatalf(err.Error()) } - return integration.DockerCmd(dockerBinary, c, args...) + result := icmd.RunCommand(dockerBinary, args...) + result.Assert(c, icmd.Expected{}) + return result.Combined(), result.ExitCode +} + +func dockerCmdWithResult(args ...string) *icmd.Result { + return icmd.RunCommand(dockerBinary, args...) +} + +func binaryWithArgs(args []string) []string { + return append([]string{dockerBinary}, args...) } // execute a docker command with a timeout -func dockerCmdWithTimeout(timeout time.Duration, args ...string) (string, int, error) { +func dockerCmdWithTimeout(timeout time.Duration, args ...string) *icmd.Result { if err := validateArgs(args...); err != nil { - return "", 0, err + return &icmd.Result{Error: err} } - return integration.DockerCmdWithTimeout(dockerBinary, timeout, args...) + return icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args), Timeout: timeout}) } // execute a docker command in a directory @@ -498,15 +488,20 @@ func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error if err := validateArgs(args...); err != nil { c.Fatalf(err.Error()) } - return integration.DockerCmdInDir(dockerBinary, path, args...) + result := icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args), Dir: path}) + return result.Combined(), result.ExitCode, result.Error } // execute a docker command in a directory with a timeout -func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...string) (string, int, error) { +func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...string) *icmd.Result { if err := validateArgs(args...); err != nil { - return "", 0, err + return &icmd.Result{Error: err} } - return integration.DockerCmdInDirWithTimeout(dockerBinary, timeout, path, args...) + return icmd.RunCmd(icmd.Cmd{ + Command: binaryWithArgs(args), + Timeout: timeout, + Dir: path, + }) } // validateArgs is a checker to ensure tests are not running commands which are @@ -1355,17 +1350,12 @@ func buildImageCmdArgs(args []string, name, dockerfile string, useCache bool) *e } func waitForContainer(contID string, args ...string) error { - args = append([]string{"run", "--name", contID}, args...) - cmd := exec.Command(dockerBinary, args...) - if _, err := runCommand(cmd); err != nil { - return err + args = append([]string{dockerBinary, "run", "--name", contID}, args...) + result := icmd.RunCmd(icmd.Cmd{Command: args}) + if result.Error != nil { + return result.Error } - - if err := waitRun(contID); err != nil { - return err - } - - return nil + return waitRun(contID) } // waitRun will wait for the specified container to be running, maximum 5 seconds. @@ -1391,22 +1381,22 @@ func waitInspectWithArgs(name, expr, expected string, timeout time.Duration, arg args := append(arg, "inspect", "-f", expr, name) for { - cmd := exec.Command(dockerBinary, args...) - out, _, err := runCommandWithOutput(cmd) - if err != nil { - if !strings.Contains(out, "No such") { - return fmt.Errorf("error executing docker inspect: %v\n%s", err, out) + result := icmd.RunCommand(dockerBinary, args...) + if result.Error != nil { + if !strings.Contains(result.Stderr(), "No such") { + return fmt.Errorf("error executing docker inspect: %v\n%s", + result.Stderr(), result.Stdout()) } select { case <-after: - return err + return result.Error default: time.Sleep(10 * time.Millisecond) continue } } - out = strings.TrimSpace(out) + out := strings.TrimSpace(result.Stdout()) if out == expected { break } diff --git a/components/engine/integration-cli/utils.go b/components/engine/integration-cli/utils.go index eac6379bcd..87d48e41b0 100644 --- a/components/engine/integration-cli/utils.go +++ b/components/engine/integration-cli/utils.go @@ -7,6 +7,7 @@ import ( "time" "github.com/docker/docker/pkg/integration" + "github.com/docker/docker/pkg/integration/cmd" ) func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) { @@ -16,36 +17,33 @@ func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) { return "", "/" } -func getExitCode(err error) (int, error) { - return integration.GetExitCode(err) +// TODO: update code to call cmd.RunCmd directly, and remove this function +func runCommandWithOutput(execCmd *exec.Cmd) (string, int, error) { + result := cmd.RunCmd(transformCmd(execCmd)) + return result.Combined(), result.ExitCode, result.Error } -func processExitCode(err error) (exitCode int) { - return integration.ProcessExitCode(err) +// TODO: update code to call cmd.RunCmd directly, and remove this function +func runCommandWithStdoutStderr(execCmd *exec.Cmd) (string, string, int, error) { + result := cmd.RunCmd(transformCmd(execCmd)) + return result.Stdout(), result.Stderr(), result.ExitCode, result.Error } -func isKilled(err error) bool { - return integration.IsKilled(err) +// TODO: update code to call cmd.RunCmd directly, and remove this function +func runCommand(execCmd *exec.Cmd) (exitCode int, err error) { + result := cmd.RunCmd(transformCmd(execCmd)) + return result.ExitCode, result.Error } -func runCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) { - return integration.RunCommandWithOutput(cmd) -} - -func runCommandWithStdoutStderr(cmd *exec.Cmd) (stdout string, stderr string, exitCode int, err error) { - return integration.RunCommandWithStdoutStderr(cmd) -} - -func runCommandWithOutputForDuration(cmd *exec.Cmd, duration time.Duration) (output string, exitCode int, timedOut bool, err error) { - return integration.RunCommandWithOutputForDuration(cmd, duration) -} - -func runCommandWithOutputAndTimeout(cmd *exec.Cmd, timeout time.Duration) (output string, exitCode int, err error) { - return integration.RunCommandWithOutputAndTimeout(cmd, timeout) -} - -func runCommand(cmd *exec.Cmd) (exitCode int, err error) { - return integration.RunCommand(cmd) +// Temporary shim for migrating commands to the new function +func transformCmd(execCmd *exec.Cmd) cmd.Cmd { + return cmd.Cmd{ + Command: execCmd.Args, + Env: execCmd.Env, + Dir: execCmd.Dir, + Stdin: execCmd.Stdin, + Stdout: execCmd.Stdout, + } } func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode int, err error) { diff --git a/components/engine/pkg/integration/cmd/command.go b/components/engine/pkg/integration/cmd/command.go new file mode 100644 index 0000000000..8fa8f79f05 --- /dev/null +++ b/components/engine/pkg/integration/cmd/command.go @@ -0,0 +1,266 @@ +package cmd + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "path/filepath" + "runtime" + "strings" + "syscall" + "time" +) + +type testingT interface { + Fatalf(string, ...interface{}) +} + +const ( + // None is a token to inform Result.Assert that the output should be empty + None string = "" +) + +// GetExitCode returns the ExitStatus of the specified error if its type is +// exec.ExitError, returns 0 and an error otherwise. +func GetExitCode(err error) (int, error) { + exitCode := 0 + if exiterr, ok := err.(*exec.ExitError); ok { + if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok { + return procExit.ExitStatus(), nil + } + } + return exitCode, fmt.Errorf("failed to get exit code") +} + +// ProcessExitCode process the specified error and returns the exit status code +// if the error was of type exec.ExitError, returns nothing otherwise. +func ProcessExitCode(err error) (exitCode int) { + if err != nil { + var exiterr error + if exitCode, exiterr = GetExitCode(err); exiterr != nil { + // TODO: Fix this so we check the error's text. + // we've failed to retrieve exit code, so we set it to 127 + exitCode = 127 + } + } + return +} + +// Result stores the result of running a command +type Result struct { + Cmd *exec.Cmd + ExitCode int + Error error + // Timeout is true if the command was killed because it ran for too long + Timeout bool + outBuffer *bytes.Buffer + errBuffer *bytes.Buffer +} + +// Assert compares the Result against the Expected struct, and fails the test if +// any of the expcetations are not met. +func (r *Result) Assert(t testingT, exp Expected) { + fails := r.Fails(exp) + if fails == "" { + return + } + + _, file, line, _ := runtime.Caller(1) + t.Fatalf("at %s:%d\n%s", filepath.Base(file), line, fails) +} + +// Fails returns a formatted string which reports on any failed expectations +func (r *Result) Fails(exp Expected) string { + errors := []string{} + add := func(format string, args ...interface{}) { + errors = append(errors, fmt.Sprintf(format, args...)) + } + + if exp.ExitCode != r.ExitCode { + add("ExitCode was %d expected %d", r.ExitCode, exp.ExitCode) + } + if exp.Timeout != r.Timeout { + if exp.Timeout { + add("Expected command to timeout") + } else { + add("Expected command to finish, but it hit the timeout") + } + } + if !matchOutput(exp.Out, r.Stdout()) { + add("Expected stdout to contain %q", exp.Out) + } + if !matchOutput(exp.Err, r.Stderr()) { + add("Expected stderr to contain %q", exp.Err) + } + switch { + // If a non-zero exit code is expected there is going to be an error. + // Don't require an error message as well as an exit code because the + // error message is going to be "exit status which is not useful + case exp.Error == "" && exp.ExitCode != 0: + case exp.Error == "" && r.Error != nil: + add("Expected no error") + case exp.Error != "" && r.Error == nil: + add("Expected error to contain %q, but there was no error", exp.Error) + case exp.Error != "" && !strings.Contains(r.Error.Error(), exp.Error): + add("Expected error to contain %q", exp.Error) + } + + if len(errors) == 0 { + return "" + } + return fmt.Sprintf("%s\nFailures:\n%s\n", r, strings.Join(errors, "\n")) +} + +func matchOutput(expected string, actual string) bool { + switch expected { + case None: + return actual == "" + default: + return strings.Contains(actual, expected) + } +} + +func (r *Result) String() string { + var timeout string + if r.Timeout { + timeout = " (timeout)" + } + + return fmt.Sprintf(` +Command: %s +ExitCode: %d%s, Error: %s +Stdout: %v +Stderr: %v +`, + strings.Join(r.Cmd.Args, " "), + r.ExitCode, + timeout, + r.Error, + r.Stdout(), + r.Stderr()) +} + +// Expected is the expected output from a Command. This struct is compared to a +// Result struct by Result.Assert(). +type Expected struct { + ExitCode int + Timeout bool + Error string + Out string + Err string +} + +// Stdout returns the stdout of the process as a string +func (r *Result) Stdout() string { + return r.outBuffer.String() +} + +// Stderr returns the stderr of the process as a string +func (r *Result) Stderr() string { + return r.errBuffer.String() +} + +// Combined returns the stdout and stderr combined into a single string +func (r *Result) Combined() string { + return r.outBuffer.String() + r.errBuffer.String() +} + +// SetExitError sets Error and ExitCode based on Error +func (r *Result) SetExitError(err error) { + if err == nil { + return + } + r.Error = err + r.ExitCode = ProcessExitCode(err) +} + +// Cmd is a command to run. One of Command or CommandArgs can be used to set the +// comand line. Command will be paased to shlex and split into a string slice. +// CommandArgs is an already split command line. +type Cmd struct { + Command []string + Timeout time.Duration + Stdin io.Reader + Stdout io.Writer + Dir string + Env []string +} + +// RunCmd runs a command and returns a Result +func RunCmd(cmd Cmd) *Result { + result := StartCmd(cmd) + if result.Error != nil { + return result + } + return WaitOnCmd(cmd.Timeout, result) +} + +// RunCommand parses a command line and runs it, returning a result +func RunCommand(command string, args ...string) *Result { + return RunCmd(Cmd{Command: append([]string{command}, args...)}) +} + +// StartCmd starts a command, but doesn't wait for it to finish +func StartCmd(cmd Cmd) *Result { + result := buildCmd(cmd) + if result.Error != nil { + return result + } + result.SetExitError(result.Cmd.Start()) + return result +} + +func buildCmd(cmd Cmd) *Result { + var execCmd *exec.Cmd + switch len(cmd.Command) { + case 1: + execCmd = exec.Command(cmd.Command[0]) + default: + execCmd = exec.Command(cmd.Command[0], cmd.Command[1:]...) + } + outBuffer := new(bytes.Buffer) + errBuffer := new(bytes.Buffer) + + execCmd.Stdin = cmd.Stdin + execCmd.Dir = cmd.Dir + execCmd.Env = cmd.Env + if cmd.Stdout != nil { + execCmd.Stdout = io.MultiWriter(outBuffer, cmd.Stdout) + } else { + execCmd.Stdout = outBuffer + } + execCmd.Stderr = errBuffer + return &Result{ + Cmd: execCmd, + outBuffer: outBuffer, + errBuffer: errBuffer, + } +} + +// WaitOnCmd waits for a command to complete. If timeout is non-nil then +// only wait until the timeout. +func WaitOnCmd(timeout time.Duration, result *Result) *Result { + if timeout == time.Duration(0) { + result.SetExitError(result.Cmd.Wait()) + return result + } + + done := make(chan error, 1) + // Wait for command to exit in a goroutine + go func() { + done <- result.Cmd.Wait() + }() + + select { + case <-time.After(timeout): + killErr := result.Cmd.Process.Kill() + if killErr != nil { + fmt.Printf("failed to kill (pid=%d): %v\n", result.Cmd.Process.Pid, killErr) + } + result.Timeout = true + case err := <-done: + result.SetExitError(err) + } + return result +} diff --git a/components/engine/pkg/integration/cmd/command_test.go b/components/engine/pkg/integration/cmd/command_test.go new file mode 100644 index 0000000000..4f4174d709 --- /dev/null +++ b/components/engine/pkg/integration/cmd/command_test.go @@ -0,0 +1,104 @@ +package cmd + +import ( + "runtime" + "strings" + "testing" + "time" + + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestRunCommand(t *testing.T) { + // TODO Windows: Port this test + if runtime.GOOS == "windows" { + t.Skip("Needs porting to Windows") + } + + result := RunCommand("ls") + result.Assert(t, Expected{}) + + result = RunCommand("doesnotexists") + expectedError := `exec: "doesnotexists": executable file not found` + result.Assert(t, Expected{ExitCode: 127, Error: expectedError}) + + result = RunCommand("ls", "-z") + result.Assert(t, Expected{ + ExitCode: 2, + Error: "exit status 2", + Err: "invalid option", + }) + assert.Contains(t, result.Combined(), "invalid option") +} + +func TestRunCommandWithCombined(t *testing.T) { + // TODO Windows: Port this test + if runtime.GOOS == "windows" { + t.Skip("Needs porting to Windows") + } + + result := RunCommand("ls", "-a") + result.Assert(t, Expected{}) + + assert.Contains(t, result.Combined(), "..") + assert.Contains(t, result.Stdout(), "..") +} + +func TestRunCommandWithTimeoutFinished(t *testing.T) { + // TODO Windows: Port this test + if runtime.GOOS == "windows" { + t.Skip("Needs porting to Windows") + } + + result := RunCmd(Cmd{ + Command: []string{"ls", "-a"}, + Timeout: 50 * time.Millisecond, + }) + result.Assert(t, Expected{Out: ".."}) +} + +func TestRunCommandWithTimeoutKilled(t *testing.T) { + // TODO Windows: Port this test + if runtime.GOOS == "windows" { + t.Skip("Needs porting to Windows") + } + + command := []string{"sh", "-c", "while true ; do echo 1 ; sleep .1 ; done"} + result := RunCmd(Cmd{Command: command, Timeout: 500 * time.Millisecond}) + result.Assert(t, Expected{Timeout: true}) + + ones := strings.Split(result.Stdout(), "\n") + assert.Equal(t, len(ones), 6) +} + +func TestRunCommandWithErrors(t *testing.T) { + result := RunCommand("/foobar") + result.Assert(t, Expected{Error: "foobar", ExitCode: 127}) +} + +func TestRunCommandWithStdoutStderr(t *testing.T) { + result := RunCommand("echo", "hello", "world") + result.Assert(t, Expected{Out: "hello world\n", Err: None}) +} + +func TestRunCommandWithStdoutStderrError(t *testing.T) { + result := RunCommand("doesnotexists") + + expected := `exec: "doesnotexists": executable file not found` + result.Assert(t, Expected{Out: None, Err: None, ExitCode: 127, Error: expected}) + + switch runtime.GOOS { + case "windows": + expected = "ls: unknown option" + default: + expected = "ls: invalid option" + } + + result = RunCommand("ls", "-z") + result.Assert(t, Expected{ + Out: None, + Err: expected, + ExitCode: 2, + Error: "exit status 2", + }) +} diff --git a/components/engine/pkg/integration/dockerCmd_utils.go b/components/engine/pkg/integration/dockerCmd_utils.go deleted file mode 100644 index fab3e062dd..0000000000 --- a/components/engine/pkg/integration/dockerCmd_utils.go +++ /dev/null @@ -1,78 +0,0 @@ -package integration - -import ( - "fmt" - "os/exec" - "strings" - "time" - - "github.com/go-check/check" -) - -// We use the elongated quote mechanism for quoting error returns as -// the use of strconv.Quote or %q in fmt.Errorf will escape characters. This -// has a big downside on Windows where the args include paths, so instead -// of something like c:\directory\file.txt, the output would be -// c:\\directory\\file.txt. This is highly misleading. -const quote = `"` - -var execCommand = exec.Command - -// DockerCmdWithError executes a docker command that is supposed to fail and returns -// the output, the exit code and the error. -func DockerCmdWithError(dockerBinary string, args ...string) (string, int, error) { - return RunCommandWithOutput(execCommand(dockerBinary, args...)) -} - -// DockerCmdWithStdoutStderr executes a docker command and returns the content of the -// stdout, stderr and the exit code. If a check.C is passed, it will fail and stop tests -// if the error is not nil. -func DockerCmdWithStdoutStderr(dockerBinary string, c *check.C, args ...string) (string, string, int) { - stdout, stderr, status, err := RunCommandWithStdoutStderr(execCommand(dockerBinary, args...)) - if c != nil { - c.Assert(err, check.IsNil, check.Commentf(quote+"%v"+quote+" failed with errors: %s, %v", strings.Join(args, " "), stderr, err)) - } - return stdout, stderr, status -} - -// DockerCmd executes a docker command and returns the output and the exit code. If the -// command returns an error, it will fail and stop the tests. -func DockerCmd(dockerBinary string, c *check.C, args ...string) (string, int) { - out, status, err := RunCommandWithOutput(execCommand(dockerBinary, args...)) - c.Assert(err, check.IsNil, check.Commentf(quote+"%v"+quote+" failed with errors: %s, %v", strings.Join(args, " "), out, err)) - return out, status -} - -// DockerCmdWithTimeout executes a docker command with a timeout, and returns the output, -// the exit code and the error (if any). -func DockerCmdWithTimeout(dockerBinary string, timeout time.Duration, args ...string) (string, int, error) { - out, status, err := RunCommandWithOutputAndTimeout(execCommand(dockerBinary, args...), timeout) - if err != nil { - return out, status, fmt.Errorf(quote+"%v"+quote+" failed with errors: %v : %q", strings.Join(args, " "), err, out) - } - return out, status, err -} - -// DockerCmdInDir executes a docker command in a directory and returns the output, the -// exit code and the error (if any). -func DockerCmdInDir(dockerBinary string, path string, args ...string) (string, int, error) { - dockerCommand := execCommand(dockerBinary, args...) - dockerCommand.Dir = path - out, status, err := RunCommandWithOutput(dockerCommand) - if err != nil { - return out, status, fmt.Errorf(quote+"%v"+quote+" failed with errors: %v : %q", strings.Join(args, " "), err, out) - } - return out, status, err -} - -// DockerCmdInDirWithTimeout executes a docker command in a directory with a timeout and -// returns the output, the exit code and the error (if any). -func DockerCmdInDirWithTimeout(dockerBinary string, timeout time.Duration, path string, args ...string) (string, int, error) { - dockerCommand := execCommand(dockerBinary, args...) - dockerCommand.Dir = path - out, status, err := RunCommandWithOutputAndTimeout(dockerCommand, timeout) - if err != nil { - return out, status, fmt.Errorf(quote+"%v"+quote+" failed with errors: %v : %q", strings.Join(args, " "), err, out) - } - return out, status, err -} diff --git a/components/engine/pkg/integration/dockerCmd_utils_test.go b/components/engine/pkg/integration/dockerCmd_utils_test.go deleted file mode 100644 index 3dd5d11461..0000000000 --- a/components/engine/pkg/integration/dockerCmd_utils_test.go +++ /dev/null @@ -1,405 +0,0 @@ -package integration - -import ( - "fmt" - "os" - "os/exec" - "testing" - - "io/ioutil" - "strings" - "time" - - "github.com/go-check/check" -) - -const dockerBinary = "docker" - -// Setup go-check for this test -func Test(t *testing.T) { - check.TestingT(t) -} - -func init() { - check.Suite(&DockerCmdSuite{}) -} - -type DockerCmdSuite struct{} - -// Fake the exec.Command to use our mock. -func (s *DockerCmdSuite) SetUpTest(c *check.C) { - execCommand = fakeExecCommand -} - -// And bring it back to normal after the test. -func (s *DockerCmdSuite) TearDownTest(c *check.C) { - execCommand = exec.Command -} - -// DockerCmdWithError tests - -func (s *DockerCmdSuite) TestDockerCmdWithError(c *check.C) { - cmds := []struct { - binary string - args []string - expectedOut string - expectedExitCode int - expectedError error - }{ - { - "doesnotexists", - []string{}, - "Command doesnotexists not found.", - 1, - fmt.Errorf("exit status 1"), - }, - { - dockerBinary, - []string{"an", "error"}, - "an error has occurred", - 1, - fmt.Errorf("exit status 1"), - }, - { - dockerBinary, - []string{"an", "exitCode", "127"}, - "an error has occurred with exitCode 127", - 127, - fmt.Errorf("exit status 127"), - }, - { - dockerBinary, - []string{"run", "-ti", "ubuntu", "echo", "hello"}, - "hello", - 0, - nil, - }, - } - for _, cmd := range cmds { - out, exitCode, error := DockerCmdWithError(cmd.binary, cmd.args...) - c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out)) - c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode)) - if cmd.expectedError != nil { - c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError)) - c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error())) - } else { - c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error)) - } - } -} - -// DockerCmdWithStdoutStderr tests - -type dockerCmdWithStdoutStderrErrorSuite struct{} - -func (s *dockerCmdWithStdoutStderrErrorSuite) Test(c *check.C) { - // Should fail, the test too - DockerCmdWithStdoutStderr(dockerBinary, c, "an", "error") -} - -type dockerCmdWithStdoutStderrSuccessSuite struct{} - -func (s *dockerCmdWithStdoutStderrSuccessSuite) Test(c *check.C) { - stdout, stderr, exitCode := DockerCmdWithStdoutStderr(dockerBinary, c, "run", "-ti", "ubuntu", "echo", "hello") - c.Assert(stdout, check.Equals, "hello") - c.Assert(stderr, check.Equals, "") - c.Assert(exitCode, check.Equals, 0) - -} - -func (s *DockerCmdSuite) TestDockerCmdWithStdoutStderrError(c *check.C) { - // Run error suite, should fail. - output := String{} - result := check.Run(&dockerCmdWithStdoutStderrErrorSuite{}, &check.RunConf{Output: &output}) - c.Check(result.Succeeded, check.Equals, 0) - c.Check(result.Failed, check.Equals, 1) -} - -func (s *DockerCmdSuite) TestDockerCmdWithStdoutStderrSuccess(c *check.C) { - // Run error suite, should fail. - output := String{} - result := check.Run(&dockerCmdWithStdoutStderrSuccessSuite{}, &check.RunConf{Output: &output}) - c.Check(result.Succeeded, check.Equals, 1) - c.Check(result.Failed, check.Equals, 0) -} - -// DockerCmd tests - -type dockerCmdErrorSuite struct{} - -func (s *dockerCmdErrorSuite) Test(c *check.C) { - // Should fail, the test too - DockerCmd(dockerBinary, c, "an", "error") -} - -type dockerCmdSuccessSuite struct{} - -func (s *dockerCmdSuccessSuite) Test(c *check.C) { - stdout, exitCode := DockerCmd(dockerBinary, c, "run", "-ti", "ubuntu", "echo", "hello") - c.Assert(stdout, check.Equals, "hello") - c.Assert(exitCode, check.Equals, 0) - -} - -func (s *DockerCmdSuite) TestDockerCmdError(c *check.C) { - // Run error suite, should fail. - output := String{} - result := check.Run(&dockerCmdErrorSuite{}, &check.RunConf{Output: &output}) - c.Check(result.Succeeded, check.Equals, 0) - c.Check(result.Failed, check.Equals, 1) -} - -func (s *DockerCmdSuite) TestDockerCmdSuccess(c *check.C) { - // Run error suite, should fail. - output := String{} - result := check.Run(&dockerCmdSuccessSuite{}, &check.RunConf{Output: &output}) - c.Check(result.Succeeded, check.Equals, 1) - c.Check(result.Failed, check.Equals, 0) -} - -// DockerCmdWithTimeout tests - -func (s *DockerCmdSuite) TestDockerCmdWithTimeout(c *check.C) { - cmds := []struct { - binary string - args []string - timeout time.Duration - expectedOut string - expectedExitCode int - expectedError error - }{ - { - "doesnotexists", - []string{}, - 200 * time.Millisecond, - `Command doesnotexists not found.`, - 1, - fmt.Errorf(`"" failed with errors: exit status 1 : "Command doesnotexists not found."`), - }, - { - dockerBinary, - []string{"an", "error"}, - 200 * time.Millisecond, - `an error has occurred`, - 1, - fmt.Errorf(`"an error" failed with errors: exit status 1 : "an error has occurred"`), - }, - { - dockerBinary, - []string{"a", "command", "that", "times", "out"}, - 5 * time.Millisecond, - "", - 0, - fmt.Errorf(`"a command that times out" failed with errors: command timed out : ""`), - }, - { - dockerBinary, - []string{"run", "-ti", "ubuntu", "echo", "hello"}, - 200 * time.Millisecond, - "hello", - 0, - nil, - }, - } - for _, cmd := range cmds { - out, exitCode, error := DockerCmdWithTimeout(cmd.binary, cmd.timeout, cmd.args...) - c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out)) - c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode)) - if cmd.expectedError != nil { - c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError)) - c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error())) - } else { - c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error)) - } - } -} - -// DockerCmdInDir tests - -func (s *DockerCmdSuite) TestDockerCmdInDir(c *check.C) { - tempFolder, err := ioutil.TempDir("", "test-docker-cmd-in-dir") - c.Assert(err, check.IsNil) - - cmds := []struct { - binary string - args []string - expectedOut string - expectedExitCode int - expectedError error - }{ - { - "doesnotexists", - []string{}, - `Command doesnotexists not found.`, - 1, - fmt.Errorf(`"dir:%s" failed with errors: exit status 1 : "Command doesnotexists not found."`, tempFolder), - }, - { - dockerBinary, - []string{"an", "error"}, - `an error has occurred`, - 1, - fmt.Errorf(`"dir:%s an error" failed with errors: exit status 1 : "an error has occurred"`, tempFolder), - }, - { - dockerBinary, - []string{"run", "-ti", "ubuntu", "echo", "hello"}, - "hello", - 0, - nil, - }, - } - for _, cmd := range cmds { - // We prepend the arguments with dir:thefolder.. the fake command will check - // that the current workdir is the same as the one we are passing. - args := append([]string{"dir:" + tempFolder}, cmd.args...) - out, exitCode, error := DockerCmdInDir(cmd.binary, tempFolder, args...) - c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out)) - c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode)) - if cmd.expectedError != nil { - c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError)) - c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error())) - } else { - c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error)) - } - } -} - -// DockerCmdInDirWithTimeout tests - -func (s *DockerCmdSuite) TestDockerCmdInDirWithTimeout(c *check.C) { - tempFolder, err := ioutil.TempDir("", "test-docker-cmd-in-dir") - c.Assert(err, check.IsNil) - - cmds := []struct { - binary string - args []string - timeout time.Duration - expectedOut string - expectedExitCode int - expectedError error - }{ - { - "doesnotexists", - []string{}, - 200 * time.Millisecond, - `Command doesnotexists not found.`, - 1, - fmt.Errorf(`"dir:%s" failed with errors: exit status 1 : "Command doesnotexists not found."`, tempFolder), - }, - { - dockerBinary, - []string{"an", "error"}, - 200 * time.Millisecond, - `an error has occurred`, - 1, - fmt.Errorf(`"dir:%s an error" failed with errors: exit status 1 : "an error has occurred"`, tempFolder), - }, - { - dockerBinary, - []string{"a", "command", "that", "times", "out"}, - 5 * time.Millisecond, - "", - 0, - fmt.Errorf(`"dir:%s a command that times out" failed with errors: command timed out : ""`, tempFolder), - }, - { - dockerBinary, - []string{"run", "-ti", "ubuntu", "echo", "hello"}, - 200 * time.Millisecond, - "hello", - 0, - nil, - }, - } - for _, cmd := range cmds { - // We prepend the arguments with dir:thefolder.. the fake command will check - // that the current workdir is the same as the one we are passing. - args := append([]string{"dir:" + tempFolder}, cmd.args...) - out, exitCode, error := DockerCmdInDirWithTimeout(cmd.binary, cmd.timeout, tempFolder, args...) - c.Assert(out, check.Equals, cmd.expectedOut, check.Commentf("Expected output %q for arguments %v, got %q", cmd.expectedOut, cmd.args, out)) - c.Assert(exitCode, check.Equals, cmd.expectedExitCode, check.Commentf("Expected exitCode %q for arguments %v, got %q", cmd.expectedExitCode, cmd.args, exitCode)) - if cmd.expectedError != nil { - c.Assert(error, check.NotNil, check.Commentf("Expected an error %q, got nothing", cmd.expectedError)) - c.Assert(error.Error(), check.Equals, cmd.expectedError.Error(), check.Commentf("Expected error %q for arguments %v, got %q", cmd.expectedError.Error(), cmd.args, error.Error())) - } else { - c.Assert(error, check.IsNil, check.Commentf("Expected no error, got %v", error)) - } - } -} - -// Helpers :) - -// Type implementing the io.Writer interface for analyzing output. -type String struct { - value string -} - -// The only function required by the io.Writer interface. Will append -// written data to the String.value string. -func (s *String) Write(p []byte) (n int, err error) { - s.value += string(p) - return len(p), nil -} - -// Helper function that mock the exec.Command call (and call the test binary) -func fakeExecCommand(command string, args ...string) *exec.Cmd { - cs := []string{"-test.run=TestHelperProcess", "--", command} - cs = append(cs, args...) - cmd := exec.Command(os.Args[0], cs...) - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} - return cmd -} - -func TestHelperProcess(t *testing.T) { - if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { - return - } - args := os.Args - - // Previous arguments are tests stuff, that looks like : - // /tmp/go-build970079519/…/_test/integration.test -test.run=TestHelperProcess -- - cmd, args := args[3], args[4:] - // Handle the case where args[0] is dir:... - if len(args) > 0 && strings.HasPrefix(args[0], "dir:") { - expectedCwd := args[0][4:] - if len(args) > 1 { - args = args[1:] - } - cwd, err := os.Getwd() - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to get workingdir: %v", err) - os.Exit(1) - } - // This checks that the given path is the same as the currend working dire - if expectedCwd != cwd { - fmt.Fprintf(os.Stderr, "Current workdir should be %q, but is %q", expectedCwd, cwd) - } - } - switch cmd { - case dockerBinary: - argsStr := strings.Join(args, " ") - switch argsStr { - case "an exitCode 127": - fmt.Fprintf(os.Stderr, "an error has occurred with exitCode 127") - os.Exit(127) - case "an error": - fmt.Fprintf(os.Stderr, "an error has occurred") - os.Exit(1) - case "a command that times out": - time.Sleep(10 * time.Second) - fmt.Fprintf(os.Stdout, "too long, should be killed") - // A random exit code (that should never happened in tests) - os.Exit(7) - case "run -ti ubuntu echo hello": - fmt.Fprintf(os.Stdout, "hello") - default: - fmt.Fprintf(os.Stdout, "no arguments") - } - default: - fmt.Fprintf(os.Stderr, "Command %s not found.", cmd) - os.Exit(1) - } - // some code here to check arguments perhaps? - os.Exit(0) -} diff --git a/components/engine/pkg/integration/utils.go b/components/engine/pkg/integration/utils.go index a8293f64cf..b52367d15b 100644 --- a/components/engine/pkg/integration/utils.go +++ b/components/engine/pkg/integration/utils.go @@ -2,7 +2,6 @@ package integration import ( "archive/tar" - "bytes" "errors" "fmt" "io" @@ -14,35 +13,10 @@ import ( "syscall" "time" + icmd "github.com/docker/docker/pkg/integration/cmd" "github.com/docker/docker/pkg/stringutils" ) -// GetExitCode returns the ExitStatus of the specified error if its type is -// exec.ExitError, returns 0 and an error otherwise. -func GetExitCode(err error) (int, error) { - exitCode := 0 - if exiterr, ok := err.(*exec.ExitError); ok { - if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok { - return procExit.ExitStatus(), nil - } - } - return exitCode, fmt.Errorf("failed to get exit code") -} - -// ProcessExitCode process the specified error and returns the exit status code -// if the error was of type exec.ExitError, returns nothing otherwise. -func ProcessExitCode(err error) (exitCode int) { - if err != nil { - var exiterr error - if exitCode, exiterr = GetExitCode(err); exiterr != nil { - // TODO: Fix this so we check the error's text. - // we've failed to retrieve exit code, so we set it to 127 - exitCode = 127 - } - } - return -} - // IsKilled process the specified error and returns whether the process was killed or not. func IsKilled(err error) bool { if exitErr, ok := err.(*exec.ExitError); ok { @@ -58,110 +32,14 @@ func IsKilled(err error) bool { return false } -// RunCommandWithOutput runs the specified command and returns the combined output (stdout/stderr) -// with the exitCode different from 0 and the error if something bad happened -func RunCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) { +func runCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) { exitCode = 0 out, err := cmd.CombinedOutput() - exitCode = ProcessExitCode(err) + exitCode = icmd.ProcessExitCode(err) output = string(out) return } -// RunCommandWithStdoutStderr runs the specified command and returns stdout and stderr separately -// with the exitCode different from 0 and the error if something bad happened -func RunCommandWithStdoutStderr(cmd *exec.Cmd) (stdout string, stderr string, exitCode int, err error) { - var ( - stderrBuffer, stdoutBuffer bytes.Buffer - ) - exitCode = 0 - cmd.Stderr = &stderrBuffer - cmd.Stdout = &stdoutBuffer - err = cmd.Run() - exitCode = ProcessExitCode(err) - - stdout = stdoutBuffer.String() - stderr = stderrBuffer.String() - return -} - -// RunCommandWithOutputForDuration runs the specified command "timeboxed" by the specified duration. -// If the process is still running when the timebox is finished, the process will be killed and . -// It will returns the output with the exitCode different from 0 and the error if something bad happened -// and a boolean whether it has been killed or not. -func RunCommandWithOutputForDuration(cmd *exec.Cmd, duration time.Duration) (output string, exitCode int, timedOut bool, err error) { - var outputBuffer bytes.Buffer - if cmd.Stdout != nil { - err = errors.New("cmd.Stdout already set") - return - } - cmd.Stdout = &outputBuffer - - if cmd.Stderr != nil { - err = errors.New("cmd.Stderr already set") - return - } - cmd.Stderr = &outputBuffer - - // Start the command in the main thread.. - err = cmd.Start() - if err != nil { - err = fmt.Errorf("Fail to start command %v : %v", cmd, err) - } - - type exitInfo struct { - exitErr error - exitCode int - } - - done := make(chan exitInfo, 1) - - go func() { - // And wait for it to exit in the goroutine :) - info := exitInfo{} - info.exitErr = cmd.Wait() - info.exitCode = ProcessExitCode(info.exitErr) - done <- info - }() - - select { - case <-time.After(duration): - killErr := cmd.Process.Kill() - if killErr != nil { - fmt.Printf("failed to kill (pid=%d): %v\n", cmd.Process.Pid, killErr) - } - timedOut = true - case info := <-done: - err = info.exitErr - exitCode = info.exitCode - } - output = outputBuffer.String() - return -} - -var errCmdTimeout = fmt.Errorf("command timed out") - -// RunCommandWithOutputAndTimeout runs the specified command "timeboxed" by the specified duration. -// It returns the output with the exitCode different from 0 and the error if something bad happened or -// if the process timed out (and has been killed). -func RunCommandWithOutputAndTimeout(cmd *exec.Cmd, timeout time.Duration) (output string, exitCode int, err error) { - var timedOut bool - output, exitCode, timedOut, err = RunCommandWithOutputForDuration(cmd, timeout) - if timedOut { - err = errCmdTimeout - } - return -} - -// RunCommand runs the specified command and returns the exitCode different from 0 -// and the error if something bad happened. -func RunCommand(cmd *exec.Cmd) (exitCode int, err error) { - exitCode = 0 - err = cmd.Run() - exitCode = ProcessExitCode(err) - return -} - // RunCommandPipelineWithOutput runs the array of commands with the output // of each pipelined with the following (like cmd1 | cmd2 | cmd3 would do). // It returns the final output, the exitCode different from 0 and the error @@ -205,7 +83,7 @@ func RunCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in } // wait on last cmd - return RunCommandWithOutput(cmds[len(cmds)-1]) + return runCommandWithOutput(cmds[len(cmds)-1]) } // ConvertSliceOfStringsToMap converts a slices of string in a map @@ -341,11 +219,9 @@ func RunAtDifferentDate(date time.Time, block func()) { const timeLayout = "010203042006" // Ensure we bring time back to now now := time.Now().Format(timeLayout) - dateReset := exec.Command("date", now) - defer RunCommand(dateReset) + defer icmd.RunCommand("date", now) - dateChange := exec.Command("date", date.Format(timeLayout)) - RunCommand(dateChange) + icmd.RunCommand("date", date.Format(timeLayout)) block() return } diff --git a/components/engine/pkg/integration/utils_test.go b/components/engine/pkg/integration/utils_test.go index 26d0e3d0c3..5afa03a9c8 100644 --- a/components/engine/pkg/integration/utils_test.go +++ b/components/engine/pkg/integration/utils_test.go @@ -7,7 +7,6 @@ import ( "os/exec" "path/filepath" "runtime" - "strconv" "strings" "testing" "time" @@ -54,203 +53,6 @@ func TestIsKilledTrueWithKilledProcess(t *testing.T) { } } -func TestRunCommandWithOutput(t *testing.T) { - var ( - echoHelloWorldCmd *exec.Cmd - expected string - ) - if runtime.GOOS != "windows" { - echoHelloWorldCmd = exec.Command("echo", "hello", "world") - expected = "hello world\n" - } else { - echoHelloWorldCmd = exec.Command("cmd", "/s", "/c", "echo", "hello", "world") - expected = "hello world\r\n" - } - - out, exitCode, err := RunCommandWithOutput(echoHelloWorldCmd) - if out != expected || exitCode != 0 || err != nil { - t.Fatalf("Expected command to output %s, got %s, %v with exitCode %v", expected, out, err, exitCode) - } -} - -func TestRunCommandWithOutputError(t *testing.T) { - var ( - p string - wrongCmd *exec.Cmd - expected string - expectedExitCode int - ) - - if runtime.GOOS != "windows" { - p = "$PATH" - wrongCmd = exec.Command("ls", "-z") - expected = `ls: invalid option -- 'z' -Try 'ls --help' for more information. -` - expectedExitCode = 2 - } else { - p = "%PATH%" - wrongCmd = exec.Command("cmd", "/s", "/c", "dir", "/Z") - expected = "Invalid switch - " + strconv.Quote("Z") + ".\r\n" - expectedExitCode = 1 - } - cmd := exec.Command("doesnotexists") - out, exitCode, err := RunCommandWithOutput(cmd) - expectedError := `exec: "doesnotexists": executable file not found in ` + p - if out != "" || exitCode != 127 || err == nil || err.Error() != expectedError { - t.Fatalf("Expected command to output %s, got %s, %v with exitCode %v", expectedError, out, err, exitCode) - } - - out, exitCode, err = RunCommandWithOutput(wrongCmd) - - if out != expected || exitCode != expectedExitCode || err == nil || !strings.Contains(err.Error(), "exit status "+strconv.Itoa(expectedExitCode)) { - t.Fatalf("Expected command to output %s, got out:xxx%sxxx, err:%v with exitCode %v", expected, out, err, exitCode) - } -} - -func TestRunCommandWithStdoutStderr(t *testing.T) { - echoHelloWorldCmd := exec.Command("echo", "hello", "world") - stdout, stderr, exitCode, err := RunCommandWithStdoutStderr(echoHelloWorldCmd) - expected := "hello world\n" - if stdout != expected || stderr != "" || exitCode != 0 || err != nil { - t.Fatalf("Expected command to output %s, got stdout:%s, stderr:%s, err:%v with exitCode %v", expected, stdout, stderr, err, exitCode) - } -} - -func TestRunCommandWithStdoutStderrError(t *testing.T) { - p := "$PATH" - if runtime.GOOS == "windows" { - p = "%PATH%" - } - cmd := exec.Command("doesnotexists") - stdout, stderr, exitCode, err := RunCommandWithStdoutStderr(cmd) - expectedError := `exec: "doesnotexists": executable file not found in ` + p - if stdout != "" || stderr != "" || exitCode != 127 || err == nil || err.Error() != expectedError { - t.Fatalf("Expected command to output out:%s, stderr:%s, got stdout:%s, stderr:%s, err:%v with exitCode %v", "", "", stdout, stderr, err, exitCode) - } - - wrongLsCmd := exec.Command("ls", "-z") - expected := `ls: invalid option -- 'z' -Try 'ls --help' for more information. -` - - stdout, stderr, exitCode, err = RunCommandWithStdoutStderr(wrongLsCmd) - if stdout != "" && stderr != expected || exitCode != 2 || err == nil || err.Error() != "exit status 2" { - t.Fatalf("Expected command to output out:%s, stderr:%s, got stdout:%s, stderr:%s, err:%v with exitCode %v", "", expectedError, stdout, stderr, err, exitCode) - } -} - -func TestRunCommandWithOutputForDurationFinished(t *testing.T) { - // TODO Windows: Port this test - if runtime.GOOS == "windows" { - t.Skip("Needs porting to Windows") - } - - cmd := exec.Command("ls") - out, exitCode, timedOut, err := RunCommandWithOutputForDuration(cmd, 50*time.Millisecond) - if out == "" || exitCode != 0 || timedOut || err != nil { - t.Fatalf("Expected the command to run for less 50 milliseconds and thus not time out, but did not : out:[%s], exitCode:[%d], timedOut:[%v], err:[%v]", out, exitCode, timedOut, err) - } -} - -func TestRunCommandWithOutputForDurationKilled(t *testing.T) { - // TODO Windows: Port this test - if runtime.GOOS == "windows" { - t.Skip("Needs porting to Windows") - } - cmd := exec.Command("sh", "-c", "while true ; do echo 1 ; sleep .1 ; done") - out, exitCode, timedOut, err := RunCommandWithOutputForDuration(cmd, 500*time.Millisecond) - ones := strings.Split(out, "\n") - if len(ones) != 6 || exitCode != 0 || !timedOut || err != nil { - t.Fatalf("Expected the command to run for 500 milliseconds (and thus print six lines (five with 1, one empty) and time out, but did not : out:[%s], exitCode:%d, timedOut:%v, err:%v", out, exitCode, timedOut, err) - } -} - -func TestRunCommandWithOutputForDurationErrors(t *testing.T) { - cmd := exec.Command("ls") - cmd.Stdout = os.Stdout - if _, _, _, err := RunCommandWithOutputForDuration(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stdout already set" { - t.Fatalf("Expected an error as cmd.Stdout was already set, did not (err:%s).", err) - } - cmd = exec.Command("ls") - cmd.Stderr = os.Stderr - if _, _, _, err := RunCommandWithOutputForDuration(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stderr already set" { - t.Fatalf("Expected an error as cmd.Stderr was already set, did not (err:%s).", err) - } -} - -func TestRunCommandWithOutputAndTimeoutFinished(t *testing.T) { - // TODO Windows: Port this test - if runtime.GOOS == "windows" { - t.Skip("Needs porting to Windows") - } - - cmd := exec.Command("ls") - out, exitCode, err := RunCommandWithOutputAndTimeout(cmd, 50*time.Millisecond) - if out == "" || exitCode != 0 || err != nil { - t.Fatalf("Expected the command to run for less 50 milliseconds and thus not time out, but did not : out:[%s], exitCode:[%d], err:[%v]", out, exitCode, err) - } -} - -func TestRunCommandWithOutputAndTimeoutKilled(t *testing.T) { - // TODO Windows: Port this test - if runtime.GOOS == "windows" { - t.Skip("Needs porting to Windows") - } - - cmd := exec.Command("sh", "-c", "while true ; do echo 1 ; sleep .1 ; done") - out, exitCode, err := RunCommandWithOutputAndTimeout(cmd, 500*time.Millisecond) - ones := strings.Split(out, "\n") - if len(ones) != 6 || exitCode != 0 || err == nil || err.Error() != "command timed out" { - t.Fatalf("Expected the command to run for 500 milliseconds (and thus print six lines (five with 1, one empty) and time out with an error 'command timed out', but did not : out:[%s], exitCode:%d, err:%v", out, exitCode, err) - } -} - -func TestRunCommandWithOutputAndTimeoutErrors(t *testing.T) { - cmd := exec.Command("ls") - cmd.Stdout = os.Stdout - if _, _, err := RunCommandWithOutputAndTimeout(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stdout already set" { - t.Fatalf("Expected an error as cmd.Stdout was already set, did not (err:%s).", err) - } - cmd = exec.Command("ls") - cmd.Stderr = os.Stderr - if _, _, err := RunCommandWithOutputAndTimeout(cmd, 1*time.Millisecond); err == nil || err.Error() != "cmd.Stderr already set" { - t.Fatalf("Expected an error as cmd.Stderr was already set, did not (err:%s).", err) - } -} - -func TestRunCommand(t *testing.T) { - // TODO Windows: Port this test - if runtime.GOOS == "windows" { - t.Skip("Needs porting to Windows") - } - - p := "$PATH" - if runtime.GOOS == "windows" { - p = "%PATH%" - } - lsCmd := exec.Command("ls") - exitCode, err := RunCommand(lsCmd) - if exitCode != 0 || err != nil { - t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err) - } - - var expectedError string - - exitCode, err = RunCommand(exec.Command("doesnotexists")) - expectedError = `exec: "doesnotexists": executable file not found in ` + p - if exitCode != 127 || err == nil || err.Error() != expectedError { - t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err) - } - wrongLsCmd := exec.Command("ls", "-z") - expected := 2 - expectedError = `exit status 2` - exitCode, err = RunCommand(wrongLsCmd) - if exitCode != expected || err == nil || err.Error() != expectedError { - t.Fatalf("Expected runCommand to run the command successfully, got: exitCode:%d, err:%v", exitCode, err) - } -} - func TestRunCommandPipelineWithOutputWithNotEnoughCmds(t *testing.T) { _, _, err := RunCommandPipelineWithOutput(exec.Command("ls")) expectedError := "pipeline does not have multiple cmds" From 0d4234bb46f57f9f56f9cd751b808cda3805e2ad Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Tue, 16 Aug 2016 17:51:38 -0400 Subject: [PATCH 3/3] Change to use c.Assert() instead of result.Assert() Fix delete containers and make sure it prints errors correctly. Rename Result.Fails to Result.Assert() Create a constant for the default expected. Signed-off-by: Daniel Nephin Upstream-commit: 243885808f5c69143679d094d964d333c47e69bf Component: engine --- .../engine/integration-cli/docker_api_test.go | 8 +-- .../integration-cli/docker_cli_attach_test.go | 2 +- .../integration-cli/docker_cli_build_test.go | 2 +- .../integration-cli/docker_cli_daemon_test.go | 4 +- .../integration-cli/docker_cli_events_test.go | 4 +- .../integration-cli/docker_cli_exec_test.go | 2 +- .../docker_cli_network_unix_test.go | 6 +- .../integration-cli/docker_cli_ps_test.go | 2 +- .../integration-cli/docker_cli_rename_test.go | 2 +- .../integration-cli/docker_cli_run_test.go | 4 +- .../integration-cli/docker_cli_volume_test.go | 2 +- .../docker_experimental_network_test.go | 6 +- .../engine/integration-cli/docker_utils.go | 33 ++++++----- .../engine/pkg/integration/cmd/command.go | 56 +++++++++++++++---- 14 files changed, 86 insertions(+), 47 deletions(-) diff --git a/components/engine/integration-cli/docker_api_test.go b/components/engine/integration-cli/docker_api_test.go index e42477a6e9..0afe76a800 100644 --- a/components/engine/integration-cli/docker_api_test.go +++ b/components/engine/integration-cli/docker_api_test.go @@ -90,11 +90,11 @@ func (s *DockerSuite) TestApiDockerApiVersion(c *check.C) { // Test using the env var first result := icmd.RunCmd(icmd.Cmd{ - Command: binaryWithArgs([]string{"-H", server.URL[7:], "version"}), - Env: []string{"DOCKER_API_VERSION=xxx"}, + Command: binaryWithArgs("-H="+server.URL[7:], "version"), + Env: appendBaseEnv(false, "DOCKER_API_VERSION=xxx"), }) - result.Assert(c, icmd.Expected{Out: "API version: xxx", ExitCode: 1}) - c.Assert(svrVersion, check.Equals, "/vxxx/version") + c.Assert(result, icmd.Matches, icmd.Expected{Out: "API version: xxx", ExitCode: 1}) + c.Assert(svrVersion, check.Equals, "/vxxx/version", check.Commentf("%s", result.Compare(icmd.Success))) } func (s *DockerSuite) TestApiErrorJSON(c *check.C) { diff --git a/components/engine/integration-cli/docker_cli_attach_test.go b/components/engine/integration-cli/docker_cli_attach_test.go index 6663f6aa6a..6b9c522921 100644 --- a/components/engine/integration-cli/docker_cli_attach_test.go +++ b/components/engine/integration-cli/docker_cli_attach_test.go @@ -163,7 +163,7 @@ func (s *DockerSuite) TestAttachPausedContainer(c *check.C) { dockerCmd(c, "pause", "test") result := dockerCmdWithResult("attach", "test") - result.Assert(c, icmd.Expected{ + c.Assert(result, icmd.Matches, icmd.Expected{ Error: "exit status 1", ExitCode: 1, Err: "You cannot attach to a paused container, unpause it first", diff --git a/components/engine/integration-cli/docker_cli_build_test.go b/components/engine/integration-cli/docker_cli_build_test.go index 86d4bf9c01..1c85021245 100644 --- a/components/engine/integration-cli/docker_cli_build_test.go +++ b/components/engine/integration-cli/docker_cli_build_test.go @@ -5065,7 +5065,7 @@ func (s *DockerSuite) TestBuildDockerfileOutsideContext(c *check.C) { filepath.Join(ctx, "dockerfile2"), } { result := dockerCmdWithResult("build", "-t", name, "--no-cache", "-f", dockerfilePath, ".") - result.Assert(c, icmd.Expected{ + c.Assert(result, icmd.Matches, icmd.Expected{ Err: "must be within the build context", ExitCode: 1, }) diff --git a/components/engine/integration-cli/docker_cli_daemon_test.go b/components/engine/integration-cli/docker_cli_daemon_test.go index 74c29495d0..ec73beac22 100644 --- a/components/engine/integration-cli/docker_cli_daemon_test.go +++ b/components/engine/integration-cli/docker_cli_daemon_test.go @@ -910,7 +910,7 @@ func (s *DockerDaemonSuite) TestDaemonDefaultNetworkInvalidClusterConfig(c *chec // Start daemon with docker0 bridge result := icmd.RunCommand("ifconfig", defaultNetworkBridge) - result.Assert(c, icmd.Expected{}) + c.Assert(result, icmd.Matches, icmd.Success) err = d.Restart(fmt.Sprintf("--cluster-store=%s", discoveryBackend)) c.Assert(err, checker.IsNil) @@ -2251,7 +2251,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithUnpausedRunningContainer(t *che ctrBinary, "--address", "unix:///var/run/docker/libcontainerd/docker-containerd.sock", "containers", "resume", cid) - result.Assert(t, icmd.Expected{}) + t.Assert(result, icmd.Matches, icmd.Success) // Give time to containerd to process the command if we don't // the resume event might be received after we do the inspect diff --git a/components/engine/integration-cli/docker_cli_events_test.go b/components/engine/integration-cli/docker_cli_events_test.go index 71864d9a57..c47f234339 100644 --- a/components/engine/integration-cli/docker_cli_events_test.go +++ b/components/engine/integration-cli/docker_cli_events_test.go @@ -63,7 +63,7 @@ func (s *DockerSuite) TestEventsUntag(c *check.C) { Command: []string{dockerBinary, "events", "--since=1"}, Timeout: time.Millisecond * 2500, }) - result.Assert(c, icmd.Expected{Timeout: true}) + c.Assert(result, icmd.Matches, icmd.Expected{Timeout: true}) events := strings.Split(result.Stdout(), "\n") nEvents := len(events) @@ -280,7 +280,7 @@ func (s *DockerSuite) TestEventsImageLoad(c *check.C) { dockerCmd(c, "load", "-i", "saveimg.tar") result := icmd.RunCommand("rm", "-rf", "saveimg.tar") - result.Assert(c, icmd.Expected{}) + c.Assert(result, icmd.Matches, icmd.Success) out, _ = dockerCmd(c, "images", "-q", "--no-trunc", myImageName) imageID := strings.TrimSpace(out) diff --git a/components/engine/integration-cli/docker_cli_exec_test.go b/components/engine/integration-cli/docker_cli_exec_test.go index 785a49094f..514a14b34a 100644 --- a/components/engine/integration-cli/docker_cli_exec_test.go +++ b/components/engine/integration-cli/docker_cli_exec_test.go @@ -124,7 +124,7 @@ func (s *DockerSuite) TestExecExitStatus(c *check.C) { runSleepingContainer(c, "-d", "--name", "top") result := icmd.RunCommand(dockerBinary, "exec", "top", "sh", "-c", "exit 23") - result.Assert(c, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) + c.Assert(result, icmd.Matches, icmd.Expected{ExitCode: 23, Error: "exit status 23"}) } func (s *DockerSuite) TestExecPausedContainer(c *check.C) { diff --git a/components/engine/integration-cli/docker_cli_network_unix_test.go b/components/engine/integration-cli/docker_cli_network_unix_test.go index 2899055ebc..eb75e998f3 100644 --- a/components/engine/integration-cli/docker_cli_network_unix_test.go +++ b/components/engine/integration-cli/docker_cli_network_unix_test.go @@ -479,7 +479,7 @@ func (s *DockerSuite) TestDockerNetworkInspectWithID(c *check.C) { func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) { result := dockerCmdWithResult("network", "inspect", "host", "none") - result.Assert(c, icmd.Expected{}) + c.Assert(result, icmd.Matches, icmd.Success) networkResources := []types.NetworkResource{} err := json.Unmarshal([]byte(result.Stdout()), &networkResources) @@ -488,7 +488,7 @@ func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) { // Should print an error, return an exitCode 1 *but* should print the host network result = dockerCmdWithResult("network", "inspect", "host", "nonexistent") - result.Assert(c, icmd.Expected{ + c.Assert(result, icmd.Matches, icmd.Expected{ ExitCode: 1, Err: "Error: No such network: nonexistent", Out: "host", @@ -500,7 +500,7 @@ func (s *DockerSuite) TestDockerInspectMultipleNetwork(c *check.C) { // Should print an error and return an exitCode, nothing else result = dockerCmdWithResult("network", "inspect", "nonexistent") - result.Assert(c, icmd.Expected{ + c.Assert(result, icmd.Matches, icmd.Expected{ ExitCode: 1, Err: "Error: No such network: nonexistent", Out: "[]", diff --git a/components/engine/integration-cli/docker_cli_ps_test.go b/components/engine/integration-cli/docker_cli_ps_test.go index 9c2b8a2389..32cba7f45c 100644 --- a/components/engine/integration-cli/docker_cli_ps_test.go +++ b/components/engine/integration-cli/docker_cli_ps_test.go @@ -206,7 +206,7 @@ func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) { c.Assert(containerOut, checker.Equals, secondID) result := dockerCmdWithTimeout(time.Second*60, "ps", "-a", "-q", "--filter=status=rubbish") - result.Assert(c, icmd.Expected{ + c.Assert(result, icmd.Matches, icmd.Expected{ ExitCode: 1, Err: "Unrecognised filter value for status", }) diff --git a/components/engine/integration-cli/docker_cli_rename_test.go b/components/engine/integration-cli/docker_cli_rename_test.go index 183cc3f868..d32daa8f3e 100644 --- a/components/engine/integration-cli/docker_cli_rename_test.go +++ b/components/engine/integration-cli/docker_cli_rename_test.go @@ -63,7 +63,7 @@ func (s *DockerSuite) TestRenameCheckNames(c *check.C) { c.Assert(name, checker.Equals, "/"+newName, check.Commentf("Failed to rename container %s", name)) result := dockerCmdWithResult("inspect", "-f={{.Name}}", "first_name") - result.Assert(c, icmd.Expected{ + c.Assert(result, icmd.Matches, icmd.Expected{ ExitCode: 1, Err: "No such container, image or task: first_name", }) diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 00a8f0d82f..8e659e551a 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -1864,11 +1864,11 @@ func (s *DockerSuite) TestRunInteractiveWithRestartPolicy(c *check.C) { }) c.Assert(result.Error, checker.IsNil) defer func() { - dockerCmdWithResult("stop", name).Assert(c, icmd.Expected{}) + dockerCmdWithResult("stop", name).Assert(c, icmd.Success) }() result = icmd.WaitOnCmd(10*time.Second, result) - result.Assert(c, icmd.Expected{ExitCode: 11}) + c.Assert(result, icmd.Matches, icmd.Expected{ExitCode: 11}) } // Test for #2267 diff --git a/components/engine/integration-cli/docker_cli_volume_test.go b/components/engine/integration-cli/docker_cli_volume_test.go index 479d61a68a..aeb2dfa777 100644 --- a/components/engine/integration-cli/docker_cli_volume_test.go +++ b/components/engine/integration-cli/docker_cli_volume_test.go @@ -57,7 +57,7 @@ func (s *DockerSuite) TestVolumeCliInspectMulti(c *check.C) { dockerCmd(c, "volume", "create", "--name", "not-shown") result := dockerCmdWithResult("volume", "inspect", "--format={{ .Name }}", "test1", "test2", "doesntexist", "not-shown") - result.Assert(c, icmd.Expected{ + c.Assert(result, icmd.Matches, icmd.Expected{ ExitCode: 1, Err: "No such volume: doesntexist", }) diff --git a/components/engine/integration-cli/docker_experimental_network_test.go b/components/engine/integration-cli/docker_experimental_network_test.go index 56aa9a7e0d..10a082ce03 100644 --- a/components/engine/integration-cli/docker_experimental_network_test.go +++ b/components/engine/integration-cli/docker_experimental_network_test.go @@ -403,7 +403,7 @@ func (s *DockerSuite) TestDockerNetworkMacVlanBridgeInternalMode(c *check.C) { // access outside of the network should fail result := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") - result.Assert(c, icmd.Expected{Timeout: true}) + c.Assert(result, icmd.Matches, icmd.Expected{Timeout: true}) // intra-network communications should succeed _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") @@ -443,7 +443,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL2InternalMode(c *check.C) { // access outside of the network should fail result := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") - c.Assert(result.Error, check.NotNil) + c.Assert(result, icmd.Matches, icmd.Expected{Timeout: true}) // intra-network communications should succeed _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") c.Assert(err, check.IsNil) @@ -484,7 +484,7 @@ func (s *DockerSuite) TestDockerNetworkIpvlanL3InternalMode(c *check.C) { // access outside of the network should fail result := dockerCmdWithTimeout(time.Second, "exec", "first", "ping", "-c", "1", "-w", "1", "8.8.8.8") - c.Assert(result.Error, check.NotNil) + c.Assert(result, icmd.Matches, icmd.Expected{Timeout: true}) // intra-network communications should succeed _, _, err := dockerCmdWithError("exec", "second", "ping", "-c", "1", "first") c.Assert(err, check.IsNil) diff --git a/components/engine/integration-cli/docker_utils.go b/components/engine/integration-cli/docker_utils.go index 00e3d74f5b..4e7d1da3f6 100644 --- a/components/engine/integration-cli/docker_utils.go +++ b/components/engine/integration-cli/docker_utils.go @@ -228,8 +228,9 @@ func readBody(b io.ReadCloser) ([]byte, error) { return ioutil.ReadAll(b) } -func deleteContainer(container string) error { - return icmd.RunCommand(dockerBinary, "rm", "-fv", container).Error +func deleteContainer(container ...string) error { + result := icmd.RunCommand(dockerBinary, append([]string{"rm", "-fv"}, container...)...) + return result.Compare(icmd.Success) } func getAllContainers() (string, error) { @@ -248,13 +249,15 @@ func deleteAllContainers() error { fmt.Println(containers) return err } - - if containers != "" { - if err = deleteContainer(containers); err != nil { - return err - } + if containers == "" { + return nil } - return nil + + err = deleteContainer(strings.Split(strings.TrimSpace(containers), "\n")...) + if err != nil { + fmt.Println(err.Error()) + } + return err } func deleteAllNetworks() error { @@ -440,7 +443,7 @@ func dockerCmdWithError(args ...string) (string, int, error) { } result := icmd.RunCommand(dockerBinary, args...) if result.Error != nil { - return result.Combined(), result.ExitCode, fmt.Errorf(result.Fails(icmd.Expected{})) + return result.Combined(), result.ExitCode, result.Compare(icmd.Success) } return result.Combined(), result.ExitCode, result.Error } @@ -453,7 +456,7 @@ func dockerCmdWithStdoutStderr(c *check.C, args ...string) (string, string, int) result := icmd.RunCommand(dockerBinary, args...) // TODO: why is c ever nil? if c != nil { - result.Assert(c, icmd.Expected{}) + c.Assert(result, icmd.Matches, icmd.Success) } return result.Stdout(), result.Stderr(), result.ExitCode } @@ -463,7 +466,7 @@ func dockerCmd(c *check.C, args ...string) (string, int) { c.Fatalf(err.Error()) } result := icmd.RunCommand(dockerBinary, args...) - result.Assert(c, icmd.Expected{}) + c.Assert(result, icmd.Matches, icmd.Success) return result.Combined(), result.ExitCode } @@ -471,7 +474,7 @@ func dockerCmdWithResult(args ...string) *icmd.Result { return icmd.RunCommand(dockerBinary, args...) } -func binaryWithArgs(args []string) []string { +func binaryWithArgs(args ...string) []string { return append([]string{dockerBinary}, args...) } @@ -480,7 +483,7 @@ func dockerCmdWithTimeout(timeout time.Duration, args ...string) *icmd.Result { if err := validateArgs(args...); err != nil { return &icmd.Result{Error: err} } - return icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args), Timeout: timeout}) + return icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args...), Timeout: timeout}) } // execute a docker command in a directory @@ -488,7 +491,7 @@ func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error if err := validateArgs(args...); err != nil { c.Fatalf(err.Error()) } - result := icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args), Dir: path}) + result := icmd.RunCmd(icmd.Cmd{Command: binaryWithArgs(args...), Dir: path}) return result.Combined(), result.ExitCode, result.Error } @@ -498,7 +501,7 @@ func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...strin return &icmd.Result{Error: err} } return icmd.RunCmd(icmd.Cmd{ - Command: binaryWithArgs(args), + Command: binaryWithArgs(args...), Timeout: timeout, Dir: path, }) diff --git a/components/engine/pkg/integration/cmd/command.go b/components/engine/pkg/integration/cmd/command.go index 8fa8f79f05..2214f265e9 100644 --- a/components/engine/pkg/integration/cmd/command.go +++ b/components/engine/pkg/integration/cmd/command.go @@ -10,6 +10,8 @@ import ( "strings" "syscall" "time" + + "github.com/go-check/check" ) type testingT interface { @@ -61,17 +63,18 @@ type Result struct { // Assert compares the Result against the Expected struct, and fails the test if // any of the expcetations are not met. func (r *Result) Assert(t testingT, exp Expected) { - fails := r.Fails(exp) - if fails == "" { + err := r.Compare(exp) + if err == nil { return } _, file, line, _ := runtime.Caller(1) - t.Fatalf("at %s:%d\n%s", filepath.Base(file), line, fails) + t.Fatalf("at %s:%d\n%s", filepath.Base(file), line, err.Error()) } -// Fails returns a formatted string which reports on any failed expectations -func (r *Result) Fails(exp Expected) string { +// Compare returns an formatted error with the command, stdout, stderr, exit +// code, and any failed expectations +func (r *Result) Compare(exp Expected) error { errors := []string{} add := func(format string, args ...interface{}) { errors = append(errors, fmt.Sprintf(format, args...)) @@ -107,9 +110,9 @@ func (r *Result) Fails(exp Expected) string { } if len(errors) == 0 { - return "" + return nil } - return fmt.Sprintf("%s\nFailures:\n%s\n", r, strings.Join(errors, "\n")) + return fmt.Errorf("%s\nFailures:\n%s\n", r, strings.Join(errors, "\n")) } func matchOutput(expected string, actual string) bool { @@ -151,6 +154,9 @@ type Expected struct { Err string } +// Success is the default expected result +var Success = Expected{} + // Stdout returns the stdout of the process as a string func (r *Result) Stdout() string { return r.outBuffer.String() @@ -175,9 +181,39 @@ func (r *Result) SetExitError(err error) { r.ExitCode = ProcessExitCode(err) } -// Cmd is a command to run. One of Command or CommandArgs can be used to set the -// comand line. Command will be paased to shlex and split into a string slice. -// CommandArgs is an already split command line. +type matches struct{} + +// Info returns the CheckerInfo +func (m *matches) Info() *check.CheckerInfo { + return &check.CheckerInfo{ + Name: "CommandMatches", + Params: []string{"result", "expected"}, + } +} + +// Check compares a result against the expected +func (m *matches) Check(params []interface{}, names []string) (bool, string) { + result, ok := params[0].(*Result) + if !ok { + return false, fmt.Sprintf("result must be a *Result, not %T", params[0]) + } + expected, ok := params[1].(Expected) + if !ok { + return false, fmt.Sprintf("expected must be an Expected, not %T", params[1]) + } + + err := result.Compare(expected) + if err == nil { + return true, "" + } + return false, err.Error() +} + +// Matches is a gocheck.Checker for comparing a Result against an Expected +var Matches = &matches{} + +// Cmd contains the arguments and options for a process to run as part of a test +// suite. type Cmd struct { Command []string Timeout time.Duration