diff --git a/integration-cli/docker_api_test.go b/integration-cli/docker_api_test.go index d09b8f193b..e42477a6e9 100644 --- a/integration-cli/docker_api_test.go +++ b/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/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index b4d59e707a..6663f6aa6a 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/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/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index 7af761d7a3..2a07c79053 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/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/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 5201f731af..86d4bf9c01 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/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/integration-cli/docker_cli_build_unix_test.go b/integration-cli/docker_cli_build_unix_test.go index 56ab66efae..0205a927dd 100644 --- a/integration-cli/docker_cli_build_unix_test.go +++ b/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/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index bf859c3e85..74c29495d0 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/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/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 260228f2cc..71864d9a57 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/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/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 77472d47b4..785a49094f 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/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/integration-cli/docker_cli_network_unix_test.go b/integration-cli/docker_cli_network_unix_test.go index faf798782f..2899055ebc 100644 --- a/integration-cli/docker_cli_network_unix_test.go +++ b/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/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 6e1756f06f..9c2b8a2389 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/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/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index f1a16fae52..183cc3f868 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/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/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 965bdb9644..00a8f0d82f 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/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/integration-cli/docker_cli_volume_test.go b/integration-cli/docker_cli_volume_test.go index dc081763da..479d61a68a 100644 --- a/integration-cli/docker_cli_volume_test.go +++ b/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/integration-cli/docker_experimental_network_test.go b/integration-cli/docker_experimental_network_test.go index f33dbd1c84..56aa9a7e0d 100644 --- a/integration-cli/docker_experimental_network_test.go +++ b/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/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 238929c16c..00e3d74f5b 100644 --- a/integration-cli/docker_utils.go +++ b/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/integration-cli/utils.go b/integration-cli/utils.go index eac6379bcd..87d48e41b0 100644 --- a/integration-cli/utils.go +++ b/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/pkg/integration/cmd/command.go b/pkg/integration/cmd/command.go new file mode 100644 index 0000000000..8fa8f79f05 --- /dev/null +++ b/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/pkg/integration/cmd/command_test.go b/pkg/integration/cmd/command_test.go new file mode 100644 index 0000000000..4f4174d709 --- /dev/null +++ b/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/pkg/integration/dockerCmd_utils.go b/pkg/integration/dockerCmd_utils.go deleted file mode 100644 index fab3e062dd..0000000000 --- a/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/pkg/integration/dockerCmd_utils_test.go b/pkg/integration/dockerCmd_utils_test.go deleted file mode 100644 index 3dd5d11461..0000000000 --- a/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/pkg/integration/utils.go b/pkg/integration/utils.go index a8293f64cf..b52367d15b 100644 --- a/pkg/integration/utils.go +++ b/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/pkg/integration/utils_test.go b/pkg/integration/utils_test.go index 26d0e3d0c3..5afa03a9c8 100644 --- a/pkg/integration/utils_test.go +++ b/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"