]> sipb.mit.edu Git - ikiwiki.git/blob - t/cvs.t
d34d409998953c9526442e679a7c6b1e0734b246
[ikiwiki.git] / t / cvs.t
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 use Test::More; my $total_tests = 9;
5 use IkiWiki;
6
7 my $default_test_methods = '^test_*';
8 my $dir = "/tmp/ikiwiki-test-cvs.$$";
9
10 sub _plan_for_test_more {
11         my $can_plan = shift;
12
13         foreach my $program (qw(
14                 cvs
15                 cvsps
16         )) {
17                 my $program_path = `which $program`;
18                 chomp $program_path;
19                 return plan(skip_all => "$program not available")
20                         unless -x $program_path;
21         }
22
23         foreach my $module (qw(
24                 File::chdir
25                 File::MimeInfo
26                 Date::Parse
27                 File::Temp
28                 File::ReadBackwards
29         )) {
30                 eval qq{use $module};
31                 return plan(skip_all => "$module not available")
32                         if $@;
33         }
34
35         return plan(skip_all => "can't create $dir: $!")
36                 unless mkdir($dir);
37         return plan(skip_all => "can't remove $dir: $!")
38                 unless rmdir($dir);
39
40         return unless $can_plan;
41
42         return plan(tests => $total_tests);
43 }
44
45
46 # http://stackoverflow.com/questions/607282/whats-the-best-way-to-discover-all-subroutines-a-perl-module-has
47
48 use B qw/svref_2object/;
49
50 sub in_package {
51         my ($coderef, $package) = @_;
52         my $cv = svref_2object($coderef);
53         return if not $cv->isa('B::CV') or $cv->GV->isa('B::SPECIAL');
54         return $cv->GV->STASH->NAME eq $package;
55 }
56
57 sub list_module {
58         my $module = shift;
59         no strict 'refs';
60         return grep {
61                 defined &{"$module\::$_"} and in_package(\&{*$_}, $module)
62         } keys %{"$module\::"};
63 }
64
65
66 # support for xUnit-style testing, a la Test::Class
67
68 sub _startup {
69         my $can_plan = shift;
70         _plan_for_test_more($can_plan);
71         _generate_test_config();
72 }
73
74 sub _shutdown {
75         my $had_plan = shift;
76         done_testing() unless $had_plan;
77 }
78
79 sub _setup {
80         _generate_test_repo();
81 }
82
83 sub _teardown {
84         system "rm -rf $dir";
85 }
86
87 sub _runtests {
88         my @coderefs = (@_);
89         for (@coderefs) {
90                 _setup();
91                 $_->();
92                 _teardown();
93         }
94 }
95
96 sub _get_matching_test_subs {
97         my $re = shift;
98         no strict 'refs';
99         return map { \&{*$_} } grep { /$re/ } list_module('main');
100 }
101
102 sub _generate_test_config {
103         %config = IkiWiki::defaultconfig();
104         $config{rcs} = "cvs";
105         $config{srcdir} = "$dir/src";
106         $config{cvsrepo} = "$dir/repo";
107         $config{cvspath} = "ikiwiki";
108         IkiWiki::loadplugins();
109         IkiWiki::checkconfig();
110 }
111
112 sub _generate_test_repo {
113         die "can't create $dir: $!"
114                 unless mkdir($dir);
115
116         my $cvs = "cvs -d $config{cvsrepo}";
117         my $dn = ">/dev/null";
118         system "$cvs init $dn";
119         system "mkdir $dir/$config{cvspath} $dn";
120         system "cd $dir/$config{cvspath} && "
121                 . "$cvs import -m import $config{cvspath} VENDOR RELEASE $dn";
122         system "rm -rf $dir/$config{cvspath} $dn";
123         system "$cvs co -d $config{srcdir} $config{cvspath} $dn";
124 }
125
126
127 # tests for general meta-behavior:
128
129 sub test_web_add_and_commit {
130         my $message = "Add a page via VCS API";
131         writefile('test1.mdwn', $config{srcdir}, readfile("t/test1.mdwn"));
132         IkiWiki::rcs_add("test1.mdwn");
133         IkiWiki::rcs_commit(
134                 file => "test1.mdwn",
135                 message => $message,
136                 token => "moo",
137         );
138
139         my @changes = IkiWiki::rcs_recentchanges(3);
140         is(
141                 $#changes,
142                 0,
143                 q{1 total commit},
144         );
145         is(
146                 $changes[0]{message}[0]{"line"},
147                 $message,
148                 q{first line of most recent commit message matches},
149         );
150         is(
151                 $changes[0]{pages}[0]{"page"},
152                 "test1",
153                 q{first pagename from most recent commit matches},
154         );
155
156         # prevent web edits from attempting to create .../CVS/foo.mdwn
157         # on case-insensitive filesystems, also prevent .../cvs/foo.mdwn
158         # unless your "CVS" is something else and we've made it configurable
159         # how much of the web-edit workflow are we actually testing?
160         # because we want to test comments:
161         # - when the first comment for page.mdwn is added, and page/ is
162         #   created to hold the comment, page/ isn't added to CVS control,
163         #   so the comment isn't either
164         # - side effect for moderated comments: after approval they
165         #   show up normally AND are still pending, too
166         # - comments.pm treats rcs_commit_staged() as returning conflicts?
167 }
168
169 sub test_manual_add_and_commit {
170         my $message = "Add a page via CVS directly";
171         writefile('test2.mdwn', $config{srcdir}, readfile("t/test2.mdwn"));
172         system "cd $config{srcdir}"
173                 . " && cvs add test2.mdwn >/dev/null 2>&1";
174         system "cd $config{srcdir}"
175                 . " && cvs commit -m \"$message\" test2.mdwn >/dev/null";
176
177         my @changes = IkiWiki::rcs_recentchanges(3);
178         is(
179                 $#changes,
180                 0,
181                 q{1 total commit},
182         );
183         is(
184                 $changes[0]{message}[0]{"line"},
185                 $message,
186                 q{first line of most recent commit message matches},
187         );
188         is(
189                 $changes[0]{pages}[0]{"page"},
190                 "test2",
191                 q{first pagename from most recent commit matches},
192         );
193
194         # CVS commits run ikiwiki once for every committed file (!)
195         # - commit_prep alone should fix this
196         # CVS multi-dir commits show only the first dir in recentchanges
197         # - commit_prep might also fix this?
198         # CVS post-commit hook is amped off to avoid locking against itself
199         # - commit_prep probably doesn't fix this... but maybe?
200 }
201
202 sub test_chdir_magic {
203         # cvs.pm operations are always occurring inside $config{srcdir}
204         # other ikiwiki operations are occurring wherever, and are unaffected
205         # when are we bothering with "local $CWD" and when aren't we?
206 }
207
208
209 # tests for VCS API calls:
210
211 sub test_genwrapper {
212         # testable directly? affects rcs_add, but are we exercising this?
213 }
214
215 sub test_checkconfig {
216         # undef cvspath, expect "ikiwiki"
217         # define cvspath normally, get it back
218         # define cvspath in a subdir, get it back?
219         # define cvspath with extra slashes, get sanitized version back
220         # - yoink test_extra_path_slashes
221         # undef cvs_wrapper, expect $config{wrappers} same size as before
222
223         my $initial_cvspath = $config{cvspath};
224         $config{cvspath} = "/ikiwiki//";
225         IkiWiki::checkconfig();
226         is(
227                 $config{cvspath},
228                 $initial_cvspath,
229                 q{rcs_recentchanges assumes checkconfig has sanitized cvspath},
230         );
231 }
232
233 sub test_getsetup {
234         # anything worth testing?
235 }
236
237 sub test_cvs_info {
238         # inspect "Repository revision" (used in code)
239         # inspect "Sticky Options" (used in tests to verify existence of "-kb")
240 }
241
242 sub test_cvs_run_cvs {
243         # extract the stdout-redirect thing
244         # - prove that it silences stdout
245         # - prove that stderr comes through just fine
246         # prove that when cvs exits nonzero (fail), function exits false
247         # prove that when cvs exits zero (success), function exits true
248         # always pass -f, just in case
249         # steal from git.pm: safe_git(), run_or_{die,cry,non}
250         # - open() instead of system()
251         # always call cvs_run_cvs(), don't ever run 'cvs' directly
252 }
253
254 sub test_cvs_run_cvsps {
255         # parameterize command like run_cvs()
256         # expose config vars for e.g. "--cvs-direct -z 30"
257         # always pass -x (unless proven otherwise)
258         # always pass -b HEAD (configurable like gitmaster_branch?)
259 }
260
261 sub test_cvs_parse_cvsps {
262         # extract method from rcs_recentchanges
263         # document expected changeset format
264         # document expected changeset delimiter
265         # try: cvsps -q -x -p && ls | sort -rn | head -100
266         # - benchmark against current impl (that uses File::ReadBackwards)
267 }
268
269 sub test_cvs_parse_log_accum {
270         # add new, preferred method for rcs_recentchanges to use
271         # teach log_accum to record commits (into transient?)
272         # script cvsps to bootstrap (or replace?) commit history
273         # teach ikiwiki-makerepo to set up log_accum and commit_prep
274         # why are NetBSD commit mails unreliable?
275         # - is it working for CVS commits and failing for web commits?
276 }
277
278 sub test_cvs_is_controlling {
279         # with no args:
280         # - if srcdir is in CVS, return true
281         # - else, return false
282         # with a dir arg:
283         # - if dir is in CVS, return true
284         # - else, return false
285         # with a file arg:
286         # - is there anything that wants the answer? if so, answer
287         # - else, die
288 }
289
290 sub test_rcs_update {
291         # can it assume we're under CVS control? or must it check?
292         # anything else worth testing?
293 }
294
295 sub test_rcs_prepedit {
296         # can it assume we're under CVS control? or must it check?
297         # for existing file, returns latest revision in repo
298         # - what's this used for? should it return latest revision in checkout?
299         # for new file, returns empty string
300 }
301
302 sub test_rcs_commit {
303         # can it assume we're under CVS control? or must it check?
304         # if someone else changed the page since rcs_prepedit was called:
305         # - try to merge into our working copy
306         # - if merge succeeds, proceed to commit
307         # - else, return page content with the conflict markers in it
308         # commit:
309         # - if success, return undef
310         # - else, revert + return content with the conflict markers in it
311         # git.pm receives "session" param -- useful here?
312         # web commits start with "web commit {by,from} "
313         # seeing File::chdir errors on commit?
314 }
315
316 sub test_rcs_commit_staged {
317         # if commit succeeds, return undef
318         # else, warn and return error message (really? or just non-undef?)
319 }
320
321 sub test_rcs_add {
322         my $dir1 = "test3";
323         my $dir2 = "test4/test5";
324         ok(
325                 mkdir($config{srcdir} . "/$dir1"),
326                 qq{can make $dir1},
327         );
328         IkiWiki::rcs_add($dir1);
329         IkiWiki::rcs_commit(
330                 file => $dir1,
331                 message => "shouldn't happen",
332                 token => "oom",
333         );
334
335         # can it assume we're under CVS control? or must it check?
336         # add a top-level text file
337         # - rcs_commit it
338         # - inspect recentchanges: new change, no -kb
339         # add a top-level dir
340         # - test mustn't hang (does it hang if we comment out genwrapper?)
341         # - inspect recentchanges: no new change
342         # - rcs_commit it
343         # - reinspect recentchanges: still no new change
344         # add a text file in that dir
345         # - rcs_commit_staged
346         # - inspect recentchanges: new change, no -kb
347         # add a top-level dir + add a binary file in it
348         # - rcs_commit_staged
349         # - inspect recentchanges: new change, yes -kb
350         # add a top-level dir + subdir + add one text and one binary file in it
351         # - rcs_commit_staged
352         # - inspect recentchanges: one new change, two files, one -kb, one not
353
354         # extract method: filetype-guessing
355         # add a binary file, remove it, add a text file by same name, no -kb?
356         # add a text file, remove it, add a binary file by same name, -kb?
357 }
358
359 sub test_rcs_remove {
360         # can it assume we're under CVS control? or must it check?
361         # remove a top-level file
362         # - rcs_commit
363         # - inspect recentchanges: one new change, file removed
364         # remove two files (in different dirs)
365         # - rcs_commit_staged
366         # - inspect recentchanges: one new change, both files removed
367 }
368
369 sub test_rcs_rename {
370         # can it assume we're under CVS control? or must it check?
371         # rename a file in the same dir
372         # - rcs_commit_staged
373         # - inspect recentchanges: one new change, one file removed, one added
374         # rename a file into a different dir
375         # - rcs_commit_staged
376         # - inspect recentchanges: one new change, one file removed, one added
377         # rename a file into a not-yet-existing dir
378         # - rcs_commit_staged
379         # - inspect recentchanges: one new change, one file removed, one added
380         # is it safe to use "mv"? what if $dest is somehow outside the wiki?
381 }
382
383 sub test_rcs_recentchanges {
384         # can it assume we're under CVS control? or must it check?
385         # don't worry whether we're called with a number (we always are)
386         # other rcs tests already inspect much of the returned structure
387         # CVS commits say "cvs" and get the right committer
388         # web commits say "web" and get the right committer
389         # - and don't start with "web commit {by,from} "
390         # "nickname" -- can we ever meaningfully set this?
391
392         # prefer log_accum, then cvsps, else die
393         # run the high-level recentchanges tests 2x (once for each method)
394         # - including in other test subs that check recentchanges?
395 }
396
397 sub test_rcs_diff {
398         # can it assume we're under CVS control? or must it check?
399         # in list context, return all lines (with \n), up to $maxlines if set
400         # in scalar context, return the whole diff, up to $maxlines if set
401 }
402
403 sub test_rcs_getctime {
404         # can it assume we're under CVS control? or must it check?
405         # given a file, find its creation time, else return 0
406         # first implement in the obvious way
407         # then cache
408 }
409
410 sub test_rcs_getmtime {
411         # can it assume we're under CVS control? or must it check?
412         # given a file, find its modification time, else return 0
413         # first implement in the obvious way
414         # then cache
415 }
416
417 sub test_rcs_receive {
418         pass(q{rcs_receive doesn't make sense for CVS});
419 }
420
421 sub test_rcs_preprevert {
422         # can it assume we're under CVS control? or must it check?
423         # given a patchset number, return structure describing what'd happen:
424         # - see doc/plugins/write.mdwn:rcs_receive()
425         # don't forget about attachments
426 }
427
428 sub test_rcs_revert {
429         # can it assume we're under CVS control? or must it check?
430         # given a patchset number, stage the revert for rcs_commit_staged()
431         # if commit succeeds, return undef
432         # else, warn and return error message (really? or just non-undef?)
433 }
434
435 sub main {
436         my $test_methods = defined $ENV{TEST_METHOD} 
437                          ? $ENV{TEST_METHOD}
438                          : $default_test_methods;
439
440         _startup($test_methods eq $default_test_methods);
441         _runtests(_get_matching_test_subs($test_methods));
442         _shutdown($test_methods eq $default_test_methods);
443 }
444
445 main();