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 <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2016-08-04 12:57:34 -04:00
parent fb42e84772
commit d7022f2b46
22 changed files with 564 additions and 998 deletions

View File

@ -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) {

View File

@ -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",
})
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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) {

View File

@ -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) {

View File

@ -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" {

View File

@ -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) {

View File

@ -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

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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 = "<NOTHING>"
)
// 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 <code> 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
}

View File

@ -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",
})
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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"