t/Makefile: clean: Delete "tmpdir"
[gitspread.git] / t / gitspreadd.t
blob8ddf4460c4deaa1199ac373eb04693c42d5112ab
1 #!/usr/bin/perl
3 #=======================================================================
4 # gitspreadd.t
5 # File ID: aed76eda-6124-11e0-aeaf-adf3f75e27a6
7 # Test suite for gitspreadd(1).
9 # Character set: UTF-8
10 # ©opyleft 2011– Øyvind A. Holm <sunny@sunbase.org>
11 # License: GNU General Public License version 2 or later, see end of
12 # file for legal stuff.
13 #=======================================================================
15 use strict;
16 use warnings;
18 BEGIN {
19 # push(@INC, "$ENV{'HOME'}/bin/STDlibdirDTS");
20 use Test::More qw{no_plan};
21 use_ok('Cwd');
22 use_ok('Fcntl');
23 use_ok('IO::Handle');
24 use_ok('POSIX');
27 use Cwd;
28 use File::Copy;
29 use Getopt::Long;
31 local $| = 1;
33 our $CMD = '../gitspreadd';
35 our %Opt = (
37 'all' => 0,
38 'help' => 0,
39 'todo' => 0,
40 'verbose' => 0,
41 'version' => 0,
45 our $progname = $0;
46 $progname =~ s/^.*\/(.*?)$/$1/;
47 our $VERSION = '0.11.0+git';
49 Getopt::Long::Configure('bundling');
50 GetOptions(
52 'all|a' => \$Opt{'all'},
53 'help|h' => \$Opt{'help'},
54 'todo|t' => \$Opt{'todo'},
55 'verbose|v+' => \$Opt{'verbose'},
56 'version' => \$Opt{'version'},
58 ) || die("$progname: Option error. Use -h for help.\n");
60 $Opt{'help'} && usage(0);
61 if ($Opt{'version'}) {
62 print_version();
63 exit(0);
66 my $SHOULD_NOT_EXIST = 0;
67 my $SHOULD_EXIST = 1;
69 my $orig_dir = cwd();
70 my $tmpdir = "$orig_dir/tmpdir";
71 my $repo = "$tmpdir/repo.git";
72 my $mirror = "$tmpdir/mirror.git";
73 my $hook = "$repo/hooks/post-receive";
74 my $wrkdir = "$tmpdir/wrkdir";
75 my $spooldir = "$tmpdir/spool";
76 my $logfile = "$tmpdir/gitspreadd.log";
77 my $pidfile = "$tmpdir/pid";
78 my $stopfile = "$tmpdir/stop";
79 my $CMD_GIT = defined($ENV{'GITSPREAD_GIT'}) ? $ENV{'GITSPREAD_GIT'} : 'git';
81 exit(main(%Opt));
83 sub main {
84 # {{{
85 my %Opt = @_;
86 my $Retval = 0;
88 diag(sprintf('========== Executing %s v%s ==========',
89 $progname,
90 $VERSION));
92 unless (ok(-e 'gitspreadd.t' && -e '../gitspreadd' && -e '../post-receive',
93 'We are in the correct directory')) {
94 diag('Has to be run from inside the gitspread/t/ directory.');
95 exit 1;
98 if ($Opt{'todo'} && !$Opt{'all'}) {
99 goto todo_section;
102 =pod
104 testcmd("$CMD command", # {{{
105 <<'END',
106 [expected stdout]
110 'description',
113 # }}}
115 =cut
117 diag('Testing -h (--help) option...');
118 likecmd("$CMD -h", # {{{
119 '/ Show this help\./',
120 '/^$/',
122 'Option -h prints help screen',
125 # }}}
126 diag('Testing -q (--quiet) option...');
127 likecmd("$CMD --version -q", # {{{
128 '/^\d\.\d+\.\d+(\+git)?\n/s',
129 '/^$/',
131 'Option -q with --version does not output program name',
134 # }}}
135 diag('Testing -v (--verbose) option...');
136 likecmd("$CMD -hv", # {{{
137 '/^\n\S+ \d\.\d+\.\d+(\+git)?\n/s',
138 '/^$/',
140 'Option --version with -h returns version number and help screen',
143 # }}}
144 diag('Testing --version option...');
145 likecmd("$CMD --version", # {{{
146 '/^\S+ \d\.\d+\.\d+(\+git)?\n/',
147 '/^$/',
149 'Option --version returns version number',
152 # }}}
154 my $datefmt = '20\d\d-\d\d-\d\d \d\d:\d\d:\d\dZ';
156 cleanup();
157 testcmd("$CMD -1 -r \"$tmpdir\"", # {{{
159 "gitspreadd: $tmpdir: Missing repository top directory\n",
161 'Complain about missing repodir',
164 # }}}
165 create_tmpdir();
166 likecmd("$CMD -1 -r \"$tmpdir\"", # {{{
167 '/^Starting gitspreadd \d\.\d+\.\d+(\+git)?, PID = \d+\n$/s',
168 '/^$/',
170 'Run with -r option',
173 # }}}
174 ok(-d $spooldir, "$spooldir exists");
175 ok(-e $logfile, "$logfile exists");
176 like(file_data($logfile), # {{{
177 "/^$datefmt - Starting gitspreadd \\d\\.\\d+\\.\\d+(\\+git)?, PID = \\d+\\n\$/s",
178 "$logfile looks ok"
181 # }}}
182 cleanup();
183 create_tmpdir();
184 likecmd("GITSPREAD_REPODIR=\"$tmpdir\" $CMD -1", # {{{
185 '/^Starting gitspreadd \d\.\d+\.\d+(\+git)?, PID = \d+\n$/s',
186 '/^$/',
188 'Use GITSPREAD_REPODIR environment variable',
191 # }}}
192 ok(-d $spooldir, "$spooldir exists");
193 ok(-e $logfile, "$logfile exists");
195 diag('Set up repositories...');
197 setup_repo();
198 setup_wrkdir();
199 setup_mirror();
201 start_daemon();
203 create_file('newfile');
204 add_and_commit_file('newfile');
205 push_to_repo_succeeds();
206 check_log($SHOULD_EXIST, $mirror, 'newfile', 'newfile exists in mirror.git');
208 diag('Check gitspread.forcepush config option...');
209 reset_wrkdir_to_first_commit();
210 push_to_repo_denied();
211 push_to_repo_force_update();
212 check_log($SHOULD_EXIST, $mirror, 'newfile', 'newfile still exists in mirror.git');
214 enable_gitspread_forcepush();
215 create_file('newfile');
216 add_and_commit_file('newfile');
217 push_to_repo_succeeds();
218 reset_wrkdir_to_first_commit();
219 push_to_repo_force_update();
220 check_log($SHOULD_NOT_EXIST, $mirror, 'newfile', 'newfile is gone from mirror.git');
222 diag('Create branch, push it, and then remove it...');
223 create_branch('newbranch1');
224 create_file('branchfile1');
225 add_and_commit_file('branchfile1');
226 create_branch('newbranch2');
227 create_file('branchfile2');
228 add_and_commit_file('branchfile2');
229 push_new_branch('newbranch1', 'newbranch2');
230 check_log($SHOULD_EXIST, $mirror, 'branchfile1', 'branchfile1 exists in mirror.git');
231 check_log($SHOULD_EXIST, $mirror, 'branchfile2', 'branchfile2 exists in mirror.git');
232 delete_branch('newbranch1', 'newbranch2');
233 delete_remote_branch('newbranch1', 'newbranch2');
234 check_log($SHOULD_NOT_EXIST, $mirror, 'branchfile1', 'branchfile1 is gone from mirror.git');
235 check_log($SHOULD_NOT_EXIST, $mirror, 'branchfile2', 'branchfile2 is gone from mirror.git');
237 stop_daemon();
238 cleanup();
240 todo_section:
243 if ($Opt{'all'} || $Opt{'todo'}) {
244 diag('Running TODO tests...'); # {{{
246 TODO: {
248 local $TODO = '';
249 # Insert TODO tests here.
252 # TODO tests }}}
255 diag('Testing finished.');
256 return($Retval);
257 # }}}
258 } # main()
260 sub cleanup {
261 # {{{
262 ok(chdir($orig_dir), "chdir $orig_dir");
263 testcmd("rm -rf \"$tmpdir\"", '', '', 0, 'Delete tmpdir');
264 ok(!-e $tmpdir, "$tmpdir does not exist");
265 return;
266 # }}}
267 } # cleanup()
269 sub create_tmpdir {
270 # {{{
271 ok(mkdir($tmpdir), "mkdir $tmpdir");
272 ok(-d $tmpdir, 'tmpdir exists');
273 return;
274 # }}}
275 } # create_tmpdir()
277 sub clone_bundle {
278 # {{{
279 my ($dir, $bare) = @_;
281 my $bare_str = $bare ? " --bare" : "";
282 my $bare_msg = $bare ? " bare repository" : "";
283 likecmd("$CMD_GIT clone$bare_str \"$orig_dir/repo.bundle\" \"$tmpdir/$dir\"",
284 '/.*/',
285 '/.*/',
287 "Clone repo.bundle into $dir"
289 ok(chdir("$tmpdir/$dir"), "chdir $tmpdir/$dir");
290 my $git_str = $bare ? "" : ".git/";
291 ok(-f "${git_str}HEAD" && -f "${git_str}config" && -d "${git_str}objects",
292 "$tmpdir/$dir looks like a real repository");
293 return;
294 # }}}
295 } # clone_bundle()
297 sub setup_repo {
298 # {{{
299 diag('Create repo.git...');
300 testcmd("rm -rf \"$repo\"", '', '', 0, 'Make sure repo.git does not exist');
301 clone_bundle('repo.git', 1);
302 my $bck_dir = cwd();
303 ok(chdir($repo), 'chdir repo.git');
304 testcmd("$CMD_GIT remote add mirror \"$mirror\"", '', '', 0, 'Set up mirror remote');
305 ok(copy("$orig_dir/../post-receive", $hook), "Copy ../post-receive to $hook");
306 ok(-e $hook, 'Yes, it was really copied');
307 ok(chmod(0755, $hook), "Make $hook executable");
308 is((stat($hook))[2] & 07777, 0755, "$hook has correct permissions");
309 ok(chdir($bck_dir), 'Return to previous directory');
310 return;
311 # }}}
312 } # setup_repo()
314 sub setup_wrkdir {
315 # {{{
316 testcmd("rm -rf \"$wrkdir\"", '', '', 0, 'Make sure wrkdir does not exist');
317 clone_bundle('wrkdir', 0);
318 ok(chdir($wrkdir), 'chdir wrkdir');
319 testcmd("$CMD_GIT remote add dest \"$repo\"", '', '', 0, 'Set up dest remote');
320 return;
321 # }}}
322 } # setup_wrkdir()
324 sub setup_mirror {
325 # {{{
326 testcmd("rm -rf \"$mirror\"", '', '', 0, 'Make sure mirror.git does not exist');
327 clone_bundle('mirror.git', 1);
328 check_log($SHOULD_NOT_EXIST, $mirror, 'newfile', 'newfile does not exist in mirror.git yet');
329 # }}}
330 } # setup_mirror()
332 sub add_and_commit_file {
333 # {{{
334 my $file = shift;
335 diag('Make a commit...');
336 ok(chdir($wrkdir), "chdir $wrkdir");
337 testcmd("$CMD_GIT add \"$file\"", '', '', 0, "Add $file for commit");
338 likecmd("$CMD_GIT commit -m 'Adding new file $file'",
339 "/^.*Adding new file $file\\n.*\$/s",
340 '/^$/',
342 "Commit addition of $file",
344 return;
345 # }}}
346 } # add_and_commit_file()
348 sub check_log {
349 # {{{
350 my ($should_exist, $dir, $file, $msg) = @_;
351 ok(chdir($dir), "chdir $dir");
352 if ($should_exist == $SHOULD_EXIST) {
353 like(`$CMD_GIT log --all`, "/^.*Adding new file $file.*\$/s", $msg);
354 } else {
355 unlike(`$CMD_GIT log --all`, "/^.*Adding new file $file.*\$/s", $msg);
357 return;
358 # }}}
359 } # check_log()
361 sub create_file {
362 # {{{
363 my $filename = shift;
364 ok(chdir($wrkdir), "chdir $wrkdir");
365 ok(open(my $newfile, '>', $filename), "Create $filename");
366 ok(print($newfile "This is $filename\n"), "Write to $filename");
367 ok(close($newfile), "Close $filename");
368 return;
369 # }}}
370 } # create_file()
372 sub create_branch {
373 # {{{
374 my $branch = shift;
375 ok(chdir($wrkdir), "chdir $wrkdir");
376 likecmd("$CMD_GIT checkout -b \"$branch\"",
377 '/^$/s',
378 "/^Switched to a new branch .$branch.\\n\$/s",
380 "Create branch $branch",
382 return;
383 # }}}
384 } # create_branch()
386 sub delete_branch {
387 # {{{
388 my @branches = @_;
389 ok(chdir($wrkdir), "chdir $wrkdir");
390 my $branch_str = join(' ', @branches);
391 likecmd("$CMD_GIT checkout master",
392 '/.*/s',
393 '/.*/s',
395 'Checkout branch master',
397 likecmd("$CMD_GIT branch -D $branch_str",
398 "/^Deleted branch $branches[0].*\$/s",
399 '/^$/s',
401 "Delete branch(es) '$branch_str'",
403 return;
404 # }}}
405 } # delete_branch()
407 sub enable_gitspread_forcepush {
408 # {{{
409 diag('Enable gitspread.forcepush in repo.git...');
410 ok(chdir($repo), 'chdir repo.git');
411 likecmd("$CMD_GIT config gitspread.forcepush true",
412 '/^$/',
413 '/^$/',
415 'Enable gitspread.forcepush'
417 return;
418 # }}}
419 } # enable_gitspread_forcepush()
421 sub push_to_repo_succeeds {
422 # {{{
423 ok(chdir($wrkdir), 'chdir wrkdir');
424 likecmd("GITSPREAD_REPODIR=\"$tmpdir\" $CMD_GIT push dest",
425 '/^$/',
426 '/^.*' .
427 'Spreading repo commits:.*' .
428 'a1989e25c8e7c23a3c455731f9433ed0932ec193 ' .
429 '[0-9a-f]{40} refs/heads/master.*' .
430 'Waiting for spreading to complete\.\.\..*' .
431 'Spreading finished.*$/s',
433 'Push to dest remote'
435 return;
436 # }}}
437 } # push_to_repo_succeeds()
439 sub push_new_branch {
440 # {{{
441 my @branches = @_;
442 ok(chdir($wrkdir), 'chdir wrkdir');
443 my $branch_str = join(' ', @branches);
444 likecmd("GITSPREAD_REPODIR=\"$tmpdir\" $CMD_GIT push dest $branch_str",
445 '/^$/',
446 '/^.*' .
447 'Spreading repo commits:.*' .
448 '0{40} ' .
449 "[0-9a-f]{40} refs/heads/$branches[0].*" .
450 'Waiting for spreading to complete\.\.\..*' .
451 'Spreading finished.*$/s',
453 "Push '$branch_str' branch(es) to dest remote"
455 return;
456 # }}}
457 } # push_new_branch()
459 sub delete_remote_branch {
460 # {{{
461 my @branches = @_;
462 ok(chdir($wrkdir), 'chdir wrkdir');
463 my $branch_str = ':' . join(' :', @branches);
464 likecmd("GITSPREAD_REPODIR=\"$tmpdir\" $CMD_GIT push dest $branch_str",
465 '/^$/',
466 '/^.*' .
467 'Spreading repo commits:.*' .
468 '[0-9a-f]{40} ' .
469 "0{40} refs/heads/$branches[0].*" .
470 'Waiting for spreading to complete\.\.\..*' .
471 'Spreading finished.*$/s',
473 "Delete remote branch(es) '$branch_str'"
475 return;
476 # }}}
477 } # delete_remote_branch()
479 sub push_to_repo_denied {
480 # {{{
481 ok(chdir($wrkdir), 'chdir wrkdir');
482 likecmd("GITSPREAD_REPODIR=\"$tmpdir\" $CMD_GIT push dest",
483 '/^.*$/s',
484 '/^.*$/s',
486 'Denied non-fast-forward push'
488 check_log($SHOULD_EXIST, $repo, 'newfile', "newfile still exists in $repo");
489 return;
490 # }}}
491 } # push_to_repo_denied()
493 sub push_to_repo_force_update {
494 # {{{
495 ok(chdir($wrkdir), 'chdir wrkdir');
496 likecmd("GITSPREAD_REPODIR=\"$tmpdir\" $CMD_GIT push -f dest",
497 '/^$/',
498 '/^.*' .
499 'Spreading repo commits:.*' .
500 '[0-9a-f]{40} ' .
501 'a1989e25c8e7c23a3c455731f9433ed0932ec193 refs/heads/master.*' .
502 'Waiting for spreading to complete\.\.\..*' .
503 'Spreading finished.*/s',
505 'Force-push to dest remote'
507 return;
508 # }}}
509 } # push_to_repo_force_update()
511 sub reset_wrkdir_to_first_commit {
512 # {{{
513 ok(chdir($wrkdir), 'chdir wrkdir');
514 likecmd("$CMD_GIT reset --hard a1989e2",
515 '/^HEAD is now at a198[0-9a-f]* Initial empty commit\n$/',
516 '/^$/',
518 'Reset HEAD to first commit'
520 return;
521 # }}}
522 } # reset_wrkdir_to_first_commit()
524 sub start_daemon {
525 # {{{
526 diag('Starting daemon...');
527 my $tmpfile = ".gitspread-start-output.tmp";
528 system("\"$orig_dir/$CMD\" -r \"$tmpdir\" >$tmpfile");
529 like(file_data($tmpfile),
530 '/^Starting gitspreadd \d+\.\d+\.\d+(\+git)?, PID = \d+\n$/s',
531 'stdout from daemon looks ok',
533 ok(-e $pidfile, 'PID file exists');
534 like(file_data($pidfile), '/^\d+\n$/s', 'PID file looks ok');
535 return;
536 # }}}
537 } # start_daemon()
539 sub stop_daemon {
540 # {{{
541 diag('Stopping daemon...');
542 ok(open(my $stopfh, '>', $stopfile), "Create stop file $stopfile");
543 ok(close($stopfh), 'Close stop file');
544 diag('Waiting for process to stop...');
545 while(-e $stopfile) { }
546 ok(!-e $pidfile, 'PID file is removed');
547 return;
548 # }}}
549 } # stop_daemon()
551 sub regexp_friendly {
552 # {{{
553 my $str = shift;
554 $str =~ s/\//\\\//gs;
555 $str =~ s/\./\\./gs;
556 return($str)
557 # }}}
558 } # regexp_friendly()
560 sub testcmd {
561 # {{{
562 my ($Cmd, $Exp_stdout, $Exp_stderr, $Exp_retval, $Desc) = @_;
563 my $stderr_cmd = '';
564 my $Txt = join('',
565 "\"$Cmd\"",
566 defined($Desc)
567 ? " - $Desc"
568 : ''
570 my $TMP_STDERR = 'gitspreadd-stderr.tmp';
572 if (defined($Exp_stderr)) {
573 $stderr_cmd = " 2>$TMP_STDERR";
575 is(`$Cmd$stderr_cmd`, $Exp_stdout, "$Txt (stdout)");
576 my $ret_val = $?;
577 if (defined($Exp_stderr)) {
578 is(file_data($TMP_STDERR), $Exp_stderr, "$Txt (stderr)");
579 unlink($TMP_STDERR);
580 } else {
581 diag("Warning: stderr not defined for '$Txt'");
583 is($ret_val >> 8, $Exp_retval, "$Txt (retval)");
584 return;
585 # }}}
586 } # testcmd()
588 sub likecmd {
589 # {{{
590 my ($Cmd, $Exp_stdout, $Exp_stderr, $Exp_retval, $Desc) = @_;
591 my $stderr_cmd = '';
592 my $Txt = join('',
593 "\"$Cmd\"",
594 defined($Desc)
595 ? " - $Desc"
596 : ''
598 my $TMP_STDERR = 'gitspreadd-stderr.tmp';
600 if (defined($Exp_stderr)) {
601 $stderr_cmd = " 2>$TMP_STDERR";
603 like(`$Cmd$stderr_cmd`, $Exp_stdout, "$Txt (stdout)");
604 my $ret_val = $?;
605 if (defined($Exp_stderr)) {
606 like(file_data($TMP_STDERR), $Exp_stderr, "$Txt (stderr)");
607 unlink($TMP_STDERR);
608 } else {
609 diag("Warning: stderr not defined for '$Txt'");
611 is($ret_val >> 8, $Exp_retval, "$Txt (retval)");
612 return;
613 # }}}
614 } # likecmd()
616 sub file_data {
617 # Return file content as a string {{{
618 my $File = shift;
619 my $Txt;
620 if (open(my $fp, '<', $File)) {
621 local $/ = undef;
622 $Txt = <$fp>;
623 close($fp);
624 return($Txt);
625 } else {
626 return;
628 # }}}
629 } # file_data()
631 sub print_version {
632 # Print program version {{{
633 print("$progname $VERSION\n");
634 return;
635 # }}}
636 } # print_version()
638 sub usage {
639 # Send the help message to stdout {{{
640 my $Retval = shift;
642 if ($Opt{'verbose'}) {
643 print("\n");
644 print_version();
646 print(<<"END");
648 Usage: $progname [options] [file [files [...]]]
650 Contains tests for the gitspreadd(1) program.
652 Options:
654 -a, --all
655 Run all tests, also TODOs.
656 -h, --help
657 Show this help.
658 -t, --todo
659 Run only the TODO tests.
660 -v, --verbose
661 Increase level of verbosity. Can be repeated.
662 --version
663 Print version information. "Semantic versioning" is used, described
664 at <http://semver.org>.
666 To use an alternative version of git, set the \$GITSPREAD_GIT
667 environment variable to the git executable to use. For example:
669 export GITSPREAD_GIT=/usr/local/bin/git
670 ./gitspreadd.t
673 exit($Retval);
674 # }}}
675 } # usage()
677 sub msg {
678 # Print a status message to stderr based on verbosity level {{{
679 my ($verbose_level, $Txt) = @_;
681 if ($Opt{'verbose'} >= $verbose_level) {
682 print(STDERR "$progname: $Txt\n");
684 return;
685 # }}}
686 } # msg()
688 __END__
690 # This program is free software: you can redistribute it and/or modify
691 # it under the terms of the GNU General Public License as published by
692 # the Free Software Foundation, either version 2 of the License, or (at
693 # your option) any later version.
695 # This program is distributed in the hope that it will be useful, but
696 # WITHOUT ANY WARRANTY; without even the implied warranty of
697 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
698 # See the GNU General Public License in the file COPYING for more
699 # details.
701 # You should have received a copy of the GNU General Public License
702 # along with this program.
703 # If not, see L<http://www.gnu.org/licenses/gpl-2.0.txt>.
705 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :