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