Class-Abstract
view release on metacpan or search on metacpan
t/edge_cases.t view on Meta::CPAN
is ref($obj), $config{pkg_abstract},
'SECURITY: resulting object is blessed into the abstract class';
};
};
# ===========================================================================
# SECTION 14: Context sensitivity for is_abstract()
#
# is_abstract() returns a scalar integer. In list context it must still
# deliver a single meaningful value. In boolean context the result must
# be truthy for abstract classes and falsy for concrete ones.
# ===========================================================================
# Purpose: is_abstract() behaves consistently across calling contexts
subtest 'is_abstract() -- consistent in list, scalar, and boolean contexts' => sub {
plan tests => 5;
# List context: must return a list of exactly one element
my @list = EC::Abstract->is_abstract();
is scalar @list, 1,
'is_abstract() in list context returns a 1-element list';
is $list[0], $TRUE,
'is_abstract() list context: first element is 1 for abstract class';
# Scalar context: must return the integer 1 for abstract class
my $scalar = EC::Abstract->is_abstract();
is $scalar, $TRUE,
'is_abstract() in scalar context returns 1 for abstract class';
# Boolean context: abstract must be truthy, concrete must be falsy.
# Explicit parens prevent 'ok CLASS->method' being parsed as 'CLASS->ok(...)'.
ok( EC::Abstract->is_abstract(),
'EC::Abstract->is_abstract() is truthy in boolean context' );
ok( !EC::Concrete->is_abstract(),
'EC::Concrete->is_abstract() is falsy in boolean context' );
};
# ===========================================================================
# SECTION 15: Weakened reference as invocant
#
# Scalar::Util::weaken() removes the strong reference count. Once all
# strong references are gone the object is destroyed and the weak ref becomes
# undef. A live (still-referenced) weakened blessed ref must pass new().
# A dead (garbage-collected) weakened ref becomes undef and must croak.
# ===========================================================================
# Purpose: live weakened blessed ref works; dead weakened ref croaks
subtest 'weakened references as invocants' => sub {
plan tests => 3;
# Live weak ref: a strong ref also exists, so the object survives
my $strong = EC::Concrete->new();
my $weak = $strong;
weaken($weak);
diag 'weak ref: ' . (defined($weak) ? ref($weak) : 'undef') if $ENV{TEST_VERBOSE};
# $weak is still alive because $strong holds a strong reference
ok defined($weak) && blessed($weak),
'precondition: weakened ref is still alive (strong ref holds it)';
# A live weakened blessed ref must be accepted by new()
lives_ok { $strong->new() }
'live weakened blessed ref: new() succeeds via strong ref';
# Dead weak ref: no strong references, object is garbage-collected immediately
my $dead_weak = do { my $obj = EC::Concrete->new(); weaken($obj); $obj };
# The dead weak ref is undef; ref(undef) = "", so it hits the undef guard
throws_ok { Class::Abstract::new($dead_weak) }
$config{err_new_undef},
'dead weakened ref (undef) causes new() to croak with defined-class-name error';
};
# ===========================================================================
# SECTION 16: $_ is never clobbered by any public method
#
# Any grep or map over @ISA inside _is_direct_abstract could overwrite $_.
# Verify $_ is unchanged across all four public methods under varied inputs.
# ===========================================================================
# Purpose: no public method may mutate $_ in the calling scope
subtest '$_ not clobbered by any public method under varied inputs' => sub {
plan tests => 4;
# Use a value containing characters that would cause obvious corruption
Readonly::Scalar my $SENTINEL => 'SENTINEL_DO_NOT_MUTATE';
# import() must leave $_ alone
{
local $_ = $SENTINEL;
{ package EC::DontClobberImport; our @ISA = (); Class::Abstract->import() }
is $_, $SENTINEL, '$_ unchanged after import()';
}
# new() must leave $_ alone
{
local $_ = $SENTINEL;
EC::Concrete->new();
is $_, $SENTINEL, '$_ unchanged after new()';
}
# check_abstract() must leave $_ alone
{
local $_ = $SENTINEL;
enforcement_on { Class::Abstract::check_abstract($config{pkg_concrete}) };
is $_, $SENTINEL, '$_ unchanged after check_abstract()';
}
# is_abstract() must leave $_ alone (grep on @ISA is the risk)
{
local $_ = $SENTINEL;
EC::Abstract->is_abstract();
EC::Concrete->is_abstract();
Class::Abstract->is_abstract($config{pkg_abstract});
is $_, $SENTINEL, '$_ unchanged after multiple is_abstract() calls';
}
};
done_testing;
( run in 1.254 second using v1.01-cache-2.11-cpan-df04353d9ac )