EV-Redis

 view release on metacpan or  search on metacpan

t/pubsub.t  view on Meta::CPAN


    eval { $r->ping(sub {}) };
    like $@, qr/MONITOR is active/, 'command while monitoring croaks';

    # Flag clears with the connection
    $r->disconnect;
    $r->connect_unix($connect_info{sock});
    my $pong;
    $r->ping(sub { $pong = $_[0]; EV::break });
    my $t2; $t2 = EV::timer 2, 0, sub { undef $t2; EV::break };
    EV::run;
    undef $t2;  # kill pending timeout (see above)
    is $pong, 'PONG', 'commands work again after reconnect clears monitor state';
    $r->disconnect;
}

# Test: subscribe with no channels croaks (server would reject it and the
# persistent tracking entry would strand until disconnect).
{
    my $r = EV::Redis->new( path => $connect_info{sock} );
    eval { $r->subscribe(sub {}) };
    like $@, qr/subscribe requires at least one channel/, 'no-arg subscribe croaks';
    eval { $r->psubscribe(sub {}) };
    like $@, qr/psubscribe requires at least one channel/, 'no-arg psubscribe croaks';
    $r->disconnect;
}

# Test: disconnect with active subscription — callback should fire exactly once
# with a meaningful error (not empty string), and not be double-invoked.
{
    my $sub = EV::Redis->new(path => $connect_info{sock});
    my @cb_calls;
    my $subscribed = 0;

    $sub->on_error(sub {}); # suppress

    $sub->subscribe('disconnect_test_ch', sub {
        my ($result, $error) = @_;
        push @cb_calls, [$result, $error];
        if ($result && ref $result eq 'ARRAY' && $result->[0] eq 'subscribe') {
            $subscribed = 1;
            # Disconnect without unsubscribing
            $sub->disconnect;
        }
    });

    my $t; $t = EV::timer 2, 0, sub { undef $t; EV::break };
    EV::run;
    undef $t;  # kill pending timeout (see above)

    ok $subscribed, 'subscribed before disconnect';
    # Expect: subscribe confirmation + exactly one disconnect error
    my @errors = grep { defined $_->[1] } @cb_calls;
    is scalar(@errors), 1, 'subscribe callback invoked exactly once with error on disconnect';
    ok $errors[0][1], 'error string is truthy (not empty)';
    like $errors[0][1], qr/disconnected/, 'error string is "disconnected"';
}

# Test: multi-channel subscribe + disconnect — one error callback total.
# (Historically flaky here: guard timers leaked by earlier blocks — kept
# alive by their self-capturing callbacks — fired their deferred EV::break
# inside this block's run, ending it before the subscriber's read interest
# was registered. Fixed by the undef-after-EV::run lines above; a failure
# here now means a real regression.)
{
    my $sub = EV::Redis->new(path => $connect_info{sock});
    my @cb_calls;
    my $sub_count = 0;

    $sub->on_error(sub { warn "MULTIDC on_error: @_\n" if $ENV{EV_REDIS_DIAG} });

    $sub->subscribe('multi_dc_ch1', 'multi_dc_ch2', sub {
        my ($result, $error) = @_;
        push @cb_calls, [$result, $error];
        if ($result && ref $result eq 'ARRAY' && $result->[0] eq 'subscribe') {
            $sub_count++;
            if ($sub_count == 2) {
                $sub->disconnect;
            }
        }
    });

    my $t; $t = EV::timer 5, 0, sub { undef $t; EV::break };
    EV::run;
    undef $t;  # kill pending timeout (see above)

    is $sub_count, 2, 'both channels subscribed';
    my @errors = grep { defined $_->[1] } @cb_calls;
    # With multi-channel, hiredis fires once per channel on teardown.
    for my $e (@errors) {
        ok $e->[1], 'error string is truthy (not empty)';
    }
    # 2 subscribe confirmations + at most one error per channel;
    # no extra invocation from remove_cb_queue_sv
    ok scalar(@errors) <= 2, 'no more than 2 error callbacks for 2-channel subscribe';
}

done_testing;



( run in 2.435 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )