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