github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/helpers/path_test.go (about) 1 // Copyright 2015 The Hugo Authors. 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 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package helpers 15 16 import ( 17 "fmt" 18 "os" 19 "path/filepath" 20 "reflect" 21 "runtime" 22 "strconv" 23 "strings" 24 "testing" 25 "time" 26 27 "github.com/gohugoio/hugo/langs" 28 29 qt "github.com/frankban/quicktest" 30 31 "github.com/gohugoio/hugo/hugofs" 32 "github.com/spf13/afero" 33 ) 34 35 func TestMakePath(t *testing.T) { 36 c := qt.New(t) 37 tests := []struct { 38 input string 39 expected string 40 removeAccents bool 41 }{ 42 {"dot.slash/backslash\\underscore_pound#plus+hyphen-", "dot.slash/backslash\\underscore_pound#plus+hyphen-", true}, 43 {"abcXYZ0123456789", "abcXYZ0123456789", true}, 44 {"%20 %2", "%20-2", true}, 45 {"foo- bar", "foo-bar", true}, 46 {" Foo bar ", "Foo-bar", true}, 47 {"Foo.Bar/foo_Bar-Foo", "Foo.Bar/foo_Bar-Foo", true}, 48 {"fOO,bar:foobAR", "fOObarfoobAR", true}, 49 {"FOo/BaR.html", "FOo/BaR.html", true}, 50 {"трям/трям", "трям/трям", true}, 51 {"은행", "은행", true}, 52 {"Банковский кассир", "Банковскии-кассир", true}, 53 // Issue #1488 54 {"संस्कृत", "संस्कृत", false}, 55 {"a%C3%B1ame", "a%C3%B1ame", false}, // Issue #1292 56 {"this+is+a+test", "this+is+a+test", false}, // Issue #1290 57 {"~foo", "~foo", false}, // Issue #2177 58 {"foo--bar", "foo--bar", true}, // Issue #7288 59 {"foo@bar", "foo@bar", true}, // Issue #10548 60 } 61 62 for _, test := range tests { 63 v := newTestCfg() 64 v.Set("removePathAccents", test.removeAccents) 65 66 l := langs.NewDefaultLanguage(v) 67 p, err := NewPathSpec(hugofs.NewMem(v), l, nil) 68 c.Assert(err, qt.IsNil) 69 70 output := p.MakePath(test.input) 71 if output != test.expected { 72 t.Errorf("Expected %#v, got %#v\n", test.expected, output) 73 } 74 } 75 } 76 77 func TestMakePathSanitized(t *testing.T) { 78 v := newTestCfg() 79 80 p, _ := NewPathSpec(hugofs.NewMem(v), v, nil) 81 82 tests := []struct { 83 input string 84 expected string 85 }{ 86 {" FOO bar ", "foo-bar"}, 87 {"Foo.Bar/fOO_bAr-Foo", "foo.bar/foo_bar-foo"}, 88 {"FOO,bar:FooBar", "foobarfoobar"}, 89 {"foo/BAR.HTML", "foo/bar.html"}, 90 {"трям/трям", "трям/трям"}, 91 {"은행", "은행"}, 92 } 93 94 for _, test := range tests { 95 output := p.MakePathSanitized(test.input) 96 if output != test.expected { 97 t.Errorf("Expected %#v, got %#v\n", test.expected, output) 98 } 99 } 100 } 101 102 func TestMakePathSanitizedDisablePathToLower(t *testing.T) { 103 v := newTestCfg() 104 105 v.Set("disablePathToLower", true) 106 107 l := langs.NewDefaultLanguage(v) 108 p, _ := NewPathSpec(hugofs.NewMem(v), l, nil) 109 110 tests := []struct { 111 input string 112 expected string 113 }{ 114 {" FOO bar ", "FOO-bar"}, 115 {"Foo.Bar/fOO_bAr-Foo", "Foo.Bar/fOO_bAr-Foo"}, 116 {"FOO,bar:FooBar", "FOObarFooBar"}, 117 {"foo/BAR.HTML", "foo/BAR.HTML"}, 118 {"трям/трям", "трям/трям"}, 119 {"은행", "은행"}, 120 } 121 122 for _, test := range tests { 123 output := p.MakePathSanitized(test.input) 124 if output != test.expected { 125 t.Errorf("Expected %#v, got %#v\n", test.expected, output) 126 } 127 } 128 } 129 130 func TestMakePathRelative(t *testing.T) { 131 type test struct { 132 inPath, path1, path2, output string 133 } 134 135 data := []test{ 136 {"/abc/bcd/ab.css", "/abc/bcd", "/bbc/bcd", "/ab.css"}, 137 {"/abc/bcd/ab.css", "/abcd/bcd", "/abc/bcd", "/ab.css"}, 138 } 139 140 for i, d := range data { 141 output, _ := makePathRelative(d.inPath, d.path1, d.path2) 142 if d.output != output { 143 t.Errorf("Test #%d failed. Expected %q got %q", i, d.output, output) 144 } 145 } 146 _, error := makePathRelative("a/b/c.ss", "/a/c", "/d/c", "/e/f") 147 148 if error == nil { 149 t.Errorf("Test failed, expected error") 150 } 151 } 152 153 func TestGetDottedRelativePath(t *testing.T) { 154 // on Windows this will receive both kinds, both country and western ... 155 for _, f := range []func(string) string{filepath.FromSlash, func(s string) string { return s }} { 156 doTestGetDottedRelativePath(f, t) 157 } 158 } 159 160 func doTestGetDottedRelativePath(urlFixer func(string) string, t *testing.T) { 161 type test struct { 162 input, expected string 163 } 164 data := []test{ 165 {"", "./"}, 166 {urlFixer("/"), "./"}, 167 {urlFixer("post"), "../"}, 168 {urlFixer("/post"), "../"}, 169 {urlFixer("post/"), "../"}, 170 {urlFixer("tags/foo.html"), "../"}, 171 {urlFixer("/tags/foo.html"), "../"}, 172 {urlFixer("/post/"), "../"}, 173 {urlFixer("////post/////"), "../"}, 174 {urlFixer("/foo/bar/index.html"), "../../"}, 175 {urlFixer("/foo/bar/foo/"), "../../../"}, 176 {urlFixer("/foo/bar/foo"), "../../../"}, 177 {urlFixer("foo/bar/foo/"), "../../../"}, 178 {urlFixer("foo/bar/foo/bar"), "../../../../"}, 179 {"404.html", "./"}, 180 {"404.xml", "./"}, 181 {"/404.html", "./"}, 182 } 183 for i, d := range data { 184 output := GetDottedRelativePath(d.input) 185 if d.expected != output { 186 t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output) 187 } 188 } 189 } 190 191 func TestMakeTitle(t *testing.T) { 192 type test struct { 193 input, expected string 194 } 195 data := []test{ 196 {"Make-Title", "Make Title"}, 197 {"MakeTitle", "MakeTitle"}, 198 {"make_title", "make_title"}, 199 } 200 for i, d := range data { 201 output := MakeTitle(d.input) 202 if d.expected != output { 203 t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output) 204 } 205 } 206 } 207 208 func TestDirExists(t *testing.T) { 209 type test struct { 210 input string 211 expected bool 212 } 213 214 data := []test{ 215 {".", true}, 216 {"./", true}, 217 {"..", true}, 218 {"../", true}, 219 {"./..", true}, 220 {"./../", true}, 221 {os.TempDir(), true}, 222 {os.TempDir() + FilePathSeparator, true}, 223 {"/", true}, 224 {"/some-really-random-directory-name", false}, 225 {"/some/really/random/directory/name", false}, 226 {"./some-really-random-local-directory-name", false}, 227 {"./some/really/random/local/directory/name", false}, 228 } 229 230 for i, d := range data { 231 exists, _ := DirExists(filepath.FromSlash(d.input), new(afero.OsFs)) 232 if d.expected != exists { 233 t.Errorf("Test %d failed. Expected %t got %t", i, d.expected, exists) 234 } 235 } 236 } 237 238 func TestIsDir(t *testing.T) { 239 type test struct { 240 input string 241 expected bool 242 } 243 data := []test{ 244 {"./", true}, 245 {"/", true}, 246 {"./this-directory-does-not-existi", false}, 247 {"/this-absolute-directory/does-not-exist", false}, 248 } 249 250 for i, d := range data { 251 252 exists, _ := IsDir(d.input, new(afero.OsFs)) 253 if d.expected != exists { 254 t.Errorf("Test %d failed. Expected %t got %t", i, d.expected, exists) 255 } 256 } 257 } 258 259 func createZeroSizedFileInTempDir() (*os.File, error) { 260 filePrefix := "_path_test_" 261 f, e := os.CreateTemp("", filePrefix) // dir is os.TempDir() 262 if e != nil { 263 // if there was an error no file was created. 264 // => no requirement to delete the file 265 return nil, e 266 } 267 return f, nil 268 } 269 270 func createNonZeroSizedFileInTempDir() (*os.File, error) { 271 f, err := createZeroSizedFileInTempDir() 272 if err != nil { 273 // no file ?? 274 return nil, err 275 } 276 byteString := []byte("byteString") 277 err = os.WriteFile(f.Name(), byteString, 0644) 278 if err != nil { 279 // delete the file 280 deleteFileInTempDir(f) 281 return nil, err 282 } 283 return f, nil 284 } 285 286 func deleteFileInTempDir(f *os.File) { 287 _ = os.Remove(f.Name()) 288 } 289 290 func TestExists(t *testing.T) { 291 zeroSizedFile, _ := createZeroSizedFileInTempDir() 292 defer deleteFileInTempDir(zeroSizedFile) 293 nonZeroSizedFile, _ := createNonZeroSizedFileInTempDir() 294 defer deleteFileInTempDir(nonZeroSizedFile) 295 emptyDirectory := t.TempDir() 296 nonExistentFile := os.TempDir() + "/this-file-does-not-exist.txt" 297 nonExistentDir := os.TempDir() + "/this/directory/does/not/exist/" 298 299 type test struct { 300 input string 301 expectedResult bool 302 expectedErr error 303 } 304 305 data := []test{ 306 {zeroSizedFile.Name(), true, nil}, 307 {nonZeroSizedFile.Name(), true, nil}, 308 {emptyDirectory, true, nil}, 309 {nonExistentFile, false, nil}, 310 {nonExistentDir, false, nil}, 311 } 312 for i, d := range data { 313 exists, err := Exists(d.input, new(afero.OsFs)) 314 if d.expectedResult != exists { 315 t.Errorf("Test %d failed. Expected result %t got %t", i, d.expectedResult, exists) 316 } 317 if d.expectedErr != err { 318 t.Errorf("Test %d failed. Expected %q got %q", i, d.expectedErr, err) 319 } 320 } 321 } 322 323 func TestAbsPathify(t *testing.T) { 324 type test struct { 325 inPath, workingDir, expected string 326 } 327 data := []test{ 328 {os.TempDir(), filepath.FromSlash("/work"), filepath.Clean(os.TempDir())}, // TempDir has trailing slash 329 {"dir", filepath.FromSlash("/work"), filepath.FromSlash("/work/dir")}, 330 } 331 332 windowsData := []test{ 333 {"c:\\banana\\..\\dir", "c:\\foo", "c:\\dir"}, 334 {"\\dir", "c:\\foo", "c:\\foo\\dir"}, 335 {"c:\\", "c:\\foo", "c:\\"}, 336 } 337 338 unixData := []test{ 339 {"/banana/../dir/", "/work", "/dir"}, 340 } 341 342 for i, d := range data { 343 // todo see comment in AbsPathify 344 ps := newTestDefaultPathSpec("workingDir", d.workingDir) 345 346 expected := ps.AbsPathify(d.inPath) 347 if d.expected != expected { 348 t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected) 349 } 350 } 351 t.Logf("Running platform specific path tests for %s", runtime.GOOS) 352 if runtime.GOOS == "windows" { 353 for i, d := range windowsData { 354 ps := newTestDefaultPathSpec("workingDir", d.workingDir) 355 356 expected := ps.AbsPathify(d.inPath) 357 if d.expected != expected { 358 t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected) 359 } 360 } 361 } else { 362 for i, d := range unixData { 363 ps := newTestDefaultPathSpec("workingDir", d.workingDir) 364 365 expected := ps.AbsPathify(d.inPath) 366 if d.expected != expected { 367 t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected) 368 } 369 } 370 } 371 } 372 373 func TestExtractAndGroupRootPaths(t *testing.T) { 374 in := []string{ 375 filepath.FromSlash("/a/b/c/d"), 376 filepath.FromSlash("/a/b/c/e"), 377 filepath.FromSlash("/a/b/e/f"), 378 filepath.FromSlash("/a/b"), 379 filepath.FromSlash("/a/b/c/b/g"), 380 filepath.FromSlash("/c/d/e"), 381 } 382 383 inCopy := make([]string, len(in)) 384 copy(inCopy, in) 385 386 result := ExtractAndGroupRootPaths(in) 387 388 c := qt.New(t) 389 c.Assert(fmt.Sprint(result), qt.Equals, filepath.FromSlash("[/a/b/{c,e} /c/d/e]")) 390 391 // Make sure the original is preserved 392 c.Assert(in, qt.DeepEquals, inCopy) 393 } 394 395 func TestExtractRootPaths(t *testing.T) { 396 tests := []struct { 397 input []string 398 expected []string 399 }{{ 400 []string{ 401 filepath.FromSlash("a/b"), filepath.FromSlash("a/b/c/"), "b", 402 filepath.FromSlash("/c/d"), filepath.FromSlash("d/"), filepath.FromSlash("//e//"), 403 }, 404 []string{"a", "a", "b", "c", "d", "e"}, 405 }} 406 407 for _, test := range tests { 408 output := ExtractRootPaths(test.input) 409 if !reflect.DeepEqual(output, test.expected) { 410 t.Errorf("Expected %#v, got %#v\n", test.expected, output) 411 } 412 } 413 } 414 415 func TestFindCWD(t *testing.T) { 416 type test struct { 417 expectedDir string 418 expectedErr error 419 } 420 421 // cwd, _ := os.Getwd() 422 data := []test{ 423 //{cwd, nil}, 424 // Commenting this out. It doesn't work properly. 425 // There's a good reason why we don't use os.Getwd(), it doesn't actually work the way we want it to. 426 // I really don't know a better way to test this function. - SPF 2014.11.04 427 } 428 for i, d := range data { 429 dir, err := FindCWD() 430 if d.expectedDir != dir { 431 t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedDir, dir) 432 } 433 if d.expectedErr != err { 434 t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, err) 435 } 436 } 437 } 438 439 func TestSafeWriteToDisk(t *testing.T) { 440 emptyFile, _ := createZeroSizedFileInTempDir() 441 defer deleteFileInTempDir(emptyFile) 442 tmpDir := t.TempDir() 443 444 randomString := "This is a random string!" 445 reader := strings.NewReader(randomString) 446 447 fileExists := fmt.Errorf("%v already exists", emptyFile.Name()) 448 449 type test struct { 450 filename string 451 expectedErr error 452 } 453 454 now := time.Now().Unix() 455 nowStr := strconv.FormatInt(now, 10) 456 data := []test{ 457 {emptyFile.Name(), fileExists}, 458 {tmpDir + "/" + nowStr, nil}, 459 } 460 461 for i, d := range data { 462 e := SafeWriteToDisk(d.filename, reader, new(afero.OsFs)) 463 if d.expectedErr != nil { 464 if d.expectedErr.Error() != e.Error() { 465 t.Errorf("Test %d failed. Expected error %q but got %q", i, d.expectedErr.Error(), e.Error()) 466 } 467 } else { 468 if d.expectedErr != e { 469 t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, e) 470 } 471 contents, _ := os.ReadFile(d.filename) 472 if randomString != string(contents) { 473 t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents)) 474 } 475 } 476 reader.Seek(0, 0) 477 } 478 } 479 480 func TestWriteToDisk(t *testing.T) { 481 emptyFile, _ := createZeroSizedFileInTempDir() 482 defer deleteFileInTempDir(emptyFile) 483 tmpDir := t.TempDir() 484 485 randomString := "This is a random string!" 486 reader := strings.NewReader(randomString) 487 488 type test struct { 489 filename string 490 expectedErr error 491 } 492 493 now := time.Now().Unix() 494 nowStr := strconv.FormatInt(now, 10) 495 data := []test{ 496 {emptyFile.Name(), nil}, 497 {tmpDir + "/" + nowStr, nil}, 498 } 499 500 for i, d := range data { 501 e := WriteToDisk(d.filename, reader, new(afero.OsFs)) 502 if d.expectedErr != e { 503 t.Errorf("Test %d failed. WriteToDisk Error Expected %q but got %q", i, d.expectedErr, e) 504 } 505 contents, e := os.ReadFile(d.filename) 506 if e != nil { 507 t.Errorf("Test %d failed. Could not read file %s. Reason: %s\n", i, d.filename, e) 508 } 509 if randomString != string(contents) { 510 t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents)) 511 } 512 reader.Seek(0, 0) 513 } 514 } 515 516 func TestGetTempDir(t *testing.T) { 517 dir := os.TempDir() 518 if FilePathSeparator != dir[len(dir)-1:] { 519 dir = dir + FilePathSeparator 520 } 521 testDir := "hugoTestFolder" + FilePathSeparator 522 tests := []struct { 523 input string 524 expected string 525 }{ 526 {"", dir}, 527 {testDir + " Foo bar ", dir + testDir + " Foo bar " + FilePathSeparator}, 528 {testDir + "Foo.Bar/foo_Bar-Foo", dir + testDir + "Foo.Bar/foo_Bar-Foo" + FilePathSeparator}, 529 {testDir + "fOO,bar:foo%bAR", dir + testDir + "fOObarfoo%bAR" + FilePathSeparator}, 530 {testDir + "fOO,bar:foobAR", dir + testDir + "fOObarfoobAR" + FilePathSeparator}, 531 {testDir + "FOo/BaR.html", dir + testDir + "FOo/BaR.html" + FilePathSeparator}, 532 {testDir + "трям/трям", dir + testDir + "трям/трям" + FilePathSeparator}, 533 {testDir + "은행", dir + testDir + "은행" + FilePathSeparator}, 534 {testDir + "Банковский кассир", dir + testDir + "Банковский кассир" + FilePathSeparator}, 535 } 536 537 for _, test := range tests { 538 output := GetTempDir(test.input, new(afero.MemMapFs)) 539 if output != test.expected { 540 t.Errorf("Expected %#v, got %#v\n", test.expected, output) 541 } 542 } 543 }