+ # prefer log_accum, then cvsps, else die
+ # run the high-level recentchanges tests 2x (once for each method)
+ # - including in other test subs that check recentchanges?
+}
+
+sub test_rcs_diff {
+ # can it assume we're under CVS control? or must it check?
+ # in list context, return all lines (with \n), up to $maxlines if set
+ # in scalar context, return the whole diff, up to $maxlines if set
+}
+
+sub test_rcs_getctime {
+ # can it assume we're under CVS control? or must it check?
+ # given a file, find its creation time, else return 0
+ # first implement in the obvious way
+ # then cache
+}
+
+sub test_rcs_getmtime {
+ # can it assume we're under CVS control? or must it check?
+ # given a file, find its modification time, else return 0
+ # first implement in the obvious way
+ # then cache
+}
+
+sub test_rcs_receive {
+ pass(q{rcs_receive doesn't make sense for CVS});
+}
+
+sub test_rcs_preprevert {
+ # can it assume we're under CVS control? or must it check?
+ # given a patchset number, return structure describing what'd happen:
+ # - see doc/plugins/write.mdwn:rcs_receive()
+ # don't forget about attachments
+}
+
+sub test_rcs_revert {
+ # can it assume we're under CVS control? or must it check?
+ # given a patchset number, stage the revert for rcs_commit_staged()
+ # if commit succeeds, return undef
+ # else, warn and return error message (really? or just non-undef?)
+}
+
+sub main {
+ my $test_methods = defined $ENV{TEST_METHOD}
+ ? $ENV{TEST_METHOD}
+ : $default_test_methods;
+
+ _startup($test_methods eq $default_test_methods);
+ _runtests(_get_matching_test_subs($test_methods));
+ _shutdown($test_methods eq $default_test_methods);
+}
+
+main();
+
+
+# INTERNAL SUPPORT ROUTINES
+
+sub _plan_for_test_more {
+ my $can_plan = shift;
+
+ foreach my $program (@required_programs) {
+ my $program_path = `which $program`;
+ chomp $program_path;
+ return plan(skip_all => "$program not available")
+ unless -x $program_path;
+ }
+
+ foreach my $module (@required_modules) {
+ eval qq{use $module};
+ return plan(skip_all => "$module not available")
+ if $@;
+ }
+
+ return plan(skip_all => "can't create $dir: $!")
+ unless mkdir($dir);
+ return plan(skip_all => "can't remove $dir: $!")
+ unless rmdir($dir);
+
+ return unless $can_plan;
+
+ return plan(tests => $total_tests);
+}
+
+# http://stackoverflow.com/questions/607282/whats-the-best-way-to-discover-all-subroutines-a-perl-module-has
+
+use B qw/svref_2object/;
+
+sub in_package {
+ my ($coderef, $package) = @_;
+ my $cv = svref_2object($coderef);
+ return if not $cv->isa('B::CV') or $cv->GV->isa('B::SPECIAL');
+ return $cv->GV->STASH->NAME eq $package;
+}
+
+sub list_module {
+ my $module = shift;
+ no strict 'refs';
+ return grep {
+ defined &{"$module\::$_"} and in_package(\&{*$_}, $module)
+ } keys %{"$module\::"};
+}
+
+
+# support for xUnit-style testing, a la Test::Class
+
+sub _startup {
+ my $can_plan = shift;
+ _plan_for_test_more($can_plan);
+ _generate_test_config();
+}
+
+sub _shutdown {
+ my $had_plan = shift;
+ done_testing() unless $had_plan;
+}
+
+sub _setup {
+ _generate_test_repo();
+}
+
+sub _teardown {
+ system "rm -rf $dir";
+}
+
+sub _runtests {
+ my @coderefs = (@_);
+ for (@coderefs) {
+ _setup();
+ $_->();
+ _teardown();
+ }
+}
+
+sub _get_matching_test_subs {
+ my $re = shift;
+ no strict 'refs';
+ return map { \&{*$_} } grep { /$re/ } sort(list_module('main'));
+}
+
+sub _generate_test_config {
+ %config = IkiWiki::defaultconfig();
+ $config{rcs} = "cvs";
+ $config{srcdir} = "$dir/src";
+ $config{cvsrepo} = "$dir/repo";
+ $config{cvspath} = "ikiwiki";
+ IkiWiki::loadplugins();