]> Sergey Matveev's repositories - public-inbox.git/blob - xt/nntpd-validate.t
use Net::SSLeay (OpenSSL) for SHA-(1|256) if installed
[public-inbox.git] / xt / nntpd-validate.t
1 # Copyright (C) all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3
4 # Integration test to validate compression.
5 use strict;
6 use warnings;
7 use Test::More;
8 use Symbol qw(gensym);
9 use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
10 use POSIX qw(_exit);
11 use PublicInbox::TestCommon;
12 use PublicInbox::SHA;
13 my $inbox_dir = $ENV{GIANT_INBOX_DIR};
14 plan skip_all => "GIANT_INBOX_DIR not defined for $0" unless $inbox_dir;
15 my $mid = $ENV{TEST_MID};
16
17 # Net::NNTP is part of the standard library, but distros may split it off...
18 require_mods(qw(DBD::SQLite Net::NNTP Compress::Raw::Zlib));
19 my $test_compress = Net::NNTP->can('compress');
20 if (!$test_compress) {
21         diag 'Your Net::NNTP does not yet support compression';
22         diag 'See: https://rt.cpan.org/Ticket/Display.html?id=129967';
23 }
24 my $test_tls = $ENV{TEST_SKIP_TLS} ? 0 : eval { require IO::Socket::SSL };
25 my $cert = 'certs/server-cert.pem';
26 my $key = 'certs/server-key.pem';
27 if ($test_tls && !-r $key || !-r $cert) {
28         plan skip_all => "certs/ missing for $0, run $^X ./certs/create-certs.perl";
29 }
30 my ($tmpdir, $ftd) = tmpdir();
31 $File::Temp::KEEP_ALL = !!$ENV{TEST_KEEP_TMP};
32 my (%OPT, $td, $host_port, $group);
33 my $batch = 1000;
34 if (($ENV{NNTP_TEST_URL} // '') =~ m!\Anntp://([^/]+)/([^/]+)\z!) {
35         ($host_port, $group) = ($1, $2);
36         $host_port .= ":119" unless index($host_port, ':') > 0;
37 } else {
38         make_local_server();
39 }
40 my $test_article = $ENV{TEST_ARTICLE} // 0;
41 my $test_xover = $ENV{TEST_XOVER} // 1;
42
43 if ($test_tls) {
44         my $nntp = Net::NNTP->new($host_port, %OPT);
45         ok($nntp->starttls, 'STARTTLS works');
46         ok($nntp->compress, 'COMPRESS works') if $test_compress;
47         ok($nntp->quit, 'QUIT after starttls OK');
48 }
49 if ($test_compress) {
50         my $nntp = Net::NNTP->new($host_port, %OPT);
51         ok($nntp->compress, 'COMPRESS works');
52         ok($nntp->quit, 'QUIT after compress OK');
53 }
54
55 sub do_get_all {
56         my ($methods) = @_;
57         my $desc = join(',', @$methods);
58         my $t0 = clock_gettime(CLOCK_MONOTONIC);
59         my $dig = PublicInbox::SHA->new(1);
60         my $digfh = gensym;
61         my $tmpfh;
62         if ($File::Temp::KEEP_ALL) {
63                 open $tmpfh, '>', "$tmpdir/$desc.raw" or die $!;
64         }
65         my $tmp = { dig => $dig, tmpfh => $tmpfh };
66         tie *$digfh, 'DigestPipe', $tmp;
67         my $nntp = Net::NNTP->new($host_port, %OPT);
68         $nntp->article("<$mid>", $digfh) if $mid;
69         foreach my $m (@$methods) {
70                 my $res = $nntp->$m;
71                 print STDERR "# $m got $res ($desc)\n" if !$res;
72         }
73         $nntp->article("<$mid>", $digfh) if $mid;
74         my ($num, $first, $last) = $nntp->group($group);
75         unless (defined $num && defined $first && defined $last) {
76                 warn "Invalid group\n";
77                 return undef;
78         }
79         my $i;
80         for ($i = $first; $i < $last; $i += $batch) {
81                 my $j = $i + $batch - 1;
82                 $j = $last if $j > $last;
83                 if ($test_xover) {
84                         my $xover = $nntp->xover("$i-$j");
85                         for my $n (sort { $a <=> $b } keys %$xover) {
86                                 my $line = join("\t", @{$xover->{$n}});
87                                 $line =~ tr/\r//d;
88                                 $dig->add("$n\t".$line);
89                         }
90                 }
91                 if ($test_article) {
92                         for my $n ($i..$j) {
93                                 $nntp->article($n, $digfh) and next;
94                                 next if $nntp->code == 423;
95                                 my $res = $nntp->code.' '.  $nntp->message;
96
97                                 $res =~ tr/\r\n//d;
98                                 print STDERR "# Article $n ($desc): $res\n";
99                         }
100                 }
101         }
102
103         # hacky bytes_read thing added to Net::NNTP for testing:
104         my $bytes_read = '';
105         if ($nntp->can('bytes_read')) {
106                 $bytes_read .= ' '.$nntp->bytes_read.'b';
107         }
108         my $q = $nntp->quit;
109         print STDERR "# quit failed: ".$nntp->code."\n" if !$q;
110         my $elapsed = sprintf('%0.3f', clock_gettime(CLOCK_MONOTONIC) - $t0);
111         my $res = $dig->hexdigest;
112         print STDERR "# $desc - $res (${elapsed}s)$bytes_read\n";
113         $res;
114 }
115 my @tests = ([]);
116 push @tests, [ 'compress' ] if $test_compress;
117 push @tests, [ 'starttls' ] if $test_tls;
118 push @tests, [ 'starttls', 'compress' ] if $test_tls && $test_compress;
119 my (@keys, %thr, %res);
120 for my $m (@tests) {
121         my $key = join(',', @$m);
122         push @keys, $key;
123         pipe(my ($r, $w)) or die;
124         my $pid = fork;
125         if ($pid == 0) {
126                 close $r or die;
127                 my $res = do_get_all($m);
128                 print $w $res or die;
129                 $w->flush;
130                 _exit(0);
131         }
132         close $w or die;
133         $thr{$key} = [ $pid, $r ];
134 }
135 for my $key (@keys) {
136         my ($pid, $r) = @{delete $thr{$key}};
137         local $/;
138         $res{$key} = <$r>;
139         defined $res{$key} or die "nothing for $key";
140         my $w = waitpid($pid, 0);
141         defined($w) or die;
142         $w == $pid or die "waitpid($pid) != $w)";
143         is($?, 0, "`$key' exited successfully")
144 }
145
146 my $plain = $res{''};
147 ok($plain, "plain got $plain");
148 is($res{$_}, $plain, "$_ matches '' result") for @keys;
149
150 done_testing();
151
152 sub make_local_server {
153         require PublicInbox::Inbox;
154         $group = 'inbox.test.perf.nntpd';
155         my $ibx = { inboxdir => $inbox_dir, newsgroup => $group };
156         $ibx = PublicInbox::Inbox->new($ibx);
157         my $pi_config = "$tmpdir/config";
158         {
159                 open my $fh, '>', $pi_config or die "open($pi_config): $!";
160                 print $fh <<"" or die "print $pi_config: $!";
161 [publicinbox "test"]
162         newsgroup = $group
163         inboxdir = $inbox_dir
164         address = test\@example.com
165
166                 close $fh or die "close($pi_config): $!";
167         }
168         my ($out, $err) = ("$tmpdir/out", "$tmpdir/err");
169         for ($out, $err) {
170                 open my $fh, '>', $_ or die "truncate: $!";
171         }
172         my $sock = tcp_server();
173         $host_port = tcp_host_port($sock);
174
175         # not using multiple workers, here, since we want to increase
176         # the chance of tripping concurrency bugs within PublicInbox/NNTP*.pm
177         my $cmd = [ '-nntpd', "--stdout=$out", "--stderr=$err", '-W0' ];
178         push @$cmd, "-lnntp://$host_port";
179         if ($test_tls) {
180                 push @$cmd, "--cert=$cert", "--key=$key";
181                 %OPT = (
182                         SSL_hostname => 'server.local',
183                         SSL_verifycn_name => 'server.local',
184                         SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_PEER(),
185                         SSL_ca_file => 'certs/test-ca.pem',
186                 );
187         }
188         print STDERR "# CMD ". join(' ', @$cmd). "\n";
189         my $env = { PI_CONFIG => $pi_config };
190         $td = start_script($cmd, $env, { 3 => $sock });
191 }
192
193 package DigestPipe;
194 use strict;
195 use warnings;
196
197 sub TIEHANDLE {
198         my ($class, $self) = @_;
199         bless $self, $class;
200 }
201
202 sub PRINT {
203         my $self = shift;
204         my $data = join('', @_);
205         # Net::NNTP emit different line-endings depending on TLS or not...:
206         $data =~ tr/\r//d;
207         $self->{dig}->add($data);
208         if (my $tmpfh = $self->{tmpfh}) {
209                 print $tmpfh $data;
210         }
211         1;
212 }
213 1;