# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
use strict;
use Test::More;
+use Time::HiRes ();
use PublicInbox::TestCommon;
-require_mods(qw(DBD::SQLite Mail::IMAPClient));
+use PublicInbox::Config;
+use PublicInbox::Spawn qw(which);
+require_mods(qw(DBD::SQLite Mail::IMAPClient Linux::Inotify2));
my $level = '-Lbasic';
SKIP: {
require_mods('Search::Xapian', 1);
};
my @V = (1);
-#push(@V, 2) if require_git('2.6', 1);
+push(@V, 2) if require_git('2.6', 1);
my ($tmpdir, $for_destroy) = tmpdir();
my $home = "$tmpdir/home";
ok($mic->compress, 'compress enabled');
$compress_logout->($mic);
+my $have_inotify = eval { require Linux::Inotify2; 1 };
+
+my $pi_config = PublicInbox::Config->new;
+$pi_config->each_inbox(sub {
+ my ($ibx) = @_;
+ my $env = { ORIGINAL_RECIPIENT => $ibx->{-primary_address} };
+ my $name = $ibx->{name};
+ my $ng = $ibx->{newsgroup};
+ my $mic = Mail::IMAPClient->new(%mic_opt);
+ ok($mic && $mic->login && $mic->IsAuthenticated, "authed $name");
+ is_deeply([$mic->has_capability('IDLE')], ['IDLE'], "IDLE capa $name");
+ ok(!$mic->idle, "IDLE fails w/o SELECT/EXAMINE $name");
+ ok($mic->examine($ng), "EXAMINE $ng succeeds");
+ ok($mic->idle, "IDLE succeeds on $ng");
+
+ open(my $fh, '<', 't/data/message_embed.eml') or BAIL_OUT("open: $!");
+ run_script(['-mda', '--no-precheck'], $env, { 0 => $fh }) or
+ BAIL_OUT('-mda delivery');
+ my $t0 = Time::HiRes::time();
+ ok(my @res = $mic->idle_data(11), "IDLE succeeds on $ng");
+ is(grep(/\A\* [0-9] EXISTS\b/, @res), 1, 'got EXISTS message');
+ ok((Time::HiRes::time() - $t0) < 10, 'IDLE client notified');
+
+ my (@ino_info, $ino_fdinfo);
+ SKIP: {
+ skip 'no inotify support', 1 unless $have_inotify;
+ skip 'missing /proc/$PID/fd', 1 if !-d "/proc/$td->{pid}/fd";
+ my @ino = grep {
+ readlink($_) =~ /\binotify\b/
+ } glob("/proc/$td->{pid}/fd/*");
+ is(scalar(@ino), 1, 'only one inotify FD');
+ my $ino_fd = (split('/', $ino[0]))[-1];
+ $ino_fdinfo = "/proc/$td->{pid}/fdinfo/$ino_fd";
+ if (open my $fh, '<', $ino_fdinfo) {
+ local $/ = "\n";
+ @ino_info = grep(/^inotify wd:/, <$fh>);
+ ok(scalar(@ino_info), 'inotify has watches');
+ } else {
+ skip "$ino_fdinfo missing: $!", 1;
+ }
+ };
+
+ # ensure IDLE persists across HUP, w/o extra watches or FDs
+ $td->kill('HUP') or BAIL_OUT "failed to kill -imapd: $!";
+ SKIP: {
+ skip 'no inotify fdinfo (or support)', 2 if !@ino_info;
+ my (@tmp, %prev);
+ local $/ = "\n";
+ my $end = time + 5;
+ until (time > $end) {
+ select undef, undef, undef, 0.01;
+ open my $fh, '<', $ino_fdinfo or
+ BAIL_OUT "$ino_fdinfo: $!";
+ %prev = map { $_ => 1 } @ino_info;
+ @tmp = grep(/^inotify wd:/, <$fh>);
+ if (scalar(@tmp) == scalar(@ino_info)) {
+ delete @prev{@tmp};
+ last if scalar(keys(%prev)) == @ino_info;
+ }
+ }
+ is(scalar @tmp, scalar @ino_info,
+ 'old inotify watches replaced');
+ is(scalar keys %prev, scalar @ino_info,
+ 'no previous watches overlap');
+ };
+
+ open($fh, '<', 't/data/0001.patch') or BAIL_OUT("open: $!");
+ run_script(['-mda', '--no-precheck'], $env, { 0 => $fh }) or
+ BAIL_OUT('-mda delivery');
+ $t0 = Time::HiRes::time();
+ ok(@res = $mic->idle_data(11), "IDLE succeeds on $ng after HUP");
+ is(grep(/\A\* [0-9] EXISTS\b/, @res), 1, 'got EXISTS message');
+ ok((Time::HiRes::time() - $t0) < 10, 'IDLE client notified');
+});
+
$td->kill;
$td->join;
is($?, 0, 'no error in exited process');