Atomic-Pipe

 view release on metacpan or  search on metacpan

t/truncated_mixed.t  view on Meta::CPAN

use Test2::V0;
use Atomic::Pipe;

# get_line_burst_or_data must never spin inside a single call. A message
# truncated by EOF must throw (poisoned pipe), and a message still in flight
# on a non-blocking pipe must return empty so the caller can select() and
# retry. Both cases used to loop forever inside one call.

# Capture the real wire bytes of one mixed-mode message so the replays below
# use genuine framing.
my $wire = do {
    my ($r, $w) = Atomic::Pipe->pair(mixed_data_mode => 1);
    $w->write_message('A' x 100);
    $w->close;
    my ($bytes, $buf) = ('', '');
    $bytes .= $buf while sysread($r->rh, $buf, 1024);
    $bytes;
};
ok(length($wire) > 100, "captured the on-wire form of a 100-byte message");

# Cut mid-payload: past the prefix+key+header (18 bytes) but well short of
# the end.
my $cut = 30;

# A single call that used to hang; the alarm turns a regression into a test
# failure instead of a stuck suite.
sub one_call {
    my ($pipe) = @_;
    local $SIG{ALRM} = sub { die "SINGLE CALL DEADLINE EXCEEDED\n" };
    alarm 10;
    my @got = eval { $pipe->get_line_burst_or_data };
    my $err = $@;
    alarm 0;
    return ($err, @got);
}

subtest truncated_message_at_eof_throws => sub {
    my ($r, $w) = Atomic::Pipe->pair(mixed_data_mode => 1);
    syswrite($w->wh, substr($wire, 0, $cut)) or die "syswrite: $!";
    $w->close;
    $r->blocking(0);

    my ($err) = one_call($r);
    unlike($err, qr/DEADLINE/, "the call returned instead of spinning");
    like($err, qr/invalid state/i, "a message truncated by EOF throws");
};

subtest partial_message_pre_eof_returns_empty => sub {
    my ($r, $w) = Atomic::Pipe->pair(mixed_data_mode => 1);
    syswrite($w->wh, substr($wire, 0, $cut)) or die "syswrite: $!";
    $r->blocking(0);

    my ($err, @got) = one_call($r);
    unlike($err, qr/DEADLINE/, "the call returned instead of spinning");
    is($err, '', "no exception while the writer is still alive");
    ok(!@got, "returned empty: nothing complete yet, caller may retry");

    # Deliver the rest; the reassembled message must come through intact.
    syswrite($w->wh, substr($wire, $cut)) or die "syswrite: $!";
    ($err, @got) = one_call($r);
    is($err, '', "no exception once the message completed");
    my %got = @got;
    is($got{message}, 'A' x 100, "the completed message arrived intact");
};

subtest partial_burst_pre_eof_returns_empty => sub {
    my ($r, $w) = Atomic::Pipe->pair(mixed_data_mode => 1);
    syswrite($w->wh, "\x0Epartial-burst-without-terminator") or die "syswrite: $!";
    $r->blocking(0);

    my ($err, @got) = one_call($r);
    is($err, '', "no exception");
    ok(!@got, "a burst still in flight returns empty");
};

done_testing;



( run in 1.219 second using v1.01-cache-2.11-cpan-df04353d9ac )