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