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