blob: e03d60a8ae7775b0b98d2d621f22303146419cf2 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"bytes"
"flag"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// Attention: The tests in this file execute the test binary again with the `-run` flag.
// This is needed as they want to test an `exec`, which terminates the test process.
var internalexececho = flag.Bool("internalexececho", false, "internal flag used for tests that exec")
func TestProcessEnvExecPathAndArgs(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
if *internalexececho {
execEcho(ctx, &command{
Path: "some_binary",
Args: []string{"arg1", "arg2"},
})
return
}
logLines := forkAndReadEcho(ctx)
if !strings.HasSuffix(logLines[0], "/some_binary arg1 arg2") {
t.Errorf("incorrect path or args: %s", logLines[0])
}
})
}
func TestProcessEnvExecAddEnv(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
if *internalexececho {
execEcho(ctx, &command{
Path: "some_binary",
EnvUpdates: []string{"ABC=xyz"},
})
return
}
logLines := forkAndReadEcho(ctx)
for _, ll := range logLines {
if ll == "ABC=xyz" {
return
}
}
t.Errorf("could not find new env variable: %s", logLines)
})
}
func TestProcessEnvExecUpdateEnv(t *testing.T) {
if os.Getenv("PATH") == "" {
t.Fatal("no PATH environment variable found!")
}
withTestContext(t, func(ctx *testContext) {
if *internalexececho {
execEcho(ctx, &command{
Path: "some_binary",
EnvUpdates: []string{"PATH=xyz"},
})
return
}
logLines := forkAndReadEcho(ctx)
for _, ll := range logLines {
if ll == "PATH=xyz" {
return
}
}
t.Errorf("could not find updated env variable: %s", logLines)
})
}
func TestProcessEnvExecDeleteEnv(t *testing.T) {
if os.Getenv("PATH") == "" {
t.Fatal("no PATH environment variable found!")
}
withTestContext(t, func(ctx *testContext) {
if *internalexececho {
execEcho(ctx, &command{
Path: "some_binary",
EnvUpdates: []string{"PATH="},
})
return
}
logLines := forkAndReadEcho(ctx)
for _, ll := range logLines {
if strings.HasPrefix(ll, "PATH=") {
t.Errorf("path env was not removed: %s", ll)
}
}
})
}
func TestProcessEnvRunCmdPathAndArgs(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
cmd := &command{
Path: "some_binary",
Args: []string{"arg1", "arg2"},
}
logLines := runAndEcho(ctx, cmd)
if !strings.HasSuffix(logLines[0], "/some_binary arg1 arg2") {
t.Errorf("incorrect path or args: %s", logLines[0])
}
})
}
func TestProcessEnvRunCmdAddEnv(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
cmd := &command{
Path: "some_binary",
EnvUpdates: []string{"ABC=xyz"},
}
logLines := runAndEcho(ctx, cmd)
for _, ll := range logLines {
if ll == "ABC=xyz" {
return
}
}
t.Errorf("could not find new env variable: %s", logLines)
})
}
func TestProcessEnvRunCmdUpdateEnv(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
if os.Getenv("PATH") == "" {
t.Fatal("no PATH environment variable found!")
}
cmd := &command{
Path: "some_binary",
EnvUpdates: []string{"PATH=xyz"},
}
logLines := runAndEcho(ctx, cmd)
for _, ll := range logLines {
if ll == "PATH=xyz" {
return
}
}
t.Errorf("could not find updated env variable: %s", logLines)
})
}
func TestProcessEnvRunCmdDeleteEnv(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
if os.Getenv("PATH") == "" {
t.Fatal("no PATH environment variable found!")
}
cmd := &command{
Path: "some_binary",
EnvUpdates: []string{"PATH="},
}
logLines := runAndEcho(ctx, cmd)
for _, ll := range logLines {
if strings.HasPrefix(ll, "PATH=") {
t.Errorf("path env was not removed: %s", ll)
}
}
})
}
func TestNewProcessEnvResolvesPwdAwayProperly(t *testing.T) {
// This test cannot be t.Parallel(), since it modifies our environment.
const envPwd = "PWD"
oldEnvPwd := os.Getenv(envPwd)
defer func() {
if oldEnvPwd == "" {
os.Unsetenv(envPwd)
} else {
os.Setenv(envPwd, oldEnvPwd)
}
}()
os.Unsetenv(envPwd)
initialWd, err := os.Getwd()
if initialWd == "/proc/self/cwd" {
t.Fatalf("Working directory should never be %q when env is unset", initialWd)
}
defer func() {
if err := os.Chdir(initialWd); err != nil {
t.Errorf("Changing back to %q failed: %v", initialWd, err)
}
}()
tempDir, err := ioutil.TempDir("", "wrapper_env_test")
if err != nil {
t.Fatalf("Failed making temp dir: %v", err)
}
// Nothing we can do if this breaks, unfortunately.
defer os.RemoveAll(tempDir)
tempDirLink := tempDir + ".symlink"
if err := os.Symlink(tempDir, tempDirLink); err != nil {
t.Fatalf("Failed creating symlink %q => %q: %v", tempDirLink, tempDir, err)
}
if err := os.Chdir(tempDir); err != nil {
t.Fatalf("Failed chdir'ing to tempdir at %q: %v", tempDirLink, err)
}
if err := os.Setenv(envPwd, tempDirLink); err != nil {
t.Fatalf("Failed setting pwd to tempdir at %q: %v", tempDirLink, err)
}
// Ensure that we don't resolve symlinks if they're present in our CWD somehow, except for
// /proc/self/cwd, which tells us nothing about where we are.
env, err := newProcessEnv()
if err != nil {
t.Fatalf("Failed making a new env: %v", err)
}
if wd := env.getwd(); wd != tempDirLink {
t.Errorf("Environment setup had a wd of %q; wanted %q", wd, tempDirLink)
}
const cwdLink = "/proc/self/cwd"
if err := os.Setenv(envPwd, cwdLink); err != nil {
t.Fatalf("Failed setting pwd to /proc/self/cwd: %v", err)
}
env, err = newProcessEnv()
if err != nil {
t.Fatalf("Failed making a new env: %v", err)
}
if wd := env.getwd(); wd != tempDir {
t.Errorf("Environment setup had a wd of %q; wanted %q", cwdLink, tempDir)
}
}
func execEcho(ctx *testContext, cmd *command) {
env := &processEnv{}
err := env.exec(createEcho(ctx, cmd))
if err != nil {
os.Stderr.WriteString(err.Error())
}
os.Exit(1)
}
func forkAndReadEcho(ctx *testContext) []string {
testBin, err := os.Executable()
if err != nil {
ctx.t.Fatalf("unable to read the executable: %s", err)
}
subCmd := exec.Command(testBin, "-internalexececho", "-test.run="+ctx.t.Name())
output, err := subCmd.CombinedOutput()
if err != nil {
ctx.t.Fatalf("error calling test binary again for exec: %s", err)
}
return strings.Split(string(output), "\n")
}
func runAndEcho(ctx *testContext, cmd *command) []string {
env, err := newProcessEnv()
if err != nil {
ctx.t.Fatalf("creation of process env failed: %s", err)
}
buffer := bytes.Buffer{}
if err := env.run(createEcho(ctx, cmd), nil, &buffer, &buffer); err != nil {
ctx.t.Fatalf("run failed: %s", err)
}
return strings.Split(buffer.String(), "\n")
}
func createEcho(ctx *testContext, cmd *command) *command {
content := `
/bin/echo "$0" "$@"
/usr/bin/env
`
fullPath := filepath.Join(ctx.tempDir, cmd.Path)
ctx.writeFile(fullPath, content)
// Note: Using a self executable wrapper does not work due to a race condition
// on unix systems. See https://github.com/golang/go/issues/22315
return &command{
Path: "bash",
Args: append([]string{fullPath}, cmd.Args...),
EnvUpdates: cmd.EnvUpdates,
}
}