lei/store: stop shard workers + cat-file on idle
[public-inbox.git] / t / v2reindex.t
blob8c49e15409b4de0e6604e1c105f05d5d8d07e05e
1 # Copyright (C) all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 use strict; use v5.10.1; use PublicInbox::TestCommon;
4 use PublicInbox::Eml;
5 use PublicInbox::ContentHash qw(content_digest);
6 use File::Path qw(remove_tree);
7 require_git(2.6);
8 require_mods(qw(DBD::SQLite Xapian));
9 use_ok 'PublicInbox::V2Writable';
10 use_ok 'PublicInbox::OverIdx';
11 my ($inboxdir, $for_destroy) = tmpdir();
12 my $ibx_config = {
13         inboxdir => $inboxdir,
14         name => 'test-v2writable',
15         version => 2,
16         -primary_address => 'test@example.com',
17         indexlevel => 'full',
18         -no_fsync => 1,
20 my $agpl = do {
21         open my $fh, '<', 'COPYING' or die "can't open COPYING: $!";
22         local $/;
23         <$fh>;
25 my $phrase = q("defending all users' freedom");
26 my $mime = PublicInbox::Eml->new(<<'EOF'.$agpl);
27 From: a@example.com
28 To: test@example.com
29 Subject: this is a subject
30 Date: Fri, 02 Oct 1993 00:00:00 +0000
32 EOF
33 my $minmax;
34 my $msgmap;
35 my ($mark1, $mark2, $mark3, $mark4);
37         my %config = %$ibx_config;
38         my $ibx = PublicInbox::Inbox->new(\%config);
39         my $im = PublicInbox::V2Writable->new($ibx, {nproc => 1});
40         my $im0 = $im->importer(0);
41         foreach my $i (1..10) {
42                 $mime->header_set('Message-Id', "<$i\@example.com>");
43                 ok($im->add($mime), "message $i added");
44                 if ($i == 4) {
45                         $mark1 = $im0->get_mark($im0->{tip});
46                         $im->remove($mime);
47                         $mark2 = $im0->get_mark($im0->{tip});
48                 }
49         }
51         if ('test remove later') {
52                 $mark3 = $im0->get_mark($im0->{tip});
53                 $mime->header_set('Message-Id', "<5\@example.com>");
54                 $im->remove($mime);
55                 $mark4 = $im0->get_mark($im0->{tip});
56         }
58         $im->done;
59         $minmax = [ $ibx->mm->minmax ];
60         ok(defined $minmax->[0] && defined $minmax->[1], 'minmax defined');
61         is_deeply($minmax, [ 1, 10 ], 'minmax as expected');
62         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
64         my ($min, $max) = @$minmax;
65         $msgmap = $ibx->mm->msg_range(\$min, $max);
66         is_deeply($msgmap, [
67                           [1, '1@example.com' ],
68                           [2, '2@example.com' ],
69                           [3, '3@example.com' ],
70                           [6, '6@example.com' ],
71                           [7, '7@example.com' ],
72                           [8, '8@example.com' ],
73                           [9, '9@example.com' ],
74                           [10, '10@example.com' ],
75                   ], 'msgmap as expected');
79         my %config = %$ibx_config;
80         my $ibx = PublicInbox::Inbox->new(\%config);
81         my $im = PublicInbox::V2Writable->new($ibx, 1);
82         eval { $im->index_sync({reindex => 1}) };
83         is($@, '', 'no error from reindexing');
84         $im->done;
86         delete $ibx->{mm};
87         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
88         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
90         my ($min, $max) = $ibx->mm->minmax;
91         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
94 my $xap = "$inboxdir/xap".PublicInbox::Search::SCHEMA_VERSION();
95 remove_tree($xap);
96 ok(!-d $xap, 'Xapian directories removed');
98         my %config = %$ibx_config;
99         my $ibx = PublicInbox::Inbox->new(\%config);
100         my $im = PublicInbox::V2Writable->new($ibx, 1);
101         eval { $im->index_sync({reindex => 1}) };
102         is($@, '', 'no error from reindexing');
103         $im->done;
104         ok(-d $xap, 'Xapian directories recreated');
106         delete $ibx->{mm};
107         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
108         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
110         my ($min, $max) = $ibx->mm->minmax;
111         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
114 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
115 remove_tree($xap);
116 ok(!-d $xap, 'Xapian directories removed again');
118         my @warn;
119         local $SIG{__WARN__} = sub { push @warn, @_ };
120         my %config = %$ibx_config;
121         my $ibx = PublicInbox::Inbox->new(\%config);
122         my $im = PublicInbox::V2Writable->new($ibx, 1);
123         eval { $im->index_sync({reindex => 1}) };
124         is($@, '', 'no error from reindexing without msgmap');
125         is(scalar(@warn), 0, 'no warnings from reindexing');
126         $im->done;
127         ok(-d $xap, 'Xapian directories recreated');
128         delete $ibx->{mm};
129         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
130         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
132         my ($min, $max) = $ibx->mm->minmax;
133         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
136 my %sizes;
137 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
138 remove_tree($xap);
139 ok(!-d $xap, 'Xapian directories removed again');
141         my @warn;
142         local $SIG{__WARN__} = sub { push @warn, @_ };
143         my %config = %$ibx_config;
144         my $ibx = PublicInbox::Inbox->new(\%config);
145         my $im = PublicInbox::V2Writable->new($ibx, 1);
146         eval { $im->index_sync({reindex => 1}) };
147         is($@, '', 'no error from reindexing without msgmap');
148         is_deeply(\@warn, [], 'no warnings');
149         $im->done;
150         ok(-d $xap, 'Xapian directories recreated');
151         delete $ibx->{mm};
152         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
153         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
154         my $mset = $ibx->search->mset($phrase);
155         isnt($mset->size, 0, "phrase search succeeds on indexlevel=full");
156         for (glob("$xap/*/*")) { $sizes{$ibx->{indexlevel}} += -s _ if -f $_ }
158         my ($min, $max) = $ibx->mm->minmax;
159         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
162 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
163 remove_tree($xap);
164 ok(!-d $xap, 'Xapian directories removed again');
166         my @warn;
167         local $SIG{__WARN__} = sub { push @warn, @_ };
168         my %config = %$ibx_config;
169         $config{indexlevel} = 'medium';
170         my $ibx = PublicInbox::Inbox->new(\%config);
171         my $im = PublicInbox::V2Writable->new($ibx);
172         eval { $im->index_sync({reindex => 1}) };
173         is($@, '', 'no error from reindexing without msgmap');
174         is_deeply(\@warn, [], 'no warnings');
175         $im->done;
176         ok(-d $xap, 'Xapian directories recreated');
177         delete $ibx->{mm};
178         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
179         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
181         if (0) {
182                 # not sure why, but Xapian seems to fallback to terms and
183                 # phrase searches still work
184                 delete $ibx->{search};
185                 my $mset = $ibx->search->mset($phrase);
186                 is($mset->size, 0, 'phrase search does not work on medium');
187         }
188         my $words = $phrase;
189         $words =~ tr/"'//d;
190         my $mset = $ibx->search->mset($words);
191         isnt($mset->size, 0, "normal search works on indexlevel=medium");
192         for (glob("$xap/*/*")) { $sizes{$ibx->{indexlevel}} += -s _ if -f $_ }
194         ok($sizes{full} > $sizes{medium}, 'medium is smaller than full');
197         my ($min, $max) = $ibx->mm->minmax;
198         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
201 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
202 remove_tree($xap);
203 ok(!-d $xap, 'Xapian directories removed again');
205         my @warn;
206         local $SIG{__WARN__} = sub { push @warn, @_ };
207         my %config = %$ibx_config;
208         $config{indexlevel} = 'basic';
209         my $ibx = PublicInbox::Inbox->new(\%config);
210         my $im = PublicInbox::V2Writable->new($ibx);
211         eval { $im->index_sync({reindex => 1}) };
212         is($@, '', 'no error from reindexing without msgmap');
213         is_deeply(\@warn, [], 'no warnings');
214         $im->done;
215         ok(-d $xap, 'Xapian directories recreated');
216         delete $ibx->{mm};
217         is_deeply([ $ibx->mm->minmax ], $minmax, 'minmax unchanged');
218         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
220         isnt($ibx->search, 'no search for basic');
222         for (glob("$xap/*/*")) { $sizes{$ibx->{indexlevel}} += -s _ if -f $_ }
223         ok($sizes{medium} > $sizes{basic}, 'basic is smaller than medium');
225         my ($min, $max) = $ibx->mm->minmax;
226         is_deeply($ibx->mm->msg_range(\$min, $max), $msgmap, 'msgmap unchanged');
230 # An incremental indexing test
231 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
232 remove_tree($xap);
233 ok(!-d $xap, 'Xapian directories removed again');
235         my @warn;
236         local $SIG{__WARN__} = sub { push @warn, @_ };
237         my %config = %$ibx_config;
238         my $ibx = PublicInbox::Inbox->new(\%config);
239         # mark1 4 simple additions in the same index_sync
240         $ibx->{ref_head} = $mark1;
241         my $im = PublicInbox::V2Writable->new($ibx);
242         eval { $im->index_sync() };
243         is($@, '', 'no error from reindexing without msgmap');
244         is_deeply(\@warn, [], 'no warnings');
245         $im->done;
246         my ($min, $max) = $ibx->mm->minmax;
247         is($min, 1, 'min as expected');
248         is($max, 4, 'max as expected');
249         is($ibx->mm->num_highwater, 4, 'num_highwater as expected');
250         is_deeply($ibx->mm->msg_range(\$min, $max),
251                   [
252                    [1, '1@example.com' ],
253                    [2, '2@example.com' ],
254                    [3, '3@example.com' ],
255                    [4, '4@example.com' ],
256                   ], 'msgmap as expected' );
259         my @warn;
260         local $SIG{__WARN__} = sub { push @warn, @_ };
261         my %config = %$ibx_config;
262         my $ibx = PublicInbox::Inbox->new(\%config);
263         # mark2 A delete separated from an add in the same index_sync
264         $ibx->{ref_head} = $mark2;
265         my $im = PublicInbox::V2Writable->new($ibx);
266         eval { $im->index_sync() };
267         is($@, '', 'no error from reindexing without msgmap');
268         is_deeply(\@warn, [], 'no warnings');
269         $im->done;
270         my ($min, $max) = $ibx->mm->minmax;
271         is($min, 1, 'min as expected');
272         is($max, 3, 'max as expected');
273         is($ibx->mm->num_highwater, 4, 'num_highwater as expected');
274         is_deeply($ibx->mm->msg_range(\$min, $max),
275                   [
276                    [1, '1@example.com' ],
277                    [2, '2@example.com' ],
278                    [3, '3@example.com' ],
279                   ], 'msgmap as expected' );
282         my @warn;
283         local $SIG{__WARN__} = sub { push @warn, @_ };
284         my %config = %$ibx_config;
285         my $ibx = PublicInbox::Inbox->new(\%config);
286         # mark3 adds following the delete at mark2
287         $ibx->{ref_head} = $mark3;
288         my $im = PublicInbox::V2Writable->new($ibx);
289         eval { $im->index_sync() };
290         is($@, '', 'no error from reindexing without msgmap');
291         is_deeply(\@warn, [], 'no warnings');
292         $im->done;
293         my ($min, $max) = $ibx->mm->minmax;
294         is($min, 1, 'min as expected');
295         is($max, 10, 'max as expected');
296         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
297         is_deeply($ibx->mm->msg_range(\$min, $max),
298                   [
299                    [1, '1@example.com' ],
300                    [2, '2@example.com' ],
301                    [3, '3@example.com' ],
302                    [5, '5@example.com' ],
303                    [6, '6@example.com' ],
304                    [7, '7@example.com' ],
305                    [8, '8@example.com' ],
306                    [9, '9@example.com' ],
307                    [10, '10@example.com' ],
308                   ], 'msgmap as expected' );
311         my @warn;
312         local $SIG{__WARN__} = sub { push @warn, @_ };
313         my %config = %$ibx_config;
314         my $ibx = PublicInbox::Inbox->new(\%config);
315         # mark4 A delete of an older message
316         $ibx->{ref_head} = $mark4;
317         my $im = PublicInbox::V2Writable->new($ibx);
318         eval { $im->index_sync() };
319         is($@, '', 'no error from reindexing without msgmap');
320         is_deeply(\@warn, [], 'no warnings');
321         $im->done;
322         my ($min, $max) = $ibx->mm->minmax;
323         is($min, 1, 'min as expected');
324         is($max, 10, 'max as expected');
325         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
326         is_deeply($ibx->mm->msg_range(\$min, $max),
327                   [
328                    [1, '1@example.com' ],
329                    [2, '2@example.com' ],
330                    [3, '3@example.com' ],
331                    [6, '6@example.com' ],
332                    [7, '7@example.com' ],
333                    [8, '8@example.com' ],
334                    [9, '9@example.com' ],
335                    [10, '10@example.com' ],
336                   ], 'msgmap as expected' );
340 # Another incremental indexing test
341 ok(unlink "$inboxdir/msgmap.sqlite3", 'remove msgmap');
342 remove_tree($xap);
343 ok(!-d $xap, 'Xapian directories removed again');
345         my @warn;
346         local $SIG{__WARN__} = sub { push @warn, @_ };
347         my %config = %$ibx_config;
348         my $ibx = PublicInbox::Inbox->new(\%config);
349         # mark2 an add and it's delete in the same index_sync
350         $ibx->{ref_head} = $mark2;
351         my $im = PublicInbox::V2Writable->new($ibx);
352         eval { $im->index_sync() };
353         is($@, '', 'no error from reindexing without msgmap');
354         is_deeply(\@warn, [], 'no warnings');
355         $im->done;
356         my ($min, $max) = $ibx->mm->minmax;
357         is($min, 1, 'min as expected');
358         is($max, 3, 'max as expected');
359         is($ibx->mm->num_highwater, 4, 'num_highwater as expected');
360         is_deeply($ibx->mm->msg_range(\$min, $max),
361                   [
362                    [1, '1@example.com' ],
363                    [2, '2@example.com' ],
364                    [3, '3@example.com' ],
365                   ], 'msgmap as expected' );
368         my @warn;
369         local $SIG{__WARN__} = sub { push @warn, @_ };
370         my %config = %$ibx_config;
371         my $ibx = PublicInbox::Inbox->new(\%config);
372         # mark3 adds following the delete at mark2
373         $ibx->{ref_head} = $mark3;
374         my $im = PublicInbox::V2Writable->new($ibx);
375         eval { $im->index_sync() };
376         is($@, '', 'no error from reindexing without msgmap');
377         is_deeply(\@warn, [], 'no warnings');
378         $im->done;
379         my ($min, $max) = $ibx->mm->minmax;
380         is($min, 1, 'min as expected');
381         is($max, 10, 'max as expected');
382         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
383         is_deeply($ibx->mm->msg_range(\$min, $max),
384                   [
385                    [1, '1@example.com' ],
386                    [2, '2@example.com' ],
387                    [3, '3@example.com' ],
388                    [5, '5@example.com' ],
389                    [6, '6@example.com' ],
390                    [7, '7@example.com' ],
391                    [8, '8@example.com' ],
392                    [9, '9@example.com' ],
393                    [10, '10@example.com' ],
394                   ], 'msgmap as expected' );
397         my @warn;
398         local $SIG{__WARN__} = sub { push @warn, @_ };
399         my %config = %$ibx_config;
400         my $ibx = PublicInbox::Inbox->new(\%config);
401         # mark4 A delete of an older message
402         $ibx->{ref_head} = $mark4;
403         my $im = PublicInbox::V2Writable->new($ibx);
404         eval { $im->index_sync() };
405         is($@, '', 'no error from reindexing without msgmap');
406         is_deeply(\@warn, [], 'no warnings');
407         $im->done;
408         my ($min, $max) = $ibx->mm->minmax;
409         is($min, 1, 'min as expected');
410         is($max, 10, 'max as expected');
411         is($ibx->mm->num_highwater, 10, 'num_highwater as expected');
412         is_deeply($ibx->mm->msg_range(\$min, $max),
413                   [
414                    [1, '1@example.com' ],
415                    [2, '2@example.com' ],
416                    [3, '3@example.com' ],
417                    [6, '6@example.com' ],
418                    [7, '7@example.com' ],
419                    [8, '8@example.com' ],
420                    [9, '9@example.com' ],
421                    [10, '10@example.com' ],
422                   ], 'msgmap as expected' );
425 my $check_rethread = sub {
426         my ($desc) = @_;
427         my @warn;
428         local $SIG{__WARN__} = sub { push @warn, @_ };
429         my %config = %$ibx_config;
430         my $ibx = PublicInbox::Inbox->new(\%config);
431         my $f = $ibx->over->{dbh}->sqlite_db_filename;
432         my $over = PublicInbox::OverIdx->new($f);
433         my $dbh = $over->dbh;
434         my $non_ghost_tids = sub {
435                 $dbh->selectall_arrayref(<<'');
436 SELECT tid FROM over WHERE num > 0 ORDER BY tid ASC
438         };
439         my $before = $non_ghost_tids->();
441         # mess up threading:
442         my $tid = PublicInbox::OverIdx::get_counter($dbh, 'thread');
443         my $nr = $dbh->do('UPDATE over SET tid = ?', undef, $tid);
444         diag "messing up all threads with tid=$tid";
446         my $v2w = PublicInbox::V2Writable->new($ibx);
447         my @pr;
448         my $pr = sub { push @pr, @_ };
449         $v2w->index_sync({reindex => 1, rethread => 1, -progress => $pr});
450         # diag "@pr"; # nobody cares
451         is_deeply(\@warn, [], 'no warnings on reindex + rethread');
453         my @n = $dbh->selectrow_array(<<EOS, undef, $tid);
454 SELECT COUNT(*) FROM over WHERE tid <= ?
456         is_deeply(\@n, [ 0 ], 'rethread dropped old threadids');
457         my $after = $non_ghost_tids->();
458         ok($after->[0]->[0] > $before->[-1]->[0],
459                 'all tids greater than before');
460         is(scalar @$after, scalar @$before, 'thread count unchanged');
463 $check_rethread->('no-monster');
465 # A real example from linux-renesas-soc on lore where a 3-headed monster
466 # of a message has 3 sets of common headers.  Another normal message
467 # previously existed with a single Message-ID that conflicts with one
468 # of the Message-IDs in the 3-headed monster.
470         my @warn;
471         local $SIG{__WARN__} = sub { push @warn, @_ };
472         my %config = %$ibx_config;
473         $config{indexlevel} = 'medium';
474         my $ibx = PublicInbox::Inbox->new(\%config);
475         my $im = PublicInbox::V2Writable->new($ibx);
476         my $m3 = PublicInbox::Eml->new(<<'EOF');
477 Date: Tue, 24 May 2016 14:34:22 -0700 (PDT)
478 Message-Id: <20160524.143422.552507610109476444.d@example.com>
479 To: t@example.com
480 Cc: c@example.com
481 Subject: Re: [PATCH v2 2/2] uno
482 From: <f@example.com>
483 In-Reply-To: <1463825855-7363-2-git-send-email-y@example.com>
484 References: <1463825855-7363-1-git-send-email-y@example.com>
485         <1463825855-7363-2-git-send-email-y@example.com>
486 Date: Wed, 25 May 2016 10:01:51 +0900
487 From: h@example.com
488 To: g@example.com
489 Cc: m@example.com
490 Subject: Re: [PATCH] dos
491 Message-ID: <20160525010150.GD7292@example.com>
492 References: <1463498133-23918-1-git-send-email-g+r@example.com>
493 In-Reply-To: <1463498133-23918-1-git-send-email-g+r@example.com>
494 From: s@example.com
495 To: h@example.com
496 Cc: m@example.com
497 Subject: [PATCH 12/13] tres
498 Date: Wed, 01 Jun 2016 01:32:35 +0300
499 Message-ID: <1923946.Jvi0TDUXFC@wasted.example.com>
500 In-Reply-To: <13205049.n7pM8utpHF@wasted.example.com>
501 References: <13205049.n7pM8utpHF@wasted.example.com>
503 Somehow we got a message with 3 sets of headers into one
504 message, could've been something broken on the archiver side.
507         my $m1 = PublicInbox::Eml->new(<<'EOF');
508 From: a@example.com
509 To: t@example.com
510 Subject: [PATCH 12/13]
511 Date: Wed, 01 Jun 2016 01:32:35 +0300
512 Message-ID: <1923946.Jvi0TDUXFC@wasted.example.com>
513 In-Reply-To: <13205049.n7pM8utpHF@wasted.example.com>
514 References: <13205049.n7pM8utpHF@wasted.example.com>
516 This is probably one of the original messages
519         $im->add($m1);
520         $im->add($m3);
521         $im->done;
522         remove_tree($xap);
523         eval { $im->index_sync() };
524         is($@, '', 'no error from initial indexing');
525         is_deeply(\@warn, [], 'no warnings from initial index');
526         eval { $im->index_sync({reindex=>1}) };
527         is($@, '', 'no error from reindexing after reused Message-ID (x3)');
528         is_deeply(\@warn, [], 'no warnings on reindex');
530         my %uniq;
531         for my $s (qw(uno dos tres)) {
532                 my $mset = $ibx->search->mset("s:$s");
533                 my $msgs = $ibx->search->mset_to_smsg($ibx, $mset);
534                 is(scalar(@$msgs), 1, "only one result for `$s'");
535                 $uniq{$msgs->[0]->{num}}++;
536         }
537         is_deeply([values %uniq], [3], 'search on different subjects');
540 # XXX: not deterministic when dealing with ambiguous messages, oh well
541 $check_rethread->('3-headed-monster once');
542 $check_rethread->('3-headed-monster twice');
544 my $rdr = { 2 => \(my $err = '') };
545 my $env = { PI_CONFIG => '/dev/null' };
546 ok(run_script([qw(-index --reindex --xapian-only), $inboxdir], $env, $rdr),
547         '--xapian-only works');
548 is($err, '', 'no errors from --xapian-only');
549 undef $for_destroy;
550 SKIP: {
551         skip 'only testing lsof(8) output on Linux', 1 if $^O ne 'linux';
552         my $rdr = { 2 => \(my $null_err) };
553         my @d = grep m!/xap[0-9]+/!, lsof_pid $$, $rdr;
554         is_deeply(\@d, [], 'no deleted index files') or diag explain(\@d);
556 done_testing();