Browse code

Refactor test environment

split all non-cli portions into a new internal/test/environment package

Set a test environment on packages instead of creating new ones.

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/08/26 07:48:36
Showing 20 changed files
... ...
@@ -2,10 +2,12 @@ package main
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"io/ioutil"
5 6
 	"net/http/httptest"
6 7
 	"os"
7 8
 	"path"
8 9
 	"path/filepath"
10
+	"strconv"
9 11
 	"sync"
10 12
 	"syscall"
11 13
 	"testing"
... ...
@@ -20,6 +22,7 @@ import (
20 20
 	"github.com/docker/docker/integration-cli/environment"
21 21
 	"github.com/docker/docker/integration-cli/fixtures/plugin"
22 22
 	"github.com/docker/docker/integration-cli/registry"
23
+	ienv "github.com/docker/docker/internal/test/environment"
23 24
 	"github.com/docker/docker/pkg/reexec"
24 25
 	"github.com/go-check/check"
25 26
 	"golang.org/x/net/context"
... ...
@@ -57,20 +60,14 @@ func init() {
57 57
 
58 58
 func TestMain(m *testing.M) {
59 59
 	dockerBinary = testEnv.DockerBinary()
60
-
61
-	if testEnv.LocalDaemon() {
62
-		fmt.Println("INFO: Testing against a local daemon")
63
-	} else {
64
-		fmt.Println("INFO: Testing against a remote daemon")
65
-	}
66
-	exitCode := m.Run()
67
-	os.Exit(exitCode)
60
+	testEnv.Print()
61
+	os.Exit(m.Run())
68 62
 }
69 63
 
70 64
 func Test(t *testing.T) {
71
-	cli.EnsureTestEnvIsLoaded(t)
72
-	fakestorage.EnsureTestEnvIsLoaded(t)
73
-	environment.ProtectImages(t, testEnv)
65
+	cli.SetTestEnvironment(testEnv)
66
+	fakestorage.SetTestEnvironment(&testEnv.Execution)
67
+	ienv.ProtectImages(t, &testEnv.Execution)
74 68
 	check.TestingT(t)
75 69
 }
76 70
 
... ...
@@ -82,13 +79,25 @@ type DockerSuite struct {
82 82
 }
83 83
 
84 84
 func (s *DockerSuite) OnTimeout(c *check.C) {
85
-	if testEnv.DaemonPID() > 0 && testEnv.LocalDaemon() {
86
-		daemon.SignalDaemonDump(testEnv.DaemonPID())
85
+	path := filepath.Join(os.Getenv("DEST"), "docker.pid")
86
+	b, err := ioutil.ReadFile(path)
87
+	if err != nil {
88
+		c.Fatalf("Failed to get daemon PID from %s\n", path)
89
+	}
90
+
91
+	rawPid, err := strconv.ParseInt(string(b), 10, 32)
92
+	if err != nil {
93
+		c.Fatalf("Failed to parse pid from %s: %s\n", path, err)
94
+	}
95
+
96
+	daemonPid := int(rawPid)
97
+	if daemonPid > 0 && testEnv.IsLocalDaemon() {
98
+		daemon.SignalDaemonDump(daemonPid)
87 99
 	}
88 100
 }
89 101
 
90 102
 func (s *DockerSuite) TearDownTest(c *check.C) {
91
-	testEnv.Clean(c, dockerBinary)
103
+	testEnv.Clean(c)
92 104
 }
93 105
 
94 106
 func init() {
... ...
@@ -11,9 +11,11 @@ import (
11 11
 
12 12
 	"github.com/docker/docker/integration-cli/cli/build/fakecontext"
13 13
 	"github.com/docker/docker/integration-cli/cli/build/fakestorage"
14
+	"github.com/stretchr/testify/require"
14 15
 )
15 16
 
16 17
 type testingT interface {
18
+	require.TestingT
17 19
 	logT
18 20
 	Fatal(args ...interface{})
19 21
 	Fatalf(string, ...interface{})
... ...
@@ -30,7 +30,7 @@ func ensureHTTPServerImage(t testingT) {
30 30
 	}
31 31
 	defer os.RemoveAll(tmp)
32 32
 
33
-	goos := testEnv.DaemonPlatform()
33
+	goos := testEnv.DaemonInfo.OSType
34 34
 	if goos == "" {
35 35
 		goos = "linux"
36 36
 	}
... ...
@@ -8,39 +8,20 @@ import (
8 8
 	"net/url"
9 9
 	"os"
10 10
 	"strings"
11
-	"sync"
12 11
 
13 12
 	"github.com/docker/docker/integration-cli/cli"
14 13
 	"github.com/docker/docker/integration-cli/cli/build"
15 14
 	"github.com/docker/docker/integration-cli/cli/build/fakecontext"
16
-	"github.com/docker/docker/integration-cli/environment"
17 15
 	"github.com/docker/docker/integration-cli/request"
16
+	"github.com/docker/docker/internal/test/environment"
18 17
 	"github.com/docker/docker/pkg/stringutils"
18
+	"github.com/stretchr/testify/require"
19 19
 )
20 20
 
21
-var (
22
-	testEnv  *environment.Execution
23
-	onlyOnce sync.Once
24
-)
25
-
26
-// EnsureTestEnvIsLoaded make sure the test environment is loaded for this package
27
-func EnsureTestEnvIsLoaded(t testingT) {
28
-	var doIt bool
29
-	var err error
30
-	onlyOnce.Do(func() {
31
-		doIt = true
32
-	})
33
-
34
-	if !doIt {
35
-		return
36
-	}
37
-	testEnv, err = environment.New()
38
-	if err != nil {
39
-		t.Fatalf("error loading testenv : %v", err)
40
-	}
41
-}
21
+var testEnv *environment.Execution
42 22
 
43 23
 type testingT interface {
24
+	require.TestingT
44 25
 	logT
45 26
 	Fatal(args ...interface{})
46 27
 	Fatalf(string, ...interface{})
... ...
@@ -58,11 +39,20 @@ type Fake interface {
58 58
 	CtxDir() string
59 59
 }
60 60
 
61
+// SetTestEnvironment sets a static test environment
62
+// TODO: decouple this package from environment
63
+func SetTestEnvironment(env *environment.Execution) {
64
+	testEnv = env
65
+}
66
+
61 67
 // New returns a static file server that will be use as build context.
62 68
 func New(t testingT, dir string, modifiers ...func(*fakecontext.Fake) error) Fake {
69
+	if testEnv == nil {
70
+		t.Fatal("fakstorage package requires SetTestEnvironment() to be called before use.")
71
+	}
63 72
 	ctx := fakecontext.New(t, dir, modifiers...)
64
-	if testEnv.LocalDaemon() {
65
-		return newLocalFakeStorage(t, ctx)
73
+	if testEnv.IsLocalDaemon() {
74
+		return newLocalFakeStorage(ctx)
66 75
 	}
67 76
 	return newRemoteFileServer(t, ctx)
68 77
 }
... ...
@@ -86,7 +76,7 @@ func (s *localFileStorage) Close() error {
86 86
 	return s.Fake.Close()
87 87
 }
88 88
 
89
-func newLocalFakeStorage(t testingT, ctx *fakecontext.Fake) *localFileStorage {
89
+func newLocalFakeStorage(ctx *fakecontext.Fake) *localFileStorage {
90 90
 	handler := http.FileServer(http.Dir(ctx.Dir))
91 91
 	server := httptest.NewServer(handler)
92 92
 	return &localFileStorage{
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"fmt"
5 5
 	"io"
6 6
 	"strings"
7
-	"sync"
8 7
 	"time"
9 8
 
10 9
 	"github.com/docker/docker/integration-cli/daemon"
... ...
@@ -13,26 +12,12 @@ import (
13 13
 	"github.com/pkg/errors"
14 14
 )
15 15
 
16
-var (
17
-	testEnv  *environment.Execution
18
-	onlyOnce sync.Once
19
-)
20
-
21
-// EnsureTestEnvIsLoaded make sure the test environment is loaded for this package
22
-func EnsureTestEnvIsLoaded(t testingT) {
23
-	var doIt bool
24
-	var err error
25
-	onlyOnce.Do(func() {
26
-		doIt = true
27
-	})
16
+var testEnv *environment.Execution
28 17
 
29
-	if !doIt {
30
-		return
31
-	}
32
-	testEnv, err = environment.New()
33
-	if err != nil {
34
-		t.Fatalf("error loading testenv : %v", err)
35
-	}
18
+// SetTestEnvironment sets a static test environment
19
+// TODO: decouple this package from environment
20
+func SetTestEnvironment(env *environment.Execution) {
21
+	testEnv = env
36 22
 }
37 23
 
38 24
 // CmdOperator defines functions that can modify a command
... ...
@@ -130,7 +115,7 @@ func Docker(cmd icmd.Cmd, cmdOperators ...CmdOperator) *icmd.Result {
130 130
 // validateArgs is a checker to ensure tests are not running commands which are
131 131
 // not supported on platforms. Specifically on Windows this is 'busybox top'.
132 132
 func validateArgs(args ...string) error {
133
-	if testEnv.DaemonPlatform() != "windows" {
133
+	if testEnv.DaemonInfo.OSType != "windows" {
134 134
 		return nil
135 135
 	}
136 136
 	foundBusybox := -1
... ...
@@ -2215,7 +2215,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
2215 2215
 
2216 2216
 	out, err = inspectMountSourceField("dark_helmet", prefix+slash+`foo`)
2217 2217
 	c.Assert(err, check.IsNil)
2218
-	if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.VolumesConfigPath())) {
2218
+	if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.PlatformDefaults.VolumesConfigPath)) {
2219 2219
 		c.Fatalf("Volume was not defined for %s/foo\n%q", prefix, out)
2220 2220
 	}
2221 2221
 
... ...
@@ -2226,7 +2226,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
2226 2226
 
2227 2227
 	out, err = inspectMountSourceField("dark_helmet", prefix+slash+"bar")
2228 2228
 	c.Assert(err, check.IsNil)
2229
-	if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.VolumesConfigPath())) {
2229
+	if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.PlatformDefaults.VolumesConfigPath)) {
2230 2230
 		c.Fatalf("Volume was not defined for %s/bar\n%q", prefix, out)
2231 2231
 	}
2232 2232
 }
... ...
@@ -234,7 +234,7 @@ func readFile(src string, c *check.C) (content string) {
234 234
 }
235 235
 
236 236
 func containerStorageFile(containerID, basename string) string {
237
-	return filepath.Join(testEnv.ContainerStoragePath(), containerID, basename)
237
+	return filepath.Join(testEnv.PlatformDefaults.ContainerStoragePath, containerID, basename)
238 238
 }
239 239
 
240 240
 // docker commands that use this function must be run with the '-d' switch.
... ...
@@ -266,7 +266,7 @@ func readContainerFileWithExec(c *check.C, containerID, filename string) []byte
266 266
 
267 267
 // daemonTime provides the current time on the daemon host
268 268
 func daemonTime(c *check.C) time.Time {
269
-	if testEnv.LocalDaemon() {
269
+	if testEnv.IsLocalDaemon() {
270 270
 		return time.Now()
271 271
 	}
272 272
 	cli, err := client.NewEnvClient()
273 273
deleted file mode 100644
... ...
@@ -1,198 +0,0 @@
1
-package environment
2
-
3
-import (
4
-	"regexp"
5
-	"strings"
6
-
7
-	"github.com/docker/docker/api/types"
8
-	"github.com/docker/docker/api/types/filters"
9
-	"github.com/docker/docker/client"
10
-	"github.com/gotestyourself/gotestyourself/icmd"
11
-	"golang.org/x/net/context"
12
-)
13
-
14
-type testingT interface {
15
-	logT
16
-	Fatalf(string, ...interface{})
17
-}
18
-
19
-type logT interface {
20
-	Logf(string, ...interface{})
21
-}
22
-
23
-// Clean the environment, preserving protected objects (images, containers, ...)
24
-// and removing everything else. It's meant to run after any tests so that they don't
25
-// depend on each others.
26
-func (e *Execution) Clean(t testingT, dockerBinary string) {
27
-	cli, err := client.NewEnvClient()
28
-	if err != nil {
29
-		t.Fatalf("%v", err)
30
-	}
31
-	defer cli.Close()
32
-
33
-	if (e.DaemonPlatform() != "windows") || (e.DaemonPlatform() == "windows" && e.Isolation() == "hyperv") {
34
-		unpauseAllContainers(t, dockerBinary)
35
-	}
36
-	deleteAllContainers(t, dockerBinary)
37
-	deleteAllImages(t, dockerBinary, e.protectedElements.images)
38
-	deleteAllVolumes(t, cli)
39
-	deleteAllNetworks(t, cli, e.DaemonPlatform())
40
-	if e.DaemonPlatform() == "linux" {
41
-		deleteAllPlugins(t, cli, dockerBinary)
42
-	}
43
-}
44
-
45
-func unpauseAllContainers(t testingT, dockerBinary string) {
46
-	containers := getPausedContainers(t, dockerBinary)
47
-	if len(containers) > 0 {
48
-		icmd.RunCommand(dockerBinary, append([]string{"unpause"}, containers...)...).Assert(t, icmd.Success)
49
-	}
50
-}
51
-
52
-func getPausedContainers(t testingT, dockerBinary string) []string {
53
-	result := icmd.RunCommand(dockerBinary, "ps", "-f", "status=paused", "-q", "-a")
54
-	result.Assert(t, icmd.Success)
55
-	return strings.Fields(result.Combined())
56
-}
57
-
58
-var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
59
-
60
-func deleteAllContainers(t testingT, dockerBinary string) {
61
-	containers := getAllContainers(t, dockerBinary)
62
-	if len(containers) > 0 {
63
-		result := icmd.RunCommand(dockerBinary, append([]string{"rm", "-fv"}, containers...)...)
64
-		if result.Error != nil {
65
-			// If the error is "No such container: ..." this means the container doesn't exists anymore,
66
-			// or if it is "... removal of container ... is already in progress" it will be removed eventually.
67
-			// We can safely ignore those.
68
-			if strings.Contains(result.Stderr(), "No such container") || alreadyExists.MatchString(result.Stderr()) {
69
-				return
70
-			}
71
-			t.Fatalf("error removing containers %v : %v (%s)", containers, result.Error, result.Combined())
72
-		}
73
-	}
74
-}
75
-
76
-func getAllContainers(t testingT, dockerBinary string) []string {
77
-	result := icmd.RunCommand(dockerBinary, "ps", "-q", "-a")
78
-	result.Assert(t, icmd.Success)
79
-	return strings.Fields(result.Combined())
80
-}
81
-
82
-func deleteAllImages(t testingT, dockerBinary string, protectedImages map[string]struct{}) {
83
-	result := icmd.RunCommand(dockerBinary, "images", "--digests")
84
-	result.Assert(t, icmd.Success)
85
-	lines := strings.Split(string(result.Combined()), "\n")[1:]
86
-	imgMap := map[string]struct{}{}
87
-	for _, l := range lines {
88
-		if l == "" {
89
-			continue
90
-		}
91
-		fields := strings.Fields(l)
92
-		imgTag := fields[0] + ":" + fields[1]
93
-		if _, ok := protectedImages[imgTag]; !ok {
94
-			if fields[0] == "<none>" || fields[1] == "<none>" {
95
-				if fields[2] != "<none>" {
96
-					imgMap[fields[0]+"@"+fields[2]] = struct{}{}
97
-				} else {
98
-					imgMap[fields[3]] = struct{}{}
99
-				}
100
-				// continue
101
-			} else {
102
-				imgMap[imgTag] = struct{}{}
103
-			}
104
-		}
105
-	}
106
-	if len(imgMap) != 0 {
107
-		imgs := make([]string, 0, len(imgMap))
108
-		for k := range imgMap {
109
-			imgs = append(imgs, k)
110
-		}
111
-		icmd.RunCommand(dockerBinary, append([]string{"rmi", "-f"}, imgs...)...).Assert(t, icmd.Success)
112
-	}
113
-}
114
-
115
-func deleteAllVolumes(t testingT, c client.APIClient) {
116
-	var errs []string
117
-	volumes, err := getAllVolumes(c)
118
-	if err != nil {
119
-		t.Fatalf("%v", err)
120
-	}
121
-	for _, v := range volumes {
122
-		err := c.VolumeRemove(context.Background(), v.Name, true)
123
-		if err != nil {
124
-			errs = append(errs, err.Error())
125
-			continue
126
-		}
127
-	}
128
-	if len(errs) > 0 {
129
-		t.Fatalf("%v", strings.Join(errs, "\n"))
130
-	}
131
-}
132
-
133
-func getAllVolumes(c client.APIClient) ([]*types.Volume, error) {
134
-	volumes, err := c.VolumeList(context.Background(), filters.Args{})
135
-	if err != nil {
136
-		return nil, err
137
-	}
138
-	return volumes.Volumes, nil
139
-}
140
-
141
-func deleteAllNetworks(t testingT, c client.APIClient, daemonPlatform string) {
142
-	networks, err := getAllNetworks(c)
143
-	if err != nil {
144
-		t.Fatalf("%v", err)
145
-	}
146
-	var errs []string
147
-	for _, n := range networks {
148
-		if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
149
-			continue
150
-		}
151
-		if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
152
-			// nat is a pre-defined network on Windows and cannot be removed
153
-			continue
154
-		}
155
-		err := c.NetworkRemove(context.Background(), n.ID)
156
-		if err != nil {
157
-			errs = append(errs, err.Error())
158
-			continue
159
-		}
160
-	}
161
-	if len(errs) > 0 {
162
-		t.Fatalf("%v", strings.Join(errs, "\n"))
163
-	}
164
-}
165
-
166
-func getAllNetworks(c client.APIClient) ([]types.NetworkResource, error) {
167
-	networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
168
-	if err != nil {
169
-		return nil, err
170
-	}
171
-	return networks, nil
172
-}
173
-
174
-func deleteAllPlugins(t testingT, c client.APIClient, dockerBinary string) {
175
-	plugins, err := getAllPlugins(c)
176
-	if err != nil {
177
-		t.Fatalf("%v", err)
178
-	}
179
-	var errs []string
180
-	for _, p := range plugins {
181
-		err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
182
-		if err != nil {
183
-			errs = append(errs, err.Error())
184
-			continue
185
-		}
186
-	}
187
-	if len(errs) > 0 {
188
-		t.Fatalf("%v", strings.Join(errs, "\n"))
189
-	}
190
-}
191
-
192
-func getAllPlugins(c client.APIClient) (types.PluginsListResponse, error) {
193
-	plugins, err := c.PluginList(context.Background(), filters.Args{})
194
-	if err != nil {
195
-		return nil, err
196
-	}
197
-	return plugins, nil
198
-}
... ...
@@ -1,19 +1,11 @@
1 1
 package environment
2 2
 
3 3
 import (
4
-	"fmt"
5
-	"io/ioutil"
6 4
 	"os"
5
+
7 6
 	"os/exec"
8
-	"path/filepath"
9
-	"strconv"
10
-	"strings"
11 7
 
12
-	"github.com/docker/docker/api/types"
13
-	"github.com/docker/docker/api/types/container"
14
-	"github.com/docker/docker/client"
15
-	"github.com/docker/docker/opts"
16
-	"golang.org/x/net/context"
8
+	"github.com/docker/docker/internal/test/environment"
17 9
 )
18 10
 
19 11
 var (
... ...
@@ -23,89 +15,28 @@ var (
23 23
 
24 24
 func init() {
25 25
 	if DefaultClientBinary == "" {
26
-		// TODO: to be removed once we no longer depend on the docker cli for integration tests
27
-		//panic("TEST_CLIENT_BINARY must be set")
28 26
 		DefaultClientBinary = "docker"
29 27
 	}
30 28
 }
31 29
 
32
-// Execution holds informations about the test execution environment.
30
+// Execution contains information about the current test execution and daemon
31
+// under test
33 32
 type Execution struct {
34
-	daemonPlatform      string
35
-	localDaemon         bool
36
-	experimentalDaemon  bool
37
-	daemonStorageDriver string
38
-	isolation           container.Isolation
39
-	daemonPid           int
40
-	daemonKernelVersion string
41
-	// For a local daemon on Linux, these values will be used for testing
42
-	// user namespace support as the standard graph path(s) will be
43
-	// appended with the root remapped uid.gid prefix
44
-	dockerBasePath       string
45
-	volumesConfigPath    string
46
-	containerStoragePath string
47
-	// baseImage is the name of the base image for testing
48
-	// Environment variable WINDOWS_BASE_IMAGE can override this
49
-	baseImage    string
33
+	environment.Execution
50 34
 	dockerBinary string
35
+}
51 36
 
52
-	protectedElements protectedElements
37
+// DockerBinary returns the docker binary for this testing environment
38
+func (e *Execution) DockerBinary() string {
39
+	return e.dockerBinary
53 40
 }
54 41
 
55
-// New creates a new Execution struct
42
+// New returns details about the testing environment
56 43
 func New() (*Execution, error) {
57
-	localDaemon := true
58
-	// Deterministically working out the environment in which CI is running
59
-	// to evaluate whether the daemon is local or remote is not possible through
60
-	// a build tag.
61
-	//
62
-	// For example Windows to Linux CI under Jenkins tests the 64-bit
63
-	// Windows binary build with the daemon build tag, but calls a remote
64
-	// Linux daemon.
65
-	//
66
-	// We can't just say if Windows then assume the daemon is local as at
67
-	// some point, we will be testing the Windows CLI against a Windows daemon.
68
-	//
69
-	// Similarly, it will be perfectly valid to also run CLI tests from
70
-	// a Linux CLI (built with the daemon tag) against a Windows daemon.
71
-	if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
72
-		localDaemon = false
73
-	}
74
-	info, err := getDaemonDockerInfo()
44
+	env, err := environment.New()
75 45
 	if err != nil {
76 46
 		return nil, err
77 47
 	}
78
-	daemonPlatform := info.OSType
79
-	if daemonPlatform != "linux" && daemonPlatform != "windows" {
80
-		return nil, fmt.Errorf("Cannot run tests against platform: %s", daemonPlatform)
81
-	}
82
-	baseImage := "scratch"
83
-	volumesConfigPath := filepath.Join(info.DockerRootDir, "volumes")
84
-	containerStoragePath := filepath.Join(info.DockerRootDir, "containers")
85
-	// Make sure in context of daemon, not the local platform. Note we can't
86
-	// use filepath.FromSlash or ToSlash here as they are a no-op on Unix.
87
-	if daemonPlatform == "windows" {
88
-		volumesConfigPath = strings.Replace(volumesConfigPath, `/`, `\`, -1)
89
-		containerStoragePath = strings.Replace(containerStoragePath, `/`, `\`, -1)
90
-
91
-		baseImage = "microsoft/windowsservercore"
92
-		if len(os.Getenv("WINDOWS_BASE_IMAGE")) > 0 {
93
-			baseImage = os.Getenv("WINDOWS_BASE_IMAGE")
94
-			fmt.Println("INFO: Windows Base image is ", baseImage)
95
-		}
96
-	} else {
97
-		volumesConfigPath = strings.Replace(volumesConfigPath, `\`, `/`, -1)
98
-		containerStoragePath = strings.Replace(containerStoragePath, `\`, `/`, -1)
99
-	}
100
-
101
-	var daemonPid int
102
-	dest := os.Getenv("DEST")
103
-	b, err := ioutil.ReadFile(filepath.Join(dest, "docker.pid"))
104
-	if err == nil {
105
-		if p, err := strconv.ParseInt(string(b), 10, 32); err == nil {
106
-			daemonPid = int(p)
107
-		}
108
-	}
109 48
 
110 49
 	dockerBinary, err := exec.LookPath(DefaultClientBinary)
111 50
 	if err != nil {
... ...
@@ -113,117 +44,36 @@ func New() (*Execution, error) {
113 113
 	}
114 114
 
115 115
 	return &Execution{
116
-		localDaemon:          localDaemon,
117
-		daemonPlatform:       daemonPlatform,
118
-		daemonStorageDriver:  info.Driver,
119
-		daemonKernelVersion:  info.KernelVersion,
120
-		dockerBasePath:       info.DockerRootDir,
121
-		volumesConfigPath:    volumesConfigPath,
122
-		containerStoragePath: containerStoragePath,
123
-		isolation:            info.Isolation,
124
-		daemonPid:            daemonPid,
125
-		experimentalDaemon:   info.ExperimentalBuild,
126
-		baseImage:            baseImage,
127
-		dockerBinary:         dockerBinary,
128
-		protectedElements: protectedElements{
129
-			images: map[string]struct{}{},
130
-		},
116
+		Execution:    *env,
117
+		dockerBinary: dockerBinary,
131 118
 	}, nil
132 119
 }
133
-func getDaemonDockerInfo() (types.Info, error) {
134
-	// FIXME(vdemeester) should be safe to use as is
135
-	client, err := client.NewEnvClient()
136
-	if err != nil {
137
-		return types.Info{}, err
138
-	}
139
-	return client.Info(context.Background())
120
+
121
+// DockerBasePath is the base path of the docker folder (by default it is -/var/run/docker)
122
+// TODO: remove
123
+// Deprecated: use Execution.DaemonInfo.DockerRootDir
124
+func (e *Execution) DockerBasePath() string {
125
+	return e.DaemonInfo.DockerRootDir
140 126
 }
141 127
 
142
-// LocalDaemon is true if the daemon under test is on the same
143
-// host as the CLI.
144
-func (e *Execution) LocalDaemon() bool {
145
-	return e.localDaemon
128
+// ExperimentalDaemon tell whether the main daemon has
129
+// experimental features enabled or not
130
+// Deprecated: use DaemonInfo.ExperimentalBuild
131
+func (e *Execution) ExperimentalDaemon() bool {
132
+	return e.DaemonInfo.ExperimentalBuild
146 133
 }
147 134
 
148 135
 // DaemonPlatform is held globally so that tests can make intelligent
149 136
 // decisions on how to configure themselves according to the platform
150 137
 // of the daemon. This is initialized in docker_utils by sending
151 138
 // a version call to the daemon and examining the response header.
139
+// Deprecated: use Execution.DaemonInfo.OSType
152 140
 func (e *Execution) DaemonPlatform() string {
153
-	return e.daemonPlatform
154
-}
155
-
156
-// DockerBasePath is the base path of the docker folder (by default it is -/var/run/docker)
157
-func (e *Execution) DockerBasePath() string {
158
-	return e.dockerBasePath
159
-}
160
-
161
-// VolumesConfigPath is the path of the volume configuration for the testing daemon
162
-func (e *Execution) VolumesConfigPath() string {
163
-	return e.volumesConfigPath
164
-}
165
-
166
-// ContainerStoragePath is the path where the container are stored for the testing daemon
167
-func (e *Execution) ContainerStoragePath() string {
168
-	return e.containerStoragePath
169
-}
170
-
171
-// DaemonStorageDriver is held globally so that tests can know the storage
172
-// driver of the daemon. This is initialized in docker_utils by sending
173
-// a version call to the daemon and examining the response header.
174
-func (e *Execution) DaemonStorageDriver() string {
175
-	return e.daemonStorageDriver
176
-}
177
-
178
-// Isolation is the isolation mode of the daemon under test
179
-func (e *Execution) Isolation() container.Isolation {
180
-	return e.isolation
181
-}
182
-
183
-// DaemonPID is the pid of the main test daemon
184
-func (e *Execution) DaemonPID() int {
185
-	return e.daemonPid
186
-}
187
-
188
-// ExperimentalDaemon tell whether the main daemon has
189
-// experimental features enabled or not
190
-func (e *Execution) ExperimentalDaemon() bool {
191
-	return e.experimentalDaemon
141
+	return e.DaemonInfo.OSType
192 142
 }
193 143
 
194 144
 // MinimalBaseImage is the image used for minimal builds (it depends on the platform)
145
+// Deprecated: use Execution.PlatformDefaults.BaseImage
195 146
 func (e *Execution) MinimalBaseImage() string {
196
-	return e.baseImage
197
-}
198
-
199
-// DaemonKernelVersion is the kernel version of the daemon as a string, as returned
200
-// by an INFO call to the daemon.
201
-func (e *Execution) DaemonKernelVersion() string {
202
-	return e.daemonKernelVersion
203
-}
204
-
205
-// DaemonKernelVersionNumeric is the kernel version of the daemon as an integer.
206
-// Mostly useful on Windows where DaemonKernelVersion holds the full string such
207
-// as `10.0 14393 (14393.447.amd64fre.rs1_release_inmarket.161102-0100)`, but
208
-// integration tests really only need the `14393` piece to make decisions.
209
-func (e *Execution) DaemonKernelVersionNumeric() int {
210
-	if e.daemonPlatform != "windows" {
211
-		return -1
212
-	}
213
-	v, _ := strconv.Atoi(strings.Split(e.daemonKernelVersion, " ")[1])
214
-	return v
215
-}
216
-
217
-// DockerBinary returns the docker binary for this testing environment
218
-func (e *Execution) DockerBinary() string {
219
-	return e.dockerBinary
220
-}
221
-
222
-// DaemonHost return the daemon host string for this test execution
223
-func DaemonHost() string {
224
-	daemonURLStr := "unix://" + opts.DefaultUnixSocket
225
-	if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
226
-		daemonURLStr = daemonHostVar
227
-	}
228
-	return daemonURLStr
147
+	return e.PlatformDefaults.BaseImage
229 148
 }
230 149
deleted file mode 100644
... ...
@@ -1,48 +0,0 @@
1
-package environment
2
-
3
-import (
4
-	"strings"
5
-
6
-	"github.com/docker/docker/integration-cli/fixtures/load"
7
-	"github.com/gotestyourself/gotestyourself/icmd"
8
-)
9
-
10
-type protectedElements struct {
11
-	images map[string]struct{}
12
-}
13
-
14
-// ProtectImage adds the specified image(s) to be protected in case of clean
15
-func (e *Execution) ProtectImage(t testingT, images ...string) {
16
-	for _, image := range images {
17
-		e.protectedElements.images[image] = struct{}{}
18
-	}
19
-}
20
-
21
-// ProtectImages protects existing images and on linux frozen images from being
22
-// cleaned up at the end of test runs
23
-func ProtectImages(t testingT, testEnv *Execution) {
24
-	images := getExistingImages(t, testEnv)
25
-
26
-	if testEnv.DaemonPlatform() == "linux" {
27
-		images = append(images, ensureFrozenImagesLinux(t, testEnv)...)
28
-	}
29
-	testEnv.ProtectImage(t, images...)
30
-}
31
-
32
-func getExistingImages(t testingT, testEnv *Execution) []string {
33
-	// TODO: use API instead of cli
34
-	result := icmd.RunCommand(testEnv.dockerBinary, "images", "-f", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}")
35
-	result.Assert(t, icmd.Success)
36
-	return strings.Split(strings.TrimSpace(result.Stdout()), "\n")
37
-}
38
-
39
-func ensureFrozenImagesLinux(t testingT, testEnv *Execution) []string {
40
-	images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
41
-	err := load.FrozenImagesLinux(testEnv.DockerBinary(), images...)
42
-	if err != nil {
43
-		result := icmd.RunCommand(testEnv.DockerBinary(), "image", "ls")
44
-		t.Logf(result.String())
45
-		t.Fatalf("%+v", err)
46
-	}
47
-	return images
48
-}
... ...
@@ -79,7 +79,7 @@ func ensureSyscallTest(c *check.C) {
79 79
 }
80 80
 
81 81
 func ensureSyscallTestBuild(c *check.C) {
82
-	err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie")
82
+	err := load.FrozenImagesLinux(testEnv.APIClient(), "buildpack-deps:jessie")
83 83
 	c.Assert(err, checker.IsNil)
84 84
 
85 85
 	var buildArgs []string
... ...
@@ -126,7 +126,7 @@ func ensureNNPTest(c *check.C) {
126 126
 }
127 127
 
128 128
 func ensureNNPTestBuild(c *check.C) {
129
-	err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie")
129
+	err := load.FrozenImagesLinux(testEnv.APIClient(), "buildpack-deps:jessie")
130 130
 	c.Assert(err, checker.IsNil)
131 131
 
132 132
 	var buildArgs []string
... ...
@@ -8,7 +8,8 @@ import (
8 8
 	"strings"
9 9
 )
10 10
 
11
-type skipT interface {
11
+// SkipT is the interface required to skip tests
12
+type SkipT interface {
12 13
 	Skip(reason string)
13 14
 }
14 15
 
... ...
@@ -17,7 +18,7 @@ type Test func() bool
17 17
 
18 18
 // Is checks if the environment satisfies the requirements
19 19
 // for the test to run or skips the tests.
20
-func Is(s skipT, requirements ...Test) {
20
+func Is(s SkipT, requirements ...Test) {
21 21
 	for _, r := range requirements {
22 22
 		isValid := r()
23 23
 		if !isValid {
... ...
@@ -6,57 +6,47 @@ import (
6 6
 	"net/http"
7 7
 	"os"
8 8
 	"os/exec"
9
+	"strconv"
9 10
 	"strings"
10 11
 	"time"
11 12
 
12 13
 	"github.com/docker/docker/integration-cli/requirement"
13
-	"github.com/go-check/check"
14 14
 )
15 15
 
16
-func PlatformIs(platform string) bool {
17
-	return testEnv.DaemonPlatform() == platform
18
-}
19
-
20
-func ArchitectureIs(arch string) bool {
21
-	return os.Getenv("DOCKER_ENGINE_GOARCH") == arch
22
-}
23
-
24 16
 func ArchitectureIsNot(arch string) bool {
25 17
 	return os.Getenv("DOCKER_ENGINE_GOARCH") != arch
26 18
 }
27 19
 
28
-func StorageDriverIs(storageDriver string) bool {
29
-	return strings.HasPrefix(testEnv.DaemonStorageDriver(), storageDriver)
30
-}
31
-
32
-func StorageDriverIsNot(storageDriver string) bool {
33
-	return !strings.HasPrefix(testEnv.DaemonStorageDriver(), storageDriver)
34
-}
35
-
36 20
 func DaemonIsWindows() bool {
37
-	return PlatformIs("windows")
21
+	return testEnv.DaemonInfo.OSType == "windows"
38 22
 }
39 23
 
40 24
 func DaemonIsWindowsAtLeastBuild(buildNumber int) func() bool {
41 25
 	return func() bool {
42
-		return DaemonIsWindows() && testEnv.DaemonKernelVersionNumeric() >= buildNumber
26
+		if testEnv.DaemonInfo.OSType != "windows" {
27
+			return false
28
+		}
29
+		version := testEnv.DaemonInfo.KernelVersion
30
+		numVersion, _ := strconv.Atoi(strings.Split(version, " ")[1])
31
+		return numVersion >= buildNumber
43 32
 	}
44 33
 }
45 34
 
46 35
 func DaemonIsLinux() bool {
47
-	return PlatformIs("linux")
36
+	return testEnv.DaemonInfo.OSType == "linux"
48 37
 }
49 38
 
39
+// Deprecated: use skip.IfCondition(t, !testEnv.DaemonInfo.ExperimentalBuild)
50 40
 func ExperimentalDaemon() bool {
51
-	return testEnv.ExperimentalDaemon()
41
+	return testEnv.DaemonInfo.ExperimentalBuild
52 42
 }
53 43
 
54 44
 func NotExperimentalDaemon() bool {
55
-	return !testEnv.ExperimentalDaemon()
45
+	return !testEnv.DaemonInfo.ExperimentalBuild
56 46
 }
57 47
 
58 48
 func IsAmd64() bool {
59
-	return ArchitectureIs("amd64")
49
+	return os.Getenv("DOCKER_ENGINE_GOARCH") == "amd64"
60 50
 }
61 51
 
62 52
 func NotArm() bool {
... ...
@@ -76,7 +66,7 @@ func NotS390X() bool {
76 76
 }
77 77
 
78 78
 func SameHostDaemon() bool {
79
-	return testEnv.LocalDaemon()
79
+	return testEnv.IsLocalDaemon()
80 80
 }
81 81
 
82 82
 func UnixCli() bool {
... ...
@@ -127,12 +117,8 @@ func NotaryServerHosting() bool {
127 127
 	return err == nil
128 128
 }
129 129
 
130
-func NotOverlay() bool {
131
-	return StorageDriverIsNot("overlay")
132
-}
133
-
134 130
 func Devicemapper() bool {
135
-	return StorageDriverIs("devicemapper")
131
+	return strings.HasPrefix(testEnv.DaemonInfo.Driver, "devicemapper")
136 132
 }
137 133
 
138 134
 func IPv6() bool {
... ...
@@ -177,21 +163,21 @@ func UserNamespaceInKernel() bool {
177 177
 }
178 178
 
179 179
 func IsPausable() bool {
180
-	if testEnv.DaemonPlatform() == "windows" {
181
-		return testEnv.Isolation() == "hyperv"
180
+	if testEnv.DaemonInfo.OSType == "windows" {
181
+		return testEnv.DaemonInfo.Isolation == "hyperv"
182 182
 	}
183 183
 	return true
184 184
 }
185 185
 
186 186
 func NotPausable() bool {
187
-	if testEnv.DaemonPlatform() == "windows" {
188
-		return testEnv.Isolation() == "process"
187
+	if testEnv.DaemonInfo.OSType == "windows" {
188
+		return testEnv.DaemonInfo.Isolation == "process"
189 189
 	}
190 190
 	return false
191 191
 }
192 192
 
193 193
 func IsolationIs(expectedIsolation string) bool {
194
-	return testEnv.DaemonPlatform() == "windows" && string(testEnv.Isolation()) == expectedIsolation
194
+	return testEnv.DaemonInfo.OSType == "windows" && string(testEnv.DaemonInfo.Isolation) == expectedIsolation
195 195
 }
196 196
 
197 197
 func IsolationIsHyperv() bool {
... ...
@@ -204,6 +190,6 @@ func IsolationIsProcess() bool {
204 204
 
205 205
 // testRequires checks if the environment satisfies the requirements
206 206
 // for the test to run or skips the tests.
207
-func testRequires(c *check.C, requirements ...requirement.Test) {
207
+func testRequires(c requirement.SkipT, requirements ...requirement.Test) {
208 208
 	requirement.Is(c, requirements...)
209 209
 }
... ...
@@ -101,7 +101,7 @@ func overlay2Supported() bool {
101 101
 		return false
102 102
 	}
103 103
 
104
-	daemonV, err := kernel.ParseRelease(testEnv.DaemonKernelVersion())
104
+	daemonV, err := kernel.ParseRelease(testEnv.DaemonInfo.KernelVersion)
105 105
 	if err != nil {
106 106
 		return false
107 107
 	}
... ...
@@ -5,12 +5,10 @@ import (
5 5
 	"os"
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/integration-cli/environment"
8
+	"github.com/docker/docker/internal/test/environment"
9 9
 )
10 10
 
11
-var (
12
-	testEnv *environment.Execution
13
-)
11
+var testEnv *environment.Execution
14 12
 
15 13
 func TestMain(m *testing.M) {
16 14
 	var err error
... ...
@@ -20,18 +18,11 @@ func TestMain(m *testing.M) {
20 20
 		os.Exit(1)
21 21
 	}
22 22
 
23
-	// TODO: replace this with `testEnv.Print()` to print the full env
24
-	if testEnv.LocalDaemon() {
25
-		fmt.Println("INFO: Testing against a local daemon")
26
-	} else {
27
-		fmt.Println("INFO: Testing against a remote daemon")
28
-	}
29
-
30
-	res := m.Run()
31
-	os.Exit(res)
23
+	testEnv.Print()
24
+	os.Exit(m.Run())
32 25
 }
33 26
 
34 27
 func setupTest(t *testing.T) func() {
35 28
 	environment.ProtectImages(t, testEnv)
36
-	return func() { testEnv.Clean(t, testEnv.DockerBinary()) }
29
+	return func() { testEnv.Clean(t) }
37 30
 }
... ...
@@ -110,7 +110,7 @@ const defaultSwarmPort = 2477
110 110
 func newSwarm(t *testing.T) *daemon.Swarm {
111 111
 	d := &daemon.Swarm{
112 112
 		Daemon: daemon.New(t, "", dockerdBinary, daemon.Config{
113
-			Experimental: testEnv.ExperimentalDaemon(),
113
+			Experimental: testEnv.DaemonInfo.ExperimentalBuild,
114 114
 		}),
115 115
 		// TODO: better method of finding an unused port
116 116
 		Port: defaultSwarmPort,
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"os"
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/integration-cli/environment"
8
+	"github.com/docker/docker/internal/test/environment"
9 9
 )
10 10
 
11 11
 var testEnv *environment.Execution
... ...
@@ -20,18 +20,11 @@ func TestMain(m *testing.M) {
20 20
 		os.Exit(1)
21 21
 	}
22 22
 
23
-	// TODO: replace this with `testEnv.Print()` to print the full env
24
-	if testEnv.LocalDaemon() {
25
-		fmt.Println("INFO: Testing against a local daemon")
26
-	} else {
27
-		fmt.Println("INFO: Testing against a remote daemon")
28
-	}
29
-
30
-	res := m.Run()
31
-	os.Exit(res)
23
+	testEnv.Print()
24
+	os.Exit(m.Run())
32 25
 }
33 26
 
34 27
 func setupTest(t *testing.T) func() {
35 28
 	environment.ProtectImages(t, testEnv)
36
-	return func() { testEnv.Clean(t, testEnv.DockerBinary()) }
29
+	return func() { testEnv.Clean(t) }
37 30
 }
38 31
new file mode 100644
... ...
@@ -0,0 +1,164 @@
0
+package environment
1
+
2
+import (
3
+	"regexp"
4
+	"strings"
5
+
6
+	"github.com/docker/docker/api/types"
7
+	"github.com/docker/docker/api/types/filters"
8
+	"github.com/docker/docker/client"
9
+	"github.com/stretchr/testify/assert"
10
+	"github.com/stretchr/testify/require"
11
+	"golang.org/x/net/context"
12
+)
13
+
14
+type testingT interface {
15
+	require.TestingT
16
+	logT
17
+	Fatalf(string, ...interface{})
18
+}
19
+
20
+type logT interface {
21
+	Logf(string, ...interface{})
22
+}
23
+
24
+// Clean the environment, preserving protected objects (images, containers, ...)
25
+// and removing everything else. It's meant to run after any tests so that they don't
26
+// depend on each others.
27
+func (e *Execution) Clean(t testingT) {
28
+	client := e.APIClient()
29
+
30
+	platform := e.DaemonInfo.OSType
31
+	if (platform != "windows") || (platform == "windows" && e.DaemonInfo.Isolation == "hyperv") {
32
+		unpauseAllContainers(t, client)
33
+	}
34
+	deleteAllContainers(t, client)
35
+	deleteAllImages(t, client, e.protectedElements.images)
36
+	deleteAllVolumes(t, client)
37
+	deleteAllNetworks(t, client, platform)
38
+	if platform == "linux" {
39
+		deleteAllPlugins(t, client)
40
+	}
41
+}
42
+
43
+func unpauseAllContainers(t testingT, client client.ContainerAPIClient) {
44
+	ctx := context.Background()
45
+	containers := getPausedContainers(ctx, t, client)
46
+	if len(containers) > 0 {
47
+		for _, container := range containers {
48
+			err := client.ContainerUnpause(ctx, container.ID)
49
+			assert.NoError(t, err, "failed to unpause container %s", container.ID)
50
+		}
51
+	}
52
+}
53
+
54
+func getPausedContainers(ctx context.Context, t testingT, client client.ContainerAPIClient) []types.Container {
55
+	filter := filters.NewArgs()
56
+	filter.Add("status", "paused")
57
+	containers, err := client.ContainerList(ctx, types.ContainerListOptions{
58
+		Filters: filter,
59
+		Quiet:   true,
60
+		All:     true,
61
+	})
62
+	assert.NoError(t, err, "failed to list containers")
63
+	return containers
64
+}
65
+
66
+var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
67
+
68
+func deleteAllContainers(t testingT, apiclient client.ContainerAPIClient) {
69
+	ctx := context.Background()
70
+	containers := getAllContainers(ctx, t, apiclient)
71
+	if len(containers) == 0 {
72
+		return
73
+	}
74
+
75
+	for _, container := range containers {
76
+		err := apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{
77
+			Force:         true,
78
+			RemoveVolumes: true,
79
+		})
80
+		if err == nil || client.IsErrNotFound(err) || alreadyExists.MatchString(err.Error()) {
81
+			continue
82
+		}
83
+		assert.NoError(t, err, "failed to remove %s", container.ID)
84
+	}
85
+}
86
+
87
+func getAllContainers(ctx context.Context, t testingT, client client.ContainerAPIClient) []types.Container {
88
+	containers, err := client.ContainerList(ctx, types.ContainerListOptions{
89
+		Quiet: true,
90
+		All:   true,
91
+	})
92
+	assert.NoError(t, err, "failed to list containers")
93
+	return containers
94
+}
95
+
96
+func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
97
+	images, err := apiclient.ImageList(context.Background(), types.ImageListOptions{})
98
+	assert.NoError(t, err, "failed to list images")
99
+
100
+	ctx := context.Background()
101
+	for _, image := range images {
102
+		tags := tagsFromImageSummary(image)
103
+		if len(tags) == 0 {
104
+			t.Logf("Removing image %s", image.ID)
105
+			removeImage(ctx, t, apiclient, image.ID)
106
+			continue
107
+		}
108
+		for _, tag := range tags {
109
+			if _, ok := protectedImages[tag]; !ok {
110
+				t.Logf("Removing image %s", tag)
111
+				removeImage(ctx, t, apiclient, tag)
112
+				continue
113
+			}
114
+		}
115
+	}
116
+}
117
+
118
+func removeImage(ctx context.Context, t testingT, apiclient client.ImageAPIClient, ref string) {
119
+	_, err := apiclient.ImageRemove(ctx, ref, types.ImageRemoveOptions{
120
+		Force: true,
121
+	})
122
+	if client.IsErrNotFound(err) {
123
+		return
124
+	}
125
+	assert.NoError(t, err, "failed to remove image %s", ref)
126
+}
127
+
128
+func deleteAllVolumes(t testingT, c client.VolumeAPIClient) {
129
+	volumes, err := c.VolumeList(context.Background(), filters.Args{})
130
+	assert.NoError(t, err, "failed to list volumes")
131
+
132
+	for _, v := range volumes.Volumes {
133
+		err := c.VolumeRemove(context.Background(), v.Name, true)
134
+		assert.NoError(t, err, "failed to remove volume %s", v.Name)
135
+	}
136
+}
137
+
138
+func deleteAllNetworks(t testingT, c client.NetworkAPIClient, daemonPlatform string) {
139
+	networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
140
+	assert.NoError(t, err, "failed to list networks")
141
+
142
+	for _, n := range networks {
143
+		if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
144
+			continue
145
+		}
146
+		if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
147
+			// nat is a pre-defined network on Windows and cannot be removed
148
+			continue
149
+		}
150
+		err := c.NetworkRemove(context.Background(), n.ID)
151
+		assert.NoError(t, err, "failed to remove network %s", n.ID)
152
+	}
153
+}
154
+
155
+func deleteAllPlugins(t testingT, c client.PluginAPIClient) {
156
+	plugins, err := c.PluginList(context.Background(), filters.Args{})
157
+	assert.NoError(t, err, "failed to list plugins")
158
+
159
+	for _, p := range plugins {
160
+		err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
161
+		assert.NoError(t, err, "failed to remove plugin %s", p.ID)
162
+	}
163
+}
0 164
new file mode 100644
... ...
@@ -0,0 +1,117 @@
0
+package environment
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"path/filepath"
6
+	"strings"
7
+
8
+	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/client"
10
+	"github.com/pkg/errors"
11
+	"golang.org/x/net/context"
12
+)
13
+
14
+// Execution contains information about the current test execution and daemon
15
+// under test
16
+type Execution struct {
17
+	client            client.APIClient
18
+	DaemonInfo        types.Info
19
+	PlatformDefaults  PlatformDefaults
20
+	protectedElements protectedElements
21
+}
22
+
23
+// PlatformDefaults are defaults values for the platform of the daemon under test
24
+type PlatformDefaults struct {
25
+	BaseImage            string
26
+	VolumesConfigPath    string
27
+	ContainerStoragePath string
28
+}
29
+
30
+// New creates a new Execution struct
31
+func New() (*Execution, error) {
32
+	client, err := client.NewEnvClient()
33
+	if err != nil {
34
+		return nil, errors.Wrapf(err, "failed to create client")
35
+	}
36
+
37
+	info, err := client.Info(context.Background())
38
+	if err != nil {
39
+		return nil, errors.Wrapf(err, "failed to get info from daemon")
40
+	}
41
+
42
+	return &Execution{
43
+		client:            client,
44
+		DaemonInfo:        info,
45
+		PlatformDefaults:  getPlatformDefaults(info),
46
+		protectedElements: newProtectedElements(),
47
+	}, nil
48
+}
49
+
50
+func getPlatformDefaults(info types.Info) PlatformDefaults {
51
+	volumesPath := filepath.Join(info.DockerRootDir, "volumes")
52
+	containersPath := filepath.Join(info.DockerRootDir, "containers")
53
+
54
+	switch info.OSType {
55
+	case "linux":
56
+		return PlatformDefaults{
57
+			BaseImage:            "scratch",
58
+			VolumesConfigPath:    toSlash(volumesPath),
59
+			ContainerStoragePath: toSlash(containersPath),
60
+		}
61
+	case "windows":
62
+		baseImage := "microsoft/windowsservercore"
63
+		if override := os.Getenv("WINDOWS_BASE_IMAGE"); override != "" {
64
+			baseImage = override
65
+			fmt.Println("INFO: Windows Base image is ", baseImage)
66
+		}
67
+		return PlatformDefaults{
68
+			BaseImage:            baseImage,
69
+			VolumesConfigPath:    filepath.FromSlash(volumesPath),
70
+			ContainerStoragePath: filepath.FromSlash(containersPath),
71
+		}
72
+	default:
73
+		panic(fmt.Sprintf("unknown info.OSType for daemon: %s", info.OSType))
74
+	}
75
+}
76
+
77
+// Make sure in context of daemon, not the local platform. Note we can't
78
+// use filepath.FromSlash or ToSlash here as they are a no-op on Unix.
79
+func toSlash(path string) string {
80
+	return strings.Replace(path, `\`, `/`, -1)
81
+}
82
+
83
+// IsLocalDaemon is true if the daemon under test is on the same
84
+// host as the CLI.
85
+//
86
+// Deterministically working out the environment in which CI is running
87
+// to evaluate whether the daemon is local or remote is not possible through
88
+// a build tag.
89
+//
90
+// For example Windows to Linux CI under Jenkins tests the 64-bit
91
+// Windows binary build with the daemon build tag, but calls a remote
92
+// Linux daemon.
93
+//
94
+// We can't just say if Windows then assume the daemon is local as at
95
+// some point, we will be testing the Windows CLI against a Windows daemon.
96
+//
97
+// Similarly, it will be perfectly valid to also run CLI tests from
98
+// a Linux CLI (built with the daemon tag) against a Windows daemon.
99
+func (e *Execution) IsLocalDaemon() bool {
100
+	return os.Getenv("DOCKER_REMOTE_DAEMON") == ""
101
+}
102
+
103
+// Print the execution details to stdout
104
+// TODO: print everything
105
+func (e *Execution) Print() {
106
+	if e.IsLocalDaemon() {
107
+		fmt.Println("INFO: Testing against a local daemon")
108
+	} else {
109
+		fmt.Println("INFO: Testing against a remote daemon")
110
+	}
111
+}
112
+
113
+// APIClient returns an APIClient connected to the daemon under test
114
+func (e *Execution) APIClient() client.APIClient {
115
+	return e.client
116
+}
0 117
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+package environment
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/docker/docker/api/types"
6
+	"github.com/docker/docker/api/types/filters"
7
+	"github.com/docker/docker/integration-cli/fixtures/load"
8
+	"github.com/stretchr/testify/require"
9
+)
10
+
11
+type protectedElements struct {
12
+	images map[string]struct{}
13
+}
14
+
15
+// ProtectImage adds the specified image(s) to be protected in case of clean
16
+func (e *Execution) ProtectImage(t testingT, images ...string) {
17
+	for _, image := range images {
18
+		e.protectedElements.images[image] = struct{}{}
19
+	}
20
+}
21
+
22
+func newProtectedElements() protectedElements {
23
+	return protectedElements{
24
+		images: map[string]struct{}{},
25
+	}
26
+}
27
+
28
+// ProtectImages protects existing images and on linux frozen images from being
29
+// cleaned up at the end of test runs
30
+func ProtectImages(t testingT, testEnv *Execution) {
31
+	images := getExistingImages(t, testEnv)
32
+
33
+	if testEnv.DaemonInfo.OSType == "linux" {
34
+		images = append(images, ensureFrozenImagesLinux(t, testEnv)...)
35
+	}
36
+	testEnv.ProtectImage(t, images...)
37
+}
38
+
39
+func getExistingImages(t testingT, testEnv *Execution) []string {
40
+	client := testEnv.APIClient()
41
+	filter := filters.NewArgs()
42
+	filter.Add("dangling", "false")
43
+	imageList, err := client.ImageList(context.Background(), types.ImageListOptions{
44
+		Filters: filter,
45
+	})
46
+	require.NoError(t, err, "failed to list images")
47
+
48
+	images := []string{}
49
+	for _, image := range imageList {
50
+		images = append(images, tagsFromImageSummary(image)...)
51
+	}
52
+	return images
53
+}
54
+
55
+func tagsFromImageSummary(image types.ImageSummary) []string {
56
+	result := []string{}
57
+	for _, tag := range image.RepoTags {
58
+		if tag != "<none>:<none>" {
59
+			result = append(result, tag)
60
+		}
61
+	}
62
+	for _, digest := range image.RepoDigests {
63
+		if digest != "<none>@<none>" {
64
+			result = append(result, digest)
65
+		}
66
+	}
67
+	return result
68
+}
69
+
70
+func ensureFrozenImagesLinux(t testingT, testEnv *Execution) []string {
71
+	images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
72
+	err := load.FrozenImagesLinux(testEnv.APIClient(), images...)
73
+	if err != nil {
74
+		t.Fatalf("Failed to load frozen images: %s", err)
75
+	}
76
+	return images
77
+}