1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package filepath_test
6
7import (
8	"fmt"
9	"internal/testenv"
10	"io/ioutil"
11	"os"
12	. "path/filepath"
13	"reflect"
14	"runtime"
15	"sort"
16	"strings"
17	"testing"
18)
19
20type MatchTest struct {
21	pattern, s string
22	match      bool
23	err        error
24}
25
26var matchTests = []MatchTest{
27	{"abc", "abc", true, nil},
28	{"*", "abc", true, nil},
29	{"*c", "abc", true, nil},
30	{"a*", "a", true, nil},
31	{"a*", "abc", true, nil},
32	{"a*", "ab/c", false, nil},
33	{"a*/b", "abc/b", true, nil},
34	{"a*/b", "a/c/b", false, nil},
35	{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
36	{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
37	{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
38	{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
39	{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
40	{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
41	{"ab[c]", "abc", true, nil},
42	{"ab[b-d]", "abc", true, nil},
43	{"ab[e-g]", "abc", false, nil},
44	{"ab[^c]", "abc", false, nil},
45	{"ab[^b-d]", "abc", false, nil},
46	{"ab[^e-g]", "abc", true, nil},
47	{"a\\*b", "a*b", true, nil},
48	{"a\\*b", "ab", false, nil},
49	{"a?b", "a☺b", true, nil},
50	{"a[^a]b", "a☺b", true, nil},
51	{"a???b", "a☺b", false, nil},
52	{"a[^a][^a][^a]b", "a☺b", false, nil},
53	{"[a-ζ]*", "α", true, nil},
54	{"*[a-ζ]", "A", false, nil},
55	{"a?b", "a/b", false, nil},
56	{"a*b", "a/b", false, nil},
57	{"[\\]a]", "]", true, nil},
58	{"[\\-]", "-", true, nil},
59	{"[x\\-]", "x", true, nil},
60	{"[x\\-]", "-", true, nil},
61	{"[x\\-]", "z", false, nil},
62	{"[\\-x]", "x", true, nil},
63	{"[\\-x]", "-", true, nil},
64	{"[\\-x]", "a", false, nil},
65	{"[]a]", "]", false, ErrBadPattern},
66	{"[-]", "-", false, ErrBadPattern},
67	{"[x-]", "x", false, ErrBadPattern},
68	{"[x-]", "-", false, ErrBadPattern},
69	{"[x-]", "z", false, ErrBadPattern},
70	{"[-x]", "x", false, ErrBadPattern},
71	{"[-x]", "-", false, ErrBadPattern},
72	{"[-x]", "a", false, ErrBadPattern},
73	{"\\", "a", false, ErrBadPattern},
74	{"[a-b-c]", "a", false, ErrBadPattern},
75	{"[", "a", false, ErrBadPattern},
76	{"[^", "a", false, ErrBadPattern},
77	{"[^bc", "a", false, ErrBadPattern},
78	{"a[", "a", false, nil},
79	{"a[", "ab", false, ErrBadPattern},
80	{"*x", "xxx", true, nil},
81}
82
83func errp(e error) string {
84	if e == nil {
85		return "<nil>"
86	}
87	return e.Error()
88}
89
90func TestMatch(t *testing.T) {
91	for _, tt := range matchTests {
92		pattern := tt.pattern
93		s := tt.s
94		if runtime.GOOS == "windows" {
95			if strings.Contains(pattern, "\\") {
96				// no escape allowed on windows.
97				continue
98			}
99			pattern = Clean(pattern)
100			s = Clean(s)
101		}
102		ok, err := Match(pattern, s)
103		if ok != tt.match || err != tt.err {
104			t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
105		}
106	}
107}
108
109// contains reports whether vector contains the string s.
110func contains(vector []string, s string) bool {
111	for _, elem := range vector {
112		if elem == s {
113			return true
114		}
115	}
116	return false
117}
118
119var globTests = []struct {
120	pattern, result string
121}{
122	{"match.go", "match.go"},
123	{"mat?h.go", "match.go"},
124	{"*", "match.go"},
125	// Does not work in gccgo test environment.
126	// {"../*/match.go", "../filepath/match.go"},
127}
128
129func TestGlob(t *testing.T) {
130	for _, tt := range globTests {
131		pattern := tt.pattern
132		result := tt.result
133		if runtime.GOOS == "windows" {
134			pattern = Clean(pattern)
135			result = Clean(result)
136		}
137		matches, err := Glob(pattern)
138		if err != nil {
139			t.Errorf("Glob error for %q: %s", pattern, err)
140			continue
141		}
142		if !contains(matches, result) {
143			t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result)
144		}
145	}
146	for _, pattern := range []string{"no_match", "../*/no_match"} {
147		matches, err := Glob(pattern)
148		if err != nil {
149			t.Errorf("Glob error for %q: %s", pattern, err)
150			continue
151		}
152		if len(matches) != 0 {
153			t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
154		}
155	}
156}
157
158func TestGlobError(t *testing.T) {
159	_, err := Glob("[]")
160	if err == nil {
161		t.Error("expected error for bad pattern; got none")
162	}
163}
164
165func TestGlobUNC(t *testing.T) {
166	// Just make sure this runs without crashing for now.
167	// See issue 15879.
168	Glob(`\\?\C:\*`)
169}
170
171var globSymlinkTests = []struct {
172	path, dest string
173	brokenLink bool
174}{
175	{"test1", "link1", false},
176	{"test2", "link2", true},
177}
178
179func TestGlobSymlink(t *testing.T) {
180	testenv.MustHaveSymlink(t)
181
182	tmpDir, err := ioutil.TempDir("", "globsymlink")
183	if err != nil {
184		t.Fatal("creating temp dir:", err)
185	}
186	defer os.RemoveAll(tmpDir)
187
188	for _, tt := range globSymlinkTests {
189		path := Join(tmpDir, tt.path)
190		dest := Join(tmpDir, tt.dest)
191		f, err := os.Create(path)
192		if err != nil {
193			t.Fatal(err)
194		}
195		if err := f.Close(); err != nil {
196			t.Fatal(err)
197		}
198		err = os.Symlink(path, dest)
199		if err != nil {
200			t.Fatal(err)
201		}
202		if tt.brokenLink {
203			// Break the symlink.
204			os.Remove(path)
205		}
206		matches, err := Glob(dest)
207		if err != nil {
208			t.Errorf("GlobSymlink error for %q: %s", dest, err)
209		}
210		if !contains(matches, dest) {
211			t.Errorf("Glob(%#q) = %#v want %v", dest, matches, dest)
212		}
213	}
214}
215
216type globTest struct {
217	pattern string
218	matches []string
219}
220
221func (test *globTest) buildWant(root string) []string {
222	want := make([]string, 0)
223	for _, m := range test.matches {
224		want = append(want, root+FromSlash(m))
225	}
226	sort.Strings(want)
227	return want
228}
229
230func (test *globTest) globAbs(root, rootPattern string) error {
231	p := FromSlash(rootPattern + `\` + test.pattern)
232	have, err := Glob(p)
233	if err != nil {
234		return err
235	}
236	sort.Strings(have)
237	want := test.buildWant(root + `\`)
238	if strings.Join(want, "_") == strings.Join(have, "_") {
239		return nil
240	}
241	return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
242}
243
244func (test *globTest) globRel(root string) error {
245	p := root + FromSlash(test.pattern)
246	have, err := Glob(p)
247	if err != nil {
248		return err
249	}
250	sort.Strings(have)
251	want := test.buildWant(root)
252	if strings.Join(want, "_") == strings.Join(have, "_") {
253		return nil
254	}
255	// try also matching version without root prefix
256	wantWithNoRoot := test.buildWant("")
257	if strings.Join(wantWithNoRoot, "_") == strings.Join(have, "_") {
258		return nil
259	}
260	return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
261}
262
263func TestWindowsGlob(t *testing.T) {
264	if runtime.GOOS != "windows" {
265		t.Skipf("skipping windows specific test")
266	}
267
268	tmpDir, err := ioutil.TempDir("", "TestWindowsGlob")
269	if err != nil {
270		t.Fatal(err)
271	}
272	defer os.RemoveAll(tmpDir)
273
274	// /tmp may itself be a symlink
275	tmpDir, err = EvalSymlinks(tmpDir)
276	if err != nil {
277		t.Fatal("eval symlink for tmp dir:", err)
278	}
279
280	if len(tmpDir) < 3 {
281		t.Fatalf("tmpDir path %q is too short", tmpDir)
282	}
283	if tmpDir[1] != ':' {
284		t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
285	}
286
287	dirs := []string{
288		"a",
289		"b",
290		"dir/d/bin",
291	}
292	files := []string{
293		"dir/d/bin/git.exe",
294	}
295	for _, dir := range dirs {
296		err := os.MkdirAll(Join(tmpDir, dir), 0777)
297		if err != nil {
298			t.Fatal(err)
299		}
300	}
301	for _, file := range files {
302		err := ioutil.WriteFile(Join(tmpDir, file), nil, 0666)
303		if err != nil {
304			t.Fatal(err)
305		}
306	}
307
308	tests := []globTest{
309		{"a", []string{"a"}},
310		{"b", []string{"b"}},
311		{"c", []string{}},
312		{"*", []string{"a", "b", "dir"}},
313		{"d*", []string{"dir"}},
314		{"*i*", []string{"dir"}},
315		{"*r", []string{"dir"}},
316		{"?ir", []string{"dir"}},
317		{"?r", []string{}},
318		{"d*/*/bin/git.exe", []string{"dir/d/bin/git.exe"}},
319	}
320
321	// test absolute paths
322	for _, test := range tests {
323		var p string
324		err = test.globAbs(tmpDir, tmpDir)
325		if err != nil {
326			t.Error(err)
327		}
328		// test C:\*Documents and Settings\...
329		p = tmpDir
330		p = strings.Replace(p, `:\`, `:\*`, 1)
331		err = test.globAbs(tmpDir, p)
332		if err != nil {
333			t.Error(err)
334		}
335		// test C:\Documents and Settings*\...
336		p = tmpDir
337		p = strings.Replace(p, `:\`, `:`, 1)
338		p = strings.Replace(p, `\`, `*\`, 1)
339		p = strings.Replace(p, `:`, `:\`, 1)
340		err = test.globAbs(tmpDir, p)
341		if err != nil {
342			t.Error(err)
343		}
344	}
345
346	// test relative paths
347	wd, err := os.Getwd()
348	if err != nil {
349		t.Fatal(err)
350	}
351	err = os.Chdir(tmpDir)
352	if err != nil {
353		t.Fatal(err)
354	}
355	defer func() {
356		err := os.Chdir(wd)
357		if err != nil {
358			t.Fatal(err)
359		}
360	}()
361	for _, test := range tests {
362		err := test.globRel("")
363		if err != nil {
364			t.Error(err)
365		}
366		err = test.globRel(`.\`)
367		if err != nil {
368			t.Error(err)
369		}
370		err = test.globRel(tmpDir[:2]) // C:
371		if err != nil {
372			t.Error(err)
373		}
374	}
375}
376
377func TestNonWindowsGlobEscape(t *testing.T) {
378	if runtime.GOOS == "windows" {
379		t.Skipf("skipping non-windows specific test")
380	}
381	pattern := `\match.go`
382	want := []string{"match.go"}
383	matches, err := Glob(pattern)
384	if err != nil {
385		t.Fatalf("Glob error for %q: %s", pattern, err)
386	}
387	if !reflect.DeepEqual(matches, want) {
388		t.Fatalf("Glob(%#q) = %v want %v", pattern, matches, want)
389	}
390}
391