diff options
Diffstat (limited to 'src/os/exec/dot_test.go')
-rw-r--r-- | src/os/exec/dot_test.go | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/src/os/exec/dot_test.go b/src/os/exec/dot_test.go new file mode 100644 index 0000000..66c92f7 --- /dev/null +++ b/src/os/exec/dot_test.go @@ -0,0 +1,192 @@ +// 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 os.Chdir and t.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) + } + cwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer func() { + if err := os.Chdir(cwd); err != nil { + panic(err) + } + }() + if err = os.Chdir(tmpDir); err != nil { + t.Fatal(err) + } + t.Setenv("PWD", 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) + } + } + }) +} |