Async-Redis
view release on metacpan or search on metacpan
t/lib/Test/Async/Redis.pm view on Meta::CPAN
}
skip_all("Redis not available at " . redis_host() . ":" . redis_port() . ": $@");
}
# Skip if no Redis available - returns connected Redis object
sub skip_without_redis {
return $test_redis if $test_redis;
my $redis = eval {
my $r = Async::Redis->new(
host => redis_host(),
port => redis_port(),
connect_timeout => 2,
);
run { $r->connect };
$r;
};
if ($redis) {
$test_redis = $redis;
return $redis;
}
skip_all("Redis not available at " . redis_host() . ":" . redis_port() . ": $@");
}
# Clean up test keys
async sub cleanup_keys {
my ($redis, $pattern) = @_;
my $keys = await $redis->keys($pattern);
return unless @$keys;
await $redis->del(@$keys);
}
# Test with timeout wrapper
async sub with_timeout {
my ($timeout, $future) = @_;
my $timeout_f = Future::IO->sleep($timeout)->then(sub {
Future->fail("Test timeout after ${timeout}s");
});
return await Future->wait_any($future, $timeout_f);
}
# Assert Future fails with specific error type
sub fails_with {
my ($future, $error_class, $message) = @_;
my $error;
eval { run { $future } } or $error = $@;
ok($error && ref($error) && $error->isa($error_class), $message)
or diag("Expected $error_class, got: " . (ref($error) || $error // 'undef'));
}
# Async delay
async sub delay {
my ($seconds) = @_;
await Future::IO->sleep($seconds);
}
# Force EOF on the socket under the reader. Next read returns 0 bytes,
# triggering the reader's EOF handling path. Uses shutdown() rather
# than close() so the file descriptor stays valid for Future::IO's
# select loop â close() on an fh that Future::IO has an active poller
# on leaves a stale watcher whose fileno is undef, which taints select()
# with uninit warnings.
sub inject_eof {
my ($redis) = @_;
shutdown($redis->{socket}, 2) if $redis->{socket};
}
# Feed bytes directly into the parser, bypassing the socket. Useful
# for exercising the reader's decode/dispatch path with a crafted frame.
sub inject_unexpected_frame {
my ($redis, $raw_bytes) = @_;
$redis->{parser}->parse($raw_bytes) if $redis->{parser};
}
# Synthesize a fatal timeout directly. Routes through the detach-first
# _reader_fatal path so the typed Async::Redis::Error::Timeout is
# propagated to all inflight futures (not a generic cancellation).
sub force_read_timeout {
my ($redis) = @_;
require Async::Redis::Error::Timeout;
$redis->_reader_fatal(Async::Redis::Error::Timeout->new(
message => "synthetic timeout for test",
timeout => 0,
));
}
1;
__END__
=head1 NAME
Test::Async::Redis - Test utilities for Async::Redis
=head1 SYNOPSIS
use Test::Lib;
use Test::Async::Redis ':redis';
use Future::AsyncAwait;
# Tests auto-skip if Redis unavailable
# Use run {} to execute async code in tests:
my $result = run {
my $redis = Async::Redis->new;
await $redis->connect;
await $redis->set('key', 'value');
await $redis->get('key');
};
is($result, 'value', 'got value');
# Or get Redis from skip_without_redis:
my $redis = skip_without_redis();
my $pong = run { await $redis->ping };
is($pong, 'PONG', 'ping works');
=head1 DESCRIPTION
Test utilities for Async::Redis using async/await. Uses Future::IO's
built-in default implementation (IO::Poll based) for event loop management.
( run in 3.667 seconds using v1.01-cache-2.11-cpan-7fcb06a456a )