App-karr
view release on metacpan or search on metacpan
t/52-syncguard-lifetime.t view on Meta::CPAN
use strict;
use warnings;
use Test::More;
use App::karr::Role::SyncLifecycle;
# Regression for ticket #28:
# Every command calls $self->sync_before; in VOID context. sync_before built
# a SyncGuard and returned it, but nobody kept the returned guard alive, so it
# was DESTROYed the instant sync_before returned -- firing a redundant push
# BEFORE the command body ran (the doubled "Push attempt 1 of 3..." seen on
# every karr move/handoff). The documented "insurance on die before
# sync_after" therefore never engaged: by the time the body died, the guard
# was long gone.
#
# The fix keeps the guard alive for the duration of the command by stashing it
# on the SyncLifecycle role; sync_after neutralises it after a successful push.
# A counting Git double: records the exact order of pull/push calls so we can
# assert *when* the guard fires relative to the command body.
{
package CountingGit;
sub new { bless { log => [], pushes => 0, pulls => 0 }, shift }
sub pull { my ($self) = @_; $self->{pulls}++; CORE::push @{ $self->{log} }, 'pull'; 1 }
sub push { my ($self) = @_; $self->{pushes}++; CORE::push @{ $self->{log} }, 'push'; 1 }
sub mark { my ($self, $what) = @_; CORE::push @{ $self->{log} }, $what }
sub last_error { undef }
sub pushes { $_[0]{pushes} }
sub events { @{ $_[0]{log} } }
}
# Minimal stand-in for a command: composes the sync lifecycle role and calls
# sync_before / sync_after in VOID context, exactly as every real Cmd/* does.
{
package LifecycleBoard;
use Moo;
use MooX::Options; # SyncLifecycle now carries a MooX::Options option (--quiet)
with 'App::karr::Role::SyncLifecycle';
has git => ( is => 'ro', required => 1 );
}
# Silence the "Pull attempt.../Push attempt..." retry chatter these calls emit
# on STDERR; this test asserts on push *counts and ordering*, not on messages.
sub silent (&) {
my ($code) = @_;
local *STDERR;
open STDERR, '>', \(my $buf) or die "cannot redirect STDERR: $!";
$code->();
}
subtest 'sync_before performs no push in void context (guard is held)' => sub {
my $git = CountingGit->new;
silent {
my $board = LifecycleBoard->new( git => $git );
$board->sync_before; # void context -- just like every command
is $git->pushes, 0,
'sync_before does not push (guard survives, not discarded in void context)';
$board->sync_after; # neutralise + clean teardown
};
};
subtest 'full lifecycle: pull -> body -> push, exactly one push' => sub {
my $git = CountingGit->new;
silent {
my $board = LifecycleBoard->new( git => $git );
$board->sync_before;
$git->mark('body'); # the command body does its work here
$board->sync_after;
undef $board; # a clean sync_after leaves the guard neutralised
};
( run in 1.051 second using v1.01-cache-2.11-cpan-14f38c9f855 )