• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package main
6
7import (
8	"bytes"
9	"flag"
10	"io/ioutil"
11	"os"
12	"os/exec"
13	"path/filepath"
14	"strings"
15	"testing"
16)
17
18// Attention: The tests in this file execute the test binary again with the `-run` flag.
19// This is needed as they want to test an `exec`, which terminates the test process.
20var internalexececho = flag.Bool("internalexececho", false, "internal flag used for tests that exec")
21
22func TestProcessEnvExecPathAndArgs(t *testing.T) {
23	withTestContext(t, func(ctx *testContext) {
24		if *internalexececho {
25			execEcho(ctx, &command{
26				Path: "some_binary",
27				Args: []string{"arg1", "arg2"},
28			})
29			return
30		}
31		logLines := forkAndReadEcho(ctx)
32		if !strings.HasSuffix(logLines[0], "/some_binary arg1 arg2") {
33			t.Errorf("incorrect path or args: %s", logLines[0])
34		}
35	})
36}
37
38func TestProcessEnvExecAddEnv(t *testing.T) {
39	withTestContext(t, func(ctx *testContext) {
40		if *internalexececho {
41			execEcho(ctx, &command{
42				Path:       "some_binary",
43				EnvUpdates: []string{"ABC=xyz"},
44			})
45			return
46		}
47
48		logLines := forkAndReadEcho(ctx)
49		for _, ll := range logLines {
50			if ll == "ABC=xyz" {
51				return
52			}
53		}
54		t.Errorf("could not find new env variable: %s", logLines)
55	})
56}
57
58func TestProcessEnvExecUpdateEnv(t *testing.T) {
59	if os.Getenv("PATH") == "" {
60		t.Fatal("no PATH environment variable found!")
61	}
62	withTestContext(t, func(ctx *testContext) {
63		if *internalexececho {
64			execEcho(ctx, &command{
65				Path:       "some_binary",
66				EnvUpdates: []string{"PATH=xyz"},
67			})
68			return
69		}
70		logLines := forkAndReadEcho(ctx)
71		for _, ll := range logLines {
72			if ll == "PATH=xyz" {
73				return
74			}
75		}
76		t.Errorf("could not find updated env variable: %s", logLines)
77	})
78}
79
80func TestProcessEnvExecDeleteEnv(t *testing.T) {
81	if os.Getenv("PATH") == "" {
82		t.Fatal("no PATH environment variable found!")
83	}
84	withTestContext(t, func(ctx *testContext) {
85		if *internalexececho {
86			execEcho(ctx, &command{
87				Path:       "some_binary",
88				EnvUpdates: []string{"PATH="},
89			})
90			return
91		}
92		logLines := forkAndReadEcho(ctx)
93		for _, ll := range logLines {
94			if strings.HasPrefix(ll, "PATH=") {
95				t.Errorf("path env was not removed: %s", ll)
96			}
97		}
98	})
99}
100
101func TestProcessEnvRunCmdPathAndArgs(t *testing.T) {
102	withTestContext(t, func(ctx *testContext) {
103		cmd := &command{
104			Path: "some_binary",
105			Args: []string{"arg1", "arg2"},
106		}
107		logLines := runAndEcho(ctx, cmd)
108		if !strings.HasSuffix(logLines[0], "/some_binary arg1 arg2") {
109			t.Errorf("incorrect path or args: %s", logLines[0])
110		}
111	})
112}
113
114func TestProcessEnvRunCmdAddEnv(t *testing.T) {
115	withTestContext(t, func(ctx *testContext) {
116		cmd := &command{
117			Path:       "some_binary",
118			EnvUpdates: []string{"ABC=xyz"},
119		}
120		logLines := runAndEcho(ctx, cmd)
121		for _, ll := range logLines {
122			if ll == "ABC=xyz" {
123				return
124			}
125		}
126		t.Errorf("could not find new env variable: %s", logLines)
127	})
128}
129
130func TestProcessEnvRunCmdUpdateEnv(t *testing.T) {
131	withTestContext(t, func(ctx *testContext) {
132		if os.Getenv("PATH") == "" {
133			t.Fatal("no PATH environment variable found!")
134		}
135		cmd := &command{
136			Path:       "some_binary",
137			EnvUpdates: []string{"PATH=xyz"},
138		}
139		logLines := runAndEcho(ctx, cmd)
140		for _, ll := range logLines {
141			if ll == "PATH=xyz" {
142				return
143			}
144		}
145		t.Errorf("could not find updated env variable: %s", logLines)
146	})
147}
148
149func TestProcessEnvRunCmdDeleteEnv(t *testing.T) {
150	withTestContext(t, func(ctx *testContext) {
151		if os.Getenv("PATH") == "" {
152			t.Fatal("no PATH environment variable found!")
153		}
154		cmd := &command{
155			Path:       "some_binary",
156			EnvUpdates: []string{"PATH="},
157		}
158		logLines := runAndEcho(ctx, cmd)
159		for _, ll := range logLines {
160			if strings.HasPrefix(ll, "PATH=") {
161				t.Errorf("path env was not removed: %s", ll)
162			}
163		}
164	})
165}
166
167func TestNewProcessEnvResolvesPwdAwayProperly(t *testing.T) {
168	// This test cannot be t.Parallel(), since it modifies our environment.
169	const envPwd = "PWD"
170
171	oldEnvPwd := os.Getenv(envPwd)
172	defer func() {
173		if oldEnvPwd == "" {
174			os.Unsetenv(envPwd)
175		} else {
176			os.Setenv(envPwd, oldEnvPwd)
177		}
178	}()
179
180	os.Unsetenv(envPwd)
181
182	initialWd, err := os.Getwd()
183	if initialWd == "/proc/self/cwd" {
184		t.Fatalf("Working directory should never be %q when env is unset", initialWd)
185	}
186
187	defer func() {
188		if err := os.Chdir(initialWd); err != nil {
189			t.Errorf("Changing back to %q failed: %v", initialWd, err)
190		}
191	}()
192
193	tempDir, err := ioutil.TempDir("", "wrapper_env_test")
194	if err != nil {
195		t.Fatalf("Failed making temp dir: %v", err)
196	}
197
198	// Nothing we can do if this breaks, unfortunately.
199	defer os.RemoveAll(tempDir)
200
201	tempDirLink := tempDir + ".symlink"
202	if err := os.Symlink(tempDir, tempDirLink); err != nil {
203		t.Fatalf("Failed creating symlink %q => %q: %v", tempDirLink, tempDir, err)
204	}
205
206	if err := os.Chdir(tempDir); err != nil {
207		t.Fatalf("Failed chdir'ing to tempdir at %q: %v", tempDirLink, err)
208	}
209
210	if err := os.Setenv(envPwd, tempDirLink); err != nil {
211		t.Fatalf("Failed setting pwd to tempdir at %q: %v", tempDirLink, err)
212	}
213
214	// Ensure that we don't resolve symlinks if they're present in our CWD somehow, except for
215	// /proc/self/cwd, which tells us nothing about where we are.
216	env, err := newProcessEnv()
217	if err != nil {
218		t.Fatalf("Failed making a new env: %v", err)
219	}
220
221	if wd := env.getwd(); wd != tempDirLink {
222		t.Errorf("Environment setup had a wd of %q; wanted %q", wd, tempDirLink)
223	}
224
225	const cwdLink = "/proc/self/cwd"
226	if err := os.Setenv(envPwd, cwdLink); err != nil {
227		t.Fatalf("Failed setting pwd to /proc/self/cwd: %v", err)
228	}
229
230	env, err = newProcessEnv()
231	if err != nil {
232		t.Fatalf("Failed making a new env: %v", err)
233	}
234
235	if wd := env.getwd(); wd != tempDir {
236		t.Errorf("Environment setup had a wd of %q; wanted %q", cwdLink, tempDir)
237	}
238}
239
240func execEcho(ctx *testContext, cmd *command) {
241	env := &processEnv{}
242	err := env.exec(createEcho(ctx, cmd))
243	if err != nil {
244		os.Stderr.WriteString(err.Error())
245	}
246	os.Exit(1)
247}
248
249func forkAndReadEcho(ctx *testContext) []string {
250	testBin, err := os.Executable()
251	if err != nil {
252		ctx.t.Fatalf("unable to read the executable: %s", err)
253	}
254
255	subCmd := exec.Command(testBin, "-internalexececho", "-test.run="+ctx.t.Name())
256	output, err := subCmd.CombinedOutput()
257	if err != nil {
258		ctx.t.Fatalf("error calling test binary again for exec: %s", err)
259	}
260	return strings.Split(string(output), "\n")
261}
262
263func runAndEcho(ctx *testContext, cmd *command) []string {
264	env, err := newProcessEnv()
265	if err != nil {
266		ctx.t.Fatalf("creation of process env failed: %s", err)
267	}
268	buffer := bytes.Buffer{}
269	if err := env.run(createEcho(ctx, cmd), nil, &buffer, &buffer); err != nil {
270		ctx.t.Fatalf("run failed: %s", err)
271	}
272	return strings.Split(buffer.String(), "\n")
273}
274
275func createEcho(ctx *testContext, cmd *command) *command {
276	content := `
277/bin/echo "$0" "$@"
278/usr/bin/env
279`
280	fullPath := filepath.Join(ctx.tempDir, cmd.Path)
281	ctx.writeFile(fullPath, content)
282	// Note: Using a self executable wrapper does not work due to a race condition
283	// on unix systems. See https://github.com/golang/go/issues/22315
284	return &command{
285		Path:       "bash",
286		Args:       append([]string{fullPath}, cmd.Args...),
287		EnvUpdates: cmd.EnvUpdates,
288	}
289}
290