github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/util_test.go (about) 1 // Copyright 2011 Google Inc. All Rights Reserved. 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 package nin 16 17 import ( 18 "runtime" 19 "strconv" 20 "strings" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 ) 25 26 func TestCanonicalizePath_PathSamples(t *testing.T) { 27 type row struct { 28 in string 29 want string 30 } 31 data := []row{ 32 {"", ""}, 33 {"foo.h", "foo.h"}, 34 {"./foo.h", "foo.h"}, 35 {"./foo/./bar.h", "foo/bar.h"}, 36 {"./x/foo/../bar.h", "x/bar.h"}, 37 {"./x/foo/../../bar.h", "bar.h"}, 38 {"foo//bar", "foo/bar"}, 39 {"foo//.//..///bar", "bar"}, 40 {"./x/../foo/../../bar.h", "../bar.h"}, 41 {"foo/./.", "foo"}, 42 {"foo/bar/..", "foo"}, 43 {"foo/.hidden_bar", "foo/.hidden_bar"}, 44 {"/foo", "/foo"}, 45 {"/", ""}, 46 {"/foo/..", ""}, 47 {".", "."}, 48 {"./.", "."}, 49 {"foo/..", "."}, 50 // CanonicalizePath.UpDir: 51 {"../../foo/bar.h", "../../foo/bar.h"}, 52 {"test/../../foo/bar.h", "../foo/bar.h"}, 53 // CanonicalizePath.AbsolutePath 54 {"/usr/include/stdio.h", "/usr/include/stdio.h"}, 55 } 56 if runtime.GOOS == "windows" { 57 data = append(data, row{"//foo", "//foo"}) 58 } else { 59 data = append(data, row{"//foo", "/foo"}) 60 } 61 for i, l := range data { 62 t.Run(strconv.Itoa(i), func(t *testing.T) { 63 got := CanonicalizePath(l.in) 64 if l.want != got { 65 t.Fatalf("want: %q, got: %q", l.want, got) 66 } 67 got2, _ := CanonicalizePathBits(l.in) 68 if got != got2 { 69 t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits") 70 } 71 }) 72 } 73 } 74 75 func TestCanonicalizePath_PathSamplesWindows(t *testing.T) { 76 if runtime.GOOS != "windows" { 77 t.Skip("windows only") 78 } 79 type row struct { 80 in string 81 want string 82 } 83 data := []row{ 84 {"", ""}, 85 {"foo.", "foo."}, 86 {".\\foo.h", "foo.h"}, 87 {".\\foo\\.\\bar.h", "foo/bar.h"}, 88 {".\\x\\foo\\..\\bar.h", "x/bar.h"}, 89 {".\\x\\foo\\..\\..\\bar.h", "bar.h"}, 90 {"foo\\\\bar", "foo/bar"}, 91 {"foo\\\\.\\\\..\\\\\\bar", "bar"}, 92 {".\\x\\..\\foo\\..\\..\\bar.h", "../bar.h"}, 93 {"foo\\.\\.", "foo"}, 94 {"foo\\bar\\..", "foo"}, 95 {"foo\\.hidden_bar", "foo/.hidden_bar"}, 96 {"\\foo", "/foo"}, 97 {"\\\\foo", "//foo"}, 98 {"\\", ""}, 99 } 100 for i, l := range data { 101 t.Run(strconv.Itoa(i), func(t *testing.T) { 102 got := CanonicalizePath(l.in) 103 if l.want != got { 104 t.Fatalf("want: %q, got: %q", l.want, got) 105 } 106 got2, _ := CanonicalizePathBits(l.in) 107 if got != got2 { 108 t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits") 109 } 110 }) 111 } 112 } 113 114 func TestCanonicalizePathBits_SlashTracking(t *testing.T) { 115 if runtime.GOOS != "windows" { 116 t.Skip("windows only") 117 } 118 type row struct { 119 in string 120 want string 121 wantBits uint64 122 } 123 data := []row{ 124 {"", "", 0}, 125 {"foo.h", "foo.h", 0}, 126 {"foo.h", "foo.h", 0}, 127 {"a\\foo.h", "a/foo.h", 1}, 128 {"a/bcd/efh\\foo.h", "a/bcd/efh/foo.h", 4}, 129 {"a\\bcd/efh\\foo.h", "a/bcd/efh/foo.h", 5}, 130 {"a\\bcd\\efh\\foo.h", "a/bcd/efh/foo.h", 7}, 131 {"a/bcd/efh/foo.h", "a/bcd/efh/foo.h", 0}, 132 {"a\\./efh\\foo.h", "a/efh/foo.h", 3}, 133 {"a\\../efh\\foo.h", "efh/foo.h", 1}, 134 {"a\\b\\c\\d\\e\\f\\g\\foo.h", "a/b/c/d/e/f/g/foo.h", 127}, 135 {"a\\b\\c\\..\\..\\..\\g\\foo.h", "g/foo.h", 1}, 136 {"a\\b/c\\../../..\\g\\foo.h", "g/foo.h", 1}, 137 {"a\\b/c\\./../..\\g\\foo.h", "a/g/foo.h", 3}, 138 {"a\\b/c\\./../..\\g/foo.h", "a/g/foo.h", 1}, 139 {"a\\\\\\foo.h", "a/foo.h", 1}, 140 {"a/\\\\foo.h", "a/foo.h", 0}, 141 {"a\\//foo.h", "a/foo.h", 1}, 142 } 143 for i, l := range data { 144 t.Run(strconv.Itoa(i), func(t *testing.T) { 145 got, slashBits := CanonicalizePathBits(l.in) 146 if l.want != got { 147 t.Fatalf("want: %q, got: %q", l.want, got) 148 } 149 if slashBits != l.wantBits { 150 t.Fatalf("want: %d, got: %d", l.wantBits, slashBits) 151 } 152 got2 := CanonicalizePath(l.in) 153 if got != got2 { 154 t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits") 155 } 156 }) 157 } 158 } 159 160 func TestCanonicalizePath_CanonicalizeNotExceedingLen(t *testing.T) { 161 if runtime.GOOS != "windows" { 162 t.Skip("windows only") 163 } 164 t.Skip("This test is irrelevant in Go. Remove once conversion is done") 165 } 166 167 func TestCanonicalizePath_TooManyComponents(t *testing.T) { 168 if runtime.GOOS != "windows" { 169 t.Skip("windows only") 170 } 171 t.Skip("TODO") 172 type row struct { 173 in string 174 //want string 175 wantBits uint64 176 } 177 data := []row{ 178 // 64 is OK. 179 { 180 "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x.h", 181 0, 182 }, 183 // Backslashes version. 184 { 185 "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x.h", 186 0xffffffff, 187 }, 188 // 65 is OK if #component is less than 60 after path canonicalization. 189 { 190 "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x/y.h", 191 0, 192 }, 193 // Backslashes version. 194 { 195 "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x\\y.h", 196 0x1ffffffff, 197 }, 198 // 59 after canonicalization is OK. 199 { 200 "a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/x/y.h", 201 0, 202 }, 203 // Backslashes version. 204 { 205 "a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\x\\y.h", 206 0x3ffffffffffffff, 207 }, 208 } 209 // Manual check that the last 2 ones have 58 items. 210 if 58 != strings.Count(data[4].in, "/") { 211 t.Fatal("expected equal") 212 } 213 if 58 != strings.Count(data[5].in, "\\") { 214 t.Fatal("expected equal") 215 } 216 217 for i, l := range data { 218 t.Run(strconv.Itoa(i), func(t *testing.T) { 219 got, slashBits := CanonicalizePathBits(l.in) 220 if slashBits != l.wantBits { 221 t.Fatalf("want: %d, got: %d", l.wantBits, slashBits) 222 } 223 got2 := CanonicalizePath(l.in) 224 if got != got2 { 225 t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits") 226 } 227 }) 228 } 229 } 230 231 func TestCanonicalizePath_NotNullTerminated(t *testing.T) { 232 t.Skip("This test is irrelevant in Go. Remove once conversion is done") 233 } 234 235 func TestPathEscaping_TortureTest(t *testing.T) { 236 got := getWin32EscapedString("foo bar\\\"'$@d!st!c'\\path'\\") 237 if diff := cmp.Diff("\"foo bar\\\\\\\"'$@d!st!c'\\path'\\\\\"", got); diff != "" { 238 t.Fatalf("+want, -got: %s", diff) 239 } 240 got = getShellEscapedString("foo bar\"/'$@d!st!c'/path'") 241 if diff := cmp.Diff("'foo bar\"/'\\''$@d!st!c'\\''/path'\\'''", got); diff != "" { 242 t.Fatalf("+want, -got: %s", diff) 243 } 244 } 245 246 func TestPathEscaping_SensiblePathsAreNotNeedlesslyEscaped(t *testing.T) { 247 path := "some/sensible/path/without/crazy/characters.c++" 248 got := getWin32EscapedString(path) 249 if diff := cmp.Diff(path, got); diff != "" { 250 t.Fatalf("+want, -got: %s", diff) 251 } 252 got = getShellEscapedString(path) 253 if diff := cmp.Diff(path, got); diff != "" { 254 t.Fatalf("+want, -got: %s", diff) 255 } 256 } 257 258 func TestPathEscaping_SensibleWin32PathsAreNotNeedlesslyEscaped(t *testing.T) { 259 path := "some\\sensible\\path\\without\\crazy\\characters.c++" 260 result := getWin32EscapedString(path) 261 if path != result { 262 t.Fatal("expected equal") 263 } 264 } 265 266 func TestElideMiddle_NothingToElide(t *testing.T) { 267 input := "Nothing to elide in this short string." 268 if input != elideMiddle(input, 80) { 269 t.Fatal("expected equal") 270 } 271 if input != elideMiddle(input, 38) { 272 t.Fatal("expected equal") 273 } 274 if "" != elideMiddle(input, 0) { 275 t.Fatal("expected equal") 276 } 277 if "." != elideMiddle(input, 1) { 278 t.Fatal("expected equal") 279 } 280 if ".." != elideMiddle(input, 2) { 281 t.Fatal("expected equal") 282 } 283 if "..." != elideMiddle(input, 3) { 284 t.Fatal("expected equal") 285 } 286 } 287 288 func TestElideMiddle_ElideInTheMiddle(t *testing.T) { 289 input := "01234567890123456789" 290 elided := elideMiddle(input, 10) 291 if "012...789" != elided { 292 t.Fatal("expected equal") 293 } 294 if "01234567...23456789" != elideMiddle(input, 19) { 295 t.Fatal("expected equal") 296 } 297 } 298 299 var dummyBenchmarkValue = "" 300 301 // The C++ version is canonPerftest. It runs 2000000 iterations. 302 // 303 // On my workstation: 304 // 305 // The C++ version has an minimum of 82ms. 306 // 307 // The Go version with "go test -cpu 1 -bench=. -run BenchmarkCanonicalizePath" 308 // has a minimum of 157ns, which multiplied by 2000000 gives 306ms. So the code 309 // is nearly 4x slower. I'll have to optimize later. 310 func BenchmarkCanonicalizePathBits(b *testing.B) { 311 b.ReportAllocs() 312 kPath := "../../third_party/WebKit/Source/WebCore/platform/leveldb/LevelDBWriteBatch.cpp" 313 s := "" 314 for i := 0; i < b.N; i++ { 315 s, _ = CanonicalizePathBits(kPath) 316 } 317 // Use s so it's not optimized out. 318 dummyBenchmarkValue = s 319 } 320 321 func BenchmarkCanonicalizePath(b *testing.B) { 322 b.ReportAllocs() 323 kPath := "../../third_party/WebKit/Source/WebCore/platform/leveldb/LevelDBWriteBatch.cpp" 324 s := "" 325 for i := 0; i < b.N; i++ { 326 s = CanonicalizePath(kPath) 327 } 328 // Use s so it's not optimized out. 329 dummyBenchmarkValue = s 330 }