Browse Source

Add unit tests for integration cli utils function

- utils_test.go and docker_utils_test.go
- Moved docker related function to docker_utils.go
- add a test for integration-cli/checker

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
Vincent Demeester 9 years ago
parent
commit
5109071706

+ 2 - 1
integration-cli/docker_api_containers_test.go

@@ -15,6 +15,7 @@ import (
 	"time"
 
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/pkg/integration"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/runconfig"
 	"github.com/go-check/check"
@@ -319,7 +320,7 @@ func (s *DockerSuite) TestGetContainerStatsRmRunning(c *check.C) {
 	out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
 	id := strings.TrimSpace(out)
 
-	buf := &channelBuffer{make(chan []byte, 1)}
+	buf := &integration.ChannelBuffer{make(chan []byte, 1)}
 	defer buf.Close()
 	chErr := make(chan error)
 	go func() {

+ 1 - 1
integration-cli/docker_cli_pull_test.go

@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/docker/distribution/digest"
-	"github.com/docker/docker/integration-cli/checker"
+	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/go-check/check"
 )
 

+ 1 - 1
integration-cli/docker_cli_volume_test.go

@@ -4,7 +4,7 @@ import (
 	"os/exec"
 	"strings"
 
-	"github.com/docker/docker/integration-cli/checker"
+	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/go-check/check"
 )
 

+ 57 - 0
integration-cli/docker_utils.go

@@ -1403,3 +1403,60 @@ func buildImageCmdArgs(args []string, name, dockerfile string, useCache bool) *e
 	return buildCmd
 
 }
+
+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
+	}
+
+	if err := waitRun(contID); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// waitRun will wait for the specified container to be running, maximum 5 seconds.
+func waitRun(contID string) error {
+	return waitInspect(contID, "{{.State.Running}}", "true", 5)
+}
+
+// waitInspect will wait for the specified container to have the specified string
+// in the inspect output. It will wait until the specified timeout (in seconds)
+// is reached.
+func waitInspect(name, expr, expected string, timeout int) error {
+	after := time.After(time.Duration(timeout) * time.Second)
+
+	for {
+		cmd := exec.Command(dockerBinary, "inspect", "-f", expr, name)
+		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)
+			}
+			select {
+			case <-after:
+				return err
+			default:
+				time.Sleep(10 * time.Millisecond)
+				continue
+			}
+		}
+
+		out = strings.TrimSpace(out)
+		if out == expected {
+			break
+		}
+
+		select {
+		case <-after:
+			return fmt.Errorf("condition \"%q == %q\" not true in time", out, expected)
+		default:
+		}
+
+		time.Sleep(100 * time.Millisecond)
+	}
+	return nil
+}

+ 18 - 294
integration-cli/utils.go

@@ -1,354 +1,78 @@
 package main
 
 import (
-	"archive/tar"
-	"bytes"
-	"encoding/json"
-	"errors"
-	"fmt"
 	"io"
 	"os"
 	"os/exec"
-	"path"
-	"reflect"
-	"strings"
-	"syscall"
 	"time"
 
-	"github.com/docker/docker/pkg/stringutils"
+	"github.com/docker/docker/pkg/integration"
 )
 
 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")
+	return integration.GetExitCode(err)
 }
 
 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
+	return integration.ProcessExitCode(err)
 }
 
 func isKilled(err error) bool {
-	if exitErr, ok := err.(*exec.ExitError); ok {
-		status, ok := exitErr.Sys().(syscall.WaitStatus)
-		if !ok {
-			return false
-		}
-		// status.ExitStatus() is required on Windows because it does not
-		// implement Signal() nor Signaled(). Just check it had a bad exit
-		// status could mean it was killed (and in tests we do kill)
-		return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
-	}
-	return false
+	return integration.IsKilled(err)
 }
 
 func runCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) {
-	exitCode = 0
-	out, err := cmd.CombinedOutput()
-	exitCode = processExitCode(err)
-	output = string(out)
-	return
+	return integration.RunCommandWithOutput(cmd)
 }
 
 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
+	return integration.RunCommandWithStdoutStderr(cmd)
 }
 
 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
-
-	done := make(chan error)
-	go func() {
-		exitErr := cmd.Run()
-		exitCode = processExitCode(exitErr)
-		done <- exitErr
-	}()
-
-	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
-		break
-	case err = <-done:
-		break
-	}
-	output = outputBuffer.String()
-	return
+	return integration.RunCommandWithOutputForDuration(cmd, duration)
 }
 
-var errCmdTimeout = fmt.Errorf("command timed out")
-
 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
+	return integration.RunCommandWithOutputAndTimeout(cmd, timeout)
 }
 
 func runCommand(cmd *exec.Cmd) (exitCode int, err error) {
-	exitCode = 0
-	err = cmd.Run()
-	exitCode = processExitCode(err)
-	return
+	return integration.RunCommand(cmd)
 }
 
 func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode int, err error) {
-	if len(cmds) < 2 {
-		return "", 0, errors.New("pipeline does not have multiple cmds")
-	}
-
-	// connect stdin of each cmd to stdout pipe of previous cmd
-	for i, cmd := range cmds {
-		if i > 0 {
-			prevCmd := cmds[i-1]
-			cmd.Stdin, err = prevCmd.StdoutPipe()
-
-			if err != nil {
-				return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err)
-			}
-		}
-	}
-
-	// start all cmds except the last
-	for _, cmd := range cmds[:len(cmds)-1] {
-		if err = cmd.Start(); err != nil {
-			return "", 0, fmt.Errorf("starting %s failed with error: %v", cmd.Path, err)
-		}
-	}
-
-	defer func() {
-		// wait all cmds except the last to release their resources
-		for _, cmd := range cmds[:len(cmds)-1] {
-			cmd.Wait()
-		}
-	}()
-
-	// wait on last cmd
-	return runCommandWithOutput(cmds[len(cmds)-1])
+	return integration.RunCommandPipelineWithOutput(cmds...)
 }
 
 func unmarshalJSON(data []byte, result interface{}) error {
-	if err := json.Unmarshal(data, result); err != nil {
-		return err
-	}
-
-	return nil
+	return integration.UnmarshalJSON(data, result)
 }
 
 func convertSliceOfStringsToMap(input []string) map[string]struct{} {
-	output := make(map[string]struct{})
-	for _, v := range input {
-		output[v] = struct{}{}
-	}
-	return output
-}
-
-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
-	}
-
-	if err := waitRun(contID); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// waitRun will wait for the specified container to be running, maximum 5 seconds.
-func waitRun(contID string) error {
-	return waitInspect(contID, "{{.State.Running}}", "true", 5)
-}
-
-// waitInspect will wait for the specified container to have the specified string
-// in the inspect output. It will wait until the specified timeout (in seconds)
-// is reached.
-func waitInspect(name, expr, expected string, timeout int) error {
-	after := time.After(time.Duration(timeout) * time.Second)
-
-	for {
-		cmd := exec.Command(dockerBinary, "inspect", "-f", expr, name)
-		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)
-			}
-			select {
-			case <-after:
-				return err
-			default:
-				time.Sleep(10 * time.Millisecond)
-				continue
-			}
-		}
-
-		out = strings.TrimSpace(out)
-		if out == expected {
-			break
-		}
-
-		select {
-		case <-after:
-			return fmt.Errorf("condition \"%q == %q\" not true in time", out, expected)
-		default:
-		}
-
-		time.Sleep(100 * time.Millisecond)
-	}
-	return nil
+	return integration.ConvertSliceOfStringsToMap(input)
 }
 
 func compareDirectoryEntries(e1 []os.FileInfo, e2 []os.FileInfo) error {
-	var (
-		e1Entries = make(map[string]struct{})
-		e2Entries = make(map[string]struct{})
-	)
-	for _, e := range e1 {
-		e1Entries[e.Name()] = struct{}{}
-	}
-	for _, e := range e2 {
-		e2Entries[e.Name()] = struct{}{}
-	}
-	if !reflect.DeepEqual(e1Entries, e2Entries) {
-		return fmt.Errorf("entries differ")
-	}
-	return nil
+	return integration.CompareDirectoryEntries(e1, e2)
 }
 
 func listTar(f io.Reader) ([]string, error) {
-	tr := tar.NewReader(f)
-	var entries []string
-
-	for {
-		th, err := tr.Next()
-		if err == io.EOF {
-			// end of tar archive
-			return entries, nil
-		}
-		if err != nil {
-			return entries, err
-		}
-		entries = append(entries, th.Name)
-	}
+	return integration.ListTar(f)
 }
 
-// randomUnixTmpDirPath provides a temporary unix path with rand string appended.
-// does not create or checks if it exists.
 func randomUnixTmpDirPath(s string) string {
-	return path.Join("/tmp", fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10)))
+	return integration.RandomUnixTmpDirPath(s)
 }
 
-// Reads chunkSize bytes from reader after every interval.
-// Returns total read bytes.
 func consumeWithSpeed(reader io.Reader, chunkSize int, interval time.Duration, stop chan bool) (n int, err error) {
-	buffer := make([]byte, chunkSize)
-	for {
-		select {
-		case <-stop:
-			return
-		default:
-			var readBytes int
-			readBytes, err = reader.Read(buffer)
-			n += readBytes
-			if err != nil {
-				if err == io.EOF {
-					err = nil
-				}
-				return
-			}
-			time.Sleep(interval)
-		}
-	}
+	return integration.ConsumeWithSpeed(reader, chunkSize, interval, stop)
 }
 
-// Parses 'procCgroupData', which is output of '/proc/<pid>/cgroup', and returns
-// a map which cgroup name as key and path as value.
 func parseCgroupPaths(procCgroupData string) map[string]string {
-	cgroupPaths := map[string]string{}
-	for _, line := range strings.Split(procCgroupData, "\n") {
-		parts := strings.Split(line, ":")
-		if len(parts) != 3 {
-			continue
-		}
-		cgroupPaths[parts[1]] = parts[2]
-	}
-	return cgroupPaths
-}
-
-type channelBuffer struct {
-	c chan []byte
-}
-
-func (c *channelBuffer) Write(b []byte) (int, error) {
-	c.c <- b
-	return len(b), nil
-}
-
-func (c *channelBuffer) Close() error {
-	close(c.c)
-	return nil
-}
-
-func (c *channelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
-	select {
-	case b := <-c.c:
-		return copy(p[0:], b), nil
-	case <-time.After(n):
-		return -1, fmt.Errorf("timeout reading from channel")
-	}
+	return integration.ParseCgroupPaths(procCgroupData)
 }
 
 func runAtDifferentDate(date time.Time, block func()) {
-	// Layout for date. MMDDhhmmYYYY
-	const timeLayout = "010203042006"
-	// Ensure we bring time back to now
-	now := time.Now().Format(timeLayout)
-	dateReset := exec.Command("date", now)
-	defer runCommand(dateReset)
-
-	dateChange := exec.Command("date", date.Format(timeLayout))
-	runCommand(dateChange)
-	block()
-	return
+	integration.RunAtDifferentDate(date, block)
 }

+ 0 - 0
integration-cli/checker/checker.go → pkg/integration/checker/checker.go


+ 57 - 0
pkg/integration/checker/checker_test.go

@@ -0,0 +1,57 @@
+package checker
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/go-check/check"
+)
+
+func Test(t *testing.T) {
+	check.TestingT(t)
+}
+
+func init() {
+	check.Suite(&CheckersS{})
+}
+
+type CheckersS struct{}
+
+var _ = check.Suite(&CheckersS{})
+
+func testInfo(c *check.C, checker check.Checker, name string, paramNames []string) {
+	info := checker.Info()
+	if info.Name != name {
+		c.Fatalf("Got name %s, expected %s", info.Name, name)
+	}
+	if !reflect.DeepEqual(info.Params, paramNames) {
+		c.Fatalf("Got param names %#v, expected %#v", info.Params, paramNames)
+	}
+}
+
+func testCheck(c *check.C, checker check.Checker, expectedResult bool, expectedError string, params ...interface{}) ([]interface{}, []string) {
+	info := checker.Info()
+	if len(params) != len(info.Params) {
+		c.Fatalf("unexpected param count in test; expected %d got %d", len(info.Params), len(params))
+	}
+	names := append([]string{}, info.Params...)
+	result, error := checker.Check(params, names)
+	if result != expectedResult || error != expectedError {
+		c.Fatalf("%s.Check(%#v) returned (%#v, %#v) rather than (%#v, %#v)",
+			info.Name, params, result, error, expectedResult, expectedError)
+	}
+	return params, names
+}
+
+func (s *CheckersS) TestContains(c *check.C) {
+	testInfo(c, Contains, "Contains", []string{"value", "substring"})
+
+	testCheck(c, Contains, true, "", "abcd", "bc")
+	testCheck(c, Contains, false, "", "abcd", "efg")
+	testCheck(c, Contains, false, "", "", "bc")
+	testCheck(c, Contains, true, "", "abcd", "")
+	testCheck(c, Contains, true, "", "", "")
+
+	testCheck(c, Contains, false, "Obtained value is not a string and has no .String()", 12, "1")
+	testCheck(c, Contains, false, "Substring must be a string", "", 1)
+}

+ 332 - 0
pkg/integration/utils.go

@@ -0,0 +1,332 @@
+package integration
+
+import (
+	"archive/tar"
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"path"
+	"reflect"
+	"strings"
+	"syscall"
+	"time"
+
+	"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 {
+		status, ok := exitErr.Sys().(syscall.WaitStatus)
+		if !ok {
+			return false
+		}
+		// status.ExitStatus() is required on Windows because it does not
+		// implement Signal() nor Signaled(). Just check it had a bad exit
+		// status could mean it was killed (and in tests we do kill)
+		return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
+	}
+	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) {
+	exitCode = 0
+	out, err := cmd.CombinedOutput()
+	exitCode = 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
+
+	done := make(chan error)
+	go func() {
+		exitErr := cmd.Run()
+		exitCode = ProcessExitCode(exitErr)
+		done <- exitErr
+	}()
+
+	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
+		break
+	case err = <-done:
+		break
+	}
+	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
+// if something bad happened.
+func RunCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode int, err error) {
+	if len(cmds) < 2 {
+		return "", 0, errors.New("pipeline does not have multiple cmds")
+	}
+
+	// connect stdin of each cmd to stdout pipe of previous cmd
+	for i, cmd := range cmds {
+		if i > 0 {
+			prevCmd := cmds[i-1]
+			cmd.Stdin, err = prevCmd.StdoutPipe()
+
+			if err != nil {
+				return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err)
+			}
+		}
+	}
+
+	// start all cmds except the last
+	for _, cmd := range cmds[:len(cmds)-1] {
+		if err = cmd.Start(); err != nil {
+			return "", 0, fmt.Errorf("starting %s failed with error: %v", cmd.Path, err)
+		}
+	}
+
+	defer func() {
+		// wait all cmds except the last to release their resources
+		for _, cmd := range cmds[:len(cmds)-1] {
+			cmd.Wait()
+		}
+	}()
+
+	// wait on last cmd
+	return RunCommandWithOutput(cmds[len(cmds)-1])
+}
+
+// UnmarshalJSON deserialize a JSON in the given interface.
+func UnmarshalJSON(data []byte, result interface{}) error {
+	if err := json.Unmarshal(data, result); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ConvertSliceOfStringsToMap converts a slices of string in a map
+// with the strings as key and an empty string as values.
+func ConvertSliceOfStringsToMap(input []string) map[string]struct{} {
+	output := make(map[string]struct{})
+	for _, v := range input {
+		output[v] = struct{}{}
+	}
+	return output
+}
+
+// CompareDirectoryEntries compares two sets of FileInfo (usually taken from a directory)
+// and returns an error if different.
+func CompareDirectoryEntries(e1 []os.FileInfo, e2 []os.FileInfo) error {
+	var (
+		e1Entries = make(map[string]struct{})
+		e2Entries = make(map[string]struct{})
+	)
+	for _, e := range e1 {
+		e1Entries[e.Name()] = struct{}{}
+	}
+	for _, e := range e2 {
+		e2Entries[e.Name()] = struct{}{}
+	}
+	if !reflect.DeepEqual(e1Entries, e2Entries) {
+		return fmt.Errorf("entries differ")
+	}
+	return nil
+}
+
+// ListTar lists the entries of a tar.
+func ListTar(f io.Reader) ([]string, error) {
+	tr := tar.NewReader(f)
+	var entries []string
+
+	for {
+		th, err := tr.Next()
+		if err == io.EOF {
+			// end of tar archive
+			return entries, nil
+		}
+		if err != nil {
+			return entries, err
+		}
+		entries = append(entries, th.Name)
+	}
+}
+
+// RandomUnixTmpDirPath provides a temporary unix path with rand string appended.
+// does not create or checks if it exists.
+func RandomUnixTmpDirPath(s string) string {
+	return path.Join("/tmp", fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10)))
+}
+
+// ConsumeWithSpeed reads chunkSize bytes from reader after every interval.
+// Returns total read bytes.
+func ConsumeWithSpeed(reader io.Reader, chunkSize int, interval time.Duration, stop chan bool) (n int, err error) {
+	buffer := make([]byte, chunkSize)
+	for {
+		select {
+		case <-stop:
+			return
+		default:
+			var readBytes int
+			readBytes, err = reader.Read(buffer)
+			n += readBytes
+			if err != nil {
+				if err == io.EOF {
+					err = nil
+				}
+				return
+			}
+			time.Sleep(interval)
+		}
+	}
+}
+
+// ParseCgroupPaths arses 'procCgroupData', which is output of '/proc/<pid>/cgroup', and returns
+// a map which cgroup name as key and path as value.
+func ParseCgroupPaths(procCgroupData string) map[string]string {
+	cgroupPaths := map[string]string{}
+	for _, line := range strings.Split(procCgroupData, "\n") {
+		parts := strings.Split(line, ":")
+		if len(parts) != 3 {
+			continue
+		}
+		cgroupPaths[parts[1]] = parts[2]
+	}
+	return cgroupPaths
+}
+
+// ChannelBuffer holds a chan of byte array that can be populate in a goroutine.
+type ChannelBuffer struct {
+	C chan []byte
+}
+
+// Write implements Writer.
+func (c *ChannelBuffer) Write(b []byte) (int, error) {
+	c.C <- b
+	return len(b), nil
+}
+
+// Close closes the go channel.
+func (c *ChannelBuffer) Close() error {
+	close(c.C)
+	return nil
+}
+
+// ReadTimeout reads the content of the channel in the specified byte array with
+// the specified duration as timeout.
+func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
+	select {
+	case b := <-c.C:
+		return copy(p[0:], b), nil
+	case <-time.After(n):
+		return -1, fmt.Errorf("timeout reading from channel")
+	}
+}
+
+// RunAtDifferentDate runs the specifed function with the given time.
+// It changes the date of the system, which can led to weird behaviors.
+func RunAtDifferentDate(date time.Time, block func()) {
+	// Layout for date. MMDDhhmmYYYY
+	const timeLayout = "010203042006"
+	// Ensure we bring time back to now
+	now := time.Now().Format(timeLayout)
+	dateReset := exec.Command("date", now)
+	defer RunCommand(dateReset)
+
+	dateChange := exec.Command("date", date.Format(timeLayout))
+	RunCommand(dateChange)
+	block()
+	return
+}

+ 488 - 0
pkg/integration/utils_test.go

@@ -0,0 +1,488 @@
+package integration
+
+import (
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"strings"
+	"testing"
+	"time"
+)
+
+func TestIsKilledFalseWithNonKilledProcess(t *testing.T) {
+	lsCmd := exec.Command("ls")
+	lsCmd.Start()
+	// Wait for it to finish
+	err := lsCmd.Wait()
+	if IsKilled(err) {
+		t.Fatalf("Expected the ls command to not be killed, was.")
+	}
+}
+
+func TestIsKilledTrueWithKilledProcess(t *testing.T) {
+	longCmd := exec.Command("top")
+	// Start a command
+	longCmd.Start()
+	// Capture the error when *dying*
+	done := make(chan error, 1)
+	go func() {
+		done <- longCmd.Wait()
+	}()
+	// Then kill it
+	longCmd.Process.Kill()
+	// Get the error
+	err := <-done
+	if !IsKilled(err) {
+		t.Fatalf("Expected the command to be killed, was not.")
+	}
+}
+
+func TestRunCommandWithOutput(t *testing.T) {
+	echoHelloWorldCmd := exec.Command("echo", "hello", "world")
+	out, exitCode, err := RunCommandWithOutput(echoHelloWorldCmd)
+	expected := "hello world\n"
+	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) {
+	cmd := exec.Command("doesnotexists")
+	out, exitCode, err := RunCommandWithOutput(cmd)
+	expectedError := `exec: "doesnotexists": executable file not found in $PATH`
+	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)
+	}
+
+	wrongLsCmd := exec.Command("ls", "-z")
+	expected := `ls: invalid option -- 'z'
+Try 'ls --help' for more information.
+`
+	out, exitCode, err = RunCommandWithOutput(wrongLsCmd)
+
+	if out != expected || exitCode != 2 || err == nil || err.Error() != "exit status 2" {
+		t.Fatalf("Expected command to output %s, got out:%s, 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) {
+	cmd := exec.Command("doesnotexists")
+	stdout, stderr, exitCode, err := RunCommandWithStdoutStderr(cmd)
+	expectedError := `exec: "doesnotexists": executable file not found in $PATH`
+	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) {
+	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) {
+	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) {
+	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) {
+	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) {
+	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 $PATH`
+	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"
+	if err == nil || err.Error() != expectedError {
+		t.Fatalf("Expected an error with %s, got err:%s", expectedError, err)
+	}
+}
+
+func TestRunCommandPipelineWithOutputErrors(t *testing.T) {
+	cmd1 := exec.Command("ls")
+	cmd1.Stdout = os.Stdout
+	cmd2 := exec.Command("anything really")
+	_, _, err := RunCommandPipelineWithOutput(cmd1, cmd2)
+	if err == nil || err.Error() != "cannot set stdout pipe for anything really: exec: Stdout already set" {
+		t.Fatalf("Expected an error, got %v", err)
+	}
+
+	cmdWithError := exec.Command("doesnotexists")
+	cmdCat := exec.Command("cat")
+	_, _, err = RunCommandPipelineWithOutput(cmdWithError, cmdCat)
+	if err == nil || err.Error() != `starting doesnotexists failed with error: exec: "doesnotexists": executable file not found in $PATH` {
+		t.Fatalf("Expected an error, got %v", err)
+	}
+}
+
+func TestRunCommandPipelineWithOutput(t *testing.T) {
+	cmds := []*exec.Cmd{
+		// Print 2 characters
+		exec.Command("echo", "-n", "11"),
+		// Count the number or char from stdin (previous command)
+		exec.Command("wc", "-m"),
+	}
+	out, exitCode, err := RunCommandPipelineWithOutput(cmds...)
+	expectedOutput := "2\n"
+	if out != expectedOutput || exitCode != 0 || err != nil {
+		t.Fatalf("Expected %s for commands %v, got out:%s, exitCode:%d, err:%v", expectedOutput, cmds, out, exitCode, err)
+	}
+}
+
+// Simple simple test as it is just a passthrough for json.Unmarshal
+func TestUnmarshalJSON(t *testing.T) {
+	emptyResult := struct{}{}
+	if err := UnmarshalJSON([]byte(""), &emptyResult); err == nil {
+		t.Fatalf("Expected an error, got nothing")
+	}
+	result := struct{ Name string }{}
+	if err := UnmarshalJSON([]byte(`{"name": "name"}`), &result); err != nil {
+		t.Fatal(err)
+	}
+	if result.Name != "name" {
+		t.Fatalf("Expected result.name to be 'name', was '%s'", result.Name)
+	}
+}
+
+func TestConvertSliceOfStringsToMap(t *testing.T) {
+	input := []string{"a", "b"}
+	actual := ConvertSliceOfStringsToMap(input)
+	for _, key := range input {
+		if _, ok := actual[key]; !ok {
+			t.Fatalf("Expected output to contains key %s, did not: %v", key, actual)
+		}
+	}
+}
+
+func TestCompareDirectoryEntries(t *testing.T) {
+	tmpFolder, err := ioutil.TempDir("", "integration-cli-utils-compare-directories")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpFolder)
+
+	file1 := path.Join(tmpFolder, "file1")
+	file2 := path.Join(tmpFolder, "file2")
+	os.Create(file1)
+	os.Create(file2)
+
+	fi1, err := os.Stat(file1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fi1bis, err := os.Stat(file1)
+	if err != nil {
+		t.Fatal(err)
+	}
+	fi2, err := os.Stat(file2)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cases := []struct {
+		e1          []os.FileInfo
+		e2          []os.FileInfo
+		shouldError bool
+	}{
+		// Empty directories
+		{
+			[]os.FileInfo{},
+			[]os.FileInfo{},
+			false,
+		},
+		// Same FileInfos
+		{
+			[]os.FileInfo{fi1},
+			[]os.FileInfo{fi1},
+			false,
+		},
+		// Different FileInfos but same names
+		{
+			[]os.FileInfo{fi1},
+			[]os.FileInfo{fi1bis},
+			false,
+		},
+		// Different FileInfos, different names
+		{
+			[]os.FileInfo{fi1},
+			[]os.FileInfo{fi2},
+			true,
+		},
+	}
+	for _, elt := range cases {
+		err := CompareDirectoryEntries(elt.e1, elt.e2)
+		if elt.shouldError && err == nil {
+			t.Fatalf("Should have return an error, did not with %v and %v", elt.e1, elt.e2)
+		}
+		if !elt.shouldError && err != nil {
+			t.Fatalf("Should have not returned an error, but did : %v with %v and %v", err, elt.e1, elt.e2)
+		}
+	}
+}
+
+// FIXME make an "unhappy path" test for ListTar without "panicing" :-)
+func TestListTar(t *testing.T) {
+	tmpFolder, err := ioutil.TempDir("", "integration-cli-utils-list-tar")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpFolder)
+
+	// Let's create a Tar file
+	srcFile := path.Join(tmpFolder, "src")
+	tarFile := path.Join(tmpFolder, "src.tar")
+	os.Create(srcFile)
+	cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile)
+	_, err = cmd.CombinedOutput()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	reader, err := os.Open(tarFile)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer reader.Close()
+
+	entries, err := ListTar(reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(entries) != 1 && entries[0] != "src" {
+		t.Fatalf("Expected a tar file with 1 entry (%s), got %v", srcFile, entries)
+	}
+}
+
+func TestRandomUnixTmpDirPath(t *testing.T) {
+	path := RandomUnixTmpDirPath("something")
+
+	prefix := "/tmp/something"
+	expectedSize := len(prefix) + 11
+
+	if !strings.HasPrefix(path, prefix) {
+		t.Fatalf("Expected generated path to have '%s' as prefix, got %s'", prefix, path)
+	}
+	if len(path) != expectedSize {
+		t.Fatalf("Expected generated path to be %d, got %d", expectedSize, len(path))
+	}
+}
+
+func TestConsumeWithSpeedWith(t *testing.T) {
+	reader := strings.NewReader("1234567890")
+	chunksize := 2
+
+	bytes1, err := ConsumeWithSpeed(reader, chunksize, 1*time.Millisecond, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if bytes1 != 10 {
+		t.Fatalf("Expected to have read 10 bytes, got %s", bytes1)
+	}
+
+}
+
+func TestConsumeWithSpeedWithStop(t *testing.T) {
+	reader := strings.NewReader("1234567890")
+	chunksize := 2
+
+	stopIt := make(chan bool)
+
+	go func() {
+		time.Sleep(1 * time.Millisecond)
+		stopIt <- true
+	}()
+
+	bytes1, err := ConsumeWithSpeed(reader, chunksize, 2*time.Millisecond, stopIt)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if bytes1 != 2 {
+		t.Fatalf("Expected to have read 2 bytes, got %s", bytes1)
+	}
+
+}
+
+func TestParseCgroupPathsEmpty(t *testing.T) {
+	cgroupMap := ParseCgroupPaths("")
+	if len(cgroupMap) != 0 {
+		t.Fatalf("Expected an empty map, got %v", cgroupMap)
+	}
+	cgroupMap = ParseCgroupPaths("\n")
+	if len(cgroupMap) != 0 {
+		t.Fatalf("Expected an empty map, got %v", cgroupMap)
+	}
+	cgroupMap = ParseCgroupPaths("something:else\nagain:here")
+	if len(cgroupMap) != 0 {
+		t.Fatalf("Expected an empty map, got %v", cgroupMap)
+	}
+}
+
+func TestParseCgroupPaths(t *testing.T) {
+	cgroupMap := ParseCgroupPaths("2:memory:/a\n1:cpuset:/b")
+	if len(cgroupMap) != 2 {
+		t.Fatalf("Expected a map with 2 entries, got %v", cgroupMap)
+	}
+	if value, ok := cgroupMap["memory"]; !ok || value != "/a" {
+		t.Fatalf("Expected cgroupMap to contains an entry for 'memory' with value '/a', got %v", cgroupMap)
+	}
+	if value, ok := cgroupMap["cpuset"]; !ok || value != "/b" {
+		t.Fatalf("Expected cgroupMap to contains an entry for 'cpuset' with value '/b', got %v", cgroupMap)
+	}
+}
+
+func TestChannelBufferTimeout(t *testing.T) {
+	expected := "11"
+
+	buf := &ChannelBuffer{make(chan []byte, 1)}
+	defer buf.Close()
+
+	go func() {
+		time.Sleep(100 * time.Millisecond)
+		io.Copy(buf, strings.NewReader(expected))
+	}()
+
+	// Wait long enough
+	b := make([]byte, 2)
+	_, err := buf.ReadTimeout(b, 50*time.Millisecond)
+	if err == nil && err.Error() != "timeout reading from channel" {
+		t.Fatalf("Expected an error, got %s", err)
+	}
+
+	// Wait for the end :)
+	time.Sleep(150 * time.Millisecond)
+}
+
+func TestChannelBuffer(t *testing.T) {
+	expected := "11"
+
+	buf := &ChannelBuffer{make(chan []byte, 1)}
+	defer buf.Close()
+
+	go func() {
+		time.Sleep(100 * time.Millisecond)
+		io.Copy(buf, strings.NewReader(expected))
+	}()
+
+	// Wait long enough
+	b := make([]byte, 2)
+	_, err := buf.ReadTimeout(b, 200*time.Millisecond)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if string(b) != expected {
+		t.Fatalf("Expected '%s', got '%s'", expected, string(b))
+	}
+}
+
+// FIXME doesn't work
+// func TestRunAtDifferentDate(t *testing.T) {
+// 	var date string
+
+// 	// Layout for date. MMDDhhmmYYYY
+// 	const timeLayout = "20060102"
+// 	expectedDate := "20100201"
+// 	theDate, err := time.Parse(timeLayout, expectedDate)
+// 	if err != nil {
+// 		t.Fatal(err)
+// 	}
+
+// 	RunAtDifferentDate(theDate, func() {
+// 		cmd := exec.Command("date", "+%Y%M%d")
+// 		out, err := cmd.Output()
+// 		if err != nil {
+// 			t.Fatal(err)
+// 		}
+// 		date = string(out)
+// 	})
+// }