summaryrefslogtreecommitdiff
path: root/src/os/exec/dot_test.go
blob: e2d2dba7a5bb3ada55da8b105b799a32f18acbd3 (plain)
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
// 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)

	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 _, 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)
				}
			}

			if _, err := LookPath("execabs-test"); 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)
			}

			cmd := Command("execabs-test")
			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

			// 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)
			}
		}
	})
}