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