Skip to content

Commit 3f10753

Browse files
authored
Merge pull request docker#9563 from milas/e2e-env
e2e: isolate test command env from system env
2 parents 71b89c2 + a261682 commit 3f10753

File tree

4 files changed

+145
-72
lines changed

4 files changed

+145
-72
lines changed

pkg/e2e/compose_build_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package e2e
1818

1919
import (
2020
"net/http"
21-
"os"
2221
"strings"
2322
"testing"
2423
"time"
@@ -81,11 +80,6 @@ func TestLocalComposeBuild(t *testing.T) {
8180
})
8281

8382
t.Run("build failed with ssh default value", func(t *testing.T) {
84-
//unset SSH_AUTH_SOCK to be sure we don't have a default value for the SSH Agent
85-
defaultSSHAUTHSOCK := os.Getenv("SSH_AUTH_SOCK")
86-
os.Unsetenv("SSH_AUTH_SOCK") //nolint:errcheck
87-
defer os.Setenv("SSH_AUTH_SOCK", defaultSSHAUTHSOCK) //nolint:errcheck
88-
8983
res := c.RunDockerComposeCmdNoCheck(t, "--project-directory", "fixtures/build-test", "build", "--ssh", "")
9084
res.Assert(t, icmd.Expected{
9185
ExitCode: 1,

pkg/e2e/compose_environment_test.go

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package e2e
1818

1919
import (
20-
"os"
2120
"strings"
2221
"testing"
2322

@@ -43,13 +42,11 @@ func TestEnvPriority(t *testing.T) {
4342
// 4. Dockerfile
4443
// 5. Variable is not defined
4544
t.Run("compose file priority", func(t *testing.T) {
46-
os.Setenv("WHEREAMI", "shell") //nolint:errcheck
47-
defer os.Unsetenv("WHEREAMI") //nolint:errcheck
48-
49-
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
50-
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override",
51-
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
52-
45+
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose-with-env.yaml",
46+
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run",
47+
"--rm", "-e", "WHEREAMI", "env-compose-priority")
48+
cmd.Env = append(cmd.Env, "WHEREAMI=shell")
49+
res := icmd.RunCmd(cmd)
5350
assert.Equal(t, strings.TrimSpace(res.Stdout()), "Compose File")
5451
})
5552

@@ -60,12 +57,11 @@ func TestEnvPriority(t *testing.T) {
6057
// 4. Dockerfile
6158
// 5. Variable is not defined
6259
t.Run("shell priority", func(t *testing.T) {
63-
os.Setenv("WHEREAMI", "shell") //nolint:errcheck
64-
defer os.Unsetenv("WHEREAMI") //nolint:errcheck
65-
66-
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml",
67-
"--project-directory", projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override",
68-
"run", "--rm", "-e", "WHEREAMI", "env-compose-priority")
60+
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-priority/compose.yaml", "--project-directory",
61+
projectDir, "--env-file", "./fixtures/environment/env-priority/.env.override", "run", "--rm", "-e",
62+
"WHEREAMI", "env-compose-priority")
63+
cmd.Env = append(cmd.Env, "WHEREAMI=shell")
64+
res := icmd.RunCmd(cmd)
6965
assert.Equal(t, strings.TrimSpace(res.Stdout()), "shell")
7066
})
7167

@@ -137,11 +133,10 @@ func TestEnvInterpolation(t *testing.T) {
137133
// 4. Dockerfile
138134
// 5. Variable is not defined
139135
t.Run("shell priority from run command", func(t *testing.T) {
140-
os.Setenv("WHEREAMI", "shell") //nolint:errcheck
141-
defer os.Unsetenv("WHEREAMI") //nolint:errcheck
142-
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/environment/env-interpolation/compose.yaml",
136+
cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/environment/env-interpolation/compose.yaml",
143137
"--project-directory", projectDir, "config")
144-
138+
cmd.Env = append(cmd.Env, "WHEREAMI=shell")
139+
res := icmd.RunCmd(cmd)
145140
res.Assert(t, icmd.Expected{Out: `IMAGE: default_env:shell`})
146141
})
147142
}

pkg/e2e/ddev_test.go

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,52 @@ package e2e
1919
import (
2020
"fmt"
2121
"os"
22+
"os/exec"
2223
"path/filepath"
2324
"runtime"
2425
"strings"
2526
"testing"
2627

28+
"github.com/stretchr/testify/require"
2729
"gotest.tools/v3/assert"
2830
)
2931

3032
const ddevVersion = "v1.19.1"
3133

3234
func TestComposeRunDdev(t *testing.T) {
3335
if !composeStandaloneMode {
34-
t.Skip("Not running on standalone mode.")
36+
t.Skip("Not running in plugin mode - ddev only supports invoking standalone `docker-compose`")
3537
}
3638
if runtime.GOOS == "windows" {
3739
t.Skip("Running on Windows. Skipping...")
3840
}
39-
_ = os.Setenv("DDEV_DEBUG", "true")
4041

41-
c := NewParallelCLI(t)
42-
dir, err := os.MkdirTemp("", t.Name()+"-")
43-
assert.NilError(t, err)
42+
// ddev shells out to `docker` and `docker-compose` (standalone), so a
43+
// temporary directory is created with symlinks to system Docker and the
44+
// locally-built standalone Compose binary to use as PATH
45+
requiredTools := []string{
46+
findToolInPath(t, DockerExecutableName),
47+
ComposeStandalonePath(t),
48+
findToolInPath(t, "tar"),
49+
findToolInPath(t, "gzip"),
50+
}
51+
pathDir := t.TempDir()
52+
for _, tool := range requiredTools {
53+
require.NoError(t, os.Symlink(tool, filepath.Join(pathDir, filepath.Base(tool))),
54+
"Could not create symlink for %q", tool)
55+
}
4456

45-
// ddev needs to be able to find mkcert to figure out where certs are.
46-
_ = os.Setenv("PATH", fmt.Sprintf("%s:%s", os.Getenv("PATH"), dir))
57+
c := NewCLI(t, WithEnv(
58+
"DDEV_DEBUG=true",
59+
fmt.Sprintf("PATH=%s", pathDir),
60+
))
4761

48-
siteName := filepath.Base(dir)
62+
ddevDir := t.TempDir()
63+
siteName := filepath.Base(ddevDir)
4964

5065
t.Cleanup(func() {
51-
_ = c.RunCmdInDir(t, dir, "./ddev", "delete", "-Oy")
52-
_ = c.RunCmdInDir(t, dir, "./ddev", "poweroff")
53-
_ = os.RemoveAll(dir)
66+
_ = c.RunCmdInDir(t, ddevDir, "./ddev", "delete", "-Oy")
67+
_ = c.RunCmdInDir(t, ddevDir, "./ddev", "poweroff")
5468
})
5569

5670
osName := "linux"
@@ -59,28 +73,34 @@ func TestComposeRunDdev(t *testing.T) {
5973
}
6074

6175
compressedFilename := fmt.Sprintf("ddev_%s-%s.%s.tar.gz", osName, runtime.GOARCH, ddevVersion)
62-
c.RunCmdInDir(t, dir, "curl", "-LO",
63-
fmt.Sprintf("https://github.com/drud/ddev/releases/download/%s/%s",
64-
ddevVersion,
65-
compressedFilename))
76+
c.RunCmdInDir(t, ddevDir, "curl", "-LO", fmt.Sprintf("https://github.com/drud/ddev/releases/download/%s/%s",
77+
ddevVersion,
78+
compressedFilename))
6679

67-
c.RunCmdInDir(t, dir, "tar", "-xzf", compressedFilename)
80+
c.RunCmdInDir(t, ddevDir, "tar", "-xzf", compressedFilename)
6881

6982
// Create a simple index.php we can test against.
70-
c.RunCmdInDir(t, dir, "sh", "-c", "echo '<?php\nprint \"ddev is working\";' >index.php")
83+
c.RunCmdInDir(t, ddevDir, "sh", "-c", "echo '<?php\nprint \"ddev is working\";' >index.php")
7184

72-
c.RunCmdInDir(t, dir, "./ddev", "config", "--auto")
73-
c.RunCmdInDir(t, dir, "./ddev", "config", "global", "--use-docker-compose-from-path")
74-
vRes := c.RunCmdInDir(t, dir, "./ddev", "version")
85+
c.RunCmdInDir(t, ddevDir, "./ddev", "config", "--auto")
86+
c.RunCmdInDir(t, ddevDir, "./ddev", "config", "global", "--use-docker-compose-from-path")
87+
vRes := c.RunCmdInDir(t, ddevDir, "./ddev", "version")
7588
out := vRes.Stdout()
7689
fmt.Printf("ddev version: %s\n", out)
7790

78-
c.RunCmdInDir(t, dir, "./ddev", "poweroff")
91+
c.RunCmdInDir(t, ddevDir, "./ddev", "poweroff")
7992

80-
c.RunCmdInDir(t, dir, "./ddev", "start", "-y")
93+
c.RunCmdInDir(t, ddevDir, "./ddev", "start", "-y")
8194

82-
curlRes := c.RunCmdInDir(t, dir, "curl", "-sSL", fmt.Sprintf("http://%s.ddev.site", siteName))
95+
curlRes := c.RunCmdInDir(t, ddevDir, "curl", "-sSL", fmt.Sprintf("http://%s.ddev.site", siteName))
8396
out = curlRes.Stdout()
8497
fmt.Println(out)
8598
assert.Assert(t, strings.Contains(out, "ddev is working"), "Could not start project")
8699
}
100+
101+
func findToolInPath(t testing.TB, name string) string {
102+
t.Helper()
103+
binPath, err := exec.LookPath(name)
104+
require.NoError(t, err, "Could not find %q in path", name)
105+
return binPath
106+
}

pkg/e2e/framework.go

Lines changed: 89 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import (
3030
"time"
3131

3232
"github.com/pkg/errors"
33+
"github.com/stretchr/testify/require"
3334
"gotest.tools/v3/assert"
34-
is "gotest.tools/v3/assert/cmp"
3535
"gotest.tools/v3/icmd"
3636
"gotest.tools/v3/poll"
3737

@@ -59,19 +59,59 @@ func init() {
5959

6060
// CLI is used to wrap the CLI for end to end testing
6161
type CLI struct {
62+
// ConfigDir for Docker configuration (set as DOCKER_CONFIG)
6263
ConfigDir string
64+
65+
// HomeDir for tools that look for user files (set as HOME)
66+
HomeDir string
67+
68+
// env overrides to apply to every invoked command
69+
//
70+
// To populate, use WithEnv when creating a CLI instance.
71+
env []string
6372
}
6473

65-
// NewParallelCLI returns a configured CLI with t.Parallel() set
66-
func NewParallelCLI(t *testing.T) *CLI {
74+
// CLIOption to customize behavior for all commands for a CLI instance.
75+
type CLIOption func(c *CLI)
76+
77+
// NewParallelCLI marks the parent test as parallel and returns a CLI instance
78+
// suitable for usage across child tests.
79+
func NewParallelCLI(t *testing.T, opts ...CLIOption) *CLI {
80+
t.Helper()
6781
t.Parallel()
68-
return NewCLI(t)
82+
return NewCLI(t, opts...)
6983
}
7084

71-
// NewCLI returns a CLI to use for E2E tests
72-
func NewCLI(t testing.TB) *CLI {
73-
d, err := ioutil.TempDir("", "")
74-
assert.Check(t, is.Nil(err))
85+
// NewCLI creates a CLI instance for running E2E tests.
86+
func NewCLI(t testing.TB, opts ...CLIOption) *CLI {
87+
t.Helper()
88+
89+
configDir := t.TempDir()
90+
initializePlugins(t, configDir)
91+
92+
c := &CLI{
93+
ConfigDir: configDir,
94+
HomeDir: t.TempDir(),
95+
}
96+
97+
for _, opt := range opts {
98+
opt(c)
99+
}
100+
101+
return c
102+
}
103+
104+
// WithEnv sets environment variables that will be passed to commands.
105+
func WithEnv(env ...string) CLIOption {
106+
return func(c *CLI) {
107+
c.env = append(c.env, env...)
108+
}
109+
}
110+
111+
// initializePlugins copies the necessary plugin files to the temporary config
112+
// directory for the test.
113+
func initializePlugins(t testing.TB, d string) {
114+
t.Helper()
75115

76116
t.Cleanup(func() {
77117
if t.Failed() {
@@ -101,8 +141,6 @@ func NewCLI(t testing.TB) *CLI {
101141
panic(err)
102142
}
103143
}
104-
105-
return &CLI{ConfigDir: d}
106144
}
107145

108146
func dirContents(dir string) []string {
@@ -154,28 +192,33 @@ func CopyFile(sourceFile string, destinationFile string) error {
154192
return err
155193
}
156194

195+
// BaseEnvironment provides the minimal environment variables used across all
196+
// Docker / Compose commands.
197+
func (c *CLI) BaseEnvironment() []string {
198+
return []string{
199+
"HOME=" + c.HomeDir,
200+
"USER=" + os.Getenv("USER"),
201+
"DOCKER_CONFIG=" + c.ConfigDir,
202+
"KUBECONFIG=invalid",
203+
}
204+
}
205+
157206
// NewCmd creates a cmd object configured with the test environment set
158207
func (c *CLI) NewCmd(command string, args ...string) icmd.Cmd {
159-
env := append(os.Environ(),
160-
"DOCKER_CONFIG="+c.ConfigDir,
161-
"KUBECONFIG=invalid",
162-
)
163208
return icmd.Cmd{
164209
Command: append([]string{command}, args...),
165-
Env: env,
210+
Env: append(c.BaseEnvironment(), c.env...),
166211
}
167212
}
168213

169214
// NewCmdWithEnv creates a cmd object configured with the test environment set with additional env vars
170215
func (c *CLI) NewCmdWithEnv(envvars []string, command string, args ...string) icmd.Cmd {
171-
env := append(os.Environ(),
172-
append(envvars,
173-
"DOCKER_CONFIG="+c.ConfigDir,
174-
"KUBECONFIG=invalid")...,
175-
)
216+
// base env -> CLI overrides -> cmd overrides
217+
cmdEnv := append(c.BaseEnvironment(), c.env...)
218+
cmdEnv = append(cmdEnv, envvars...)
176219
return icmd.Cmd{
177220
Command: append([]string{command}, args...),
178-
Env: env,
221+
Env: cmdEnv,
179222
}
180223
}
181224

@@ -234,13 +277,34 @@ func (c *CLI) RunDockerComposeCmd(t testing.TB, args ...string) *icmd.Result {
234277

235278
// RunDockerComposeCmdNoCheck runs a docker compose command, don't presume of any expectation and returns a result
236279
func (c *CLI) RunDockerComposeCmdNoCheck(t testing.TB, args ...string) *icmd.Result {
280+
return icmd.RunCmd(c.NewDockerComposeCmd(t, args...))
281+
}
282+
283+
// NewDockerComposeCmd creates a command object for Compose, either in plugin
284+
// or standalone mode (based on build tags).
285+
func (c *CLI) NewDockerComposeCmd(t testing.TB, args ...string) icmd.Cmd {
286+
t.Helper()
237287
if composeStandaloneMode {
238-
composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
239-
assert.NilError(t, err)
240-
return icmd.RunCmd(c.NewCmd(composeBinary, args...))
288+
return c.NewCmd(ComposeStandalonePath(t), args...)
241289
}
242290
args = append([]string{"compose"}, args...)
243-
return icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
291+
return c.NewCmd(DockerExecutableName, args...)
292+
}
293+
294+
// ComposeStandalonePath returns the path to the locally-built Compose
295+
// standalone binary from the repo.
296+
//
297+
// This function will fail the test immediately if invoked when not running
298+
// in standalone test mode.
299+
func ComposeStandalonePath(t testing.TB) string {
300+
t.Helper()
301+
if !composeStandaloneMode {
302+
require.Fail(t, "Not running in standalone mode")
303+
}
304+
composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
305+
require.NoError(t, err, "Could not find standalone Compose binary (%q)",
306+
DockerComposeExecutableName)
307+
return composeBinary
244308
}
245309

246310
// StdoutContains returns a predicate on command result expecting a string in stdout

0 commit comments

Comments
 (0)