use 5.006000; use strict; use warnings; use lib 't/tlib'; use Test::More tests => 25; use Test::AnyEvent::RedisHandle; use Test::AnyEvent::RedisEmulator; use Test::AnyEvent::EVLoop; use Scalar::Util qw( weaken ); my $T_CLASS; BEGIN { $T_CLASS = 'AnyEvent::Redis::RipeRedis'; use_ok( $T_CLASS, qw( :err_codes ) ); } can_ok( $T_CLASS, 'new' ); my %GENERIC_PARAMS = ( host => 'localhost', port => '6379', ); t_no_connection(); t_reconnect(); t_broken_connection(); t_cmd_on_error(); t_invalid_password(); t_oprn_not_permitted(); t_sub_after_multi(); t_conn_closed_by_client(); t_loading_dataset(); t_invalid_db_index(); t_read_timeout(); #### sub t_no_connection { Test::AnyEvent::RedisHandle->down_connection(); my $t_redis; my @t_errors; ev_loop( sub { my $cv = shift; $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, reconnect => 0, on_connect_error => sub { my $err_msg = shift; push( @t_errors, $err_msg ); }, on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); }, ] ); $t_redis->ping( { on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); $cv->send(); } } ); }, ); ev_loop( sub { my $cv = shift; $t_redis->ping( { on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); $cv->send(); } } ); }, ); Test::AnyEvent::RedisHandle->up_connection(); is_deeply( \@t_errors, [ [ "Command 'ping' aborted: Can't connect to localhost:6379:" . " Connection timed out", E_CANT_CONN ], "Can't connect to localhost:6379: Connection timed out", [ "Can't handle the command 'ping'. No connection to the server", E_NO_CONN ], ], "Can't connect" ); return; } #### sub t_reconnect { my @t_data; my $t_redis; ev_loop( sub { my $cv = shift; $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', on_connect => sub { push( @t_data, 'Connected' ); }, on_disconnect => sub { push( @t_data, 'Disconnected' ); Test::AnyEvent::RedisHandle->up_connection(); $cv->send(); }, on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_data, [ $err_msg, $err_code ] ); }, ] ); $t_redis->ping( { on_done => sub { Test::AnyEvent::RedisHandle->down_connection(); } } ); }, ); ev_loop( sub { my $cv = shift; $t_redis->ping( { on_done => sub { my $resp = shift; push( @t_data, $resp ); $cv->send(); } } ); }, ); is_deeply( \@t_data, [ 'Connected', [ 'Connection closed by remote host', E_CONN_CLOSED_BY_REMOTE_HOST ], 'Disconnected', 'Connected', 'PONG', ], 'Reconnect' ); return; } #### sub t_broken_connection { my @t_data; my $t_redis; ev_loop( sub { my $cv = shift; $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', on_connect => sub { push( @t_data, 'Connected' ); }, on_disconnect => sub { push( @t_data, 'Disconnected' ); }, on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_data, [ $err_msg, $err_code ] ); $cv->send(); }, ] ); { my $t_redis = $t_redis; weaken( $t_redis ); $t_redis->ping( { on_done => sub { Test::AnyEvent::RedisHandle->down_connection(); $t_redis->ping(); }, } ); } }, ); Test::AnyEvent::RedisHandle->up_connection(); is_deeply( \@t_data, [ 'Connected', [ "Command 'ping' aborted: Broken pipe", E_IO ], [ "Broken pipe", E_IO ], 'Disconnected', ], 'Broken connection' ); return; } #### sub t_cmd_on_error { my $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', ] ); local %SIG; my $t_err; ev_loop( sub { my $cv = shift; $t_redis->set( 'bar', 'Some string' ); $SIG{__WARN__} = sub { $t_err = shift; chomp( $t_err ); $cv->send(); }; $t_redis->incr( 'bar' ); }, ); is( $t_err, 'ERR value is not an integer or out of range', "Default 'on_error' callback" ); my @t_errors; ev_loop( sub { my $cv = shift; $t_redis->multi(); $t_redis->set( '', undef, { on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); }, } ); $t_redis->incr( 'bar' ); $t_redis->exec( { on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); $cv->send(); }, } ); }, ); is_deeply( \@t_errors, [ [ "ERR wrong number of arguments for 'set' command", E_OPRN_ERROR ], [ 'ERR value is not an integer or out of range', E_OPRN_ERROR ], ], "'on_error' callback in the method of the command" ); return; } #### sub t_invalid_password { my $t_redis; my @t_errors; ev_loop( sub { my $cv = shift; $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'invalid', on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); }, ] ); $t_redis->ping( { on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); $cv->send(); } } ); }, ); is_deeply( \@t_errors, [ [ "Command 'ping' aborted: ERR invalid password", E_INVALID_PASS ], [ 'ERR invalid password', E_INVALID_PASS ], ], 'Invalid password' ); return; } #### sub t_oprn_not_permitted { my $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, ] ); my $t_err_msg; my $t_err_code; ev_loop( sub { my $cv = shift; $t_redis->ping( { on_error => sub { $t_err_msg = shift; $t_err_code = shift; $cv->send(); } } ); }, ); is_deeply( [ $t_err_msg, $t_err_code ], [ 'ERR operation not permitted', E_OPRN_NOT_PERMITTED ], 'Operation not permitted' ); return; } #### sub t_sub_after_multi { my $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', ] ); my $t_err_msg; my $t_err_code; ev_loop( sub { my $cv = shift; $t_redis->multi(); $t_redis->subscribe( 'channel', { on_message => sub { my $msg = shift; }, on_error => sub { $t_err_msg = shift; $t_err_code = shift; $cv->send(); } } ); }, ); is_deeply( [ $t_err_msg, $t_err_code ], [ "Command 'subscribe' not allowed" . " after 'multi' command. First, the transaction must be completed", E_OPRN_ERROR ], 'Invalid context for subscribtion' ); return; } #### sub t_conn_closed_by_client { my $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', ] ); my $t_err_msg; my $t_err_code; $t_redis->ping( { on_error => sub { $t_err_msg = shift; $t_err_code = shift; }, } ); $t_redis->disconnect(); is_deeply( [ $t_err_msg, $t_err_code ], [ "Command 'ping' aborted: Connection" . " closed by client", E_CONN_CLOSED_BY_CLIENT ], 'Connection closed by client' ); return; } #### sub t_loading_dataset { Test::AnyEvent::RedisEmulator->loading_dataset( 1 ); my $t_redis; my @t_errors; ev_loop( sub { my $cv = shift; $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); $cv->send(); }, ] ); $t_redis->ping( { on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); }, } ); }, ); Test::AnyEvent::RedisEmulator->loading_dataset( 0 ); is_deeply( \@t_errors, [ [ "Command 'ping' aborted: LOADING Redis is loading the dataset in memory", E_LOADING_DATASET ], [ "LOADING Redis is loading the dataset in memory", E_LOADING_DATASET ], ], 'Loading dataset' ); return; } #### sub t_invalid_db_index { my $t_redis; my @t_errors; ev_loop( sub { my $cv = shift; $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', database => 16, on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); $cv->send(); }, ] ); $t_redis->ping( { on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); }, } ); }, ); is_deeply( \@t_errors, [ [ "Command 'ping' aborted: ERR invalid DB index", E_OPRN_ERROR ], [ "ERR invalid DB index", E_OPRN_ERROR ], ], 'Invalid DB index' ); return; } #### sub t_read_timeout { my $t_redis; my @t_errors; ev_loop( sub { my $cv = shift; $t_redis = new_ok( $T_CLASS, [ %GENERIC_PARAMS, password => 'test', reconnect => 0, read_timeout => 5, on_connect => sub { Test::AnyEvent::RedisHandle->freeze_connection(); }, on_error => sub { my $err_msg = shift; my $err_code = shift; push( @t_errors, [ $err_msg, $err_code ] ); $cv->send(); }, ] ); }, ); Test::AnyEvent::RedisHandle->thaw_connection(); is_deeply( \@t_errors, [ [ "Command 'auth' aborted: Read timed out", E_READ_TIMEDOUT ], [ 'Read timed out', E_READ_TIMEDOUT ], ], 'Read timed out' ); return; }