File size: 7,632 Bytes
e36aeda | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | // Copyright 2022 The Go 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 exec_test
import (
"errors"
"internal/testenv"
"os"
. "os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
var pathVar string = func() string {
if runtime.GOOS == "plan9" {
return "path"
}
return "PATH"
}()
func TestLookPath(t *testing.T) {
testenv.MustHaveExec(t)
// Not parallel: uses Chdir and Setenv.
tmpDir := filepath.Join(t.TempDir(), "testdir")
if err := os.Mkdir(tmpDir, 0777); err != nil {
t.Fatal(err)
}
executable := "execabs-test"
if runtime.GOOS == "windows" {
executable += ".exe"
}
if err := os.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0777); err != nil {
t.Fatal(err)
}
t.Chdir(tmpDir)
t.Logf(". is %#q", tmpDir)
origPath := os.Getenv(pathVar)
// Add "." to PATH so that exec.LookPath looks in the current directory on all systems.
// And try to trick it with "../testdir" too.
for _, errdot := range []string{"1", "0"} {
t.Run("GODEBUG=execerrdot="+errdot, func(t *testing.T) {
t.Setenv("GODEBUG", "execerrdot="+errdot+",execwait=2")
for _, dir := range []string{".", "../testdir"} {
t.Run(pathVar+"="+dir, func(t *testing.T) {
t.Setenv(pathVar, dir+string(filepath.ListSeparator)+origPath)
good := dir + "/execabs-test"
if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
t.Fatalf(`LookPath(%#q) = %#q, %v, want "%s...", nil`, good, found, err, good)
}
if runtime.GOOS == "windows" {
good = dir + `\execabs-test`
if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
t.Fatalf(`LookPath(%#q) = %#q, %v, want "%s...", nil`, good, found, err, good)
}
}
_, err := LookPath("execabs-test")
if errdot == "1" {
if err == nil {
t.Fatalf("LookPath didn't fail when finding a non-relative path")
} else if !errors.Is(err, ErrDot) {
t.Fatalf("LookPath returned unexpected error: want Is ErrDot, got %q", err)
}
} else {
if err != nil {
t.Fatalf("LookPath failed unexpectedly: %v", err)
}
}
cmd := Command("execabs-test")
if errdot == "1" {
if cmd.Err == nil {
t.Fatalf("Command didn't fail when finding a non-relative path")
} else if !errors.Is(cmd.Err, ErrDot) {
t.Fatalf("Command returned unexpected error: want Is ErrDot, got %q", cmd.Err)
}
cmd.Err = nil
} else {
if cmd.Err != nil {
t.Fatalf("Command failed unexpectedly: %v", err)
}
}
// Clearing cmd.Err should let the execution proceed,
// and it should fail because it's not a valid binary.
if err := cmd.Run(); err == nil {
t.Fatalf("Run did not fail: expected exec error")
} else if errors.Is(err, ErrDot) {
t.Fatalf("Run returned unexpected error ErrDot: want error like ENOEXEC: %q", err)
}
})
}
})
}
// Test the behavior when the first entry in PATH is an absolute name for the
// current directory.
//
// On Windows, "." may or may not be implicitly included before the explicit
// %PATH%, depending on the process environment;
// see https://go.dev/issue/4394.
//
// If the relative entry from "." resolves to the same executable as what
// would be resolved from an absolute entry in %PATH% alone, LookPath should
// return the absolute version of the path instead of ErrDot.
// (See https://go.dev/issue/53536.)
//
// If PATH does not implicitly include "." (such as on Unix platforms, or on
// Windows configured with NoDefaultCurrentDirectoryInExePath), then this
// lookup should succeed regardless of the behavior for ".", so it may be
// useful to run as a control case even on those platforms.
t.Run(pathVar+"=$PWD", func(t *testing.T) {
t.Setenv(pathVar, tmpDir+string(filepath.ListSeparator)+origPath)
good := filepath.Join(tmpDir, "execabs-test")
if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
t.Fatalf(`LookPath(%#q) = %#q, %v, want \"%s...\", nil`, good, found, err, good)
}
if found, err := LookPath("execabs-test"); err != nil || !strings.HasPrefix(found, good) {
t.Fatalf(`LookPath(%#q) = %#q, %v, want \"%s...\", nil`, "execabs-test", found, err, good)
}
cmd := Command("execabs-test")
if cmd.Err != nil {
t.Fatalf("Command(%#q).Err = %v; want nil", "execabs-test", cmd.Err)
}
})
t.Run(pathVar+"=$OTHER", func(t *testing.T) {
// Control case: if the lookup returns ErrDot when PATH is empty, then we
// know that PATH implicitly includes ".". If it does not, then we don't
// expect to see ErrDot at all in this test (because the path will be
// unambiguously absolute).
wantErrDot := false
t.Setenv(pathVar, "")
if found, err := LookPath("execabs-test"); errors.Is(err, ErrDot) {
wantErrDot = true
} else if err == nil {
t.Fatalf(`with PATH='', LookPath(%#q) = %#q; want non-nil error`, "execabs-test", found)
}
// Set PATH to include an explicit directory that contains a completely
// independent executable that happens to have the same name as an
// executable in ".". If "." is included implicitly, looking up the
// (unqualified) executable name will return ErrDot; otherwise, the
// executable in "." should have no effect and the lookup should
// unambiguously resolve to the directory in PATH.
dir := t.TempDir()
executable := "execabs-test"
if runtime.GOOS == "windows" {
executable += ".exe"
}
if err := os.WriteFile(filepath.Join(dir, executable), []byte{1, 2, 3}, 0777); err != nil {
t.Fatal(err)
}
t.Setenv(pathVar, dir+string(filepath.ListSeparator)+origPath)
found, err := LookPath("execabs-test")
if wantErrDot {
wantFound := filepath.Join(".", executable)
if found != wantFound || !errors.Is(err, ErrDot) {
t.Fatalf(`LookPath(%#q) = %#q, %v, want %#q, Is ErrDot`, "execabs-test", found, err, wantFound)
}
} else {
wantFound := filepath.Join(dir, executable)
if found != wantFound || err != nil {
t.Fatalf(`LookPath(%#q) = %#q, %v, want %#q, nil`, "execabs-test", found, err, wantFound)
}
}
})
checker := func(test string) func(t *testing.T) {
return func(t *testing.T) {
t.Helper()
t.Logf("PATH=%s", os.Getenv("PATH"))
p, err := LookPath(test)
if err == nil {
t.Errorf("%q: error expected, got nil", test)
}
if p != "" {
t.Errorf("%q: path returned should be \"\". Got %q", test, p)
}
}
}
// Reference behavior for the next test
t.Run(pathVar+"=$OTHER2", func(t *testing.T) {
t.Run("empty", checker(""))
t.Run("dot", checker("."))
t.Run("dotdot1", checker("abc/.."))
t.Run("dotdot2", checker(".."))
})
// Test the behavior when PATH contains an executable file which is not a directory
t.Run(pathVar+"=exe", func(t *testing.T) {
// Inject an executable file (not a directory) in PATH.
// Use our own binary os.Args[0].
t.Setenv(pathVar, testenv.Executable(t))
t.Run("empty", checker(""))
t.Run("dot", checker("."))
t.Run("dotdot1", checker("abc/.."))
t.Run("dotdot2", checker(".."))
})
// Test the behavior when PATH contains an executable file which is not a directory
t.Run(pathVar+"=exe/xx", func(t *testing.T) {
// Inject an executable file (not a directory) in PATH.
// Use our own binary os.Args[0].
t.Setenv(pathVar, filepath.Join(testenv.Executable(t), "xx"))
t.Run("empty", checker(""))
t.Run("dot", checker("."))
t.Run("dotdot1", checker("abc/.."))
t.Run("dotdot2", checker(".."))
})
}
|