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