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