1#!/usr/bin/perl 2use Test::More; 3#use Data::Dumper; 4use SVN::Notify; 5use Cwd; 6use Config; 7my $SECURE_PERL_PATH = $Config{perlpath}; 8if ($^O ne 'VMS') { 9 $SECURE_PERL_PATH.= $Config{_exe} 10 unless $SECURE_PERL_PATH =~ m/$Config{_exe}$/i; 11} 12my $PWD = getcwd; 13my $USER = $ENV{USER}; 14my $SVNLOOK = $ENV{SVNLOOK} || SVN::Notify->find_exe('svnlook'); 15my $SVNADMIN = $ENV{SVNADMIN} || SVN::Notify->find_exe('svnadmin'); 16 17if ( !defined($SVNLOOK) ) { 18 plan skip_all => "Cannot find svnlook!\n". 19 "Please start the tests like this:\n". 20 " SVNLOOK=/path/to/svnlook make test"; 21} 22elsif ( !defined($SVNADMIN) ) { 23 plan skip_all => "Cannot find svnadmin!\n". 24 "Please start the tests like this:\n". 25 " SVNADMIN=/path/to/svnadmin make test"; 26} 27else { 28 plan no_plan; 29} 30 31my $repos_path = "$PWD/t/test-repos"; 32 33my $wc_map = { 34 'wc-trunk' => 35 { 36 path => 'project1/trunk', 37 base_rev => 1, 38 command => 'update', 39 }, 40 'wc-tag' => 41 { 42 path => 'project1/tags/TRUNK-1135534439', 43 base_rev => 3, 44 command => 'switch', 45 switch_rev => 7, 46 switch_path => 'project1/tags/TRUNK-1135538253', 47 tag_regex => 'TRUNK-', 48 }, 49 'wc-branch' => 50 { 51 path => 'project1/branches/branch1', 52 base_rev => 4, 53 command => 'update', 54 }, 55}; 56 57my $wc_rsync_map; 58foreach my $key ( keys %$wc_map ) { 59 $wc_rsync_map->{$key.'-r'} = $wc_map->{$key}; 60} 61 62my ($repos_history, $changes); 63my $eval; 64while (<DATA>) { 65 $eval .= $_; 66} 67eval $eval; 68 69my $maxrev = 7; # change this later to be the actual number of revs 70 71sub reset_all_tests { 72 create_test_repos(); 73 create_test_wcs(); 74 reset_test_wcs(); 75} 76 77# Create a repository fill it with sample values the first time through 78sub create_test_repos { 79 unless ( -d $repos_path ) { 80 system(<<"") == 0 or die "system failed: $?"; 81$SVNADMIN create $repos_path 82 83 system(<<"") == 0 or die "system failed: $?"; 84$SVNADMIN load --quiet $repos_path < ${repos_path}.dump 85 86 } 87} 88 89# Create test WC's before proceeding with tests the first time 90sub create_test_wcs { 91 unless ( -d "$PWD/t/wc-trunk" ) { 92 foreach my $wc ( keys %{$wc_map} ) { 93 my $path = $wc_map->{$wc}->{'path'}; 94 my $rev = $wc_map->{$wc}->{'base_rev'}; 95 96 my $cmd = "svn checkout -q -r$rev ". 97 "file://$repos_path/$path $PWD/t/$wc"; 98 system($cmd) == 0 or die "system failed: $?"; 99 } 100 } 101} 102 103# Reset the working copies 104sub reset_test_wcs { 105 foreach my $wc ( keys %{$wc_map} ) { 106 my $path = $wc_map->{$wc}->{'path'}; 107 my $rev = $wc_map->{$wc}->{'base_rev'}; 108 my $command = $wc_map->{$wc}->{'command'}; 109 110 my $cmd = "svn $command -q -r$rev "; 111 $cmd .= "file://$repos_path/$path " 112 if ( $command =~ /switch/ ); # accomodate older svn's 113 $cmd .= "$PWD/t/$wc"; 114 system($cmd) == 0 or die "system failed: $?"; 115 } 116} 117 118sub run_tests { 119 my $command = shift; 120 my $TESTER; 121 my $rsync_test = 0; 122 123 for (my $rev = 1; $rev <= $maxrev; $rev++) { 124 foreach my $wc ( keys %{$wc_map} ) { 125 next unless $rev >= $wc_map->{$wc}->{'base_rev'}; 126 my %args = @_; 127 # Common to all tests 128 $args{'repos-path'} = $repos_path; 129 $args{'handler'} = defined $args{'ssh-host'} 130 ? 'Mirror::SSH' 131 : defined $args{'rsync-host'} 132 ? 'Mirror::Rsync' 133 : 'Mirror'; 134 135 136 $args{'to'} = "$PWD/t/$wc"; 137 $args{'revision'} = $rev; 138 139 my $path = $wc_map->{$wc}->{'path'}; 140 my $change = $changes->[$rev]->{$path} 141 if exists $changes->[$rev]->{$path}; 142 next unless $change; 143 144 # special case the switched directories 145 if ( $wc_map->{$wc}->{'command'} eq 'switch' 146 && $rev >= $wc_map->{$wc}->{'switch_rev'} ) { 147 $path = $wc_map->{$wc}->{'switch_path'}; 148 $args{'tag-regex'} = $wc_map->{$wc}->{'tag_regex'}; 149 } 150 151 # need to specify destination for rsync tests 152 if ( defined $args{'rsync-host'} ) { 153 $rsync_test = 1; 154 $args{'rsync-dest'} = "$PWD/t/$wc\-r"; 155 } 156 157 _test( 158 $change, 159 $path, 160 $command, 161 %args 162 ); 163 } 164 _compare_directories($rev, $wc_map); 165 166 if ($rsync_test) { 167 _compare_directories($rev, $wc_rsync_map); 168 $rsync_test = 0; 169 } 170 } 171 172} 173 174sub _test { 175 my ($expected, $prefix, $command, %args) = @_; 176 my $test = {}; 177 178 open $TESTER, '-|', _build_command($command, %args); 179 while (<$TESTER>) { 180 chomp; 181 next if ( /^Updating '.+':/ ); 182 if ( /^At revision (\d+)\./ ) { 183 ok ( $1 == $args{revision} , "No changes in $prefix at revision: " 184 . $args{revision} ); 185 last; # no need to read any more input 186 } 187 elsif ( /^Updated to revision (\d+)\./ ) { 188 ok ( $1 == $args{revision} , "Updated $prefix to correct revision: " 189 . $args{revision} ); 190 } 191 else { 192 my ($status, $target) = split; 193 $test->{$prefix.'/'.$target} = $status; 194 } 195 } 196 close $TESTER; 197 is_deeply( 198 $test, 199 $expected, 200 "Correct files updated in $prefix at rev: " . $args{revision} 201 ) if scalar(keys %$test) > 0; 202} 203 204sub _build_command { 205 my ($command, %args) = @_; 206 my @commandline = split " ", $command; 207 208 if ( $command =~ /svnnotify/ ) { 209 # hate to hardcode this, but what else can we do 210 foreach my $key ( keys(%args) ) { 211 push @commandline, "\-\-$key", $args{$key}; 212 } 213 } 214 else { 215 push @commandline, $args{'repos-path'}, $args{'revision'}; 216 } 217 return @commandline; 218} 219 220sub _compare_directories { 221 my ($rev, $wc_hash) = @_; 222 my $history = $repos_history->[$rev]; 223 my $this_rev = {}; 224 225 foreach my $wc ( keys %$wc_hash ) { 226 next unless $rev >= $wc_hash->{$wc}->{'base_rev'}; 227 my $subhistory = _expand_path($history, $wc_hash->{$wc}->{'path'}); 228 $this_rev = _scan_dir("t/$wc"); 229 is_deeply( 230 $subhistory, 231 $this_rev, 232 "Directories are consistent at rev: $rev" 233 ); 234 } 235} 236 237sub _expand_path { 238 my ($tree, $path) = @_; 239 my @paths = split('/',$path); 240 my $eval = "\$tree->{'".join("'}->{'", @paths)."'}"; 241 return eval $eval; 242} 243 244sub _scan_dir { 245 my ($dir) = @_; 246 my $fsize; 247 my $this_rev = {}; 248 249 opendir my($DIR), $dir; 250 my @directory = grep !/^\..*/, readdir $DIR; 251 closedir $DIR; 252 253 foreach my $file ( @directory ) { 254 if ( -d "$dir/$file" ) { 255 $this_rev->{$file} = _scan_dir( "$dir/$file" ); 256 } 257 elsif ( ( -f "$dir/$file" ) && ( my $size = -s "$dir/$file" ) ) { 258 $this_rev->{$file} = $size; 259 } 260 } 261 return defined $this_rev ? $this_rev : {}; 262} 263 2641; # magic return 265__DATA__ 266$repos_history = [ 267 {}, 268 { 269 'project2' => { 270 'trunk' => {}, 271 'branches' => {}, 272 'tags' => {} 273 }, 274 'project1' => { 275 'trunk' => {}, 276 'branches' => {}, 277 'tags' => {} 278 } 279 }, 280 { 281 'project2' => { 282 'trunk' => {}, 283 'branches' => {}, 284 'tags' => {} 285 }, 286 'project1' => { 287 'trunk' => { 288 'file1' => '6', 289 'dir1' => { 290 'file2' => '6' 291 } 292 }, 293 'branches' => {}, 294 'tags' => {} 295 } 296 }, 297 { 298 'project2' => { 299 'trunk' => {}, 300 'branches' => {}, 301 'tags' => {} 302 }, 303 'project1' => { 304 'trunk' => { 305 'file1' => '6', 306 'dir1' => { 307 'file2' => '6' 308 } 309 }, 310 'branches' => {}, 311 'tags' => { 312 'TRUNK-1135534439' => { 313 'file1' => '6', 314 'dir1' => { 315 'file2' => '6' 316 } 317 } 318 } 319 } 320 }, 321 { 322 'project2' => { 323 'trunk' => {}, 324 'branches' => {}, 325 'tags' => {} 326 }, 327 'project1' => { 328 'trunk' => { 329 'file1' => '6', 330 'dir1' => { 331 'file2' => '6' 332 } 333 }, 334 'branches' => { 335 'branch1' => { 336 'file1' => '6', 337 'dir1' => { 338 'file2' => '6' 339 } 340 } 341 }, 342 'tags' => { 343 'TRUNK-1135534439' => { 344 'file1' => '6', 345 'dir1' => { 346 'file2' => '6' 347 } 348 } 349 } 350 } 351 }, 352 { 353 'project2' => { 354 'trunk' => {}, 355 'branches' => {}, 356 'tags' => {} 357 }, 358 'project1' => { 359 'trunk' => { 360 'file1' => '6', 361 'dir1' => { 362 'file2' => '6' 363 } 364 }, 365 'branches' => { 366 'branch1' => { 367 'file1' => '6', 368 'file3' => '6', 369 'dir2' => { 370 'file4' => '6' 371 }, 372 'dir1' => { 373 'file2' => '6' 374 } 375 } 376 }, 377 'tags' => { 378 'TRUNK-1135534439' => { 379 'file1' => '6', 380 'dir1' => { 381 'file2' => '6' 382 } 383 } 384 } 385 } 386 }, 387 { 388 'project2' => { 389 'trunk' => {}, 390 'branches' => {}, 391 'tags' => {} 392 }, 393 'project1' => { 394 'trunk' => { 395 'file1' => '6', 396 'file3' => '6', 397 'dir2' => { 398 'file4' => '6' 399 }, 400 'dir1' => { 401 'file2' => '6' 402 } 403 }, 404 'branches' => { 405 'branch1' => { 406 'file1' => '6', 407 'file3' => '6', 408 'dir2' => { 409 'file4' => '6' 410 }, 411 'dir1' => { 412 'file2' => '6' 413 } 414 } 415 }, 416 'tags' => { 417 'TRUNK-1135534439' => { 418 'file1' => '6', 419 'dir1' => { 420 'file2' => '6' 421 } 422 } 423 } 424 } 425 }, 426 { 427 'project2' => { 428 'trunk' => {}, 429 'branches' => {}, 430 'tags' => {} 431 }, 432 'project1' => { 433 'trunk' => { 434 'file1' => '6', 435 'file3' => '6', 436 'dir2' => { 437 'file4' => '6' 438 }, 439 'dir1' => { 440 'file2' => '6' 441 } 442 }, 443 'branches' => { 444 'branch1' => { 445 'file1' => '6', 446 'file3' => '6', 447 'dir2' => { 448 'file4' => '6' 449 }, 450 'dir1' => { 451 'file2' => '6' 452 } 453 } 454 }, 455 'tags' => { 456 'TRUNK-1135538253' => { 457 'file1' => '6', 458 'file3' => '6', 459 'dir2' => { 460 'file4' => '6' 461 }, 462 'dir1' => { 463 'file2' => '6' 464 } 465 }, 466 'TRUNK-1135534439' => { 467 'file1' => '6', 468 'dir1' => { 469 'file2' => '6' 470 } 471 } 472 } 473 } 474 }, 475 { 476 'project2' => { 477 'trunk' => { 478 'dir3' => { 479 'file6' => '6' 480 }, 481 'file5' => '6', 482 'dir4' => { 483 'file7' => '6', 484 'file8' => '6' 485 } 486 }, 487 'branches' => {}, 488 'tags' => {} 489 }, 490 'project1' => { 491 'trunk' => { 492 'file1' => '6', 493 'file3' => '6', 494 'dir2' => { 495 'file4' => '6' 496 }, 497 'dir1' => { 498 'file2' => '6' 499 } 500 }, 501 'branches' => { 502 'branch1' => { 503 'file1' => '6', 504 'file3' => '6', 505 'dir2' => { 506 'file4' => '6' 507 }, 508 'dir1' => { 509 'file2' => '6' 510 } 511 } 512 }, 513 'tags' => { 514 'TRUNK-1135538253' => { 515 'file1' => '6', 516 'file3' => '6', 517 'dir2' => { 518 'file4' => '6' 519 }, 520 'dir1' => { 521 'file2' => '6' 522 } 523 }, 524 'TRUNK-1135534439' => { 525 'file1' => '6', 526 'dir1' => { 527 'file2' => '6' 528 } 529 } 530 } 531 } 532 }, 533 { 534 'project2' => { 535 'trunk' => { 536 'dir3' => { 537 'file6' => '6' 538 }, 539 'file5' => '6', 540 'dir4' => { 541 'file7' => '6', 542 'file8' => '6' 543 } 544 }, 545 'branches' => {}, 546 'tags' => { 547 'TRUNK-1135538991' => { 548 'dir3' => { 549 'file6' => '6' 550 }, 551 'file5' => '6', 552 'dir4' => { 553 'file7' => '6', 554 'file8' => '6' 555 } 556 } 557 } 558 }, 559 'project1' => { 560 'trunk' => { 561 'file1' => '6', 562 'file3' => '6', 563 'dir2' => { 564 'file4' => '6' 565 }, 566 'dir1' => { 567 'file2' => '6' 568 } 569 }, 570 'branches' => { 571 'branch1' => { 572 'file1' => '6', 573 'file3' => '6', 574 'dir2' => { 575 'file4' => '6' 576 }, 577 'dir1' => { 578 'file2' => '6' 579 } 580 } 581 }, 582 'tags' => { 583 'TRUNK-1135538253' => { 584 'file1' => '6', 585 'file3' => '6', 586 'dir2' => { 587 'file4' => '6' 588 }, 589 'dir1' => { 590 'file2' => '6' 591 } 592 }, 593 'TRUNK-1135534439' => { 594 'file1' => '6', 595 'dir1' => { 596 'file2' => '6' 597 } 598 } 599 } 600 } 601 }, 602 { 603 'project2' => { 604 'trunk' => { 605 'dir3' => { 606 'file6' => '6' 607 }, 608 'file5' => '6', 609 'dir4' => { 610 'file7' => '6', 611 'file8' => '6' 612 } 613 }, 614 'branches' => { 615 'branch2' => { 616 'dir3' => { 617 'file6' => '6' 618 }, 619 'file5' => '6', 620 'dir4' => { 621 'file7' => '6', 622 'file8' => '6' 623 } 624 } 625 }, 626 'tags' => { 627 'TRUNK-1135538991' => { 628 'dir3' => { 629 'file6' => '6' 630 }, 631 'file5' => '6', 632 'dir4' => { 633 'file7' => '6', 634 'file8' => '6' 635 } 636 } 637 } 638 }, 639 'project1' => { 640 'trunk' => { 641 'file1' => '6', 642 'file3' => '6', 643 'dir2' => { 644 'file4' => '6' 645 }, 646 'dir1' => { 647 'file2' => '6' 648 } 649 }, 650 'branches' => { 651 'branch1' => { 652 'file1' => '6', 653 'file3' => '6', 654 'dir2' => { 655 'file4' => '6' 656 }, 657 'dir1' => { 658 'file2' => '6' 659 } 660 } 661 }, 662 'tags' => { 663 'TRUNK-1135538253' => { 664 'file1' => '6', 665 'file3' => '6', 666 'dir2' => { 667 'file4' => '6' 668 }, 669 'dir1' => { 670 'file2' => '6' 671 } 672 }, 673 'TRUNK-1135534439' => { 674 'file1' => '6', 675 'dir1' => { 676 'file2' => '6' 677 } 678 } 679 } 680 } 681 }, 682 { 683 'project2' => { 684 'trunk' => { 685 'dir3' => { 686 'file6' => '6' 687 }, 688 'file5' => '6', 689 'dir4' => { 690 'file7' => '6', 691 'file8' => '6' 692 } 693 }, 694 'branches' => { 695 'branch2' => { 696 'file5.new' => '6', 697 'dir5' => { 698 'file6' => '6' 699 }, 700 'dir4' => { 701 'file7' => '6', 702 'file8' => '6' 703 } 704 } 705 }, 706 'tags' => { 707 'TRUNK-1135538991' => { 708 'dir3' => { 709 'file6' => '6' 710 }, 711 'file5' => '6', 712 'dir4' => { 713 'file7' => '6', 714 'file8' => '6' 715 } 716 } 717 } 718 }, 719 'project1' => { 720 'trunk' => { 721 'file1' => '6', 722 'file3' => '6', 723 'dir2' => { 724 'file4' => '6' 725 }, 726 'dir1' => { 727 'file2' => '6' 728 } 729 }, 730 'branches' => { 731 'branch1' => { 732 'file1' => '6', 733 'file3' => '6', 734 'dir2' => { 735 'file4' => '6' 736 }, 737 'dir1' => { 738 'file2' => '6' 739 } 740 } 741 }, 742 'tags' => { 743 'TRUNK-1135538253' => { 744 'file1' => '6', 745 'file3' => '6', 746 'dir2' => { 747 'file4' => '6' 748 }, 749 'dir1' => { 750 'file2' => '6' 751 } 752 }, 753 'TRUNK-1135534439' => { 754 'file1' => '6', 755 'dir1' => { 756 'file2' => '6' 757 } 758 } 759 } 760 } 761 }, 762 { 763 'project2' => { 764 'trunk' => { 765 'dir3' => { 766 'file6' => '6' 767 }, 768 'file5' => '6', 769 'dir4' => { 770 'file7' => '6', 771 'file8' => '6' 772 } 773 }, 774 'branches' => { 775 'branch2' => { 776 'file5.new' => '6', 777 'dir5' => { 778 'file6' => '6' 779 }, 780 'dir4' => { 781 'file7' => '6', 782 'file8' => '6' 783 } 784 } 785 }, 786 'tags' => { 787 'TRUNK-1135539568' => { 788 'dir3' => { 789 'file6' => '6' 790 }, 791 'file5' => '6', 792 'dir4' => { 793 'file7' => '6', 794 'file8' => '6' 795 } 796 }, 797 'TRUNK-1135538991' => { 798 'dir3' => { 799 'file6' => '6' 800 }, 801 'file5' => '6', 802 'dir4' => { 803 'file7' => '6', 804 'file8' => '6' 805 } 806 } 807 } 808 }, 809 'project1' => { 810 'trunk' => { 811 'file1' => '6', 812 'file3' => '6', 813 'dir2' => { 814 'file4' => '6' 815 }, 816 'dir1' => { 817 'file2' => '6' 818 } 819 }, 820 'branches' => { 821 'branch1' => { 822 'file1' => '6', 823 'file3' => '6', 824 'dir2' => { 825 'file4' => '6' 826 }, 827 'dir1' => { 828 'file2' => '6' 829 } 830 } 831 }, 832 'tags' => { 833 'TRUNK-1135538253' => { 834 'file1' => '6', 835 'file3' => '6', 836 'dir2' => { 837 'file4' => '6' 838 }, 839 'dir1' => { 840 'file2' => '6' 841 } 842 }, 843 'TRUNK-1135534439' => { 844 'file1' => '6', 845 'dir1' => { 846 'file2' => '6' 847 } 848 } 849 } 850 } 851 } 852]; 853$changes = [ 854 {}, 855 { '' => 856 { 857 'project2' => 'A', 858 'project1' => 'A', 859 'project2/tags' => 'A', 860 'project1/trunk' => 'A', 861 'project2/branches' => 'A', 862 'project2/trunk' => 'A', 863 'project1/branches' => 'A', 864 'project1/tags' => 'A' 865 }, 866 }, 867 { 868 'project1/trunk' => 869 { 870 'project1/trunk/file1' => 'A', 871 'project1/trunk/dir1/file2' => 'A', 872 'project1/trunk/dir1' => 'A' 873 }, 874 }, 875 { 876 'project1/trunk' => {}, 877 'project1/tags/TRUNK-1135534439' => 878 { 879 'project1/tags/TRUNK-1135534439/file1' => 'A', 880 'project1/tags/TRUNK-1135534439/dir1/file2' => 'A', 881 'project1/tags/TRUNK-1135534439/dir1' => 'A' 882 }, 883 }, 884 { 885 'project1/trunk' => {}, 886 'project1/tags/TRUNK-1135534439' => {}, 887 'project1/branches/branch1', 888 { 889 'project1/branches/branch1/file1' => 'A', 890 'project1/branches/branch1/dir1/file2' => 'A', 891 'project1/branches/branch1/dir1' => 'A' 892 }, 893 }, 894 { 'project1/branches/branch1' => 895 { 896 'project1/branches/branch1/dir2/file4' => 'A', 897 'project1/branches/branch1/file3' => 'A', 898 'project1/branches/branch1/dir2' => 'A' 899 }, 900 }, 901 { 'project1/trunk' => 902 { 903 'project1/trunk/file3' => 'A', 904 'project1/trunk/dir2' => 'A', 905 'project1/trunk/dir2/file4' => 'A' 906 }, 907 }, 908 { 'project1/tags/TRUNK-1135538253' => 909 { 910 'project1/tags/TRUNK-1135538253/file3' => 'A', 911 'project1/tags/TRUNK-1135538253/dir2' => 'A', 912 'project1/tags/TRUNK-1135538253/dir2/file4' => 'A' 913 }, 914 }, 915 { 'project2/trunk' => 916 { 917 'project2/trunk/dir4/file7' => 'A', 918 'project2/trunk/dir4/file8' => 'A', 919 'project2/trunk/dir3/file6' => 'A', 920 'project2/trunk/dir3' => 'A', 921 'project2/trunk/file5' => 'A', 922 'project2/trunk/dir4' => 'A' 923 }, 924 }, 925 { 'project2/tags/TRUNK-1135538991' => 926 { 927 'project2/tags/TRUNK-1135538991/dir4/file7' => 'A', 928 'project2/tags/TRUNK-1135538991/dir4/file8' => 'A', 929 'project2/tags/TRUNK-1135538991/dir3/file6' => 'A', 930 'project2/tags/TRUNK-1135538991/dir3' => 'A', 931 'project2/tags/TRUNK-1135538991/file5' => 'A', 932 'project2/tags/TRUNK-1135538991/dir4' => 'A' 933 }, 934 }, 935 { 'project2/branches/branch2' => 936 { 937 'project2/branches/branch2' => 'A' 938 }, 939 }, 940 { 'project2/branches/branch2' => 941 { 942 'project2/branches/branch2/dir5' => 'A', 943 'project2/branches/branch2/file5' => 'D', 944 'project2/branches/branch2/dir3' => 'D', 945 'project2/branches/branch2/file5.new' => 'A' 946 }, 947 }, 948 { 'project2/tags/TRUNK-1135539568' => 949 { 950 'project2/tags/TRUNK-1135539568' => 'A' 951 } 952 }, 953]; 954