github.com/wsand02/massren@v1.5.5-0.20191104203215-f721006d1e4e/main_test.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 "time" 12 ) 13 14 func setup(t *testing.T) { 15 minLogLevel_ = 10 16 17 pwd, err := os.Getwd() 18 if err != nil { 19 t.Fatal(err) 20 } 21 homeDir_ = filepath.Join(pwd, "homedirtest") 22 err = os.MkdirAll(homeDir_, 0700) 23 if err != nil { 24 t.Fatal(err) 25 } 26 27 deleteTempFiles() 28 profileOpen() 29 clearHistory() 30 } 31 32 func teardown(t *testing.T) { 33 profileDelete() 34 } 35 36 func touch(filePath string) { 37 ioutil.WriteFile(filePath, []byte("testing"), 0700) 38 } 39 40 func fileExists(filePath string) bool { 41 _, err := os.Stat(filePath) 42 return err == nil 43 } 44 45 func createRandomTempFiles() []string { 46 var output []string 47 for i := 0; i < 10; i++ { 48 filePath := filepath.Join(tempFolder(), fmt.Sprintf("testfile%d", i)) 49 ioutil.WriteFile(filePath, []byte("testing"), 0700) 50 output = append(output, filePath) 51 } 52 return output 53 } 54 55 func fileGetContent(path string) string { 56 o, err := ioutil.ReadFile(path) 57 if err != nil { 58 return "" 59 } 60 return string(o) 61 } 62 63 func filePutContent(path string, content string) { 64 err := ioutil.WriteFile(path, []byte(content), 0700) 65 if err != nil { 66 panic(err) 67 } 68 } 69 70 func Test_fileActions(t *testing.T) { 71 var err error 72 73 type TestCase struct { 74 paths []string 75 content string 76 result []*FileAction 77 } 78 79 var testCases []TestCase 80 81 testCases = append(testCases, TestCase{ 82 paths: []string{ 83 "abcd", 84 "efgh", 85 "ijkl", 86 }, 87 content: ` 88 // some header 89 // some header 90 // some header 91 92 abcd 93 newname 94 // should skip this 95 ijkl 96 // ignore 97 `, 98 result: []*FileAction{ 99 &FileAction{ 100 kind: KIND_RENAME, 101 oldPath: "efgh", 102 newPath: "newname", 103 }, 104 }, 105 }) 106 107 testCases = append(testCases, TestCase{ 108 paths: []string{ 109 "abcd", 110 "efgh", 111 "ijkl", 112 }, 113 content: ` 114 // some header 115 // some header 116 // some header 117 118 //abcd 119 120 efgh 121 ijklmnop 122 `, 123 result: []*FileAction{ 124 &FileAction{ 125 kind: KIND_DELETE, 126 oldPath: "abcd", 127 newPath: "", 128 }, 129 &FileAction{ 130 kind: KIND_RENAME, 131 oldPath: "ijkl", 132 newPath: "ijklmnop", 133 }, 134 }, 135 }) 136 137 testCases = append(testCases, TestCase{ 138 paths: []string{ 139 "abcd", 140 "efgh", 141 "ijkl", 142 }, 143 content: ` 144 // some header 145 // some header 146 abcd 147 efgh 148 ijkl 149 `, 150 result: []*FileAction{}, 151 }) 152 153 testCases = append(testCases, TestCase{ 154 paths: []string{ 155 " abcd", 156 "\t efgh\t\t ", 157 }, 158 content: ` 159 abcd 160 efgh 161 `, 162 result: []*FileAction{}, 163 }) 164 165 testCases = append(testCases, TestCase{ 166 paths: []string{ 167 "abcd", 168 " efgh", 169 " ijkl\t ", 170 }, 171 content: ` 172 // abcd 173 //efgh 174 // ijkl 175 `, 176 result: []*FileAction{ 177 &FileAction{ 178 kind: KIND_DELETE, 179 oldPath: "abcd", 180 newPath: "", 181 }, 182 &FileAction{ 183 kind: KIND_DELETE, 184 oldPath: " efgh", 185 newPath: "", 186 }, 187 &FileAction{ 188 kind: KIND_DELETE, 189 oldPath: " ijkl\t ", 190 newPath: "", 191 }, 192 }, 193 }) 194 195 testCases = append(testCases, TestCase{ 196 paths: []string{ 197 " abcd", 198 "\t efgh\t\t ", 199 }, 200 content: ` 201 abcd 202 efgh 203 `, 204 result: []*FileAction{}, 205 }) 206 207 testCases = append(testCases, TestCase{ 208 paths: []string{ 209 "123", 210 "456", 211 }, 212 content: ` 213 1/23 214 4/56 215 `, 216 result: []*FileAction{ 217 &FileAction{ 218 kind: KIND_RENAME, 219 oldPath: "123", 220 newPath: "1/23", 221 }, 222 &FileAction{ 223 kind: KIND_RENAME, 224 oldPath: "456", 225 newPath: "4/56", 226 }, 227 }, 228 }) 229 230 // Force \n as newline to simplify testing 231 // across platforms. 232 newline_ = "\n" 233 234 for _, testCase := range testCases { 235 // Note: Run tests with -v in case of error 236 237 r, _ := fileActions(testCase.paths, testCase.content) 238 if len(testCase.result) != len(r) { 239 t.Errorf("Expected %d, got %d", len(testCase.result), len(r)) 240 t.Log(testCase.result) 241 t.Log(r) 242 continue 243 } 244 for i, r1 := range r { 245 r2 := testCase.result[i] 246 if r1.kind != r2.kind { 247 t.Errorf("Expected kind %d, got %d", r2.kind, r1.kind) 248 } 249 if r1.oldPath != r2.oldPath { 250 t.Errorf("Expected path %s, got %s", r2.oldPath, r1.oldPath) 251 } 252 if r1.newPath != r2.newPath { 253 t.Errorf("Expected path %s, got %s", r2.newPath, r1.newPath) 254 } 255 } 256 } 257 258 _, err = fileActions([]string{"abcd", "efgh"}, "") 259 if err == nil { 260 t.Error("Expected error, got nil") 261 } 262 263 _, err = fileActions([]string{"abcd", "efgh"}, "abcd") 264 if err == nil { 265 t.Error("Expected error, got nil") 266 } 267 } 268 269 func Test_parseEditorCommand(t *testing.T) { 270 type TestCase struct { 271 editorCmd string 272 executable string 273 args []string 274 hasError bool 275 } 276 277 var testCases []TestCase 278 279 testCases = append(testCases, TestCase{ 280 editorCmd: "subl", 281 executable: "subl", 282 args: []string{}, 283 hasError: false, 284 }) 285 286 testCases = append(testCases, TestCase{ 287 editorCmd: "/usr/bin/vim -f", 288 executable: "/usr/bin/vim", 289 args: []string{"-f"}, 290 hasError: false, 291 }) 292 293 testCases = append(testCases, TestCase{ 294 editorCmd: "\"F:\\Sublime Text 3\\sublime_text.exe\" /n /w", 295 executable: "F:\\Sublime Text 3\\sublime_text.exe", 296 args: []string{"/n", "/w"}, 297 hasError: false, 298 }) 299 300 testCases = append(testCases, TestCase{ 301 editorCmd: "subl -w --command \"something with spaces\"", 302 executable: "subl", 303 args: []string{"-w", "--command", "something with spaces"}, 304 hasError: false, 305 }) 306 307 testCases = append(testCases, TestCase{ 308 editorCmd: "notepad.exe /PT", 309 executable: "notepad.exe", 310 args: []string{"/PT"}, 311 hasError: false, 312 }) 313 314 testCases = append(testCases, TestCase{ 315 editorCmd: "vim -e \"unclosed quote", 316 executable: "", 317 args: []string{}, 318 hasError: true, 319 }) 320 321 testCases = append(testCases, TestCase{ 322 editorCmd: "subl -e 'unclosed single-quote", 323 executable: "", 324 args: []string{}, 325 hasError: true, 326 }) 327 328 testCases = append(testCases, TestCase{ 329 editorCmd: "", 330 executable: "", 331 args: []string{}, 332 hasError: true, 333 }) 334 335 for _, testCase := range testCases { 336 executable, args, err := parseEditorCommand(testCase.editorCmd) 337 if (err != nil && !testCase.hasError) || (err == nil && testCase.hasError) { 338 t.Errorf("Error status did not match: %t: %s", testCase.hasError, err) 339 } 340 if executable != testCase.executable { 341 t.Errorf("Expected '%s', got '%s'", testCase.executable, executable) 342 } 343 if len(args) != len(testCase.args) { 344 t.Errorf("Expected and result args don't have the same length: [%s], [%s]", strings.Join(testCase.args, ", "), strings.Join(args, ", ")) 345 } 346 for i, arg := range testCase.args { 347 if arg != args[i] { 348 t.Errorf("Expected and result args differ: [%s], [%s]", strings.Join(testCase.args, ", "), strings.Join(args, ", ")) 349 } 350 } 351 } 352 } 353 354 func Test_processFileActions(t *testing.T) { 355 setup(t) 356 defer teardown(t) 357 358 touch(filepath.Join(tempFolder(), "one")) 359 touch(filepath.Join(tempFolder(), "two")) 360 touch(filepath.Join(tempFolder(), "three")) 361 362 fileActions := []*FileAction{} 363 364 fileAction := NewFileAction() 365 fileAction.oldPath = filepath.Join(tempFolder(), "one") 366 fileAction.newPath = "one123" 367 fileActions = append(fileActions, fileAction) 368 369 fileAction = NewFileAction() 370 fileAction.oldPath = filepath.Join(tempFolder(), "two") 371 fileAction.newPath = "two456" 372 fileActions = append(fileActions, fileAction) 373 374 processFileActions(fileActions, false) 375 376 if !fileExists(filepath.Join(tempFolder(), "one123")) { 377 t.Error("File not found") 378 } 379 380 if !fileExists(filepath.Join(tempFolder(), "two456")) { 381 t.Error("File not found") 382 } 383 384 if !fileExists(filepath.Join(tempFolder(), "three")) { 385 t.Error("File not found") 386 } 387 388 fileActions = []*FileAction{} 389 390 fileAction = NewFileAction() 391 fileAction.oldPath = filepath.Join(tempFolder(), "two456") 392 fileAction.kind = KIND_DELETE 393 fileActions = append(fileActions, fileAction) 394 395 processFileActions(fileActions, true) 396 397 if !fileExists(filepath.Join(tempFolder(), "two456")) { 398 t.Error("File should not have been deleted") 399 } 400 401 processFileActions(fileActions, false) 402 403 if fileExists(filepath.Join(tempFolder(), "two456")) { 404 t.Error("File should have been deleted") 405 } 406 407 fileActions = []*FileAction{} 408 409 fileAction = NewFileAction() 410 fileAction.oldPath = filepath.Join(tempFolder(), "three") 411 fileAction.newPath = "nochange" 412 fileActions = append(fileActions, fileAction) 413 414 processFileActions(fileActions, true) 415 416 if !fileExists(filepath.Join(tempFolder(), "three")) { 417 t.Error("File was renamed in dry-run mode") 418 } 419 420 fileActions = []*FileAction{} 421 422 touch(filepath.Join(tempFolder(), "abcd")) 423 424 fileAction = NewFileAction() 425 fileAction.oldPath = filepath.Join(tempFolder(), "abcd") 426 fileAction.newPath = "ab/cd" 427 fileActions = append(fileActions, fileAction) 428 429 processFileActions(fileActions, false) 430 431 if !fileExists(filepath.Join(tempFolder(), "ab", "cd")) { 432 t.Error("File was not renamed or not moved to subfolder") 433 } 434 } 435 436 func Test_processFileActions_noDestinationOverwrite(t *testing.T) { 437 setup(t) 438 defer teardown(t) 439 440 // Case where a sequence of files such as 0.jpg, 1.jpg is being 441 // renamed to 1.jpg, 2.jpg 442 443 p0 := filepath.Join(tempFolder(), "0") 444 p1 := filepath.Join(tempFolder(), "1") 445 p2 := filepath.Join(tempFolder(), "2") 446 447 filePutContent(p0, "0") 448 filePutContent(p1, "1") 449 450 fileActions := []*FileAction{} 451 452 fileAction := NewFileAction() 453 fileAction.oldPath = p0 454 fileAction.newPath = "1" 455 fileActions = append(fileActions, fileAction) 456 457 fileAction = NewFileAction() 458 fileAction.oldPath = p1 459 fileAction.newPath = "2" 460 fileActions = append(fileActions, fileAction) 461 462 err := processFileActions(fileActions, false) 463 464 if err != nil { 465 t.Errorf("Expected no error, but got an error.") 466 } 467 468 if fileExists(p0) { 469 t.Error("File 0 should have been renamed") 470 } 471 472 if !fileExists(p1) { 473 t.Error("File 1 should exist") 474 } 475 476 if !fileExists(p2) { 477 t.Error("File 2 should exist") 478 } 479 480 if fileGetContent(p1) != "0" { 481 t.Error("File 1 has wrong content") 482 } 483 484 if fileGetContent(p2) != "1" { 485 t.Error("File 2 has wrong content") 486 } 487 } 488 489 func Test_processFileActions_noDestinationOverwrite2(t *testing.T) { 490 setup(t) 491 defer teardown(t) 492 493 // Case where a file is renamed to the name of 494 // another existing file. 495 496 touch(filepath.Join(tempFolder(), "0")) 497 touch(filepath.Join(tempFolder(), "1")) 498 499 originalFilePaths := []string{ 500 filepath.Join(tempFolder(), "0"), 501 } 502 503 changes := ` 504 1 505 ` 506 _, err := fileActions(originalFilePaths, changes) 507 508 if err == nil { 509 t.Error("Expected an error, but got nil.") 510 } 511 } 512 513 func Test_processFileActions_swapFilenames(t *testing.T) { 514 setup(t) 515 defer teardown(t) 516 517 p0 := filepath.Join(tempFolder(), "0") 518 p1 := filepath.Join(tempFolder(), "1") 519 520 filePutContent(p0, "0") 521 filePutContent(p1, "1") 522 523 originalFilePaths := []string{ 524 p0, 525 p1, 526 } 527 528 changes := ` 529 1 530 0 531 ` 532 actions, _ := fileActions(originalFilePaths, changes) 533 err := processFileActions(actions, false) 534 535 if err != nil { 536 t.Error("Expected no error, but got one.") 537 } 538 539 if fileGetContent(p0) != "1" { 540 t.Error("File 1 has not been renamed correctly") 541 } 542 543 if fileGetContent(p1) != "0" { 544 t.Error("File 0 has not been renamed correctly") 545 } 546 } 547 548 func Test_processFileActions_renameToSameName(t *testing.T) { 549 setup(t) 550 defer teardown(t) 551 552 touch(filepath.Join(tempFolder(), "0")) 553 touch(filepath.Join(tempFolder(), "1")) 554 555 originalFilePaths := []string{ 556 filepath.Join(tempFolder(), "0"), 557 filepath.Join(tempFolder(), "1"), 558 } 559 560 changes := ` 561 9 562 9 563 ` 564 _, err := fileActions(originalFilePaths, changes) 565 566 if err == nil { 567 t.Error("Expected an error, but got nil.") 568 } 569 } 570 571 func Test_processFileActions_dontDeleteAfterRename(t *testing.T) { 572 setup(t) 573 defer teardown(t) 574 575 p0 := filepath.Join(tempFolder(), "0") 576 p1 := filepath.Join(tempFolder(), "1") 577 filePutContent(p0, "0") 578 filePutContent(p1, "1") 579 580 originalFilePaths := []string{ 581 p0, 582 p1, 583 } 584 585 changes := ` 586 1 587 //1 588 ` 589 actions, _ := fileActions(originalFilePaths, changes) 590 err := processFileActions(actions, false) 591 592 if err != nil { 593 t.Error("Expected no error, but got one.") 594 } 595 596 if fileExists(p0) { 597 t.Error("File 0 should have been renamed but still exists.") 598 } 599 600 if !fileExists(p1) { 601 t.Error("File 1 should exist.") 602 } 603 604 if fileGetContent(p1) != "0" { 605 t.Errorf("File 0 has not been renamed correctly - content should be \"%s\", but is \"%s\"", "0", fileGetContent(p1)) 606 } 607 } 608 609 func Test_stringHash(t *testing.T) { 610 if len(stringHash("aaaa")) != 32 { 611 t.Error("hash should be 32 characters long") 612 } 613 614 if stringHash("abcd") == stringHash("efgh") || stringHash("") == stringHash("ijkl") { 615 t.Error("hashes should be different") 616 } 617 } 618 619 func Test_watchFile(t *testing.T) { 620 setup(t) 621 defer teardown(t) 622 623 filePath := tempFolder() + "watchtest" 624 ioutil.WriteFile(filePath, []byte("testing"), 0700) 625 doneChan := make(chan bool) 626 627 go func(doneChan chan bool) { 628 defer func() { 629 doneChan <- true 630 }() 631 err := watchFile(filePath) 632 if err != nil { 633 t.Error(err) 634 } 635 }(doneChan) 636 637 time.Sleep(300 * time.Millisecond) 638 ioutil.WriteFile(filePath, []byte("testing change"), 0700) 639 640 <-doneChan 641 } 642 643 func fileListsAreEqual(files1 []string, files2 []string) error { 644 if len(files1) != len(files2) { 645 return errors.New("file count is different") 646 } 647 648 for _, f1 := range files1 { 649 found := false 650 for _, f2 := range files2 { 651 if filepath.Base(f1) == filepath.Base(f2) { 652 found = true 653 } 654 } 655 if !found { 656 return errors.New("file names do not match") 657 } 658 } 659 660 return nil 661 } 662 663 func Test_filePathsFromArgs(t *testing.T) { 664 setup(t) 665 defer teardown(t) 666 667 tempFiles := createRandomTempFiles() 668 args := []string{ 669 filepath.Join(tempFolder(), "*"), 670 } 671 672 filePaths, err := filePathsFromArgs(args, true) 673 if err != nil { 674 t.Fatal(err) 675 } 676 677 err = fileListsAreEqual(filePaths, tempFiles) 678 if err != nil { 679 t.Error(err) 680 } 681 682 // If no argument is provided, the function should default to "*" 683 // in the current dir. 684 685 currentDir, err := os.Getwd() 686 if err != nil { 687 panic(err) 688 } 689 690 err = os.Chdir(tempFolder()) 691 if err != nil { 692 panic(err) 693 } 694 695 args = []string{} 696 filePaths, err = filePathsFromArgs(args, true) 697 if err != nil { 698 t.Fatal(err) 699 } 700 701 err = fileListsAreEqual(filePaths, tempFiles) 702 if err != nil { 703 t.Error(err) 704 } 705 706 os.Chdir(currentDir) 707 } 708 709 func Test_filePathsFromArgs_noDirectories(t *testing.T) { 710 setup(t) 711 defer teardown(t) 712 713 f0 := filepath.Join(tempFolder(), "0") 714 f1 := filepath.Join(tempFolder(), "1") 715 d0 := filepath.Join(tempFolder(), "dir0") 716 d1 := filepath.Join(tempFolder(), "dir1") 717 718 touch(f0) 719 touch(f1) 720 os.Mkdir(d0, 0700) 721 os.Mkdir(d1, 0700) 722 723 args := []string{ 724 filepath.Join(tempFolder(), "*"), 725 } 726 727 filePaths, _ := filePathsFromArgs(args, true) 728 err := fileListsAreEqual(filePaths, []string{f0, f1, d0, d1}) 729 if err != nil { 730 t.Error(err) 731 } 732 733 filePaths, _ = filePathsFromArgs(args, false) 734 err = fileListsAreEqual(filePaths, []string{f0, f1}) 735 if err != nil { 736 t.Error(err) 737 } 738 } 739 740 func stringListsEqual(s1 []string, s2 []string) bool { 741 for i, s := range s1 { 742 if s != s2[i] { 743 return false 744 } 745 } 746 return true 747 } 748 749 func Test_filePathsFromString(t *testing.T) { 750 newline_ = "\n" 751 752 var data []string 753 var expected [][]string 754 755 data = append(data, "// comment\n\nfile1\nfile2\n//comment\n\n\n") 756 expected = append(expected, []string{"file1", "file2"}) 757 758 data = append(data, "\n// comment\n\n") 759 expected = append(expected, []string{}) 760 761 data = append(data, "") 762 expected = append(expected, []string{}) 763 764 data = append(data, "// comment\n\n file1 \n\tfile2\n\nanother file\t\n//comment\n\n\n") 765 expected = append(expected, []string{" file1 ", "\tfile2", "another file\t"}) 766 767 for i, d := range data { 768 e := expected[i] 769 r := filePathsFromString(d) 770 if !stringListsEqual(e, r) { 771 t.Error("Expected", e, "got", r) 772 } 773 } 774 } 775 776 func Test_filePathsFromListFile(t *testing.T) { 777 setup(t) 778 defer teardown(t) 779 780 ioutil.WriteFile(filepath.Join(tempFolder(), "list.txt"), []byte("one"+newline()+"two"), PROFILE_PERM) 781 filePaths, err := filePathsFromListFile(filepath.Join(tempFolder(), "list.txt")) 782 if err != nil { 783 t.Errorf("Expected no error, got %s", err) 784 } 785 786 if len(filePaths) != 2 { 787 t.Errorf("Expected 2 paths, got %d", len(filePaths)) 788 } else { 789 if filePaths[0] != "one" || filePaths[1] != "two" { 790 t.Error("Incorrect data") 791 } 792 } 793 794 os.Remove(filepath.Join(tempFolder(), "list.txt")) 795 _, err = filePathsFromListFile(filepath.Join(tempFolder(), "list.txt")) 796 if err == nil { 797 t.Error("Expected an error, got nil") 798 } 799 } 800 801 func Test_stripBom(t *testing.T) { 802 data := [][][]byte{ 803 {{239, 187, 191}, {}}, 804 {{239, 187, 191, 239, 187, 191}, {239, 187, 191}}, 805 {{239, 187, 191, 65, 66}, {65, 66}}, 806 {{239, 191, 65, 66}, {239, 191, 65, 66}}, 807 {{}, {}}, 808 {{65, 239, 187, 191}, {65, 239, 187, 191}}, 809 } 810 811 for _, d := range data { 812 if stripBom(string(d[0])) != string(d[1]) { 813 t.Errorf("Expected %x, got %x", d[0], d[1]) 814 } 815 } 816 } 817 818 func Test_deleteTempFiles(t *testing.T) { 819 setup(t) 820 defer teardown(t) 821 822 ioutil.WriteFile(filepath.Join(tempFolder(), "one"), []byte("test1"), PROFILE_PERM) 823 ioutil.WriteFile(filepath.Join(tempFolder(), "two"), []byte("test2"), PROFILE_PERM) 824 825 deleteTempFiles() 826 827 tempFiles, _ := filepath.Glob(filepath.Join(tempFolder(), "*")) 828 if len(tempFiles) > 0 { 829 t.Fail() 830 } 831 } 832 833 func Test_newline(t *testing.T) { 834 newline_ = "" 835 nl := newline() 836 if len(nl) < 1 || len(nl) > 2 { 837 t.Fail() 838 } 839 } 840 841 func Test_guessEditorCommand(t *testing.T) { 842 editor, err := guessEditorCommand() 843 if err != nil || len(editor) <= 0 { 844 t.Fail() 845 } 846 } 847 848 func Test_bufferHeader(t *testing.T) { 849 setup(t) 850 defer teardown(t) 851 852 f0 := filepath.Join(tempFolder(), "0") 853 f1 := filepath.Join(tempFolder(), "1") 854 855 touch(f0) 856 touch(f1) 857 858 newline_ = "\n" 859 860 content := createListFileContent([]string{f0, f1}, true) 861 if strings.Index(content, "//") != 0 { 862 t.Fatal("cannot find header") 863 } 864 865 content = createListFileContent([]string{f0, f1}, false) 866 if content != "0\n1\n" { 867 t.Fatal("file content is incorrect: " + content) 868 } 869 }