]> Sergey Matveev's repositories - public-inbox.git/blob - t/mbox_reader.t
www: drop --subject from "git send-email" instructions
[public-inbox.git] / t / mbox_reader.t
1 #!perl -w
2 # Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
3 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 use strict;
5 use v5.10.1;
6 use Test::More;
7 use PublicInbox::TestCommon;
8 use List::Util qw(shuffle);
9 use PublicInbox::Eml;
10 use Fcntl qw(SEEK_SET);
11 require_ok 'PublicInbox::MboxReader';
12 require_ok 'PublicInbox::LeiToMail';
13 my %raw = (
14         hdr_only => "From: header-only\@example.com\n\n",
15         small_from => "From: small-from\@example.com\n\nFrom hell\n",
16         small => "From: small\@example.com\n\nfrom hell\n",
17         big_hdr_only => "From: big-header\@example.com\n" .
18                 (('A: '.('a' x 72)."\n") x 1000)."\n",
19         big_body => "From: big-body\@example.com\n\n".
20                 (('b: '.('b' x 72)."\n") x 1000) .
21                 "From hell\n",
22         big_all => "From: big-all\@example.com\n".
23                 (("A: ".('a' x 72)."\n") x 1000). "\n" .
24                 (("b: ".('b' x 72)."\n") x 1000) .
25                 "From hell\n",
26 );
27 {
28         my $eml = PublicInbox::Eml->new($raw{small});
29         my $mbox_keywords = PublicInbox::MboxReader->can('mbox_keywords');
30         is_deeply($mbox_keywords->($eml), [], 'no keywords');
31         $eml->header_set('Status', 'RO');
32         is_deeply($mbox_keywords->($eml), ['seen'], 'seen extracted');
33         $eml->header_set('X-Status', 'A');
34         is_deeply($mbox_keywords->($eml), [qw(answered seen)],
35                 'seen+answered extracted');
36 }
37
38 if ($ENV{TEST_EXTRA}) {
39         for my $fn (glob('t/*.eml'), glob('t/*/*.{patch,eml}')) {
40                 $raw{$fn} = eml_load($fn)->as_string;
41         }
42 }
43
44 my $reader = PublicInbox::MboxReader->new;
45 my $check_fmt = sub {
46         my $fmt = shift;
47         my @order = shuffle(keys %raw);
48         my $eml2mbox = PublicInbox::LeiToMail->can("eml2$fmt");
49         open my $fh, '+>', undef or BAIL_OUT "open: $!";
50         for my $k (@order) {
51                 my $eml = PublicInbox::Eml->new($raw{$k});
52                 my $buf = $eml2mbox->($eml);
53                 print $fh $$buf or BAIL_OUT "print $!";
54         }
55         seek($fh, 0, SEEK_SET) or BAIL_OUT "seek: $!";
56         $reader->$fmt($fh, sub {
57                 my ($eml) = @_;
58                 $eml->header_set('Status');
59                 $eml->header_set('Lines');
60                 my $cur = shift @order;
61                 my @cl = $eml->header_raw('Content-Length');
62                 if ($fmt =~ /\Amboxcl/) {
63                         is(scalar(@cl), 1, "Content-Length set $fmt $cur");
64                         my $raw = $eml->body_raw;
65                         my $adj = 0;
66                         if ($fmt eq 'mboxcl') {
67                                 my @from = ($raw =~ /^(From )/smg);
68                                 $adj = scalar(@from);
69                         }
70                         is(length($raw), $cl[0] - $adj,
71                                 "Content-Length is correct $fmt $cur");
72                         # clobber for ->as_string comparison below
73                         $eml->header_set('Content-Length');
74
75                         # special case for t/solve/bare.patch, not sure if we
76                         # should even handle it...
77                         if ($cl[0] eq '0' && ${$eml->{hdr}} eq '') {
78                                 delete $eml->{bdy};
79                         }
80                 } else {
81                         is(scalar(@cl), 0, "Content-Length unset $fmt $cur");
82                 }
83                 my $orig = PublicInbox::Eml->new($raw{$cur});
84                 is($eml->as_string, $orig->as_string,
85                         "read back original $fmt $cur");
86         });
87 };
88 my @mbox = qw(mboxrd mboxo mboxcl mboxcl2);
89 for my $fmt (@mbox) { $check_fmt->($fmt) }
90 s/\n/\r\n/sg for (values %raw);
91 for my $fmt (@mbox) { $check_fmt->($fmt) }
92
93 {
94         my $no_blank_eom = <<'EOM';
95 From x@y Fri Oct  2 00:00:00 1993
96 a: b
97
98 body1
99 From x@y Fri Oct  2 00:00:00 1993
100 c: d
101
102 body2
103 EOM
104         # chop($no_blank_eom) eq "\n" or BAIL_OUT 'broken LF';
105         for my $variant (qw(mboxrd mboxo)) {
106                 my @x;
107                 open my $fh, '<', \$no_blank_eom or BAIL_OUT 'PerlIO::scalar';
108                 $reader->$variant($fh, sub { push @x, shift });
109                 is_deeply($x[0]->{bdy}, \"body1\n", 'LF preserved in 1st');
110                 is_deeply($x[1]->{bdy}, \"body2\n", 'no LF added in 2nd');
111         }
112 }
113
114 SKIP: {
115         use PublicInbox::Spawn qw(popen_rd);
116         my $fh = popen_rd([ $^X, '-E', <<'' ]);
117 say "From x@y Fri Oct  2 00:00:00 1993";
118 print "a: b\n\n", "x" x 70000, "\n\n";
119 say "From x@y Fri Oct  2 00:00:00 2010";
120 print "Final: bit\n\n", "Incomplete\n\n";
121 exit 1
122
123         my @x;
124         eval { $reader->mboxrd($fh, sub { push @x, shift->as_string }) };
125         like($@, qr/error closing mbox/, 'detects error reading from pipe');
126         is(scalar(@x), 1, 'only saw one message');
127         is(scalar(grep(/Final/, @x)), 0, 'no incomplete bit');
128 }
129
130 {
131         my $html = <<EOM;
132 <html><head><title>hi,</title></head><body>how are you</body></html>
133 EOM
134         for my $m (qw(mboxrd mboxcl mboxcl2 mboxo)) {
135                 my (@w, @x);
136                 local $SIG{__WARN__} = sub { push @w, @_ };
137                 open my $fh, '<', \$html or xbail 'PerlIO::scalar';
138                 PublicInbox::MboxReader->$m($fh, sub {
139                         push @x, $_[0]->as_string
140                 });
141                 if ($m =~ /\Amboxcl/) {
142                         is_deeply(\@x, [], "messages in invalid $m");
143                 } else {
144                         is_deeply(\@x, [ "\n$html" ], "body-only $m");
145                 }
146                 is_deeply([grep(!/^W: leftover/, @w)], [],
147                         "no extra warnings besides leftover ($m)");
148         }
149 }
150
151 done_testing;