| | |
| | |
| | |
| |
|
| | |
| | |
| |
|
| | package exec_test |
| |
|
| | import ( |
| | "errors" |
| | "fmt" |
| | "internal/testenv" |
| | "io" |
| | "io/fs" |
| | "os" |
| | "os/exec" |
| | "path/filepath" |
| | "slices" |
| | "strings" |
| | "testing" |
| | ) |
| |
|
| | func init() { |
| | registerHelperCommand("printpath", cmdPrintPath) |
| | } |
| |
|
| | func cmdPrintPath(_ ...string) { |
| | fmt.Println(testenv.Executable(nil)) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func makePATH(root string, dirs []string) string { |
| | paths := make([]string, 0, len(dirs)) |
| | for _, d := range dirs { |
| | switch { |
| | case d == "": |
| | paths = append(paths, "") |
| | case d == "." || (len(d) >= 2 && d[0] == '.' && os.IsPathSeparator(d[1])): |
| | paths = append(paths, filepath.Clean(d)) |
| | default: |
| | paths = append(paths, filepath.Join(root, d)) |
| | } |
| | } |
| | return strings.Join(paths, string(os.PathListSeparator)) |
| | } |
| |
|
| | |
| | |
| | func installProgs(t *testing.T, root string, files []string) { |
| | for _, f := range files { |
| | dstPath := filepath.Join(root, f) |
| |
|
| | dir := filepath.Dir(dstPath) |
| | if err := os.MkdirAll(dir, 0755); err != nil { |
| | t.Fatal(err) |
| | } |
| |
|
| | if os.IsPathSeparator(f[len(f)-1]) { |
| | continue |
| | } |
| | if strings.EqualFold(filepath.Ext(f), ".bat") { |
| | installBat(t, dstPath) |
| | } else { |
| | installExe(t, dstPath) |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func installExe(t *testing.T, dstPath string) { |
| | src, err := os.Open(testenv.Executable(t)) |
| | if err != nil { |
| | t.Fatal(err) |
| | } |
| | defer src.Close() |
| |
|
| | dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o777) |
| | if err != nil { |
| | t.Fatal(err) |
| | } |
| | defer func() { |
| | if err := dst.Close(); err != nil { |
| | t.Fatal(err) |
| | } |
| | }() |
| |
|
| | _, err = io.Copy(dst, src) |
| | if err != nil { |
| | t.Fatal(err) |
| | } |
| | } |
| |
|
| | |
| | |
| | func installBat(t *testing.T, dstPath string) { |
| | dst, err := os.OpenFile(dstPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o777) |
| | if err != nil { |
| | t.Fatal(err) |
| | } |
| | defer func() { |
| | if err := dst.Close(); err != nil { |
| | t.Fatal(err) |
| | } |
| | }() |
| |
|
| | if _, err := fmt.Fprintf(dst, "@echo %s\r\n", dstPath); err != nil { |
| | t.Fatal(err) |
| | } |
| | } |
| |
|
| | type lookPathTest struct { |
| | name string |
| | PATHEXT string |
| | files []string |
| | PATH []string |
| | searchFor string |
| | want string |
| | wantErr error |
| | skipCmdExeCheck bool |
| | } |
| |
|
| | var lookPathTests = []lookPathTest{ |
| | { |
| | name: "first match", |
| | files: []string{`p1\a.exe`, `p2\a.exe`, `p2\a`}, |
| | searchFor: `a`, |
| | want: `p1\a.exe`, |
| | }, |
| | { |
| | name: "dirs with extensions", |
| | files: []string{`p1.dir\a`, `p2.dir\a.exe`}, |
| | searchFor: `a`, |
| | want: `p2.dir\a.exe`, |
| | }, |
| | { |
| | name: "first with extension", |
| | files: []string{`p1\a.exe`, `p2\a.exe`}, |
| | searchFor: `a.exe`, |
| | want: `p1\a.exe`, |
| | }, |
| | { |
| | name: "specific name", |
| | files: []string{`p1\a.exe`, `p2\b.exe`}, |
| | searchFor: `b`, |
| | want: `p2\b.exe`, |
| | }, |
| | { |
| | name: "no extension", |
| | files: []string{`p1\b`, `p2\a`}, |
| | searchFor: `a`, |
| | wantErr: exec.ErrNotFound, |
| | }, |
| | { |
| | name: "directory, no extension", |
| | files: []string{`p1\a.exe`, `p2\a.exe`}, |
| | searchFor: `p2\a`, |
| | want: `p2\a.exe`, |
| | }, |
| | { |
| | name: "no match", |
| | files: []string{`p1\a.exe`, `p2\a.exe`}, |
| | searchFor: `b`, |
| | wantErr: exec.ErrNotFound, |
| | }, |
| | { |
| | name: "no match with dir", |
| | files: []string{`p1\b.exe`, `p2\a.exe`}, |
| | searchFor: `p2\b`, |
| | wantErr: exec.ErrNotFound, |
| | }, |
| | { |
| | name: "extensionless file in CWD ignored", |
| | files: []string{`a`, `p1\a.exe`, `p2\a.exe`}, |
| | searchFor: `a`, |
| | want: `p1\a.exe`, |
| | }, |
| | { |
| | name: "extensionless file in PATH ignored", |
| | files: []string{`p1\a`, `p2\a.exe`}, |
| | searchFor: `a`, |
| | want: `p2\a.exe`, |
| | }, |
| | { |
| | name: "specific extension", |
| | files: []string{`p1\a.exe`, `p2\a.bat`}, |
| | searchFor: `a.bat`, |
| | want: `p2\a.bat`, |
| | }, |
| | { |
| | name: "mismatched extension", |
| | files: []string{`p1\a.exe`, `p2\a.exe`}, |
| | searchFor: `a.com`, |
| | wantErr: exec.ErrNotFound, |
| | }, |
| | { |
| | name: "doubled extension", |
| | files: []string{`p1\a.exe.exe`}, |
| | searchFor: `a.exe`, |
| | want: `p1\a.exe.exe`, |
| | }, |
| | { |
| | name: "extension not in PATHEXT", |
| | PATHEXT: `.COM;.BAT`, |
| | files: []string{`p1\a.exe`, `p2\a.exe`}, |
| | searchFor: `a.exe`, |
| | want: `p1\a.exe`, |
| | }, |
| | { |
| | name: "first allowed by PATHEXT", |
| | PATHEXT: `.COM;.EXE`, |
| | files: []string{`p1\a.bat`, `p2\a.exe`}, |
| | searchFor: `a`, |
| | want: `p2\a.exe`, |
| | }, |
| | { |
| | name: "first directory containing a PATHEXT match", |
| | PATHEXT: `.COM;.EXE;.BAT`, |
| | files: []string{`p1\a.bat`, `p2\a.exe`}, |
| | searchFor: `a`, |
| | want: `p1\a.bat`, |
| | }, |
| | { |
| | name: "first PATHEXT entry", |
| | PATHEXT: `.COM;.EXE;.BAT`, |
| | files: []string{`p1\a.bat`, `p1\a.exe`, `p2\a.bat`, `p2\a.exe`}, |
| | searchFor: `a`, |
| | want: `p1\a.exe`, |
| | }, |
| | { |
| | name: "ignore dir with PATHEXT extension", |
| | files: []string{`a.exe\`}, |
| | searchFor: `a`, |
| | wantErr: exec.ErrNotFound, |
| | }, |
| | { |
| | name: "ignore empty PATH entry", |
| | files: []string{`a.bat`, `p\a.bat`}, |
| | PATH: []string{`p`}, |
| | searchFor: `a`, |
| | want: `p\a.bat`, |
| | |
| | |
| | skipCmdExeCheck: true, |
| | }, |
| | { |
| | name: "return ErrDot if found by a different absolute path", |
| | files: []string{`p1\a.bat`, `p2\a.bat`}, |
| | PATH: []string{`.\p1`, `p2`}, |
| | searchFor: `a`, |
| | want: `p1\a.bat`, |
| | wantErr: exec.ErrDot, |
| | }, |
| | { |
| | name: "suppress ErrDot if also found in absolute path", |
| | files: []string{`p1\a.bat`, `p2\a.bat`}, |
| | PATH: []string{`.\p1`, `p1`, `p2`}, |
| | searchFor: `a`, |
| | want: `p1\a.bat`, |
| | }, |
| | } |
| |
|
| | func TestLookPathWindows(t *testing.T) { |
| | |
| |
|
| | |
| | |
| | |
| | maySkipHelperCommand("printpath") |
| |
|
| | |
| | |
| | |
| | cmdExe, err := exec.LookPath("cmd") |
| | if err != nil { |
| | t.Fatal(err) |
| | } |
| |
|
| | for _, tt := range lookPathTests { |
| | t.Run(tt.name, func(t *testing.T) { |
| | if tt.want == "" && tt.wantErr == nil { |
| | t.Fatalf("test must specify either want or wantErr") |
| | } |
| |
|
| | root := t.TempDir() |
| | installProgs(t, root, tt.files) |
| |
|
| | if tt.PATHEXT != "" { |
| | t.Setenv("PATHEXT", tt.PATHEXT) |
| | t.Logf("set PATHEXT=%s", tt.PATHEXT) |
| | } |
| |
|
| | var pathVar string |
| | if tt.PATH == nil { |
| | paths := make([]string, 0, len(tt.files)) |
| | for _, f := range tt.files { |
| | dir := filepath.Join(root, filepath.Dir(f)) |
| | if !slices.Contains(paths, dir) { |
| | paths = append(paths, dir) |
| | } |
| | } |
| | pathVar = strings.Join(paths, string(os.PathListSeparator)) |
| | } else { |
| | pathVar = makePATH(root, tt.PATH) |
| | } |
| | t.Setenv("PATH", pathVar) |
| | t.Logf("set PATH=%s", pathVar) |
| |
|
| | t.Chdir(root) |
| |
|
| | if !testing.Short() && !(tt.skipCmdExeCheck || errors.Is(tt.wantErr, exec.ErrDot)) { |
| | |
| | |
| | cmd := testenv.Command(t, cmdExe, "/c", tt.searchFor, "printpath") |
| | out, err := cmd.Output() |
| | if err == nil { |
| | gotAbs := strings.TrimSpace(string(out)) |
| | wantAbs := "" |
| | if tt.want != "" { |
| | wantAbs = filepath.Join(root, tt.want) |
| | } |
| | if gotAbs != wantAbs { |
| | |
| | t.Fatalf("%v\n\tresolved to %s\n\twant %s", cmd, gotAbs, wantAbs) |
| | } |
| | } else if tt.wantErr == nil { |
| | if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 { |
| | t.Fatalf("%v: %v\n%s", cmd, err, ee.Stderr) |
| | } |
| | t.Fatalf("%v: %v", cmd, err) |
| | } |
| | } |
| |
|
| | got, err := exec.LookPath(tt.searchFor) |
| | if filepath.IsAbs(got) { |
| | got, err = filepath.Rel(root, got) |
| | if err != nil { |
| | t.Fatal(err) |
| | } |
| | } |
| | if got != tt.want { |
| | t.Errorf("LookPath(%#q) = %#q; want %#q", tt.searchFor, got, tt.want) |
| | } |
| | if !errors.Is(err, tt.wantErr) { |
| | t.Errorf("LookPath(%#q): %v; want %v", tt.searchFor, err, tt.wantErr) |
| | } |
| | }) |
| | } |
| | } |
| |
|
| | type commandTest struct { |
| | name string |
| | PATH []string |
| | files []string |
| | dir string |
| | arg0 string |
| | want string |
| | wantPath string |
| | wantErrDot bool |
| | wantRunErr error |
| | } |
| |
|
| | var commandTests = []commandTest{ |
| | |
| | { |
| | name: "current directory", |
| | files: []string{`a.exe`}, |
| | PATH: []string{"."}, |
| | arg0: `a.exe`, |
| | want: `a.exe`, |
| | wantErrDot: true, |
| | }, |
| | { |
| | name: "with extra PATH", |
| | files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2", "p"}, |
| | arg0: `a.exe`, |
| | want: `a.exe`, |
| | wantErrDot: true, |
| | }, |
| | { |
| | name: "with extra PATH and no extension", |
| | files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2", "p"}, |
| | arg0: `a`, |
| | want: `a.exe`, |
| | wantErrDot: true, |
| | }, |
| | |
| | { |
| | name: "with dir", |
| | files: []string{`p\a.exe`}, |
| | PATH: []string{"."}, |
| | arg0: `p\a.exe`, |
| | want: `p\a.exe`, |
| | }, |
| | { |
| | name: "with explicit dot", |
| | files: []string{`p\a.exe`}, |
| | PATH: []string{"."}, |
| | arg0: `.\p\a.exe`, |
| | want: `p\a.exe`, |
| | }, |
| | { |
| | name: "with irrelevant PATH", |
| | files: []string{`p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2"}, |
| | arg0: `p\a.exe`, |
| | want: `p\a.exe`, |
| | }, |
| | { |
| | name: "with slash and no extension", |
| | files: []string{`p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2"}, |
| | arg0: `p\a`, |
| | want: `p\a.exe`, |
| | }, |
| | |
| | { |
| | |
| | |
| | name: "not found before Dir", |
| | files: []string{`p\a.exe`}, |
| | PATH: []string{"."}, |
| | dir: `p`, |
| | arg0: `a.exe`, |
| | want: `p\a.exe`, |
| | wantRunErr: exec.ErrNotFound, |
| | }, |
| | { |
| | |
| | |
| | name: "resolved before Dir", |
| | files: []string{`a.exe`, `p\not_important_file`}, |
| | PATH: []string{"."}, |
| | dir: `p`, |
| | arg0: `a.exe`, |
| | want: `a.exe`, |
| | wantErrDot: true, |
| | wantRunErr: fs.ErrNotExist, |
| | }, |
| | { |
| | |
| | |
| | |
| | name: "relative to Dir", |
| | files: []string{`a.exe`, `p\a.exe`}, |
| | PATH: []string{"."}, |
| | dir: `p`, |
| | arg0: `a.exe`, |
| | want: `p\a.exe`, |
| | wantErrDot: true, |
| | }, |
| | { |
| | |
| | name: "relative to Dir with extra PATH", |
| | files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2", "p"}, |
| | dir: `p`, |
| | arg0: `a.exe`, |
| | want: `p\a.exe`, |
| | wantErrDot: true, |
| | }, |
| | { |
| | |
| | name: "relative to Dir with extra PATH and no extension", |
| | files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2", "p"}, |
| | dir: `p`, |
| | arg0: `a`, |
| | want: `p\a.exe`, |
| | wantErrDot: true, |
| | }, |
| | { |
| | |
| | |
| | name: "from PATH with no match in Dir", |
| | files: []string{`p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2", "p"}, |
| | dir: `p`, |
| | arg0: `a.exe`, |
| | want: `p2\a.exe`, |
| | }, |
| | |
| | { |
| | |
| | name: "relative to Dir with explicit dot", |
| | files: []string{`p\a.exe`}, |
| | PATH: []string{"."}, |
| | dir: `p`, |
| | arg0: `.\a.exe`, |
| | want: `p\a.exe`, |
| | }, |
| | { |
| | |
| | name: "relative to Dir with dot and extra PATH", |
| | files: []string{`p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2"}, |
| | dir: `p`, |
| | arg0: `.\a.exe`, |
| | want: `p\a.exe`, |
| | }, |
| | { |
| | |
| | name: "relative to Dir with dot and extra PATH and no extension", |
| | files: []string{`p\a.exe`, `p2\a.exe`}, |
| | PATH: []string{".", "p2"}, |
| | dir: `p`, |
| | arg0: `.\a`, |
| | want: `p\a.exe`, |
| | }, |
| | { |
| | |
| | name: "relative to Dir with different extension", |
| | files: []string{`a.exe`, `p\a.bat`}, |
| | PATH: []string{"."}, |
| | dir: `p`, |
| | arg0: `.\a`, |
| | want: `p\a.bat`, |
| | }, |
| | } |
| |
|
| | func TestCommand(t *testing.T) { |
| | |
| |
|
| | |
| | |
| | |
| | maySkipHelperCommand("printpath") |
| |
|
| | for _, tt := range commandTests { |
| | t.Run(tt.name, func(t *testing.T) { |
| | if tt.PATH == nil { |
| | t.Fatalf("test must specify PATH") |
| | } |
| |
|
| | root := t.TempDir() |
| | installProgs(t, root, tt.files) |
| |
|
| | pathVar := makePATH(root, tt.PATH) |
| | t.Setenv("PATH", pathVar) |
| | t.Logf("set PATH=%s", pathVar) |
| |
|
| | t.Chdir(root) |
| |
|
| | cmd := exec.Command(tt.arg0, "printpath") |
| | cmd.Dir = filepath.Join(root, tt.dir) |
| | if tt.wantErrDot { |
| | if errors.Is(cmd.Err, exec.ErrDot) { |
| | cmd.Err = nil |
| | } else { |
| | t.Fatalf("cmd.Err = %v; want ErrDot", cmd.Err) |
| | } |
| | } |
| |
|
| | out, err := cmd.Output() |
| | if err != nil { |
| | if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 { |
| | t.Logf("%v: %v\n%s", cmd, err, ee.Stderr) |
| | } else { |
| | t.Logf("%v: %v", cmd, err) |
| | } |
| | if !errors.Is(err, tt.wantRunErr) { |
| | t.Errorf("want %v", tt.wantRunErr) |
| | } |
| | return |
| | } |
| |
|
| | got := strings.TrimSpace(string(out)) |
| | if filepath.IsAbs(got) { |
| | got, err = filepath.Rel(root, got) |
| | if err != nil { |
| | t.Fatal(err) |
| | } |
| | } |
| | if got != tt.want { |
| | t.Errorf("\nran %#q\nwant %#q", got, tt.want) |
| | } |
| |
|
| | gotPath := cmd.Path |
| | wantPath := tt.wantPath |
| | if wantPath == "" { |
| | if strings.Contains(tt.arg0, `\`) { |
| | wantPath = tt.arg0 |
| | } else if tt.wantErrDot { |
| | wantPath = strings.TrimPrefix(tt.want, tt.dir+`\`) |
| | } else { |
| | wantPath = filepath.Join(root, tt.want) |
| | } |
| | } |
| | if gotPath != wantPath { |
| | t.Errorf("\ncmd.Path = %#q\nwant %#q", gotPath, wantPath) |
| | } |
| | }) |
| | } |
| | } |
| |
|
| | func TestAbsCommandWithDoubledExtension(t *testing.T) { |
| | t.Parallel() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | comPath := filepath.Join(t.TempDir(), "example.com") |
| | batPath := comPath + ".bat" |
| | installBat(t, batPath) |
| |
|
| | cmd := exec.Command(comPath) |
| | out, err := cmd.CombinedOutput() |
| | t.Logf("%v: %v\n%s", cmd, err, out) |
| | if !errors.Is(err, fs.ErrNotExist) { |
| | t.Errorf("Command(%#q).Run: %v\nwant fs.ErrNotExist", comPath, err) |
| | } |
| |
|
| | resolved, err := exec.LookPath(comPath) |
| | if err != nil || resolved != batPath { |
| | t.Fatalf("LookPath(%#q) = %v, %v; want %#q, <nil>", comPath, resolved, err, batPath) |
| | } |
| | } |
| |
|