1// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Copyright 2009 The Go Authors. All rights reserved.
16// Use of this source code is governed by a BSD-style
17// license that can be found in the LICENSE file.
18
19package path
20
21import (
22 "fmt"
23 "reflect"
24 "runtime"
25 "slices"
26 "testing"
27)
28
29func testEachOS(t *testing.T, list []OS, fn func(t *testing.T, os OS)) {
30 for _, os := range list {
31 t.Run(fmt.Sprintf("OS=%s", os), func(t *testing.T) {
32 fn(t, os)
33 })
34 }
35}
36
37type PathTest struct {
38 path, result string
39}
40
41var cleantests = []PathTest{
42 // Already clean
43 {"abc", "abc"},
44 {"abc/def", "abc/def"},
45 {"a/b/c", "a/b/c"},
46 {".", "."},
47 {"..", ".."},
48 {"../..", "../.."},
49 {"../../abc", "../../abc"},
50 {"/abc", "/abc"},
51 {"/", "/"},
52
53 // Empty is current dir
54 {"", "."},
55
56 // Remove trailing slash
57 {"abc/", "abc"},
58 {"abc/def/", "abc/def"},
59 {"a/b/c/", "a/b/c"},
60 {"./", "."},
61 {"../", ".."},
62 {"../../", "../.."},
63 {"/abc/", "/abc"},
64
65 // Remove doubled slash
66 {"abc//def//ghi", "abc/def/ghi"},
67 {"//abc", "/abc"},
68 {"///abc", "/abc"},
69 {"//abc//", "/abc"},
70 {"abc//", "abc"},
71
72 // Remove . elements
73 {"abc/./def", "abc/def"},
74 {"/./abc/def", "/abc/def"},
75 {"abc/.", "abc"},
76
77 // Remove .. elements
78 {"abc/def/ghi/../jkl", "abc/def/jkl"},
79 {"abc/def/../ghi/../jkl", "abc/jkl"},
80 {"abc/def/..", "abc"},
81 {"abc/def/../..", "."},
82 {"/abc/def/../..", "/"},
83 {"abc/def/../../..", ".."},
84 {"/abc/def/../../..", "/"},
85 {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"},
86 {"/../abc", "/abc"},
87
88 // Combinations
89 {"abc/./../def", "def"},
90 {"abc//./../def", "def"},
91 {"abc/../../././../def", "../../def"},
92}
93
94var wincleantests = []PathTest{
95 {`c:`, `c:.`},
96 {`c:\`, `c:\`},
97 {`c:\abc`, `c:\abc`},
98 {`c:abc\..\..\.\.\..\def`, `c:..\..\def`},
99 {`c:\abc\def\..\..`, `c:\`},
100 {`c:\..\abc`, `c:\abc`},
101 {`c:..\abc`, `c:..\abc`},
102 {`\`, `\`},
103 {`/`, `\`},
104 {`\\i\..\c$`, `\c$`},
105 {`\\i\..\i\c$`, `\i\c$`},
106 {`\\i\..\I\c$`, `\I\c$`},
107 {`\\host\share\foo\..\bar`, `\\host\share\bar`},
108 {`//host/share/foo/../baz`, `\\host\share\baz`},
109 {`\\a\b\..\c`, `\\a\b\c`},
110 {`\\a\b`, `\\a\b`},
111}
112
113func TestClean(t *testing.T) {
114 testEachOS(t, []OS{Unix, Windows, Plan9}, func(t *testing.T, os OS) {
115 tests := slices.Clone(cleantests)
116 if os == Windows {
117 for i := range tests {
118 tests[i].result = FromSlash(tests[i].result, os)
119 }
120 tests = append(tests, wincleantests...)
121 }
122 for _, test := range tests {
123 if s := Clean(test.path, os); s != test.result {
124 t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result)
125 }
126 if s := Clean(test.result, os); s != test.result {
127 t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result)
128 }
129 }
130
131 if testing.Short() {
132 t.Skip("skipping malloc count in short mode")
133 }
134 if runtime.GOMAXPROCS(0) > 1 {
135 t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1")
136 return
137 }
138
139 for _, test := range tests {
140 allocs := testing.AllocsPerRun(100, func() { Clean(test.result, os) })
141 if allocs > 0 {
142 t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs)
143 }
144 }
145 })
146}
147
148func TestFromAndToSlash(t *testing.T) {
149 for _, o := range []OS{Unix, Windows, Plan9} {
150 sep := getOS(o).Separator
151
152 var slashtests = []PathTest{
153 {"", ""},
154 {"/", string(sep)},
155 {"/a/b", string([]byte{sep, 'a', sep, 'b'})},
156 {"a//b", string([]byte{'a', sep, sep, 'b'})},
157 }
158
159 for _, test := range slashtests {
160 if s := FromSlash(test.path, o); s != test.result {
161 t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result)
162 }
163 if s := ToSlash(test.result, o); s != test.path {
164 t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path)
165 }
166 }
167 }
168}
169
170type SplitListTest struct {
171 list string
172 result []string
173}
174
175var winsplitlisttests = []SplitListTest{
176 // quoted
177 {`"a"`, []string{`a`}},
178
179 // semicolon
180 {`";"`, []string{`;`}},
181 {`"a;b"`, []string{`a;b`}},
182 {`";";`, []string{`;`, ``}},
183 {`;";"`, []string{``, `;`}},
184
185 // partially quoted
186 {`a";"b`, []string{`a;b`}},
187 {`a; ""b`, []string{`a`, ` b`}},
188 {`"a;b`, []string{`a;b`}},
189 {`""a;b`, []string{`a`, `b`}},
190 {`"""a;b`, []string{`a;b`}},
191 {`""""a;b`, []string{`a`, `b`}},
192 {`a";b`, []string{`a;b`}},
193 {`a;b";c`, []string{`a`, `b;c`}},
194 {`"a";b";c`, []string{`a`, `b;c`}},
195}
196
197func TestSplitList(t *testing.T) {
198 testEachOS(t, []OS{Unix, Windows, Plan9}, func(t *testing.T, os OS) {
199 sep := getOS(os).ListSeparator
200
201 tests := []SplitListTest{
202 {"", []string{}},
203 {string([]byte{'a', sep, 'b'}), []string{"a", "b"}},
204 {string([]byte{sep, 'a', sep, 'b'}), []string{"", "a", "b"}},
205 }
206 if os == Windows {
207 tests = append(tests, winsplitlisttests...)
208 }
209 for _, test := range tests {
210 if l := SplitList(test.list, os); !reflect.DeepEqual(l, test.result) {
211 t.Errorf("SplitList(%#q, %q) = %#q, want %#q", test.list, os, l, test.result)
212 }
213 }
214 })
215}
216
217type SplitTest struct {
218 path, dir, file string
219}
220
221var unixsplittests = []SplitTest{
222 {"a/b", "a/", "b"},
223 {"a/b/", "a/b/", ""},
224 {"a/", "a/", ""},
225 {"a", "", "a"},
226 {"/", "/", ""},
227}
228
229var winsplittests = []SplitTest{
230 {`c:`, `c:`, ``},
231 {`c:/`, `c:/`, ``},
232 {`c:/foo`, `c:/`, `foo`},
233 {`c:/foo/bar`, `c:/foo/`, `bar`},
234 {`//host/share`, `//host/share`, ``},
235 {`//host/share/`, `//host/share/`, ``},
236 {`//host/share/foo`, `//host/share/`, `foo`},
237 {`\\host\share`, `\\host\share`, ``},
238 {`\\host\share\`, `\\host\share\`, ``},
239 {`\\host\share\foo`, `\\host\share\`, `foo`},
240}
241
242func TestSplit(t *testing.T) {
243 testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) {
244 tests := unixsplittests
245 if os == Windows {
246 tests = append(tests, winsplittests...)
247 }
248 for _, test := range tests {
249 pair := Split(test.path, os)
250 d, f := pair[0], pair[1]
251 if d != test.dir || f != test.file {
252 t.Errorf("Split(%q, %q) = %q, %q, want %q, %q",
253 test.path, os, d, f, test.dir, test.file)
254 }
255 }
256 })
257}
258
259type JoinTest struct {
260 elem []string
261 path string
262}
263
264var jointests = []JoinTest{
265 // zero parameters
266 {[]string{}, ""},
267
268 // one parameter
269 {[]string{""}, ""},
270 {[]string{"/"}, "/"},
271 {[]string{"a"}, "a"},
272
273 // two parameters
274 {[]string{"a", "b"}, "a/b"},
275 {[]string{"a", ""}, "a"},
276 {[]string{"", "b"}, "b"},
277 {[]string{"/", "a"}, "/a"},
278 {[]string{"/", "a/b"}, "/a/b"},
279 {[]string{"/", ""}, "/"},
280 {[]string{"//", "a"}, "/a"},
281 {[]string{"/a", "b"}, "/a/b"},
282 {[]string{"a/", "b"}, "a/b"},
283 {[]string{"a/", ""}, "a"},
284 {[]string{"", ""}, ""},
285
286 // three parameters
287 {[]string{"/", "a", "b"}, "/a/b"},
288}
289
290var winjointests = []JoinTest{
291 {[]string{`directory`, `file`}, `directory\file`},
292 {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`},
293 {[]string{`C:\Windows\`, ``}, `C:\Windows`},
294 {[]string{`C:\`, `Windows`}, `C:\Windows`},
295 {[]string{`C:`, `a`}, `C:a`},
296 {[]string{`C:`, `a\b`}, `C:a\b`},
297 {[]string{`C:`, `a`, `b`}, `C:a\b`},
298 {[]string{`C:`, ``, `b`}, `C:b`},
299 {[]string{`C:`, ``, ``, `b`}, `C:b`},
300 {[]string{`C:`, ``}, `C:.`},
301 {[]string{`C:`, ``, ``}, `C:.`},
302 {[]string{`C:.`, `a`}, `C:a`},
303 {[]string{`C:a`, `b`}, `C:a\b`},
304 {[]string{`C:a`, `b`, `d`}, `C:a\b\d`},
305 {[]string{`\\host\share`, `foo`}, `\\host\share\foo`},
306 {[]string{`\\host\share\foo`}, `\\host\share\foo`},
307 {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`},
308 {[]string{`\`}, `\`},
309 {[]string{`\`, ``}, `\`},
310 {[]string{`\`, `a`}, `\a`},
311 {[]string{`\\`, `a`}, `\a`},
312 {[]string{`\`, `a`, `b`}, `\a\b`},
313 {[]string{`\\`, `a`, `b`}, `\a\b`},
314 {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`},
315 {[]string{`\\a`, `b`, `c`}, `\a\b\c`},
316 {[]string{`\\a\`, `b`, `c`}, `\a\b\c`},
317}
318
319func TestJoin(t *testing.T) {
320 testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) {
321 tests := jointests
322 if os == Windows {
323 tests = append(tests, winjointests...)
324 }
325 for _, test := range tests {
326 expected := FromSlash(test.path, os)
327 if p := Join(test.elem, os); p != expected {
328 t.Errorf("join(%q, %q) = %q, want %q", test.elem, os, p, expected)
329 }
330 }
331 })
332}
333
334type ExtTest struct {
335 path, ext string
336}
337
338var exttests = []ExtTest{
339 {"path.go", ".go"},
340 {"path.pb.go", ".go"},
341 {"a.dir/b", ""},
342 {"a.dir/b.go", ".go"},
343 {"a.dir/", ""},
344}
345
346func TestExt(t *testing.T) {
347 testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) {
348 for _, test := range exttests {
349 if x := Ext(test.path, os); x != test.ext {
350 t.Errorf("Ext(%q, %q) = %q, want %q", test.path, os, x, test.ext)
351 }
352 }
353 })
354}
355
356var basetests = []PathTest{
357 {"", "."},
358 {".", "."},
359 {"/.", "."},
360 {"/", "/"},
361 {"////", "/"},
362 {"x/", "x"},
363 {"abc", "abc"},
364 {"abc/def", "def"},
365 {"a/b/.x", ".x"},
366 {"a/b/c.", "c."},
367 {"a/b/c.x", "c.x"},
368}
369
370var winbasetests = []PathTest{
371 {`c:\`, `\`},
372 {`c:.`, `.`},
373 {`c:\a\b`, `b`},
374 {`c:a\b`, `b`},
375 {`c:a\b\c`, `c`},
376 {`\\host\share\`, `\`},
377 {`\\host\share\a`, `a`},
378 {`\\host\share\a\b`, `b`},
379}
380
381func TestBase(t *testing.T) {
382 testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) {
383 tests := slices.Clone(basetests)
384 if os == Windows {
385 // make unix tests work on windows
386 for i := range tests {
387 tests[i].result = Clean(tests[i].result, os)
388 }
389 // add windows specific tests
390 tests = append(tests, winbasetests...)
391 }
392 for _, test := range tests {
393 if s := Base(test.path, os); s != test.result {
394 t.Errorf("Base(%q, %q) = %q, want %q", test.path, os, s, test.result)
395 }
396 }
397 })
398}
399
400var dirtests = []PathTest{
401 {"", "."},
402 {".", "."},
403 {"/.", "/"},
404 {"/", "/"},
405 {"////", "/"},
406 {"/foo", "/"},
407 {"x/", "x"},
408 {"abc", "."},
409 {"abc/def", "abc"},
410 {"a/b/.x", "a/b"},
411 {"a/b/c.", "a/b"},
412 {"a/b/c.x", "a/b"},
413}
414
415var windirtests = []PathTest{
416 {`c:\`, `c:\`},
417 {`c:.`, `c:.`},
418 {`c:\a\b`, `c:\a`},
419 {`c:a\b`, `c:a`},
420 {`c:a\b\c`, `c:a\b`},
421 {`\\host\share`, `\\host\share`},
422 {`\\host\share\`, `\\host\share\`},
423 {`\\host\share\a`, `\\host\share\`},
424 {`\\host\share\a\b`, `\\host\share\a`},
425}
426
427func TestDir(t *testing.T) {
428 testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) {
429 tests := slices.Clone(dirtests)
430 if os == Windows {
431 // make unix tests work on windows
432 for i := range tests {
433 tests[i].result = Clean(tests[i].result, os)
434 }
435 // add windows specific tests
436 tests = append(tests, windirtests...)
437 }
438 for _, test := range tests {
439 if s := Dir(test.path, os); s != test.result {
440 t.Errorf("Dir(%q, %q) = %q, want %q", test.path, os, s, test.result)
441 }
442 }
443 })
444}
445
446type IsAbsTest struct {
447 path string
448 isAbs bool
449}
450
451var isabstests = []IsAbsTest{
452 {"", false},
453 {"/", true},
454 {"/usr/bin/gcc", true},
455 {"..", false},
456 {"/a/../bb", true},
457 {".", false},
458 {"./", false},
459 {"lala", false},
460}
461
462var winisabstests = []IsAbsTest{
463 {`C:\`, true},
464 {`c\`, false},
465 {`c::`, false},
466 {`c:`, false},
467 {`/`, false},
468 {`\`, false},
469 {`\Windows`, false},
470 {`c:a\b`, false},
471 {`c:\a\b`, true},
472 {`c:/a/b`, true},
473 {`\\host\share\foo`, true},
474 {`//host/share/foo/bar`, true},
475}
476
477func TestIsAbs(t *testing.T) {
478 testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) {
479 var tests []IsAbsTest
480 if os == Windows {
481 tests = append(tests, winisabstests...)
482 // All non-windows tests should fail, because they have no volume letter.
483 for _, test := range isabstests {
484 tests = append(tests, IsAbsTest{test.path, false})
485 }
486 // All non-windows test should work as intended if prefixed with volume letter.
487 for _, test := range isabstests {
488 tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
489 }
490 // Test reserved names.
491 // tests = append(tests, IsAbsTest{"/dev/null", true})
492 tests = append(tests, IsAbsTest{"NUL", true})
493 tests = append(tests, IsAbsTest{"nul", true})
494 tests = append(tests, IsAbsTest{"CON", true})
495 } else {
496 tests = isabstests
497 }
498
499 for _, test := range tests {
500 if r := IsAbs(test.path, os); r != test.isAbs {
501 t.Errorf("IsAbs(%q, %q) = %v, want %v", test.path, os, r, test.isAbs)
502 }
503 }
504 })
505}
506
507type RelTests struct {
508 root, path, want string
509}
510
511var reltests = []RelTests{
512 {"a/b", "a/b", "."},
513 {"a/b/.", "a/b", "."},
514 {"a/b", "a/b/.", "."},
515 {"./a/b", "a/b", "."},
516 {"a/b", "./a/b", "."},
517 {"ab/cd", "ab/cde", "../cde"},
518 {"ab/cd", "ab/c", "../c"},
519 {"a/b", "a/b/c/d", "c/d"},
520 {"a/b", "a/b/../c", "../c"},
521 {"a/b/../c", "a/b", "../b"},
522 {"a/b/c", "a/c/d", "../../c/d"},
523 {"a/b", "c/d", "../../c/d"},
524 {"a/b/c/d", "a/b", "../.."},
525 {"a/b/c/d", "a/b/", "../.."},
526 {"a/b/c/d/", "a/b", "../.."},
527 {"a/b/c/d/", "a/b/", "../.."},
528 {"../../a/b", "../../a/b/c/d", "c/d"},
529 {"/a/b", "/a/b", "."},
530 {"/a/b/.", "/a/b", "."},
531 {"/a/b", "/a/b/.", "."},
532 {"/ab/cd", "/ab/cde", "../cde"},
533 {"/ab/cd", "/ab/c", "../c"},
534 {"/a/b", "/a/b/c/d", "c/d"},
535 {"/a/b", "/a/b/../c", "../c"},
536 {"/a/b/../c", "/a/b", "../b"},
537 {"/a/b/c", "/a/c/d", "../../c/d"},
538 {"/a/b", "/c/d", "../../c/d"},
539 {"/a/b/c/d", "/a/b", "../.."},
540 {"/a/b/c/d", "/a/b/", "../.."},
541 {"/a/b/c/d/", "/a/b", "../.."},
542 {"/a/b/c/d/", "/a/b/", "../.."},
543 {"/../../a/b", "/../../a/b/c/d", "c/d"},
544 {".", "a/b", "a/b"},
545 {".", "..", ".."},
546
547 // can't do purely lexically
548 {"..", ".", "err"},
549 {"..", "a", "err"},
550 {"../..", "..", "err"},
551 {"a", "/a", "err"},
552 {"/a", "a", "err"},
553}
554
555var winreltests = []RelTests{
556 {`C:a\b\c`, `C:a/b/d`, `..\d`},
557 {`C:\`, `D:\`, `err`},
558 {`C:`, `D:`, `err`},
559 {`C:\Projects`, `c:\projects\src`, `src`},
560 {`C:\Projects`, `c:\projects`, `.`},
561 {`C:\Projects\a\..`, `c:\projects`, `.`},
562}
563
564func TestRel(t *testing.T) {
565 testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) {
566 tests := slices.Clone(reltests)
567 if os == Windows {
568 for i := range tests {
569 tests[i].want = FromSlash(tests[i].want, Windows)
570 }
571 tests = append(tests, winreltests...)
572 }
573 for _, test := range tests {
574 got, err := Rel(test.root, test.path, os)
575 if test.want == "err" {
576 if err == nil {
577 t.Errorf("Rel(%q, %q, %q)=%q, want error", test.root, test.path, os, got)
578 }
579 continue
580 }
581 if err != nil {
582 t.Errorf("Rel(%q, %q, %q): want %q, got error: %s", test.root, test.path, os, test.want, err)
583 }
584 if got != test.want {
585 t.Errorf("Rel(%q, %q, %q)=%q, want %q", test.root, test.path, os, got, test.want)
586 }
587 }
588 })
589}
590
591type VolumeNameTest struct {
592 path string
593 vol string
594}
595
596var volumenametests = []VolumeNameTest{
597 {`c:/foo/bar`, `c:`},
598 {`c:`, `c:`},
599 {`2:`, ``},
600 {``, ``},
601 {`\\\host`, ``},
602 {`\\\host\`, ``},
603 {`\\\host\share`, ``},
604 {`\\\host\\share`, ``},
605 {`\\host`, ``},
606 {`//host`, ``},
607 {`\\host\`, ``},
608 {`//host/`, ``},
609 {`\\host\share`, `\\host\share`},
610 {`//host/share`, `//host/share`},
611 {`\\host\share\`, `\\host\share`},
612 {`//host/share/`, `//host/share`},
613 {`\\host\share\foo`, `\\host\share`},
614 {`//host/share/foo`, `//host/share`},
615 {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`},
616 {`//host/share//foo///bar////baz`, `//host/share`},
617 {`\\host\share\foo\..\bar`, `\\host\share`},
618 {`//host/share/foo/../bar`, `//host/share`},
619}
620
621func TestVolumeName(t *testing.T) {
622 os := Windows
623 for _, v := range volumenametests {
624 if vol := VolumeName(v.path, os); vol != v.vol {
625 t.Errorf("VolumeName(%q, %q)=%q, want %q", v.path, os, vol, v.vol)
626 }
627 }
628}