Async-Redis
view release on metacpan or search on metacpan
t/lib/Test/Async/Redis.pm view on Meta::CPAN
$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.
=head1 FUNCTIONS
=over 4
=item run { async code }
Execute an async block and return its result. This is the main
test helper - wrap any async operations in run { }.
=item await_f($future)
Await a future and return its result.
=item skip_without_redis()
Skip all tests if Redis is not available. Returns a connected
Async::Redis object if successful.
=item cleanup_keys($redis, $pattern)
Async function to delete all keys matching pattern.
=item with_timeout($seconds, $future)
Wrap a future with a timeout.
=item fails_with($future, $error_class, $message)
Assert that a future fails with the specified error class.
=item delay($seconds)
Async function that delays for the specified time.
=item redis_host()
Returns the Redis host from REDIS_HOST env var (default: localhost).
=item redis_port()
Returns the Redis port from REDIS_PORT env var (default: 6379).
=item inject_eof($redis)
Force EOF on the underlying socket via C<shutdown(..., 2)> so the next
sysread returns 0 bytes. Triggers the reader's EOF handling path. Used
in adverse-interleaving tests to simulate abrupt server disconnect.
C<shutdown> is used in preference to C<close> so the file descriptor
stays valid for any active Future::IO poller watching the socket.
=item inject_unexpected_frame($redis, $raw_bytes)
Feed raw bytes directly into the RESP parser, bypassing the socket.
Useful for exercising the reader's decode/dispatch path with a crafted
or malformed frame without needing a live server to send it.
=item force_read_timeout($redis)
Synthesize a fatal timeout by calling C<_reader_fatal> with a typed
C<Async::Redis::Error::Timeout> object. All inflight futures receive
the typed error via the detach-first path rather than generic
cancellation.
=back
=cut
( run in 2.208 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )