// Copyright 2020 CUE Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copyright 2009 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 path import ( "fmt" "reflect" "runtime" "testing" ) func testEachOS(t *testing.T, list []OS, fn func(t *testing.T, os OS)) { for _, os := range list { t.Run(fmt.Sprintf("OS=%s", os), func(t *testing.T) { fn(t, os) }) } } type PathTest struct { path, result string } var cleantests = []PathTest{ // Already clean {"abc", "abc"}, {"abc/def", "abc/def"}, {"a/b/c", "a/b/c"}, {".", "."}, {"..", ".."}, {"../..", "../.."}, {"../../abc", "../../abc"}, {"/abc", "/abc"}, {"/", "/"}, // Empty is current dir {"", "."}, // Remove trailing slash {"abc/", "abc"}, {"abc/def/", "abc/def"}, {"a/b/c/", "a/b/c"}, {"./", "."}, {"../", ".."}, {"../../", "../.."}, {"/abc/", "/abc"}, // Remove doubled slash {"abc//def//ghi", "abc/def/ghi"}, {"//abc", "/abc"}, {"///abc", "/abc"}, {"//abc//", "/abc"}, {"abc//", "abc"}, // Remove . elements {"abc/./def", "abc/def"}, {"/./abc/def", "/abc/def"}, {"abc/.", "abc"}, // Remove .. elements {"abc/def/ghi/../jkl", "abc/def/jkl"}, {"abc/def/../ghi/../jkl", "abc/jkl"}, {"abc/def/..", "abc"}, {"abc/def/../..", "."}, {"/abc/def/../..", "/"}, {"abc/def/../../..", ".."}, {"/abc/def/../../..", "/"}, {"abc/def/../../../ghi/jkl/../../../mno", "../../mno"}, {"/../abc", "/abc"}, // Combinations {"abc/./../def", "def"}, {"abc//./../def", "def"}, {"abc/../../././../def", "../../def"}, } var wincleantests = []PathTest{ {`c:`, `c:.`}, {`c:\`, `c:\`}, {`c:\abc`, `c:\abc`}, {`c:abc\..\..\.\.\..\def`, `c:..\..\def`}, {`c:\abc\def\..\..`, `c:\`}, {`c:\..\abc`, `c:\abc`}, {`c:..\abc`, `c:..\abc`}, {`\`, `\`}, {`/`, `\`}, {`\\i\..\c$`, `\c$`}, {`\\i\..\i\c$`, `\i\c$`}, {`\\i\..\I\c$`, `\I\c$`}, {`\\host\share\foo\..\bar`, `\\host\share\bar`}, {`//host/share/foo/../baz`, `\\host\share\baz`}, {`\\a\b\..\c`, `\\a\b\c`}, {`\\a\b`, `\\a\b`}, } func TestClean(t *testing.T) { testEachOS(t, []OS{Unix, Windows, Plan9}, func(t *testing.T, os OS) { tests := append([]PathTest{}, cleantests...) // TODO: replace with slices.Clone if os == Windows { for i := range tests { tests[i].result = FromSlash(tests[i].result, os) } tests = append(tests, wincleantests...) } for _, test := range tests { if s := Clean(test.path, os); s != test.result { t.Errorf("Clean(%q) = %q, want %q", test.path, s, test.result) } if s := Clean(test.result, os); s != test.result { t.Errorf("Clean(%q) = %q, want %q", test.result, s, test.result) } } if testing.Short() { t.Skip("skipping malloc count in short mode") } if runtime.GOMAXPROCS(0) > 1 { t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") return } for _, test := range tests { allocs := testing.AllocsPerRun(100, func() { Clean(test.result, os) }) if allocs > 0 { t.Errorf("Clean(%q): %v allocs, want zero", test.result, allocs) } } }) } func TestFromAndToSlash(t *testing.T) { for _, o := range []OS{Unix, Windows, Plan9} { sep := getOS(o).Separator var slashtests = []PathTest{ {"", ""}, {"/", string(sep)}, {"/a/b", string([]byte{sep, 'a', sep, 'b'})}, {"a//b", string([]byte{'a', sep, sep, 'b'})}, } for _, test := range slashtests { if s := FromSlash(test.path, o); s != test.result { t.Errorf("FromSlash(%q) = %q, want %q", test.path, s, test.result) } if s := ToSlash(test.result, o); s != test.path { t.Errorf("ToSlash(%q) = %q, want %q", test.result, s, test.path) } } } } type SplitListTest struct { list string result []string } var winsplitlisttests = []SplitListTest{ // quoted {`"a"`, []string{`a`}}, // semicolon {`";"`, []string{`;`}}, {`"a;b"`, []string{`a;b`}}, {`";";`, []string{`;`, ``}}, {`;";"`, []string{``, `;`}}, // partially quoted {`a";"b`, []string{`a;b`}}, {`a; ""b`, []string{`a`, ` b`}}, {`"a;b`, []string{`a;b`}}, {`""a;b`, []string{`a`, `b`}}, {`"""a;b`, []string{`a;b`}}, {`""""a;b`, []string{`a`, `b`}}, {`a";b`, []string{`a;b`}}, {`a;b";c`, []string{`a`, `b;c`}}, {`"a";b";c`, []string{`a`, `b;c`}}, } func TestSplitList(t *testing.T) { testEachOS(t, []OS{Unix, Windows, Plan9}, func(t *testing.T, os OS) { sep := getOS(os).ListSeparator tests := []SplitListTest{ {"", []string{}}, {string([]byte{'a', sep, 'b'}), []string{"a", "b"}}, {string([]byte{sep, 'a', sep, 'b'}), []string{"", "a", "b"}}, } if os == Windows { tests = append(tests, winsplitlisttests...) } for _, test := range tests { if l := SplitList(test.list, os); !reflect.DeepEqual(l, test.result) { t.Errorf("SplitList(%#q, %q) = %#q, want %#q", test.list, os, l, test.result) } } }) } type SplitTest struct { path, dir, file string } var unixsplittests = []SplitTest{ {"a/b", "a/", "b"}, {"a/b/", "a/b/", ""}, {"a/", "a/", ""}, {"a", "", "a"}, {"/", "/", ""}, } var winsplittests = []SplitTest{ {`c:`, `c:`, ``}, {`c:/`, `c:/`, ``}, {`c:/foo`, `c:/`, `foo`}, {`c:/foo/bar`, `c:/foo/`, `bar`}, {`//host/share`, `//host/share`, ``}, {`//host/share/`, `//host/share/`, ``}, {`//host/share/foo`, `//host/share/`, `foo`}, {`\\host\share`, `\\host\share`, ``}, {`\\host\share\`, `\\host\share\`, ``}, {`\\host\share\foo`, `\\host\share\`, `foo`}, } func TestSplit(t *testing.T) { testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) { tests := unixsplittests if os == Windows { tests = append(tests, winsplittests...) } for _, test := range tests { pair := Split(test.path, os) d, f := pair[0], pair[1] if d != test.dir || f != test.file { t.Errorf("Split(%q, %q) = %q, %q, want %q, %q", test.path, os, d, f, test.dir, test.file) } } }) } type JoinTest struct { elem []string path string } var jointests = []JoinTest{ // zero parameters {[]string{}, ""}, // one parameter {[]string{""}, ""}, {[]string{"/"}, "/"}, {[]string{"a"}, "a"}, // two parameters {[]string{"a", "b"}, "a/b"}, {[]string{"a", ""}, "a"}, {[]string{"", "b"}, "b"}, {[]string{"/", "a"}, "/a"}, {[]string{"/", "a/b"}, "/a/b"}, {[]string{"/", ""}, "/"}, {[]string{"//", "a"}, "/a"}, {[]string{"/a", "b"}, "/a/b"}, {[]string{"a/", "b"}, "a/b"}, {[]string{"a/", ""}, "a"}, {[]string{"", ""}, ""}, // three parameters {[]string{"/", "a", "b"}, "/a/b"}, } var winjointests = []JoinTest{ {[]string{`directory`, `file`}, `directory\file`}, {[]string{`C:\Windows\`, `System32`}, `C:\Windows\System32`}, {[]string{`C:\Windows\`, ``}, `C:\Windows`}, {[]string{`C:\`, `Windows`}, `C:\Windows`}, {[]string{`C:`, `a`}, `C:a`}, {[]string{`C:`, `a\b`}, `C:a\b`}, {[]string{`C:`, `a`, `b`}, `C:a\b`}, {[]string{`C:`, ``, `b`}, `C:b`}, {[]string{`C:`, ``, ``, `b`}, `C:b`}, {[]string{`C:`, ``}, `C:.`}, {[]string{`C:`, ``, ``}, `C:.`}, {[]string{`C:.`, `a`}, `C:a`}, {[]string{`C:a`, `b`}, `C:a\b`}, {[]string{`C:a`, `b`, `d`}, `C:a\b\d`}, {[]string{`\\host\share`, `foo`}, `\\host\share\foo`}, {[]string{`\\host\share\foo`}, `\\host\share\foo`}, {[]string{`//host/share`, `foo/bar`}, `\\host\share\foo\bar`}, {[]string{`\`}, `\`}, {[]string{`\`, ``}, `\`}, {[]string{`\`, `a`}, `\a`}, {[]string{`\\`, `a`}, `\a`}, {[]string{`\`, `a`, `b`}, `\a\b`}, {[]string{`\\`, `a`, `b`}, `\a\b`}, {[]string{`\`, `\\a\b`, `c`}, `\a\b\c`}, {[]string{`\\a`, `b`, `c`}, `\a\b\c`}, {[]string{`\\a\`, `b`, `c`}, `\a\b\c`}, } func TestJoin(t *testing.T) { testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) { tests := jointests if os == Windows { tests = append(tests, winjointests...) } for _, test := range tests { expected := FromSlash(test.path, os) if p := Join(test.elem, os); p != expected { t.Errorf("join(%q, %q) = %q, want %q", test.elem, os, p, expected) } } }) } type ExtTest struct { path, ext string } var exttests = []ExtTest{ {"path.go", ".go"}, {"path.pb.go", ".go"}, {"a.dir/b", ""}, {"a.dir/b.go", ".go"}, {"a.dir/", ""}, } func TestExt(t *testing.T) { testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) { for _, test := range exttests { if x := Ext(test.path, os); x != test.ext { t.Errorf("Ext(%q, %q) = %q, want %q", test.path, os, x, test.ext) } } }) } var basetests = []PathTest{ {"", "."}, {".", "."}, {"/.", "."}, {"/", "/"}, {"////", "/"}, {"x/", "x"}, {"abc", "abc"}, {"abc/def", "def"}, {"a/b/.x", ".x"}, {"a/b/c.", "c."}, {"a/b/c.x", "c.x"}, } var winbasetests = []PathTest{ {`c:\`, `\`}, {`c:.`, `.`}, {`c:\a\b`, `b`}, {`c:a\b`, `b`}, {`c:a\b\c`, `c`}, {`\\host\share\`, `\`}, {`\\host\share\a`, `a`}, {`\\host\share\a\b`, `b`}, } func TestBase(t *testing.T) { tests := basetests testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) { if os == Windows { // make unix tests work on windows for i := range tests { tests[i].result = Clean(tests[i].result, os) } // add windows specific tests tests = append(tests, winbasetests...) } for _, test := range tests { if s := Base(test.path, os); s != test.result { t.Errorf("Base(%q, %q) = %q, want %q", test.path, os, s, test.result) } } }) } var dirtests = []PathTest{ {"", "."}, {".", "."}, {"/.", "/"}, {"/", "/"}, {"////", "/"}, {"/foo", "/"}, {"x/", "x"}, {"abc", "."}, {"abc/def", "abc"}, {"a/b/.x", "a/b"}, {"a/b/c.", "a/b"}, {"a/b/c.x", "a/b"}, } var windirtests = []PathTest{ {`c:\`, `c:\`}, {`c:.`, `c:.`}, {`c:\a\b`, `c:\a`}, {`c:a\b`, `c:a`}, {`c:a\b\c`, `c:a\b`}, {`\\host\share`, `\\host\share`}, {`\\host\share\`, `\\host\share\`}, {`\\host\share\a`, `\\host\share\`}, {`\\host\share\a\b`, `\\host\share\a`}, } func TestDir(t *testing.T) { testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) { tests := dirtests if os == Windows { // make unix tests work on windows for i := range tests { tests[i].result = Clean(tests[i].result, os) } // add windows specific tests tests = append(tests, windirtests...) } for _, test := range tests { if s := Dir(test.path, os); s != test.result { t.Errorf("Dir(%q, %q) = %q, want %q", test.path, os, s, test.result) } } }) } type IsAbsTest struct { path string isAbs bool } var isabstests = []IsAbsTest{ {"", false}, {"/", true}, {"/usr/bin/gcc", true}, {"..", false}, {"/a/../bb", true}, {".", false}, {"./", false}, {"lala", false}, } var winisabstests = []IsAbsTest{ {`C:\`, true}, {`c\`, false}, {`c::`, false}, {`c:`, false}, {`/`, false}, {`\`, false}, {`\Windows`, false}, {`c:a\b`, false}, {`c:\a\b`, true}, {`c:/a/b`, true}, {`\\host\share\foo`, true}, {`//host/share/foo/bar`, true}, } func TestIsAbs(t *testing.T) { testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) { var tests []IsAbsTest if os == Windows { tests = append(tests, winisabstests...) // All non-windows tests should fail, because they have no volume letter. for _, test := range isabstests { tests = append(tests, IsAbsTest{test.path, false}) } // All non-windows test should work as intended if prefixed with volume letter. for _, test := range isabstests { tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs}) } // Test reserved names. // tests = append(tests, IsAbsTest{"/dev/null", true}) tests = append(tests, IsAbsTest{"NUL", true}) tests = append(tests, IsAbsTest{"nul", true}) tests = append(tests, IsAbsTest{"CON", true}) } else { tests = isabstests } for _, test := range tests { if r := IsAbs(test.path, os); r != test.isAbs { t.Errorf("IsAbs(%q, %q) = %v, want %v", test.path, os, r, test.isAbs) } } }) } type RelTests struct { root, path, want string } var reltests = []RelTests{ {"a/b", "a/b", "."}, {"a/b/.", "a/b", "."}, {"a/b", "a/b/.", "."}, {"./a/b", "a/b", "."}, {"a/b", "./a/b", "."}, {"ab/cd", "ab/cde", "../cde"}, {"ab/cd", "ab/c", "../c"}, {"a/b", "a/b/c/d", "c/d"}, {"a/b", "a/b/../c", "../c"}, {"a/b/../c", "a/b", "../b"}, {"a/b/c", "a/c/d", "../../c/d"}, {"a/b", "c/d", "../../c/d"}, {"a/b/c/d", "a/b", "../.."}, {"a/b/c/d", "a/b/", "../.."}, {"a/b/c/d/", "a/b", "../.."}, {"a/b/c/d/", "a/b/", "../.."}, {"../../a/b", "../../a/b/c/d", "c/d"}, {"/a/b", "/a/b", "."}, {"/a/b/.", "/a/b", "."}, {"/a/b", "/a/b/.", "."}, {"/ab/cd", "/ab/cde", "../cde"}, {"/ab/cd", "/ab/c", "../c"}, {"/a/b", "/a/b/c/d", "c/d"}, {"/a/b", "/a/b/../c", "../c"}, {"/a/b/../c", "/a/b", "../b"}, {"/a/b/c", "/a/c/d", "../../c/d"}, {"/a/b", "/c/d", "../../c/d"}, {"/a/b/c/d", "/a/b", "../.."}, {"/a/b/c/d", "/a/b/", "../.."}, {"/a/b/c/d/", "/a/b", "../.."}, {"/a/b/c/d/", "/a/b/", "../.."}, {"/../../a/b", "/../../a/b/c/d", "c/d"}, {".", "a/b", "a/b"}, {".", "..", ".."}, // can't do purely lexically {"..", ".", "err"}, {"..", "a", "err"}, {"../..", "..", "err"}, {"a", "/a", "err"}, {"/a", "a", "err"}, } var winreltests = []RelTests{ {`C:a\b\c`, `C:a/b/d`, `..\d`}, {`C:\`, `D:\`, `err`}, {`C:`, `D:`, `err`}, {`C:\Projects`, `c:\projects\src`, `src`}, {`C:\Projects`, `c:\projects`, `.`}, {`C:\Projects\a\..`, `c:\projects`, `.`}, } func TestRel(t *testing.T) { testEachOS(t, []OS{Unix, Windows}, func(t *testing.T, os OS) { tests := append([]RelTests{}, reltests...) if os == Windows { for i := range tests { tests[i].want = FromSlash(tests[i].want, Windows) } tests = append(tests, winreltests...) } for _, test := range tests { got, err := Rel(test.root, test.path, os) if test.want == "err" { if err == nil { t.Errorf("Rel(%q, %q, %q)=%q, want error", test.root, test.path, os, got) } continue } if err != nil { t.Errorf("Rel(%q, %q, %q): want %q, got error: %s", test.root, test.path, os, test.want, err) } if got != test.want { t.Errorf("Rel(%q, %q, %q)=%q, want %q", test.root, test.path, os, got, test.want) } } }) } type VolumeNameTest struct { path string vol string } var volumenametests = []VolumeNameTest{ {`c:/foo/bar`, `c:`}, {`c:`, `c:`}, {`2:`, ``}, {``, ``}, {`\\\host`, ``}, {`\\\host\`, ``}, {`\\\host\share`, ``}, {`\\\host\\share`, ``}, {`\\host`, ``}, {`//host`, ``}, {`\\host\`, ``}, {`//host/`, ``}, {`\\host\share`, `\\host\share`}, {`//host/share`, `//host/share`}, {`\\host\share\`, `\\host\share`}, {`//host/share/`, `//host/share`}, {`\\host\share\foo`, `\\host\share`}, {`//host/share/foo`, `//host/share`}, {`\\host\share\\foo\\\bar\\\\baz`, `\\host\share`}, {`//host/share//foo///bar////baz`, `//host/share`}, {`\\host\share\foo\..\bar`, `\\host\share`}, {`//host/share/foo/../bar`, `//host/share`}, } func TestVolumeName(t *testing.T) { os := Windows for _, v := range volumenametests { if vol := VolumeName(v.path, os); vol != v.vol { t.Errorf("VolumeName(%q, %q)=%q, want %q", v.path, os, vol, v.vol) } } }