]> Sergey Matveev's repositories - public-inbox.git/blob - t/v2reindex.t
v2writable: reindex handles 3-headered monsters
[public-inbox.git] / t / v2reindex.t
1 # Copyright (C) 2018-2019 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 use strict;
4 use warnings;
5 use Test::More;
6 use PublicInbox::MIME;
7 use PublicInbox::ContentId qw(content_digest);
8 use File::Temp qw/tempdir/;
9 use File::Path qw(remove_tree);
10 require './t/common.perl';
11 require_git(2.6);
12
13 foreach my $mod (qw(DBD::SQLite Search::Xapian)) {
14         eval "require $mod";
15         plan skip_all => "$mod missing for v2reindex.t" if $@;
16 }
17 use_ok 'PublicInbox::V2Writable';
18 my $inboxdir = tempdir('pi-v2reindex-XXXXXX', TMPDIR => 1, CLEANUP => 1);
19 my $ibx_config = {
20         inboxdir => $inboxdir,
21         name => 'test-v2writable',
22         version => 2,
23         -primary_address => 'test@example.com',
24         indexlevel => 'full',
25 };
26 my $agpl = eval {
27         open my $fh, '<', 'COPYING' or die "can't open COPYING: $!";
28         local $/;
29         <$fh>;
30 };
31 $agpl or die "AGPL or die :P\n";
32 my $phrase = q("defending all users' freedom");
33 my $mime = PublicInbox::MIME->create(
34         header => [
35                 From => 'a@example.com',
36                 To => 'test@example.com',
37                 Subject => 'this is a subject',
38                 Date => 'Fri, 02 Oct 1993 00:00:00 +0000',
39         ],
40         body => $agpl,
41 );
42 my $minmax;
43 my $msgmap;
44 my ($mark1, $mark2, $mark3, $mark4);
45 {
46         my %config = %$ibx_config;
47         my $ibx = PublicInbox::Inbox->new(\%config);
48         my $im = PublicInbox::V2Writable->new($ibx, {nproc => 1});
49         my $im0 = $im->importer();
50         foreach my $i (1..10) {
51                 $mime->header_set('Message-Id', "<$i\@example.com>");
52                 ok($im->add($mime), "message $i added");
53                 if ($i == 4) {
54                         $mark1 = $im0->get_mark($im0->{tip});
55                         $im->remove($mime);
56                         $mark2 = $im0->get_mark($im0->{tip});
57                 }
58         }
59
60         if ('test remove later') {
61                 $mark3 = $im0->get_mark($im0->{tip});
62                 $mime->header_set('Message-Id', "<5\@example.com>");
63                 $im->remove($mime);
64                 $mark4 = $im0->get_mark($im0->{tip});
65         }
66
67         $im->done;
68         $minmax = [ $ibx->mm->minmax ];
69         ok(defined $minmax->[0] && defined $minmax->[1], 'minmax defined');
70         is_deeply($minmax, [ 1, 10 ], 'minmax as expected');
71         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
72
73         my ($min, $max) = @$minmax;
74         $msgmap = $ibx->mm->msg_range(\$min, $max);
75         is_deeply($msgmap, [
76                           [1, '1@example.com' ],
77                           [2, '2@example.com' ],
78                           [3, '3@example.com' ],
79                           [6, '6@example.com' ],
80                           [7, '7@example.com' ],
81                           [8, '8@example.com' ],
82                           [9, '9@example.com' ],
83                           [10, '10@example.com' ],
84                   ], 'msgmap as expected');
85 }
86
87 {
88         my %config = %$ibx_config;
89         my $ibx = PublicInbox::Inbox->new(\%config);
90         my $im = PublicInbox::V2Writable->new($ibx, 1);
91         eval { $im->index_sync({reindex => 1}) };
92         is($@, '', 'no error from reindexing');
93         $im->done;
94
95         delete $ibx->{mm};
96         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
97         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
98
99         my ($min, $max) = $ibx->mm->minmax;
100         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
101 }
102
103 my $xap = "$inboxdir/xap".PublicInbox::Search::SCHEMA_VERSION();
104 remove_tree($xap);
105 ok(!-d $xap, 'Xapian directories removed');
106 {
107         my %config = %$ibx_config;
108         my $ibx = PublicInbox::Inbox->new(\%config);
109         my $im = PublicInbox::V2Writable->new($ibx, 1);
110         eval { $im->index_sync({reindex => 1}) };
111         is($@, '', 'no error from reindexing');
112         $im->done;
113         ok(-d $xap, 'Xapian directories recreated');
114
115         delete $ibx->{mm};
116         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
117         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
118
119         my ($min, $max) = $ibx->mm->minmax;
120         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
121 }
122
123 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
124 remove_tree($xap);
125 ok(!-d $xap, 'Xapian directories removed again');
126 {
127         my @warn;
128         local $SIG{__WARN__} = sub { push @warn, @_ };
129         my %config = %$ibx_config;
130         my $ibx = PublicInbox::Inbox->new(\%config);
131         my $im = PublicInbox::V2Writable->new($ibx, 1);
132         eval { $im->index_sync({reindex => 1}) };
133         is($@, '', 'no error from reindexing without msgmap');
134         is(scalar(@warn), 0, 'no warnings from reindexing');
135         $im->done;
136         ok(-d $xap, 'Xapian directories recreated');
137         delete $ibx->{mm};
138         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
139         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
140
141         my ($min, $max) = $ibx->mm->minmax;
142         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
143 }
144
145 my %sizes;
146 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
147 remove_tree($xap);
148 ok(!-d $xap, 'Xapian directories removed again');
149 {
150         my @warn;
151         local $SIG{__WARN__} = sub { push @warn, @_ };
152         my %config = %$ibx_config;
153         my $ibx = PublicInbox::Inbox->new(\%config);
154         my $im = PublicInbox::V2Writable->new($ibx, 1);
155         eval { $im->index_sync({reindex => 1}) };
156         is($@, '', 'no error from reindexing without msgmap');
157         is_deeply(\@warn, [], 'no warnings');
158         $im->done;
159         ok(-d $xap, 'Xapian directories recreated');
160         delete $ibx->{mm};
161         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
162         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
163         my $mset = $ibx->search->query($phrase, {mset=>1});
164         isnt($mset->size, 0, "phrase search succeeds on indexlevel=full");
165         for (<"$xap/*/*">) { $sizes{$ibx->{indexlevel}} += -s _ if -f $_ }
166
167         my ($min, $max) = $ibx->mm->minmax;
168         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
169 }
170
171 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
172 remove_tree($xap);
173 ok(!-d $xap, 'Xapian directories removed again');
174 {
175         my @warn;
176         local $SIG{__WARN__} = sub { push @warn, @_ };
177         my %config = %$ibx_config;
178         $config{indexlevel} = 'medium';
179         my $ibx = PublicInbox::Inbox->new(\%config);
180         my $im = PublicInbox::V2Writable->new($ibx);
181         eval { $im->index_sync({reindex => 1}) };
182         is($@, '', 'no error from reindexing without msgmap');
183         is_deeply(\@warn, [], 'no warnings');
184         $im->done;
185         ok(-d $xap, 'Xapian directories recreated');
186         delete $ibx->{mm};
187         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
188         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
189
190         if (0) {
191                 # not sure why, but Xapian seems to fallback to terms and
192                 # phrase searches still work
193                 delete $ibx->{search};
194                 my $mset = $ibx->search->query($phrase, {mset=>1});
195                 is($mset->size, 0, 'phrase search does not work on medium');
196         }
197         my $words = $phrase;
198         $words =~ tr/"'//d;
199         my $mset = $ibx->search->query($words, {mset=>1});
200         isnt($mset->size, 0, "normal search works on indexlevel=medium");
201         for (<"$xap/*/*">) { $sizes{$ibx->{indexlevel}} += -s _ if -f $_ }
202
203         ok($sizes{full} > $sizes{medium}, 'medium is smaller than full');
204
205
206         my ($min, $max) = $ibx->mm->minmax;
207         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
208 }
209
210 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
211 remove_tree($xap);
212 ok(!-d $xap, 'Xapian directories removed again');
213 {
214         my @warn;
215         local $SIG{__WARN__} = sub { push @warn, @_ };
216         my %config = %$ibx_config;
217         $config{indexlevel} = 'basic';
218         my $ibx = PublicInbox::Inbox->new(\%config);
219         my $im = PublicInbox::V2Writable->new($ibx);
220         eval { $im->index_sync({reindex => 1}) };
221         is($@, '', 'no error from reindexing without msgmap');
222         is_deeply(\@warn, [], 'no warnings');
223         $im->done;
224         ok(-d $xap, 'Xapian directories recreated');
225         delete $ibx->{mm};
226         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
227         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
228
229         isnt($ibx->search, 'no search for basic');
230
231         for (<"$xap/*/*">) { $sizes{$ibx->{indexlevel}} += -s _ if -f $_ }
232         ok($sizes{medium} > $sizes{basic}, 'basic is smaller than medium');
233
234         my ($min, $max) = $ibx->mm->minmax;
235         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
236 }
237
238
239 # An incremental indexing test
240 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
241 remove_tree($xap);
242 ok(!-d $xap, 'Xapian directories removed again');
243 {
244         my @warn;
245         local $SIG{__WARN__} = sub { push @warn, @_ };
246         my %config = %$ibx_config;
247         my $ibx = PublicInbox::Inbox->new(\%config);
248         # mark1 4 simple additions in the same index_sync
249         $ibx->{ref_head} = $mark1;
250         my $im = PublicInbox::V2Writable->new($ibx);
251         eval { $im->index_sync() };
252         is($@, '', 'no error from reindexing without msgmap');
253         is_deeply(\@warn, [], 'no warnings');
254         $im->done;
255         my ($min, $max) = $ibx->mm->minmax;
256         is($min, 1, 'min as expected');
257         is($max, 4, 'max as expected');
258         is($ibx->mm->num_highwater, 4, 'num_highwater as expected');
259         is_deeply($ibx->mm->msg_range(\$min, $max),
260                   [
261                    [1, '1@example.com' ],
262                    [2, '2@example.com' ],
263                    [3, '3@example.com' ],
264                    [4, '4@example.com' ],
265                   ], 'msgmap as expected' );
266 }
267 {
268         my @warn;
269         local $SIG{__WARN__} = sub { push @warn, @_ };
270         my %config = %$ibx_config;
271         my $ibx = PublicInbox::Inbox->new(\%config);
272         # mark2 A delete separated from an add in the same index_sync
273         $ibx->{ref_head} = $mark2;
274         my $im = PublicInbox::V2Writable->new($ibx);
275         eval { $im->index_sync() };
276         is($@, '', 'no error from reindexing without msgmap');
277         is_deeply(\@warn, [], 'no warnings');
278         $im->done;
279         my ($min, $max) = $ibx->mm->minmax;
280         is($min, 1, 'min as expected');
281         is($max, 3, 'max as expected');
282         is($ibx->mm->num_highwater, 4, 'num_highwater as expected');
283         is_deeply($ibx->mm->msg_range(\$min, $max),
284                   [
285                    [1, '1@example.com' ],
286                    [2, '2@example.com' ],
287                    [3, '3@example.com' ],
288                   ], 'msgmap as expected' );
289 }
290 {
291         my @warn;
292         local $SIG{__WARN__} = sub { push @warn, @_ };
293         my %config = %$ibx_config;
294         my $ibx = PublicInbox::Inbox->new(\%config);
295         # mark3 adds following the delete at mark2
296         $ibx->{ref_head} = $mark3;
297         my $im = PublicInbox::V2Writable->new($ibx);
298         eval { $im->index_sync() };
299         is($@, '', 'no error from reindexing without msgmap');
300         is_deeply(\@warn, [], 'no warnings');
301         $im->done;
302         my ($min, $max) = $ibx->mm->minmax;
303         is($min, 1, 'min as expected');
304         is($max, 10, 'max as expected');
305         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
306         is_deeply($ibx->mm->msg_range(\$min, $max),
307                   [
308                    [1, '1@example.com' ],
309                    [2, '2@example.com' ],
310                    [3, '3@example.com' ],
311                    [5, '5@example.com' ],
312                    [6, '6@example.com' ],
313                    [7, '7@example.com' ],
314                    [8, '8@example.com' ],
315                    [9, '9@example.com' ],
316                    [10, '10@example.com' ],
317                   ], 'msgmap as expected' );
318 }
319 {
320         my @warn;
321         local $SIG{__WARN__} = sub { push @warn, @_ };
322         my %config = %$ibx_config;
323         my $ibx = PublicInbox::Inbox->new(\%config);
324         # mark4 A delete of an older message
325         $ibx->{ref_head} = $mark4;
326         my $im = PublicInbox::V2Writable->new($ibx);
327         eval { $im->index_sync() };
328         is($@, '', 'no error from reindexing without msgmap');
329         is_deeply(\@warn, [], 'no warnings');
330         $im->done;
331         my ($min, $max) = $ibx->mm->minmax;
332         is($min, 1, 'min as expected');
333         is($max, 10, 'max as expected');
334         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
335         is_deeply($ibx->mm->msg_range(\$min, $max),
336                   [
337                    [1, '1@example.com' ],
338                    [2, '2@example.com' ],
339                    [3, '3@example.com' ],
340                    [6, '6@example.com' ],
341                    [7, '7@example.com' ],
342                    [8, '8@example.com' ],
343                    [9, '9@example.com' ],
344                    [10, '10@example.com' ],
345                   ], 'msgmap as expected' );
346 }
347
348
349 # Another incremental indexing test
350 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
351 remove_tree($xap);
352 ok(!-d $xap, 'Xapian directories removed again');
353 {
354         my @warn;
355         local $SIG{__WARN__} = sub { push @warn, @_ };
356         my %config = %$ibx_config;
357         my $ibx = PublicInbox::Inbox->new(\%config);
358         # mark2 an add and it's delete in the same index_sync
359         $ibx->{ref_head} = $mark2;
360         my $im = PublicInbox::V2Writable->new($ibx);
361         eval { $im->index_sync() };
362         is($@, '', 'no error from reindexing without msgmap');
363         is_deeply(\@warn, [], 'no warnings');
364         $im->done;
365         my ($min, $max) = $ibx->mm->minmax;
366         is($min, 1, 'min as expected');
367         is($max, 3, 'max as expected');
368         is($ibx->mm->num_highwater, 4, 'num_highwater as expected');
369         is_deeply($ibx->mm->msg_range(\$min, $max),
370                   [
371                    [1, '1@example.com' ],
372                    [2, '2@example.com' ],
373                    [3, '3@example.com' ],
374                   ], 'msgmap as expected' );
375 }
376 {
377         my @warn;
378         local $SIG{__WARN__} = sub { push @warn, @_ };
379         my %config = %$ibx_config;
380         my $ibx = PublicInbox::Inbox->new(\%config);
381         # mark3 adds following the delete at mark2
382         $ibx->{ref_head} = $mark3;
383         my $im = PublicInbox::V2Writable->new($ibx);
384         eval { $im->index_sync() };
385         is($@, '', 'no error from reindexing without msgmap');
386         is_deeply(\@warn, [], 'no warnings');
387         $im->done;
388         my ($min, $max) = $ibx->mm->minmax;
389         is($min, 1, 'min as expected');
390         is($max, 10, 'max as expected');
391         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
392         is_deeply($ibx->mm->msg_range(\$min, $max),
393                   [
394                    [1, '1@example.com' ],
395                    [2, '2@example.com' ],
396                    [3, '3@example.com' ],
397                    [5, '5@example.com' ],
398                    [6, '6@example.com' ],
399                    [7, '7@example.com' ],
400                    [8, '8@example.com' ],
401                    [9, '9@example.com' ],
402                    [10, '10@example.com' ],
403                   ], 'msgmap as expected' );
404 }
405 {
406         my @warn;
407         local $SIG{__WARN__} = sub { push @warn, @_ };
408         my %config = %$ibx_config;
409         my $ibx = PublicInbox::Inbox->new(\%config);
410         # mark4 A delete of an older message
411         $ibx->{ref_head} = $mark4;
412         my $im = PublicInbox::V2Writable->new($ibx);
413         eval { $im->index_sync() };
414         is($@, '', 'no error from reindexing without msgmap');
415         is_deeply(\@warn, [], 'no warnings');
416         $im->done;
417         my ($min, $max) = $ibx->mm->minmax;
418         is($min, 1, 'min as expected');
419         is($max, 10, 'max as expected');
420         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
421         is_deeply($ibx->mm->msg_range(\$min, $max),
422                   [
423                    [1, '1@example.com' ],
424                    [2, '2@example.com' ],
425                    [3, '3@example.com' ],
426                    [6, '6@example.com' ],
427                    [7, '7@example.com' ],
428                    [8, '8@example.com' ],
429                    [9, '9@example.com' ],
430                    [10, '10@example.com' ],
431                   ], 'msgmap as expected' );
432 }
433
434 # A real example from linux-renesas-soc on lore where a 3-headed monster
435 # of a message has 3 sets of common headers.  Another normal message
436 # previously existed with a single Message-ID that conflicts with one
437 # of the Message-IDs in the 3-headed monster.
438 {
439         my @warn;
440         local $SIG{__WARN__} = sub { push @warn, @_ };
441         my %config = %$ibx_config;
442         $config{indexlevel} = 'basic';
443         my $ibx = PublicInbox::Inbox->new(\%config);
444         my $im = PublicInbox::V2Writable->new($ibx);
445         my $m3 = PublicInbox::MIME->new(<<'EOF');
446 Date: Tue, 24 May 2016 14:34:22 -0700 (PDT)
447 Message-Id: <20160524.143422.552507610109476444.d@example.com>
448 To: t@example.com
449 Cc: c@example.com
450 Subject: Re: [PATCH v2 2/2]
451 From: <f@example.com>
452 In-Reply-To: <1463825855-7363-2-git-send-email-y@example.com>
453 References: <1463825855-7363-1-git-send-email-y@example.com>
454         <1463825855-7363-2-git-send-email-y@example.com>
455 Date: Wed, 25 May 2016 10:01:51 +0900
456 From: h@example.com
457 To: g@example.com
458 Cc: m@example.com
459 Subject: Re: [PATCH]
460 Message-ID: <20160525010150.GD7292@example.com>
461 References: <1463498133-23918-1-git-send-email-g+r@example.com>
462 In-Reply-To: <1463498133-23918-1-git-send-email-g+r@example.com>
463 From: s@example.com
464 To: h@example.com
465 Cc: m@example.com
466 Subject: [PATCH 12/13]
467 Date: Wed, 01 Jun 2016 01:32:35 +0300
468 Message-ID: <1923946.Jvi0TDUXFC@wasted.example.com>
469 In-Reply-To: <13205049.n7pM8utpHF@wasted.example.com>
470 References: <13205049.n7pM8utpHF@wasted.example.com>
471
472 Somehow we got a message with 3 sets of headers into one
473 message, could've been something broken on the archiver side.
474 EOF
475
476         my $m1 = PublicInbox::MIME->new(<<'EOF');
477 From: a@example.com
478 To: t@example.com
479 Subject: [PATCH 12/13]
480 Date: Wed, 01 Jun 2016 01:32:35 +0300
481 Message-ID: <1923946.Jvi0TDUXFC@wasted.example.com>
482 In-Reply-To: <13205049.n7pM8utpHF@wasted.example.com>
483 References: <13205049.n7pM8utpHF@wasted.example.com>
484
485 This is probably one of the original messages
486
487 EOF
488         $im->add($m1);
489         $im->add($m3);
490         $im->done;
491         remove_tree($xap);
492         eval { $im->index_sync() };
493         is($@, '', 'no error from initial indexing');
494         is_deeply(\@warn, [], 'no warnings from initial index');
495         eval { $im->index_sync({reindex=>1}) };
496         is($@, '', 'no error from reindexing after reused Message-ID (x3)');
497         is_deeply(\@warn, [], 'no warnings on reindex');
498 }
499
500 done_testing();