view release on metacpan or search on metacpan
lib/Assert/Conditional.pm view on Meta::CPAN
push @{ $his_export_tags->{$tag} }, $subname;
carp "Adding $subname to EXPORT_TAG :$tag in $package at ",__FILE__," line ",__LINE__ if $debugging;
}
}
################################################################
# Subs below are grouped by related type. Their documentation is
# in the sub <DATA> pod.
sub assert_list_context()
:Assert( qw[context] )
{
my $wantarray = his_context;
$wantarray || botch "wanted to be called in list context";
}
sub assert_nonlist_context()
:Assert( qw[context] )
{
my $wantarray = his_context;
!$wantarray || botch "wanted to be called in nonlist context";
}
sub assert_scalar_context()
:Assert( qw[context] )
{
my $wantarray = his_context;
defined($wantarray) && !$wantarray
|| botch "wanted to be called in scalar context";
}
sub assert_void_context()
:Assert( qw[context] )
{
my $wantarray = his_context;
!defined($wantarray) || botch "wanted to be called in void context";
}
sub assert_nonvoid_context()
:Assert( qw[context] )
{
my $wantarray = his_context;
defined($wantarray) || botch "wanted to be called in nonvoid context";
}
sub assert_true($)
:Assert( qw[scalar boolean] )
{
my($arg) = @_;
$arg || botch "expected true argument";
}
sub assert_false($)
:Assert( qw[scalar boolean] )
{
my($arg) = @_;
$arg && botch "expected true argument";
}
sub assert_defined($)
:Assert( qw[scalar] )
{
my($value) = @_;
defined($value) || botch "expected defined value as argument";
}
sub assert_undefined($)
:Assert( qw[scalar] )
{
my($scalar) = @_;
defined($scalar) && botch "expected undefined argument";
}
sub assert_defined_variable(\$)
:Assert( qw[scalar] )
{
&assert_scalarref;
my($sref) = @_;
defined($$sref) || botch "expected defined scalar variable as argument";
}
sub assert_defined_value($)
:Assert( qw[scalar] )
{
my($value) = @_;
defined($value) || botch "expected defined value as argument";
}
sub assert_is($$)
:Assert( qw[string] )
{
my($this, $that) = @_;
assert_defined($_) for $this, $that;
assert_nonref($_) for $this, $that;
$this eq $that || botch "string '$this' should be '$that'";
}
sub assert_isnt($$)
:Assert( qw[string] )
{
my($this, $that) = @_;
assert_defined($_) for $this, $that;
assert_nonref($_) for $this, $that;
$this ne $that || botch "string '$this' should not be '$that'";
}
sub assert_numeric($)
:Assert( qw[number] )
{
&assert_defined;
&assert_nonref;
my($n) = @_;
looks_like_number($n) || botch "'$n' doesn't look like a number";
}
sub assert_nonnumeric($)
:Assert( qw[number] )
{
&assert_nonref;
my($n) = @_;
!looks_like_number($n) || botch "'$n' looks like a number";
}
sub assert_positive($)
:Assert( qw[number] )
{
&assert_numeric;
my($n) = @_;
$n > 0 || botch "$n should be positive";
}
sub assert_nonpositive($)
:Assert( qw[number] )
{
&assert_numeric;
my($n) = @_;
$n <= 0 || botch "$n should not be positive";
}
sub assert_negative($)
:Assert( qw[number] )
{
&assert_numeric;
my($n) = @_;
$n < 0 || botch "$n should be negative";
}
sub assert_nonnegative($)
:Assert( qw[number] )
{
&assert_numeric;
my($n) = @_;
$n >= 0 || botch "$n should not be negative";
}
sub assert_zero($)
:Assert( qw[number] )
{
&assert_numeric;
my($n) = @_;
$n == 0 || botch "$n should be zero";
}
sub assert_nonzero($)
:Assert( qw[number] )
{
&assert_numeric;
my($n) = @_;
$n != 0 || botch "$n should not be zero";
}
sub assert_integer($)
:Assert( qw[number] )
{
&assert_numeric;
my($int) = @_;
$int == int($int) || botch "expected integer, not $int";
}
sub assert_fractional($)
:Assert( qw[number] )
{
&assert_numeric;
my($float) = @_;
$float != int($float) || botch "expected fractional part, not $float";
}
sub assert_signed_number($)
:Assert( qw[number] )
{
&assert_numeric;
my($n) = @_;
$n =~ /^ [-+] /x || botch "expected signed number, not $n";
}
sub assert_natural_number($)
:Assert( qw[number] )
{
&assert_positive_integer;
my($int) = @_;
}
sub assert_whole_number($)
:Assert( qw[number] )
{
&assert_nonnegative_integer;
my($int) = @_;
}
sub assert_positive_integer($)
:Assert( qw[number] )
{
&assert_integer;
&assert_positive;
}
sub assert_nonpositive_integer($)
:Assert( qw[number] )
{
&assert_integer;
&assert_nonpositive;
}
sub assert_negative_integer($)
:Assert( qw[number] )
{
&assert_integer;
&assert_negative;
}
sub assert_nonnegative_integer($)
:Assert( qw[number] )
{
&assert_integer;
&assert_nonnegative;
}
sub assert_hex_number($)
:Assert( qw[regex number] )
{
local($_) = @_;
/^ (?:0x)? \p{ahex}+ \z/ix || botch "expected only ASCII hex digits in string '$_'";
}
sub assert_box_number($)
:Assert( qw[number] )
{
local($_) = @_;
&assert_defined;
/^ (?: 0b ) [0-1]+ \z /x ||
/^ (?: 0o | 0)? [0-7]+ \z /x ||
/^ (?: 0x ) [0-9a-fA-F]+ \z /x
|| botch "I wouldn't feed '$_' to oct() if I were you";
}
sub assert_even_number($)
:Assert( qw[number] )
{
&assert_integer;
my($n) = @_;
$n % 2 == 0 || botch "$n should be even";
}
sub assert_odd_number($)
:Assert( qw[number] )
{
&assert_integer;
my($n) = @_;
$n % 2 == 1 || botch "$n should be odd";
}
sub assert_in_numeric_range($$$)
:Assert( qw[number] )
{
assert_numeric($_) for my($n, $low, $high) = @_;
$n >= $low && $n <= $high || botch "expected $low <= $n <= $high";
}
sub assert_empty($)
:Assert( qw[string] )
{
&assert_defined;
&assert_nonref;
my($string) = @_;
length($string) == 0 || botch "expected zero-length string";
}
sub assert_nonempty($)
:Assert( qw[string] )
{
&assert_defined;
&assert_nonref;
my($string) = @_;
length($string) != 0 || botch "expected non-zero-length string";
}
sub assert_blank($)
:Assert( qw[string regex] )
{
&assert_defined;
&assert_nonref;
my($string) = @_;
$string =~ /^ \p{whitespace}* \z/x || botch "found non-whitespace in string '$string'"
}
sub assert_nonblank($)
:Assert( qw[string regex] )
{
&assert_defined;
&assert_nonref;
my($string) = @_;
$string =~ / \P{whitespace}/x || botch "found no non-whitespace in string '$string'"
}
my $_single_line_rx = qr{
\A
( (?! \R ) \X )+
\R ?
\z
}x;
sub assert_single_line($)
:Assert( qw[string regex] )
{
&assert_nonempty;
my($string) = @_;
$string =~ $_single_line_rx || botch "expected at most a single linebreak at the end";
}
sub assert_multi_line($)
:Assert( qw[string regex] )
{
&assert_nonempty;
my($string) = @_;
$string !~ $_single_line_rx || botch "expected more than one linebreak";
}
sub assert_single_paragraph($)
:Assert( qw[string regex] )
{
&assert_nonempty;
my($string) = @_;
$string =~ / \A ( (?! \R ) \X )+ \R* \z /x
|| botch "expected at most a single linebreak at the end";
}
sub assert_bytes($)
:Assert( qw[string] )
{
local($_) = @_;
/^ [\x00-\xFF] + \z/x || botch "unexpected wide characters in byte string";
}
sub assert_nonbytes($)
:Assert( qw[string] )
{
&assert_wide_characters;
}
sub assert_wide_characters($)
:Assert( qw[string] )
{
local($_) = @_;
/[^\x00-\xFF]/x || botch "expected some wide characters in string";
}
sub assert_nonascii($)
:Assert( qw[string regex] )
{
local($_) = @_;
/\P{ascii}/x || botch "expected non-ASCII in string";
}
sub assert_ascii($)
:Assert( qw[string regex] )
{
local($_) = @_;
/^ \p{ASCII} + \z/x || botch "expected only ASCII in string";
}
sub assert_alphabetic($)
:Assert( qw[string regex] )
{
local($_) = @_;
/^ \p{alphabetic} + \z/x || botch "expected only alphabetics in string";
}
sub assert_nonalphabetic($)
:Assert( qw[string regex] )
{
local($_) = @_;
/^ \P{alphabetic} + \z/x || botch "expected only non-alphabetics in string";
}
sub assert_alnum($)
:Assert( qw[regex] )
{
local($_) = @_;
/^ \p{alnum} + \z/x || botch "expected only alphanumerics in string";
}
sub assert_digits($)
:Assert( qw[regex number] )
{
local($_) = @_;
/^ [0-9] + \z/x || botch "expected only ASCII digits in string";
}
sub assert_uppercased($)
:Assert( qw[case regex] )
{
local($_) = @_;
($] >= 5.014
? ! /\p{Changes_When_Uppercased}/
: $_ eq uc ) || botch "changes case when uppercased";
}
sub assert_lowercased($)
:Assert( qw[case regex] )
{
local($_) = @_;
($] >= 5.014
? ! /\p{Changes_When_Lowercased}/
: $_ eq lc ) || botch "changes case when lowercased";
}
sub assert_unicode_ident($)
:Assert( qw[regex] )
{
local($_) = @_;
/^ \p{XID_Start} \p{XID_Continue}* \z/x
|| botch "invalid identifier $_";
}
# This is a lie.
my $perl_simple_ident_rx = qr{
\b
lib/Assert/Conditional.pm view on Meta::CPAN
\p{XID_Continue} *+
\b
}x;
my $perl_qualified_ident_rx = qr{
(?: $perl_simple_ident_rx
| (?: :: | ' )
) +
}x;
sub assert_simple_perl_ident($)
:Assert( qw[regex ident] )
{
local($_) = @_;
/^ $perl_simple_ident_rx \z/x
|| botch "invalid simple perl identifier $_";
}
sub assert_full_perl_ident($)
:Assert( qw[regex ident] )
{
local($_) = @_;
/^ $perl_qualified_ident_rx \z/x
|| botch "invalid qualified perl identifier $_";
}
sub assert_qualified_ident($)
:Assert( qw[regex ident] )
{
&assert_full_perl_ident;
local($_) = @_;
/(?: ' | :: ) /x || botch "no package separators in $_";
}
sub assert_ascii_ident($)
:Assert( qw[regex ident] )
{
local($_) = @_;
/^ (?= \p{ASCII}+ \z) (?! \d) \w+ \z/x
|| botch q(expected only ASCII \\w characters in string);
}
sub assert_regex($)
:Assert( qw[regex] )
{
my($pattern) = @_;
assert_isa($pattern, "Regexp");
}
sub assert_like($$)
:Assert( qw[regex] )
{
my($string, $pattern) = @_;
assert_defined($string);
assert_nonref($string);
assert_regex($pattern);
$string =~ $pattern || botch "'$string' did not match $pattern";
}
sub assert_unlike($$)
:Assert( qw[regex] )
{
my($string, $pattern) = @_;
assert_defined($string);
assert_nonref($string);
assert_regex($pattern);
$string !~ $pattern || botch "'$string' should not match $pattern";
}
sub assert_latin1($)
:Assert( qw[string unicode] )
{
&assert_bytes;
}
sub assert_latinish($)
:Assert( qw[unicode] )
{
local($_) = @_;
/^[\p{Latin}\p{Common}\p{Inherited}]+/
|| botch "expected only Latinish characters in string";
}
sub assert_astral($)
:Assert( qw[unicode] )
{
local($_) = @_;
no warnings "utf8"; # early versions of perl complain of illegal for interchange on FFFF
/[^\x00-\x{FFFF}]/x || botch "expected non-BMP characters in string";
}
sub assert_nonastral($)
:Assert( qw[unicode] )
{
local($_) = @_;
no warnings "utf8"; # early versions of perl complain of illegal for interchange on FFFF
/^ [\x00-\x{FFFF}] * \z/x || botch "unexpected non-BMP characters in string";
}
sub assert_bmp($)
:Assert( qw[unicode] )
{
&assert_nonastral;
}
sub assert_nfc($)
:Assert( qw[unicode] )
{
my($str) = @_;
checkNFC($str) // $str eq NFC($str)
|| botch "string not in NFC form";
}
sub assert_nfkc($)
:Assert( qw[unicode] )
{
my($str) = @_;
checkNFKC($str) // $str eq NFKC($str)
|| botch "string not in NFKC form";
}
sub assert_nfd($)
:Assert( qw[unicode] )
{
my($str) = @_;
checkNFD($str) || botch "string not in NFD form";
}
sub assert_nfkd($)
:Assert( qw[unicode] )
{
my($str) = @_;
checkNFKD($str) || botch "string not in NFKD form";
}
sub assert_eq($$)
:Assert( qw[string unicode] )
{
my($this, $that) = @_;
NFC($this) eq NFC($that) || botch "'$this' and '$that' are not equivalent Unicode strings";
}
sub assert_eq_letters($$)
:Assert( qw[string unicode] )
{
my($this, $that) = @_;
UCA1($this) eq UCA1($that) || botch "'$this' and '$that' do not equivalent letters"
}
sub assert_in_list($@)
:Assert( qw[list] )
{
my($needle, @haystack) = @_;
#assert_nonref($needle);
my $undef_needle = !defined($needle);
for my $straw (@haystack) {
#assert_nonref($straw);
return if $undef_needle
? !defined($straw)
: ("$needle" eq (defined($straw) && "$straw"))
}
$needle = "undef" unless defined $needle;
botch "couldn't find $needle in " . join(", " => map { defined() ? $_ : "undef" } @haystack);
}
sub assert_not_in_list($@)
:Assert( qw[list] )
{
my($needle, @haystack) = @_;
my $found = 0;
for my $straw (@haystack) {
if (defined $needle) {
next if !defined $straw;
if ("$needle" eq "$straw") {
$found = 1;
last;
lib/Assert/Conditional.pm view on Meta::CPAN
next if defined $straw;
$found = 1;
last;
}
}
return unless $found;
$needle = "undef" unless defined $needle;
botch "found $needle in forbidden list";
}
sub assert_list_nonempty( @ )
:Assert( qw[list array] )
{
@_ || botch "list is empty";
}
sub assert_array_nonempty( \@ )
:Assert( qw[array] )
{
&assert_arrayref_nonempty;
}
sub assert_arrayref_nonempty( $ )
:Assert( qw[array] )
{
&assert_array_length;
my($aref) = @_;
assert_arrayref($aref);
my $count = @$aref;
$count > 0 || botch("array is empty");
}
sub assert_array_length( \@ ;$ )
:Assert( qw[array] )
{
if (@_ == 1) {
assert_array_length_min(@{$_[0]} => 1);
return;
}
my($aref, $want) = @_;
assert_arrayref($aref);
assert_whole_number($want);
my $have = @$aref;
$have == $want || botch_array_length($have, $want);
}
sub assert_array_length_min( \@ $ )
:Assert( qw[array] )
{
my($aref, $want) = @_;
assert_arrayref($aref);
assert_whole_number($want);
my $have = @$aref;
$have >= $want || botch_array_length($have, "$want or more");
}
sub assert_array_length_max( \@ $ )
:Assert( qw[array] )
{
my($aref, $want) = @_;
assert_arrayref($aref);
assert_whole_number($want);
my $have = @$aref;
$have <= $want || botch_array_length($have, "$want or fewer");
}
sub assert_array_length_minmax( \@ $$)
:Assert( qw[array] )
{
my($aref, $low, $high) = @_;
my $have = @$aref;
assert_whole_number($_) for $low, $high;
$have >= $low && $have <= $high
|| botch_array_length($have, "between $low and $high");
}
sub assert_argc(;$)
:Assert( qw[argc] )
{
unless (@_) {
his_args || botch_argc(0, "at least 1");
return;
}
&assert_whole_number;
my($want) = @_;
my $have = his_args;
$have == $want || botch_argc($have, $want);
}
sub assert_argc_min($)
:Assert( qw[argc] )
{
&assert_whole_number;
my($want) = @_;
my $have = his_args;
$have >= $want || botch_argc($have, "$want or more");
}
sub assert_argc_max($)
:Assert( qw[argc] )
{
&assert_whole_number;
my($want) = @_;
my $have = his_args;
$have <= $want || botch_argc($have, "$want or fewer");
}
sub assert_argc_minmax($$)
:Assert( qw[argc] )
{
assert_whole_number($_) for my($low, $high) = @_;
my $have = his_args;
$have >= $low && $have <= $high
|| botch_argc($have, "between $low and $high");
}
sub assert_hash_nonempty(\%)
:Assert( qw[hash] )
{
&assert_hashref_nonempty;
}
sub assert_hashref_nonempty($)
:Assert( qw[hash] )
{
&assert_hashref;
my($href) = @_;
%$href || botch "hash is empty";
}
sub assert_hash_keys(\% @)
:Assert( qw[hash] )
{
&assert_hashref_keys;
}
sub assert_hash_keys_required(\% @)
:Assert( qw[hash] )
{
&assert_hashref_keys_required;
}
sub assert_hash_keys_allowed(\% @)
:Assert( qw[hash] )
{
&assert_hashref_keys_allowed;
}
sub assert_hash_keys_required_and_allowed(\% $ $)
:Assert( qw[hash] )
{
&assert_hashref_keys_required_and_allowed;
}
sub assert_hash_keys_allowed_and_required(\% $ $)
:Assert( qw[hash] )
{
&assert_hashref_keys_allowed_and_required;
}
sub assert_hashref_keys($@)
:Assert( qw[hash] )
{
&assert_hashref_keys_required;
}
sub assert_hashref_keys_required($@)
:Assert( qw[hash] )
{
my($hashref, @keylist) = @_;
assert_min_keys($hashref, @keylist);
}
sub assert_hashref_keys_allowed($@)
:Assert( qw[hash] )
{
my($hashref, @keylist) = @_;
assert_max_keys($hashref, @keylist);
}
sub _promote_to_typeref($$) {
my(undef, $type) = @_;
&assert_anyref;
$_[0] = ${ $_[0] } if (reftype($_[0]) // "") =~ /^ (?: SCALAR | REF ) \z/x;
assert_reftype($type, $_[0]);
}
sub _promote_to_hashref ($) { _promote_to_typeref($_[0], "HASH") }
sub _promote_to_arrayref($) { _promote_to_typeref($_[0], "ARRAY") }
sub assert_min_keys( \[%$] @ )
:Assert( qw[hash] )
{
my($hashref, @keylist) = @_;
_promote_to_hashref($hashref);
@keylist || botch "no min keys given";
my @missing = grep { !exists $hashref->{$_} } @keylist;
return unless @missing;
my $message = "key" . (@missing > 1 && "s") . " "
. quotify_and(uca_sort @missing)
. " missing from hash";
botch $message;
}
sub assert_max_keys( \[%$] @ )
:Assert( qw[hash] )
{
my($hashref, @keylist) = @_;
_promote_to_hashref($hashref);
my %allowed = map { $_ => 1 } @keylist;
my @forbidden;
for my $key (keys %$hashref) {
delete $allowed{$key} || push @forbidden, $key;
}
return unless @forbidden;
my $message = "key" . (@forbidden > 1 && "s") . " "
. quotify_and(uca_sort @forbidden)
. " forbidden in hash";
botch $message;
}
sub assert_minmax_keys( \[%$] \[@$] \[@$] )
:Assert( qw[hash] )
{
my($hashref, $minkeys, $maxkeys) = @_;
_promote_to_hashref($hashref);
_promote_to_arrayref($minkeys);
@$minkeys || botch "no min keys given";
_promote_to_arrayref($maxkeys);
@$maxkeys || botch "no max keys given";
my @forbidden;
lib/Assert/Conditional.pm view on Meta::CPAN
my $forbidden_msg = !@forbidden ? "" :
"key" . (@forbidden > 1 && "s") . " "
. quotify_and(uca_sort @forbidden)
. " forbidden in hash";
my $message = commify_and grep { length } $missing_msg, $forbidden_msg;
botch $message;
}
sub assert_keys( \[%$] @ )
:Assert( qw[hash] )
{
my($hashref, @keylist) = @_;
_promote_to_hashref($hashref);
assert_minmax_keys($hashref, @keylist, @keylist);
}
sub assert_hashref_keys_required_and_allowed($$$)
:Assert( qw[hash] )
{
my($hashref, $required, $allowed) = @_;
assert_minmax_keys($hashref, $required, $allowed);
}
sub assert_hashref_keys_allowed_and_required($$$)
:Assert( qw[hash] )
{
my($hashref, $allowed, $required) = @_;
assert_minmax_keys($hashref, $required, $allowed);
}
# From perl5180delta, you couldn't actually get any use of
# the predicates to check whether a hash or hashref was
# locked because even though they were exported those
lib/Assert/Conditional.pm view on Meta::CPAN
_promote_to_hashref($hashref);
!hash_locked(%$hashref) || botch "hash is not locked";
}
1;
END_OF_LOCK_STUFF
}
}
sub assert_anyref($)
:Assert( qw[ref] )
{
my($arg) = @_;
ref($arg) || botch "expected reference argument";
}
sub assert_nonref($)
:Assert( qw[ref] )
{
my($arg) = @_;
!ref($arg) || botch "expected nonreference argument";
}
sub assert_reftype($$)
:Assert( qw[object ref] )
{
my($want_type, $arg) = @_;
my $have_type = reftype($arg) // "non-reference";
$have_type eq $want_type || botch "expected reftype of $want_type not $have_type";
}
sub assert_globref($)
:Assert( qw[glob ref] )
{
my($arg) = @_;
assert_reftype(GLOB => $arg);
}
sub assert_ioref($)
:Assert( qw[io ref] )
{
my($arg) = @_;
assert_reftype(IO => $arg);
}
sub assert_coderef($)
:Assert( qw[code ref] )
{
my($arg) = @_;
assert_reftype(CODE => $arg);
}
sub assert_hashref($)
:Assert( qw[hash ref] )
{
my($arg) = @_;
assert_reftype(HASH => $arg);
}
sub assert_arrayref($)
:Assert( qw[array ref] )
{
my($arg) = @_;
assert_reftype(ARRAY => $arg);
}
sub assert_refref($)
:Assert( qw[ref] )
{
my($arg) = @_;
assert_reftype(REF => $arg);
}
sub assert_scalarref($)
:Assert( qw[scalar ref] )
{
my($arg) = @_;
assert_reftype(SCALAR => $arg);
}
sub assert_unblessed_ref($)
:Assert( qw[ref object] )
{
&assert_anyref;
&assert_nonobject;
}
sub assert_method()
:Assert( qw[object] )
{
my $argc = his_args;
$argc >= 1 || botch "invocant missing from method invoked as subroutine";
}
sub assert_object_method()
:Assert( qw[object] )
{
my $argc = his_args;
$argc >= 1 || botch "no invocant found";
my($self) = his_args;
blessed($self) || botch "object method invoked as class method";
}
sub assert_class_method()
:Assert( qw[object] )
{
my $argc = his_args;
$argc >= 1 || botch "no invocant found";
my($class) = his_args;
!blessed($class) || botch "class method invoked as object method";
}
# This one is a no-op!
sub assert_public_method()
:Assert( qw[object] )
{
my $argc = his_args;
$argc >= 1 || botch "invocant missing from public method invoked as subroutine";
}
my %skip_caller = map { $_ => 1 } qw(
Class::MOP::Method::Wrapped
Moose::Meta::Method::Augmented
);
# And this one isn't *all* that hard... relatively speaking.
sub assert_private_method()
:Assert( qw[object] )
{
my $argc = his_args;
$argc >= 1 || botch "invocant missing from private method invoked as subroutine";
my $frame = 0;
my @to = caller $frame++;
my @from = caller $frame++;
while (@from && $skip_caller{ $from[CALLER_PACKAGE] }) {
lib/Assert/Conditional.pm view on Meta::CPAN
|| push @botches, "alien file $from[CALLER_FILENAME] line $from[CALLER_LINE]";
@botches == 0
|| botch "$msg " . join(" at " => @botches);
}
# But this one? This one is RIDICULOUS. O Moose how we hates you
# foreverz for ruining perl's simple inheritance model and its export
# model and its import model and its package model till the end of time!
sub assert_protected_method()
:Assert( qw[object] )
{
my $argc = his_args;
$argc >= 1 || botch "invocant missing from protected method invoked as subroutine";
my $self; # sic, no assignment
my $frame = 0;
my $next_frame = sub {
package DB;
lib/Assert/Conditional.pm view on Meta::CPAN
|| $self->DOES( $from[CALLER_PACKAGE] )
) || botch join " " => ($msg,
"called from unfriendly package"
=> $from[CALLER_PACKAGE],
at => $from[CALLER_FILENAME],
line => $from[CALLER_LINE]
);
}
sub assert_known_package($)
:Assert( qw[object ident] )
{
&assert_nonempty;
my($arg) = @_;
my $stash = do { no strict "refs"; \%{ $arg . "::" } };
no overloading;
%$stash || botch "unknown package $arg";
}
sub assert_object($)
:Assert( qw[object] )
{
no overloading;
&assert_anyref;
my($arg) = @_;
blessed($arg) || botch "expected blessed referent not $arg";
}
sub assert_nonobject($)
:Assert( qw[object] )
{
no overloading;
my($arg) = @_;
!blessed($arg) || botch "expected unblessed referent not $arg";
}
sub _get_invocant_type($) {
my($invocant) = @_;
my $type;
if (blessed $invocant) {
$type = "object";
} else {
$type = "package";
}
return $type;
}
sub assert_can($@)
:Assert( qw[object] )
{
no overloading;
my($invocant, @methods) = @_;
@methods || botch "need one or more methods to check against";
my $type = _get_invocant_type $invocant;
my @cant = grep { !$invocant->can($_) } @methods;
return unless @cant;
my $message = "cannot invoke method"
. (@cant > 1 && "s") . " "
. quotify_or(uca_sort @cant)
. " on $type $invocant";
botch $message;
}
sub assert_cant($@)
:Assert( qw[object] )
{
no overloading;
my($invocant, @methods) = @_;
@methods || botch "need one or more methods to check against";
my $type = _get_invocant_type $invocant;
my @can = grep { $invocant->can($_) } @methods;
return unless @can;
my $message = "should not be able to invoke method"
. (@can > 1 && "s") . " "
. quotify_or(uca_sort @can)
. " on $type $invocant";
botch $message;
}
sub assert_object_can($@)
:Assert( qw[object] )
{
my($instance, @methods) = @_;
assert_object($instance);
assert_can($instance, @methods);
}
sub assert_object_cant($@)
:Assert( qw[object] )
{
my($instance, @methods) = @_;
assert_object($instance);
assert_cant($instance, @methods);
}
sub assert_class_can($@)
:Assert( qw[object] )
{
my($class, @methods) = @_;
assert_known_package($class);
assert_can($class, @methods);
}
sub assert_class_cant($@)
:Assert( qw[object] )
{
my($class, @methods) = @_;
assert_known_package($class);
assert_cant($class, @methods);
}
sub assert_isa($@)
:Assert( qw[object] )
{
my($subclass, @superclasses) = @_;
@superclasses || botch "needs one or more superclasses to check against";
my $type = _get_invocant_type $subclass;
my @ainta = grep { !$subclass->isa($_) } @superclasses;
!@ainta || botch "your $subclass $type should be a subclass of " . commify_and(uca_sort @ainta);
}
sub assert_ainta($@)
:Assert( qw[object] )
{
no overloading;
my($subclass, @superclasses) = @_;
@superclasses || botch "needs one or more superclasses to check against";
my $type = _get_invocant_type $subclass;
my @isa = grep { $subclass->isa($_) } @superclasses;
!@isa || botch "your $subclass $type should not be a subclass of " . commify_or(uca_sort @isa);
}
sub assert_object_isa($@)
:Assert( qw[object] )
{
my($instance, @superclasses) = @_;
assert_object($instance);
assert_isa($instance, @superclasses);
}
sub assert_object_ainta($@)
:Assert( qw[object] )
{
my($instance, @superclasses) = @_;
assert_object($instance);
assert_ainta($instance, @superclasses);
}
sub assert_class_isa($@)
:Assert( qw[object] )
{
my($class, @superclasses) = @_;
assert_known_package($class);
assert_isa($class, @superclasses);
}
sub assert_class_ainta($@)
:Assert( qw[object] )
{
my($class, @superclasses) = @_;
assert_known_package($class);
assert_ainta($class, @superclasses);
}
sub assert_does($@)
:Assert( qw[object] )
{
no overloading;
my($invocant, @roles) = @_;
@roles || botch "needs one or more roles to check against";
my $type = _get_invocant_type $invocant;
my @doesnt = grep { !$invocant->DOES($_) } @roles;
!@doesnt || botch "your $type $invocant does not have role"
. (@doesnt > 1 && "s") . " "
. commify_or(uca_sort @doesnt);
}
sub assert_doesnt($@)
:Assert( qw[object] )
{
no overloading;
my($invocant, @roles) = @_;
@roles || botch "needs one or more roles to check against";
my $type = _get_invocant_type $invocant;
my @does = grep { $invocant->DOES($_) } @roles;
!@does || botch "your $type $invocant does not have role"
. (@does > 1 && "s") . " "
. commify_or(uca_sort @does);
}
sub assert_object_overloads($@)
:Assert( qw[object overload] )
{
no overloading;
&assert_object;
my($object, @operators) = @_;
overload::Overloaded($object) || botch "your $object isn't overloaded";
my @missing = grep { !overload::Method($object, $_) } @operators;
!@missing || botch "your $object does not overload the operator"
. (@missing > 1 && "s") . " "
. quotify_or(@missing);
}
sub assert_object_stringifies($)
:Assert( qw[object overload] )
{
my($object) = @_;
assert_object_overloads $object, q{""};
}
sub assert_object_nummifies($)
:Assert( qw[object overload] )
{
my($object) = @_;
assert_object_overloads $object, q{0+};
}
sub assert_object_boolifies($)
:Assert( qw[object overload] )
{
my($object) = @_;
assert_object_overloads $object, q{bool};
}
#########################################
# Some of these can trigger unwanted overloads.
{
lib/Assert/Conditional.pm view on Meta::CPAN
:Assert( qw[tie glob ref] )
{
&assert_globref;
my($globref) = @_;
!tied(*$globref) || botch "glob is tied";
}
} # scope for no overloading
# Common subroutine for the two happy/unhappy code tests.
sub _run_code_test($$) {
my($code, $joy) = @_;
assert_coderef($code);
return if !!&$code() == !!$joy;
botch sprintf "%s assertion %s is sadly %s",
$joy ? "happy" : "unhappy",
subname_or_code($code),
$joy ? "false" : "true";
}
sub assert_happy_code(&)
:Assert( qw[boolean code] )
{
my($cref) = @_;
_run_code_test($cref => 1);
}
sub assert_unhappy_code(&)
:Assert( qw[boolean code] )
{
my($cref) = @_;
_run_code_test($cref => 0);
}
sub assert_open_handle($)
:Assert( qw[io file] )
{
my($arg) = @_;
assert_defined($arg);
defined(openhandle($arg)) || botch "handle $arg is not an open handle";
}
sub assert_regular_file($)
:Assert( qw[file] )
{
my($arg) = @_;
assert_defined($arg);
-f $arg || botch "appears that $arg is not a plainfile"
. " nor a symlink to a plainfile";
}
sub assert_text_file($)
:Assert( qw[file] )
{
&assert_regular_file;
my($arg) = @_;
-T $arg || botch "appears that $arg does not contain text";
}
sub assert_directory($)
:Assert( qw[file] )
{
my($arg) = @_;
-d $arg || botch "appears that $arg is not a directory"
. " nor a symlink to a directory";
}
sub _WIFCORED(;$) {
my($wstat) = @_ ? $_[0] : $?;
# non-standard but nearly ubiquitous; too hard to fish from real sys/wait.h
return WIFSIGNALED($wstat) && !!($wstat & 128);
}
sub _coredump_message(;$) {
my($wstat) = @_ ? $_[0] : $?;
return _WIFCORED($wstat) && " (core dumped)";
}
sub _signum_message($) {
my($number) = @_;
my $name = sig_num2longname($number);
return "$name(#$number)";
}
sub assert_legal_exit_status(;$)
:Assert( qw[process] )
{
my($wstat) = @_ ? $_[0] : $?;
assert_whole_number($wstat);
$wstat < 2**16 || botch "exit value $wstat over 16 bits";
}
sub assert_signalled(;$)
:Assert( qw[process] )
{
&assert_legal_exit_status;
my($wstat) = @_ ? $_[0] : $?;
WIFSIGNALED($wstat) || botch "exit value $wstat indicates no signal";
}
sub assert_unsignalled(;$)
:Assert( qw[process] )
{
&assert_legal_exit_status;
my($wstat) = @_ ? $_[0] : $?;
WIFEXITED($wstat) && return;
my $signo = WTERMSIG($wstat);
my $sigmsg = _signum_message($signo);
my $cored = _coredump_message($wstat);
botch "exit value $wstat indicates process died from signal $sigmsg$cored";
}
sub assert_dumped_core(;$)
:Assert( qw[process] )
{
&assert_signalled;
my($wstat) = @_ ? $_[0] : $?;
my $signo = WTERMSIG($wstat);
my $sigmsg = _signum_message($signo);
_WIFCORED($wstat) || botch "exit value $wstat indicates signal $sigmsg but no core dump";
}
sub assert_no_coredump(;$)
:Assert( qw[process] )
{
my($wstat) = @_ ? $_[0] : $?;
my $cored = $wstat & 128; # not standard; too hard to fish from real sys/wait.h
return unless _WIFCORED($wstat);
return unless $cored;
my $signo = WTERMSIG($wstat);
my $sigmsg = _signum_message($signo);
botch "exit value $wstat shows process died of a $sigmsg and dumped core";
}
sub assert_exited(;$)
:Assert( qw[process] )
{
&assert_legal_exit_status;
my($wstat) = @_ ? $_[0] : $?;
return if WIFEXITED($wstat);
&assert_signalled;
my $signo = WTERMSIG($wstat);
my $sigmsg = _signum_message($signo);
my $cored = _coredump_message($wstat);
botch "exit value $wstat shows process did not exit but rather died of $sigmsg$cored";
}
sub assert_happy_exit(;$)
:Assert( qw[process] )
{
&assert_exited;
my($wstat) = @_ ? $_[0] : $?;
my $exit = WEXITSTATUS($wstat);
$exit == 0 || botch "exit status $exit is not a happy exit";
}
sub assert_sad_exit(;$)
:Assert( qw[process] )
{
&assert_exited;
my($wstat) = @_ ? $_[0] : $?;
my $exit = WEXITSTATUS($wstat);
$exit != 0 || botch "exit status 0 is an unexpectedly happy exit";
}
# If you actually *execute*(!) this module as though it were a perl
# script rather than merely require or compile it, it dumps out its
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
# Yes, you can actually export these that way too.
our($Assert_Debug, $Assert_Always, $Assert_Carp, $Assert_Never, $Allow_Handlers)
:Export( qw[vars] );
our $Pod_Generation;
# Let's not talk about these ones.
our(%PLURAL, %N_PLURAL)
:Export( qw[acme_plurals] );
sub _init_envariables() {
use Env qw(
ASSERT_CONDITIONAL
ASSERT_CONDITIONAL_BUILD_POD
ASSERT_CONDITIONAL_DEBUG
ASSERT_CONDITIONAL_ALLOW_HANDLERS
);
$Pod_Generation //= $ASSERT_CONDITIONAL_BUILD_POD || 0;
$Allow_Handlers //= $ASSERT_CONDITIONAL_ALLOW_HANDLERS || 0;
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
$Assert_Always ||= 1 unless $Assert_Carp || $Assert_Never;
if ($Assert_Never) {
warn q(Ignoring $Assert_Always because $Assert_Never is true) if $Assert_Always;
warn q(Ignoring $Assert_Carp because $Assert_Never is true) if $Assert_Carp;
$Assert_Always = $Assert_Carp = 0;
}
}
sub _init_public_vars() {
Acme::Plural->import();
}
# Now run that function right now, before the rest of the function:
BEGIN { _init_envariables() }
sub botch($)
:Export( qw[botch] )
{
return if $Assert_Never;
my($msg) = @_;
my $sub = his_assert;
local @SIG{<__{DIE,WARN}__>} unless $Allow_Handlers;
my $botch = "$0\[$$]: botched assertion $sub: \u$msg";
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
if ($Assert_Carp) {
Carp::carp($botch)
}
if ($Assert_Always) {
$botch = shortmess("$botch, bailing out");
Carp::confess("$botch\n Beginning stack dump from failed $sub");
}
}
sub botch_false()
:Export( qw[botch] )
{
panic "value should not be false";
}
sub botch_undef()
:Export( qw[botch] )
{
panic "value should not be undef";
}
#################################################################
#
# A few stray utility functions that are a bit too intimate with
# the assertions in this file to deserve being made public
sub botch_argc($$)
:Export( qw[botch] )
{
my($have, $want) = @_;
botch_have_thing_wanted(HAVE => $have, THING => "argument", WANTED => $want);
}
sub botch_array_length($$)
:Export( qw[botch] )
{
my($have, $want) = @_;
botch_have_thing_wanted(HAVE => $have, THING => "array element", WANTED => $want);
}
sub botch_have_thing_wanted(@)
:Export( qw[botch] )
{
my(%param) = @_;
my $have = $param{HAVE} // botch_undef;
my $thing = $param{THING} // botch_undef;
my $wanted = $param{WANTED} // botch_undef;
botch "have $N_PLURAL{$thing => $have} but wanted $wanted";
}
#################################################################
sub panic($)
:Export( qw[lint botch] )
{
my($msg) = @_;
local @SIG{<__{DIE,WARN}__>} unless $Allow_Handlers;
Carp::confess("Panicking on internal error: $msg");
}
sub FIXME()
:Export( qw[lint] )
{
panic "Unimplemented code reached; you forgot to code up a TODO section";
}
sub NOT_REACHED()
:Export( qw[lint] )
{
panic "Logically unreachable code somehow reached";
}
#################################################################
# Find the highest assert_ on the stack so that we don't misreport
# failures. For example this next one illustrated below should be
# reporting that assert_hash_keys_required botched because that's the
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
##
## Beginning stack dump from failed assert_hash_keys_required at lib/Assert/Conditional/Utils.pm line 391.
## Assert::Conditional::Utils::botch("key 'snap' missing from hash") called at lib/Assert/Conditional.pm line 1169
## Assert::Conditional::assert_min_keys(REF(0x7fe6196ec3f0), "snap") called at lib/Assert/Conditional.pm line 1135
## Assert::Conditional::assert_hashref_keys_required called at lib/Assert/Conditional.pm line 1104
## Assert::Conditional::assert_hash_keys_required(HASH(0x7fe619028f70), "snap") called at -e line 1
# But if we can't find as assert_\w+ on the stack, just use the name of the
# the thing that called the thing that called us, so presumably whatever
# called botch.
sub his_assert()
:Export( qw[frame] )
{
my $assert_rx = qr/::assert_\w+\z/x;
my $i;
my $sub = q();
for ($i = 1; $sub !~ $assert_rx; $i++) {
$sub = his_sub($i) // last;
}
$sub //= his_sub(2); # in case we couldn't find an assert_\w+ sub
while ((his_sub($i+1) // "") =~ $assert_rx) {
$sub = his_sub(++$i);
}
$sub =~ s/.*:://;
return $sub;
}
sub his_args(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
do { package DB; () = caller($frames+2); };
return @DB::args;
}
sub his_frame(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
return caller($frames+2);
}
BEGIN {
# Stealing lovely "iota" magic from the
# Go language construct of the same name.
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
CALLER_HINTHASH
);
push @{ $EXPORT_TAGS{CALLER} }, @caller_consts;
push @{ $EXPORT_TAGS{frame} },
@{ $EXPORT_TAGS{CALLER} };
}
sub his_package(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
(his_frame($frames+1))[CALLER_PACKAGE]
}
sub his_filename(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
(his_frame($frames+1))[CALLER_FILENAME]
}
sub his_line(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
(his_frame($frames+1))[CALLER_LINE]
}
sub his_subroutine(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
(his_frame($frames+1))[CALLER_SUBROUTINE]
}
sub his_sub(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
his_subroutine($frames + 1);
}
sub his_context(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
(his_frame($frames+1))[CALLER_WANTARRAY]
}
sub his_is_require(;$)
:Export( qw[frame] )
{
my $frames = @_ && $_[0];
(his_frame($frames+1))[CALLER_IS_REQUIRE]
}
#################################################################
my ($hint_bits, $warning_bits);
BEGIN {($hint_bits, $warning_bits) = ($^H, ${^WARNING_BITS})}
sub code_of_coderef($)
:Export( qw[code] )
{
my($coderef) = @_;
my $deparse = B::Deparse->new(
"-P",
"-sC",
#"-x9",
#"-q",
#"-q",
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
for ($body) {
s/^\h+(?:use|no) (?:strict|warnings|feature|integer|utf8|bytes|re)\b[^\n]*\n//gm;
s/^\h+package [^\n]*;\n//gm;
s/\A\{\n\h+([^\n;]*);\n\}\z/{ $1 }/;
}
return $body;
}
sub name_of_coderef($)
:Export( qw[code] )
{
require B;
my($coderef) = @_;
my $cv = B::svref_2object($coderef);
return unless $cv->isa("B::CV");
my $gv = $cv->GV;
return if $gv->isa("B::SPECIAL");
my $subname = $gv->NAME;
my $packname = $gv->STASH->NAME;
return $packname . "::" . $subname;
}
sub subname_or_code($)
:Export( qw[code] )
{
my($coderef) = @_;
my $name = name_of_coderef($coderef);
if ($name =~ /__ANON__/) {
return code_of_coderef($coderef);
} else {
return "$name()";
}
}
#################################################################
sub serialize_conjunction($@) {
my $conj = shift;
(@_ == 0) ? '' :
(@_ == 1) ? $_[0] :
(@_ == 2) ? join(" $conj ", @_) :
join(", ", @_[0 .. ($#_-1)], "$conj $_[-1]");
}
sub commify_series
:Export( qw[list] )
{
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
{
commify_nor map { "'$_'" } @_;
}
sub quotify_but
:Export( qw[list] )
{
commify_but map { "'$_'" } @_;
}
sub dump_exports(@)
:Export( qw[exports] )
{
my $caller_package = caller;
dump_package_exports($caller_package, @_);
}
sub dump_package_exports($@)
:Export( qw[exports] )
{
my($pkg, @exports) = @_;
my %tag2aref = do { no strict 'refs'; %{$pkg . "::EXPORT_TAGS"} };
delete $tag2aref{asserts};
my %seen; # for the all repetition
my @taglist = @exports ? @exports : ('all', uca_sort(keys %tag2aref));
my $errors = 0;
print "=head2 Export Tags\n\n=over\n\n" if $Pod_Generation;
for my $tag (@taglist) {
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
print "Conditional export tag :$tag exports ", commify_series(uca_sort @$aref), ".\n";
}
}
print "=back\n\n" if $Pod_Generation;
return $errors == 0;
}
#################################################################
sub UCA (_) :Export( qw[unicode] );
sub UCA1(_) :Export( qw[unicode] );
sub UCA2(_) :Export( qw[unicode] );
sub UCA3(_) :Export( qw[unicode] );
sub UCA4(_) :Export( qw[unicode] );
sub uca_cmp ($$) :Export( qw[unicode] );
sub uca1_cmp($$) :Export( qw[unicode] );
sub uca2_cmp($$) :Export( qw[unicode] );
sub uca3_cmp($$) :Export( qw[unicode] );
sub uca4_cmp($$) :Export( qw[unicode] );
{
my @Comparitor;
sub _get_comparitor($) {
my($level) = @_;
panic "invalid level $level" unless $level =~ /^[1-4]$/;
return $Comparitor[$level] if $Comparitor[$level];
require Unicode::Collate;
lib/Assert/Conditional/Utils.pm view on Meta::CPAN
"UCA$strength"->($that)
};
}
no warnings "once";
*UCA = \&UCA1;
*uca_cmp = \&uca1_cmp;
}
sub uca_sort(@)
:Export( qw[unicode list] )
{
state $collator = _get_comparitor(4);
return $collator->sort(@_);
}
{
sub _uniq {
my %seen;
my @out;