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