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{}, 100 * time.Millisecond, `Command doesnotexists not found.`, 1, fmt.Errorf(`"" failed with errors: exit status 1 : "Command doesnotexists not found."`), }, { dockerBinary, []string{"an", "error"}, 100 * 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"}, 100 * 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{}, 100 * 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"}, 100 * 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"}, 100 * 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.Millisecond) 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) }