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