EV-cares
view release on metacpan or search on metacpan
t/17_wait_idle.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use EV;
use EV::cares qw(:all);
# --- is_busy: idle resolver ---
{
my $r = EV::cares->new;
ok(!$r->is_busy, 'is_busy false on fresh resolver');
is($r->active_queries, 0, 'active_queries matches');
}
# --- is_busy: with pending query ---
{
my $r = EV::cares->new(timeout => 5, tries => 1);
$r->resolve('unlikely-host-xyz.invalid', sub { });
ok($r->is_busy, 'is_busy true while query pending');
$r->cancel;
# drain the cancellation callback
EV::run(EV::RUN_NOWAIT) for 1..3;
ok(!$r->is_busy, 'is_busy false after cancel drains');
}
# --- wait_idle: already idle returns immediately ---
{
my $r = EV::cares->new;
my $t0 = EV::time;
my $drained = $r->wait_idle(5);
ok($drained, 'wait_idle on idle resolver returns true');
cmp_ok(EV::time - $t0, '<', 0.1, 'wait_idle on idle is immediate');
}
# --- wait_idle: drains pending queries (file lookup, fast) ---
{
my $r = EV::cares->new(lookups => 'f');
my @completed;
for (1..3) {
$r->resolve('localhost', sub { push @completed, $_[0] });
}
ok($r->is_busy || @completed == 3,
'queries queued (or completed synchronously)');
my $drained = $r->wait_idle(5);
ok($drained, 'wait_idle returns true when channel drains');
is(scalar @completed, 3, 'all 3 callbacks fired');
ok(!$r->is_busy, 'resolver is idle after wait_idle');
}
# --- wait_idle: timeout when a query is genuinely in-flight ---
# 192.0.2.1 is RFC 5737 TEST-NET-1 â a guaranteed black-hole address that
# routes nowhere on the public Internet. c-ares will sit on it until its
# own timeout fires. If the platform refuses outbound traffic to TEST-NET
# instantly (some hardened firewalls), the query may complete fast â skip
# the timing assertions in that case.
{
my $r = EV::cares->new(timeout => 10, tries => 1);
$r->set_servers('192.0.2.1');
my $done;
$r->resolve('unlikely-name.invalid', sub { $done = 1 });
my $t0 = EV::time;
my $drained = $r->wait_idle(1);
my $elapsed = EV::time - $t0;
SKIP: {
# If the query drained within the 1s window, the OS rejected the
# TEST-NET packet immediately (no route / hardened firewall) rather
# than black-holing it, so the timeout path can't be exercised.
# This must be detected AFTER wait_idle: a synchronous is_busy check
# before the loop runs still shows the query as in-flight.
skip 'TEST-NET query did not block on this platform', 2 if $drained;
ok(!$drained, 'wait_idle returns false when timeout elapses');
cmp_ok($elapsed, '>=', 0.9, "elapsed >=~1s (actual: ${elapsed}s)");
}
# clean up â query may still be in flight
$r->cancel if $r->is_busy;
$r->wait_idle(2);
}
# --- wait_idle: croaks on destroyed resolver ---
{
my $r = EV::cares->new;
$r->destroy;
eval { $r->wait_idle(1) };
like($@, qr/destroyed/, 'wait_idle croaks on destroyed resolver');
}
# --- wait_idle with custom EV::Loop ---
# Earlier versions used EV::run/EV::timer (default loop) regardless of
# whether the resolver was constructed with `loop => $custom`, which made
# wait_idle hang on custom-loop resolvers. This test catches that.
{
my $custom = EV::Loop->new;
my $r = EV::cares->new(loop => $custom, lookups => 'f');
is(ref $r->loop, 'EV::Loop', 'loop() returns the custom EV::Loop');
my $r2 = EV::cares->new(lookups => 'f');
ok(!defined $r2->loop, 'loop() is undef on default-loop resolver');
my $fired;
$r->resolve('localhost', sub { $fired = 1 });
ok($r->is_busy || $fired, 'query pending or fired synchronously');
my $drained = $r->wait_idle(3);
ok($drained, 'wait_idle drains on a custom EV::Loop');
ok($fired, 'callback ran during wait_idle on custom loop');
}
done_testing;
( run in 2.541 seconds using v1.01-cache-2.11-cpan-364913b4093 )