diff options
43 files changed, 3561 insertions, 118 deletions
@@ -1667,6 +1667,7 @@ lib/Attribute/Handlers/t/data_convert.t Test attribute data conversion lib/Attribute/Handlers/t/linerep.t See if Attribute::Handlers works lib/Attribute/Handlers/t/multi.t See if Attribute::Handlers works lib/attributes.pm For "sub foo : attrlist" +lib/autodie.pm Functions suceed or die with lexical scope lib/AutoLoader.pm Autoloader base class lib/AutoLoader/t/01AutoLoader.t See if AutoLoader works lib/AutoLoader/t/02AutoSplit.t See if AutoSplit works @@ -3714,6 +3715,43 @@ t/io/through.t See if pipe passes data intact t/io/utf8.t See if file seeking works t/japh/abigail.t Obscure tests t/lib/1_compile.t See if the various libraries and extensions compile +t/lib/autodie/00-load.t autodie - basic load +t/lib/autodie/Fatal.t autodie - Fatal backcompatibility +t/lib/autodie/autodie.t autodie - Basic functionality +t/lib/autodie/autodie_test_module.pm autodie - test helper +t/lib/autodie/backcompat.t autodie - More Fatal backcompat +t/lib/autodie/basic_exceptions.t autodie - Basic exception tests +t/lib/autodie/binmode.t autodie - Binmode testing +t/lib/autodie/context.t autodie - Context clobbering tests +t/lib/autodie/context_lexical.t autodie - Context clobbering lexically +t/lib/autodie/crickey.t autodie - Like an Australian +t/lib/autodie/dbmopen.t autodie - dbm tests +t/lib/autodie/exception_class.t autodie - Exception class subclasses +t/lib/autodie/exceptions.t autodie - 5.10 exception tests. +t/lib/autodie/exec.t autodie - exec tests. +t/lib/autodie/filehandles.t autodie - filehandle tests +t/lib/autodie/fileno.t autodie - fileno tests +t/lib/autodie/flock.t autodie - File locking tests +t/lib/autodie/internal.t autodie - internal interface tests +t/lib/autodie/lethal.t autodie - lethal is the one true name +t/lib/autodie/lib/autodie/test/au.pm autodie - Austrlaian helper +t/lib/autodie/lib/autodie/test/au/exception.pm autodie - Australian helper +t/lib/autodie/lib/autodie/test/badname.pm autodie - Bad exception class +t/lib/autodie/lib/autodie/test/missing.pm autodie - Missing exception class +t/lib/autodie/lib/lethal.pm autodie - with a better name +t/lib/autodie/lib/pujHa/ghach.pm autodie - Like a Klingon +t/lib/autodie/lib/pujHa/ghach/Dotlh.pm autodie - With Klingon honour +t/lib/autodie/mkdir.t autodie - filesystem tests +t/lib/autodie/open.t autodie - Testing open +t/lib/autodie/recv.t autodie - send/recv tests +t/lib/autodie/repeat.t autodie - repeat autodie leak tests +t/lib/autodie/scope_leak.t autodie - file scope leak tests +t/lib/autodie/sysopen.t autodie - sysopen tests +t/lib/autodie/truncate.t autodie - File truncation tests +t/lib/autodie/unlink.t autodie - Unlink system tests. +t/lib/autodie/usersub.t autodie - user subroutine tests +t/lib/autodie/version.t autodie - versioning tests +t/lib/autodie/version_tag.t t/lib/Cname.pm Test charnames in regexes (op/pat.t) t/lib/common.pl Helper for lib/{warnings,feature}.t t/lib/commonsense.t See if configuration meets basic needs diff --git a/lib/Fatal.pm b/lib/Fatal.pm index 0b4bf9bc99..0f7ef8f124 100644 --- a/lib/Fatal.pm +++ b/lib/Fatal.pm @@ -1,193 +1,1116 @@ package Fatal; -use 5.006_001; +use 5.008; # 5.8.x needed for autodie use Carp; use strict; -our($AUTOLOAD, $Debug, $VERSION); +use warnings; -$VERSION = 1.06; +use constant LEXICAL_TAG => q{:lexical}; +use constant VOID_TAG => q{:void}; -$Debug = 0 unless defined $Debug; +use constant ERROR_NOARGS => 'Cannot use lexical %s with no arguments'; +use constant ERROR_VOID_LEX => VOID_TAG.' cannot be used with lexical scope'; +use constant ERROR_LEX_FIRST => LEXICAL_TAG.' must be used as first argument'; +use constant ERROR_NO_LEX => "no %s can only start with ".LEXICAL_TAG; +use constant ERROR_BADNAME => "Bad subroutine name for %s: %s"; +use constant ERROR_NOTSUB => "%s is not a Perl subroutine"; +use constant ERROR_NOT_BUILT => "%s is neither a builtin, nor a Perl subroutine"; +use constant ERROR_CANT_OVERRIDE => "Cannot make the non-overridable builtin %s fatal"; + +use constant ERROR_NO_IPC_SYS_SIMPLE => "IPC::System::Simple required for Fatalised/autodying system()"; + +use constant ERROR_IPC_SYS_SIMPLE_OLD => "IPC::System::Simple version %f required for Fatalised/autodying system(). We only have version %f"; + +use constant ERROR_AUTODIE_CONFLICT => q{"no autodie '%s'" is not allowed while "use Fatal '%s'" is in effect}; + +use constant ERROR_FATAL_CONFLICT => q{"use Fatal '%s'" is not allowed while "no autodie '%s'" is in effect}; + +# Older versions of IPC::System::Simple don't support all the +# features we need. + +use constant MIN_IPC_SYS_SIMPLE_VER => 0.12; + +# All the Fatal/autodie modules share the same version number. +our $VERSION = '1.997'; + +our $Debug ||= 0; + +# EWOULDBLOCK values for systems that don't supply their own. +# Even though this is defined with our, that's to help our +# test code. Please don't rely upon this variable existing in +# the future. + +our %_EWOULDBLOCK = ( + MSWin32 => 33, +); + +# We have some tags that can be passed in for use with import. +# These are all assumed to be CORE:: + +my %TAGS = ( + ':io' => [qw(:dbm :file :filesys :ipc :socket + read seek sysread syswrite sysseek )], + ':dbm' => [qw(dbmopen dbmclose)], + ':file' => [qw(open close flock sysopen fcntl fileno binmode + ioctl truncate)], + ':filesys' => [qw(opendir closedir chdir link unlink rename mkdir + symlink rmdir readlink umask)], + ':ipc' => [qw(:msg :semaphore :shm pipe)], + ':msg' => [qw(msgctl msgget msgrcv msgsnd)], + ':threads' => [qw(fork)], + ':semaphore'=>[qw(semctl semget semop)], + ':shm' => [qw(shmctl shmget shmread)], + ':system' => [qw(system exec)], + + # Can we use qw(getpeername getsockname)? What do they do on failure? + # XXX - Can socket return false? + ':socket' => [qw(accept bind connect getsockopt listen recv send + setsockopt shutdown socketpair)], + + # Our defaults don't include system(), because it depends upon + # an optional module, and it breaks the exotic form. + # + # This *may* change in the future. I'd love IPC::System::Simple + # to be a dependency rather than a recommendation, and hence for + # system() to be autodying by default. + + ':default' => [qw(:io :threads)], + + # Version specific tags. These allow someone to specify + # use autodie qw(:1.994) and know exactly what they'll get. + + ':1.994' => [qw(:default)], + ':1.995' => [qw(:default)], + ':1.996' => [qw(:default)], + ':1.997' => [qw(:default)], + +); + +$TAGS{':all'} = [ keys %TAGS ]; + +# This hash contains subroutines for which we should +# subroutine() // die() rather than subroutine() || die() + +my %Use_defined_or; + +# CORE::open returns undef on failure. It can legitimately return +# 0 on success, eg: open(my $fh, '-|') || exec(...); + +@Use_defined_or{qw( + CORE::fork + CORE::recv + CORE::send + CORE::open + CORE::fileno + CORE::read + CORE::readlink + CORE::sysread + CORE::syswrite + CORE::sysseek + CORE::umask +)} = (); + +# Cached_fatalised_sub caches the various versions of our +# fatalised subs as they're produced. This means we don't +# have to build our own replacement of CORE::open and friends +# for every single package that wants to use them. + +my %Cached_fatalised_sub = (); + +# Every time we're called with package scope, we record the subroutine +# (including package or CORE::) in %Package_Fatal. This allows us +# to detect illegal combinations of autodie and Fatal, and makes sure +# we don't accidently make a Fatal function autodying (which isn't +# very useful). + +my %Package_Fatal = (); + +# The first time we're called with a user-sub, we cache it here. +# In the case of a "no autodie ..." we put back the cached copy. + +my %Original_user_sub = (); + +# We use our package in a few hash-keys. Having it in a scalar is +# convenient. The "guard $PACKAGE" string is used as a key when +# setting up lexical guards. + +my $PACKAGE = __PACKAGE__; +my $PACKAGE_GUARD = "guard $PACKAGE"; +my $NO_PACKAGE = "no $PACKAGE"; # Used to detect 'no autodie' + +# Here's where all the magic happens when someone write 'use Fatal' +# or 'use autodie'. sub import { - my $self = shift(@_); - my($sym, $pkg); - my $void = 0; - $pkg = (caller)[0]; - foreach $sym (@_) { - if ($sym eq ":void") { - $void = 1; - } - else { - &_make_fatal($sym, $pkg, $void); - } - } -}; - -sub AUTOLOAD { - my $cmd = $AUTOLOAD; - $cmd =~ s/.*:://; - &_make_fatal($cmd, (caller)[0]); - goto &$AUTOLOAD; + my $class = shift(@_); + my $void = 0; + my $lexical = 0; + + my ($pkg, $filename) = caller(); + + @_ or return; # 'use Fatal' is a no-op. + + # If we see the :lexical flag, then _all_ arguments are + # changed lexically + + if ($_[0] eq LEXICAL_TAG) { + $lexical = 1; + shift @_; + + # If we see no arguments and :lexical, we assume they + # wanted ':default'. + + if (@_ == 0) { + push(@_, ':default'); + } + + # Don't allow :lexical with :void, it's needlessly confusing. + if ( grep { $_ eq VOID_TAG } @_ ) { + croak(ERROR_VOID_LEX); + } + } + + if ( grep { $_ eq LEXICAL_TAG } @_ ) { + # If we see the lexical tag as the non-first argument, complain. + croak(ERROR_LEX_FIRST); + } + + my @fatalise_these = @_; + + # Thiese subs will get unloaded at the end of lexical scope. + my %unload_later; + + # This hash helps us track if we've alredy done work. + my %done_this; + + # NB: we're using while/shift rather than foreach, since + # we'll be modifying the array as we walk through it. + + while (my $func = shift @fatalise_these) { + + if ($func eq VOID_TAG) { + + # When we see :void, set the void flag. + $void = 1; + + } elsif (exists $TAGS{$func}) { + + # When it's a tag, expand it. + push(@fatalise_these, @{ $TAGS{$func} }); + + } else { + + # Otherwise, fatalise it. + + # If we've already made something fatal this call, + # then don't do it twice. + + next if $done_this{$func}; + + # We're going to make a subroutine fatalistic. + # However if we're being invoked with 'use Fatal qw(x)' + # and we've already been called with 'no autodie qw(x)' + # in the same scope, we consider this to be an error. + # Mixing Fatal and autodie effects was considered to be + # needlessly confusing on p5p. + + my $sub = $func; + $sub = "${pkg}::$sub" unless $sub =~ /::/; + + # If we're being called as Fatal, and we've previously + # had a 'no X' in scope for the subroutine, then complain + # bitterly. + + if (! $lexical and $^H{$NO_PACKAGE}{$sub}) { + croak(sprintf(ERROR_FATAL_CONFLICT, $func, $func)); + } + + # We're not being used in a confusing way, so make + # the sub fatal. Note that _make_fatal returns the + # old (original) version of the sub, or undef for + # built-ins. + + my $sub_ref = $class->_make_fatal( + $func, $pkg, $void, $lexical, $filename + ); + + $done_this{$func}++; + + $Original_user_sub{$sub} ||= $sub_ref; + + # If we're making lexical changes, we need to arrange + # for them to be cleaned at the end of our scope, so + # record them here. + + $unload_later{$func} = $sub_ref if $lexical; + } + } + + if ($lexical) { + + # Dark magic to have autodie work under 5.8 + # Copied from namespace::clean, that copied it from + # autobox, that found it on an ancient scroll written + # in blood. + + # This magic bit causes %^H to be lexically scoped. + + $^H |= 0x020000; + + # Our package guard gets invoked when we leave our lexical + # scope. + + push(@ { $^H{$PACKAGE_GUARD} }, autodie::Scope::Guard->new(sub { + $class->_install_subs($pkg, \%unload_later); + })); + + } + + return; + +} + +# The code here is originally lifted from namespace::clean, +# by Robert "phaylon" Sedlacek. +# +# It's been redesigned after feedback from ikegami on perlmonks. +# See http://perlmonks.org/?node_id=693338 . Ikegami rocks. +# +# Given a package, and hash of (subname => subref) pairs, +# we install the given subroutines into the package. If +# a subref is undef, the subroutine is removed. Otherwise +# it replaces any existing subs which were already there. + +sub _install_subs { + my ($class, $pkg, $subs_to_reinstate) = @_; + + my $pkg_sym = "${pkg}::"; + + while(my ($sub_name, $sub_ref) = each %$subs_to_reinstate) { + + my $full_path = $pkg_sym.$sub_name; + + # Copy symbols across to temp area. + + no strict 'refs'; ## no critic + + local *__tmp = *{ $full_path }; + + # Nuke the old glob. + { no strict; delete $pkg_sym->{$sub_name}; } ## no critic + + # Copy innocent bystanders back. + + foreach my $slot (qw( SCALAR ARRAY HASH IO FORMAT ) ) { + next unless defined *__tmp{ $slot }; + *{ $full_path } = *__tmp{ $slot }; + } + + # Put back the old sub (if there was one). + + if ($sub_ref) { + + no strict; ## no critic + *{ $pkg_sym . $sub_name } = $sub_ref; + } + } + + return; +} + +sub unimport { + my $class = shift; + + # Calling "no Fatal" must start with ":lexical" + if ($_[0] ne LEXICAL_TAG) { + croak(sprintf(ERROR_NO_LEX,$class)); + } + + shift @_; # Remove :lexical + + my $pkg = (caller)[0]; + + # If we've been called with arguments, then the developer + # has explicitly stated 'no autodie qw(blah)', + # in which case, we disable Fatalistic behaviour for 'blah'. + + my @unimport_these = @_ ? @_ : ':all'; + + while (my $symbol = shift @unimport_these) { + + if ($symbol =~ /^:/) { + + # Looks like a tag! Expand it! + push(@unimport_these, @{ $TAGS{$symbol} }); + + next; + } + + my $sub = $symbol; + $sub = "${pkg}::$sub" unless $sub =~ /::/; + + # If 'blah' was already enabled with Fatal (which has package + # scope) then, this is considered an error. + + if (exists $Package_Fatal{$sub}) { + croak(sprintf(ERROR_AUTODIE_CONFLICT,$symbol,$symbol)); + } + + # Record 'no autodie qw($sub)' as being in effect. + # This is to catch conflicting semantics elsewhere + # (eg, mixing Fatal with no autodie) + + $^H{$NO_PACKAGE}{$sub} = 1; + + if (my $original_sub = $Original_user_sub{$sub}) { + # Hey, we've got an original one of these, put it back. + $class->_install_subs($pkg, { $symbol => $original_sub }); + next; + } + + # We don't have an original copy of the sub, on the assumption + # it's core (or doesn't exist), we'll just nuke it. + + $class->_install_subs($pkg,{ $symbol => undef }); + + } + + return; + +} + +# TODO - This is rather terribly inefficient right now. + +# NB: Perl::Critic's dump-autodie-tag-contents depends upon this +# continuing to work. + +{ + my %tag_cache; + + sub _expand_tag { + my ($class, $tag) = @_; + + if (my $cached = $tag_cache{$tag}) { + return $cached; + } + + if (not exists $TAGS{$tag}) { + croak "Invalid exception class $tag"; + } + + my @to_process = @{$TAGS{$tag}}; + + my @taglist = (); + + while (my $item = shift @to_process) { + if ($item =~ /^:/) { + push(@to_process, @{$TAGS{$item}} ); + } else { + push(@taglist, "CORE::$item"); + } + } + + $tag_cache{$tag} = \@taglist; + + return \@taglist; + + } + } +# This code is from the original Fatal. It scares me. + sub fill_protos { - my $proto = shift; - my ($n, $isref, @out, @out1, $seen_semi) = -1; - while ($proto =~ /\S/) { - $n++; - push(@out1,[$n,@out]) if $seen_semi; - push(@out, $1 . "{\$_[$n]}"), next if $proto =~ s/^\s*\\([\@%\$\&])//; - push(@out, "\$_[$n]"), next if $proto =~ s/^\s*([_*\$&])//; - push(@out, "\@_[$n..\$#_]"), last if $proto =~ s/^\s*(;\s*)?\@//; - $seen_semi = 1, $n--, next if $proto =~ s/^\s*;//; # XXXX ???? - die "Unknown prototype letters: \"$proto\""; - } - push(@out1,[$n+1,@out]); - @out1; + my $proto = shift; + my ($n, $isref, @out, @out1, $seen_semi) = -1; + while ($proto =~ /\S/) { + $n++; + push(@out1,[$n,@out]) if $seen_semi; + push(@out, $1 . "{\$_[$n]}"), next if $proto =~ s/^\s*\\([\@%\$\&])//; + push(@out, "\$_[$n]"), next if $proto =~ s/^\s*([_*\$&])//; + push(@out, "\@_[$n..\$#_]"), last if $proto =~ s/^\s*(;\s*)?\@//; + $seen_semi = 1, $n--, next if $proto =~ s/^\s*;//; # XXXX ???? + die "Internal error: Unknown prototype letters: \"$proto\""; + } + push(@out1,[$n+1,@out]); + return @out1; } +# This generates the code that will become our fatalised subroutine. + sub write_invocation { - my ($core, $call, $name, $void, @argvs) = @_; - if (@argvs == 1) { # No optional arguments - my @argv = @{$argvs[0]}; - shift @argv; - return "\t" . one_invocation($core, $call, $name, $void, @argv) . ";\n"; - } else { - my $else = "\t"; - my (@out, @argv, $n); - while (@argvs) { - @argv = @{shift @argvs}; - $n = shift @argv; - push @out, "$ {else}if (\@_ == $n) {\n"; - $else = "\t} els"; - push @out, - "\t\treturn " . one_invocation($core, $call, $name, $void, @argv) . ";\n"; - } - push @out, <<EOC; - } - die "$name(\@_): Do not expect to get ", scalar \@_, " arguments"; -EOC - return join '', @out; - } + my ($class, $core, $call, $name, $void, $lexical, $sub, @argvs) = @_; + + if (@argvs == 1) { # No optional arguments + + my @argv = @{$argvs[0]}; + shift @argv; + + return $class->one_invocation($core,$call,$name,$void,$sub,! $lexical,@argv); + + } else { + my $else = "\t"; + my (@out, @argv, $n); + while (@argvs) { + @argv = @{shift @argvs}; + $n = shift @argv; + + push @out, "${else}if (\@_ == $n) {\n"; + $else = "\t} els"; + + push @out, $class->one_invocation($core,$call,$name,$void,$sub,! $lexical,@argv); + } + push @out, q[ + } + die "Internal error: $name(\@_): Do not expect to get ", scalar \@_, " arguments"; + ]; + + return join '', @out; + } } sub one_invocation { - my ($core, $call, $name, $void, @argv) = @_; - local $" = ', '; - if ($void) { - return qq/(defined wantarray)?$call(@argv): - $call(@argv) || croak "Can't $name(\@_)/ . - ($core ? ': $!' : ', \$! is \"$!\"') . '"' - } else { - return qq{$call(@argv) || croak "Can't $name(\@_)} . - ($core ? ': $!' : ', \$! is \"$!\"') . '"'; - } + my ($class, $core, $call, $name, $void, $sub, $back_compat, @argv) = @_; + + # If someone is calling us directly (a child class perhaps?) then + # they could try to mix void without enabling backwards + # compatibility. We just don't support this at all, so we gripe + # about it rather than doing something unwise. + + if ($void and not $back_compat) { + Carp::confess("Internal error: :void mode not supported with $class"); + } + + # @argv only contains the results of the in-built prototype + # function, and is therefore safe to interpolate in the + # code generators below. + + # TODO - The following clobbers context, but that's what the + # old Fatal did. Do we care? + + if ($back_compat) { + + # TODO - Use Fatal qw(system) is not yet supported. It should be! + + if ($call eq 'CORE::system') { + return q{ + croak("UNIMPLEMENTED: use Fatal qw(system) not yet supported."); + }; + } + + local $" = ', '; + + if ($void) { + return qq/return (defined wantarray)?$call(@argv): + $call(@argv) || croak "Can't $name(\@_)/ . + ($core ? ': $!' : ', \$! is \"$!\"') . '"' + } else { + return qq{return $call(@argv) || croak "Can't $name(\@_)} . + ($core ? ': $!' : ', \$! is \"$!\"') . '"'; + } + } + + # The name of our original function is: + # $call if the function is CORE + # $sub if our function is non-CORE + + # The reason for this is that $call is what we're actualling + # calling. For our core functions, this is always + # CORE::something. However for user-defined subs, we're about to + # replace whatever it is that we're calling; as such, we actually + # calling a subroutine ref. + + # Unfortunately, none of this tells us the *ultimate* name. + # For example, if I export 'copy' from File::Copy, I'd like my + # ultimate name to be File::Copy::copy. + # + # TODO - Is there any way to find the ultimate name of a sub, as + # described above? + + my $true_sub_name = $core ? $call : $sub; + + if ($call eq 'CORE::system') { + + # Leverage IPC::System::Simple if we're making an autodying + # system. + + local $" = ", "; + + # We need to stash $@ into $E, rather than using + # local $@ for the whole sub. If we don't then + # any exceptions from internal errors in autodie/Fatal + # will mysteriously disappear before propogating + # upwards. + + return qq{ + my \$retval; + my \$E; + + + { + local \$@; + + eval { + \$retval = IPC::System::Simple::system(@argv); + }; + + \$E = \$@; + } + + if (\$E) { + + # XXX - TODO - This can't be overridden in child + # classes! + + die autodie::exception::system->new( + function => q{CORE::system}, args => [ @argv ], + message => "\$E", errno => \$!, + ); + } + + return \$retval; + }; + + } + + # Should we be testing to see if our result is defined, or + # just true? + my $use_defined_or = exists ( $Use_defined_or{$call} ); + + local $" = ', '; + + # If we're going to throw an exception, here's the code to use. + my $die = qq{ + die $class->throw( + function => q{$true_sub_name}, args => [ @argv ], + pragma => q{$class}, errno => \$!, + ) + }; + + if ($call eq 'CORE::flock') { + + # flock needs special treatment. When it fails with + # LOCK_UN and EWOULDBLOCK, then it's not really fatal, it just + # means we couldn't get the lock right now. + + require POSIX; # For POSIX::EWOULDBLOCK + + local $@; # Don't blat anyone else's $@. + + # Ensure that our vendor supports EWOULDBLOCK. If they + # don't (eg, Windows), then we use known values for its + # equivalent on other systems. + + my $EWOULDBLOCK = eval { POSIX::EWOULDBLOCK(); } + || $_EWOULDBLOCK{$^O} + || _autocroak("Internal error - can't overload flock - EWOULDBLOCK not defined on this system."); + + require Fcntl; # For Fcntl::LOCK_NB + + return qq{ + + # Try to flock. If successful, return it immediately. + + my \$retval = $call(@argv); + return \$retval if \$retval; + + # If we failed, but we're using LOCK_NB and + # returned EWOULDBLOCK, it's not a real error. + + if (\$_[1] & Fcntl::LOCK_NB() and \$! == $EWOULDBLOCK ) { + return \$retval; + } + + # Otherwise, we failed. Die noisily. + + $die; + + }; + } + + # AFAIK everything that can be given an unopned filehandle + # will fail if it tries to use it, so we don't really need + # the 'unopened' warning class here. Especially since they + # then report the wrong line number. + + return qq{ + no warnings qw(unopened); + + if (wantarray) { + my \@results = $call(@argv); + # If we got back nothing, or we got back a single + # undef, we die. + if (! \@results or (\@results == 1 and ! defined \$results[0])) { + $die; + }; + return \@results; + } + + # Otherwise, we're in scalar context. + # We're never in a void context, since we have to look + # at the result. + + my \$result = $call(@argv); + + } . ( $use_defined_or ? qq{ + + $die if not defined \$result; + + return \$result; + + } : qq{ + + return \$result || $die; + + } ) ; + } +# This returns the old copy of the sub, so we can +# put it back at end of scope. + +# TODO : Check to make sure prototypes are restored correctly. + +# TODO: Taking a huge list of arguments is awful. Rewriting to +# take a hash would be lovely. + sub _make_fatal { - my($sub, $pkg, $void) = @_; + my($class, $sub, $pkg, $void, $lexical, $filename) = @_; my($name, $code, $sref, $real_proto, $proto, $core, $call); my $ini = $sub; $sub = "${pkg}::$sub" unless $sub =~ /::/; + + # Figure if we're using lexical or package semantics and + # twiddle the appropriate bits. + + if (not $lexical) { + $Package_Fatal{$sub} = 1; + } + + # TODO - We *should* be able to do skipping, since we know when + # we've lexicalised / unlexicalised a subroutine. + $name = $sub; $name =~ s/.*::// or $name =~ s/^&//; - print "# _make_fatal: sub=$sub pkg=$pkg name=$name void=$void\n" if $Debug; - croak "Bad subroutine name for Fatal: $name" unless $name =~ /^\w+$/; - if (defined(&$sub)) { # user subroutine - $sref = \&$sub; - $proto = prototype $sref; - $call = '&$sref'; + + warn "# _make_fatal: sub=$sub pkg=$pkg name=$name void=$void\n" if $Debug; + croak(sprintf(ERROR_BADNAME, $class, $name)) unless $name =~ /^\w+$/; + + if (defined(&$sub)) { # user subroutine + + # This could be something that we've fatalised that + # was in core. + + local $@; # Don't clobber anyone else's $@ + + if ( $Package_Fatal{$sub} and eval { prototype "CORE::$name" } ) { + + # Something we previously made Fatal that was core. + # This is safe to replace with an autodying to core + # version. + + $core = 1; + $call = "CORE::$name"; + $proto = prototype $call; + + # We return our $sref from this subroutine later + # on, indicating this subroutine should be placed + # back when we're finished. + + $sref = \&$sub; + + } else { + + # A regular user sub, or a user sub wrapping a + # core sub. + + $sref = \&$sub; + $proto = prototype $sref; + $call = '&$sref'; + + } + } elsif ($sub eq $ini && $sub !~ /^CORE::GLOBAL::/) { - # Stray user subroutine - die "$sub is not a Perl subroutine" - } else { # CORE subroutine + # Stray user subroutine + croak(sprintf(ERROR_NOTSUB,$sub)); + + } elsif ($name eq 'system') { + + # If we're fatalising system, then we need to load + # helper code. + + eval { + require IPC::System::Simple; # Only load it if we need it. + require autodie::exception::system; + }; + + if ($@) { croak ERROR_NO_IPC_SYS_SIMPLE; } + + # Make sure we're using a recent version of ISS that actually + # support fatalised system. + if ($IPC::System::Simple::VERSION < MIN_IPC_SYS_SIMPLE_VER) { + croak sprintf( + ERROR_IPC_SYS_SIMPLE_OLD, MIN_IPC_SYS_SIMPLE_VER, + $IPC::System::Simple::VERSION + ); + } + + $call = 'CORE::system'; + $name = 'system'; + + } elsif ($name eq 'exec') { + # Exec doesn't have a prototype. We don't care. This + # breaks the exotic form with lexical scope, and gives + # the regular form a "do or die" beaviour as expected. + + $call = 'CORE::exec'; + $name = 'exec'; + $core = 1; + + } else { # CORE subroutine $proto = eval { prototype "CORE::$name" }; - die "$name is neither a builtin, nor a Perl subroutine" - if $@; - die "Cannot make the non-overridable builtin $name fatal" - if not defined $proto; - $core = 1; - $call = "CORE::$name"; + croak(sprintf(ERROR_NOT_BUILT,$name)) if $@; + croak(sprintf(ERROR_CANT_OVERRIDE,$name)) if not defined $proto; + $core = 1; + $call = "CORE::$name"; } + if (defined $proto) { - $real_proto = " ($proto)"; + $real_proto = " ($proto)"; } else { - $real_proto = ''; - $proto = '@'; + $real_proto = ''; + $proto = '@'; + } + + my $true_name = $core ? $call : $sub; + + # TODO: This caching works, but I don't like using $void and + # $lexical as keys. In particular, I suspect our code may end up + # wrapping already wrapped code when autodie and Fatal are used + # together. + + # NB: We must use '$sub' (the name plus package) and not + # just '$name' (the short name) here. Failing to do so + # results code that's in the wrong package, and hence has + # access to the wrong package filehandles. + + if (my $subref = $Cached_fatalised_sub{$class}{$sub}{$void}{$lexical}) { + $class->_install_subs($pkg, { $name => $subref }); + return $sref; } - $code = <<EOS; -sub$real_proto { - local(\$", \$!) = (', ', 0); -EOS + + $code = qq[ + sub$real_proto { + local(\$", \$!) = (', ', 0); # TODO - Why do we do this? + ]; + + # Don't have perl whine if exec fails, since we'll be handling + # the exception now. + $code .= "no warnings qw(exec);\n" if $call eq "CORE::exec"; + my @protos = fill_protos($proto); - $code .= write_invocation($core, $call, $name, $void, @protos); + $code .= $class->write_invocation($core, $call, $name, $void, $lexical, $sub, @protos); $code .= "}\n"; - print $code if $Debug; + warn $code if $Debug; + + # I thought that changing package was a monumental waste of + # time for CORE subs, since they'll always be the same. However + # that's not the case, since they may refer to package-based + # filehandles (eg, with open). + # + # There is potential to more aggressively cache core subs + # that we know will never want to interact with package variables + # and filehandles. + { - no strict 'refs'; # to avoid: Can't use string (...) as a symbol ref ... - $code = eval("package $pkg; use Carp; $code"); - die if $@; - no warnings; # to avoid: Subroutine foo redefined ... - *{$sub} = $code; + local $@; + no strict 'refs'; ## no critic # to avoid: Can't use string (...) as a symbol ref ... + $code = eval("package $pkg; use Carp; $code"); ## no critic + if (not $code) { + + # For some reason, using a die, croak, or confess in here + # results in the error being completely surpressed. As such, + # we need to do our own reporting. + # + # TODO: Fix the above. + + _autocroak("Internal error in autodie/Fatal processing $true_name: $@"); + + } + } + + # Now we need to wrap our fatalised sub inside an itty bitty + # closure, which can detect if we've leaked into another file. + # Luckily, we only need to do this for lexical (autodie) + # subs. Fatal subs can leak all they want, it's considered + # a "feature" (or at least backwards compatible). + + # TODO: Cache our leak guards! + + # TODO: This is pretty hairy code. A lot more tests would + # be really nice for this. + + my $leak_guard; + + if ($lexical) { + + $leak_guard = qq< + package $pkg; + + sub$real_proto { + + # If we're called from the correct file, then use the + # autodying code. + goto &\$code if ((caller)[1] eq \$filename); + + # Oh bother, we've leaked into another file. Call the + # original code. Note that \$sref may actually be a + # reference to a Fatalised version of a core built-in. + # That's okay, because Fatal *always* leaks between files. + + goto &\$sref if \$sref; + >; + + + # If we're here, it must have been a core subroutine called. + # Warning: The following code may disturb some viewers. + + # TODO: It should be possible to combine this with + # write_invocation(). + + foreach my $proto (@protos) { + local $" = ", "; # So @args is formatted correctly. + my ($count, @args) = @$proto; + $leak_guard .= qq< + if (\@_ == $count) { + return $call(@args); + } + >; + } + + $leak_guard .= qq< croak "Internal error in Fatal/autodie. Leak-guard failure"; } >; + + # warn "$leak_guard\n"; + + local $@; + + $leak_guard = eval $leak_guard; ## no critic + + die "Internal error in $class: Leak-guard installation failure: $@" if $@; + } + + $class->_install_subs($pkg, { $name => $leak_guard || $code }); + + $Cached_fatalised_sub{$class}{$sub}{$void}{$lexical} = $leak_guard || $code; + + return $sref; + +} + +# This subroutine exists primarily so that child classes can override +# it to point to their own exception class. Doing this is significantly +# less complex than overriding throw() + +sub exception_class { return "autodie::exception" }; + +{ + my %exception_class_for; + my %class_loaded; + + sub throw { + my ($class, @args) = @_; + + # Find our exception class if we need it. + my $exception_class = + $exception_class_for{$class} ||= $class->exception_class; + + if (not $class_loaded{$exception_class}) { + if ($exception_class =~ /[^\w:']/) { + confess "Bad exception class '$exception_class'.\nThe '$class->exception_class' method wants to use $exception_class\nfor exceptions, but it contains characters which are not word-characters or colons."; + } + + # Alas, Perl does turn barewords into modules unless they're + # actually barewords. As such, we're left doing a string eval + # to make sure we load our file correctly. + + my $E; + + { + local $@; # We can't clobber $@, it's wrong! + eval "require $exception_class"; ## no critic + $E = $@; # Save $E despite ending our local. + } + + # We need quotes around $@ to make sure it's stringified + # while still in scope. Without them, we run the risk of + # $@ having been cleared by us exiting the local() block. + + confess "Failed to load '$exception_class'.\nThis may be a typo in the '$class->exception_class' method,\nor the '$exception_class' module may not exist.\n\n $E" if $E; + + $class_loaded{$exception_class}++; + + } + + return $exception_class->new(@args); } } +# For some reason, dying while replacing our subs doesn't +# kill our calling program. It simply stops the loading of +# autodie and keeps going with everything else. The _autocroak +# sub allows us to die with a vegence. It should *only* ever be +# used for serious internal errors, since the results of it can't +# be captured. + +sub _autocroak { + warn Carp::longmess(@_); + exit(255); # Ugh! +} + +package autodie::Scope::Guard; + +# This code schedules the cleanup of subroutines at the end of +# scope. It's directly inspired by chocolateboy's excellent +# Scope::Guard module. + +sub new { + my ($class, $handler) = @_; + + return bless $handler, $class; +} + +sub DESTROY { + my ($self) = @_; + + $self->(); +} + 1; __END__ =head1 NAME -Fatal - replace functions with equivalents which succeed or die +Fatal - Replace functions with equivalents which succeed or die =head1 SYNOPSIS use Fatal qw(open close); + open(my $fh, "<", $filename); # No need to check errors! + + use File::Copy qw(move); + use Fatal qw(move); + + move($file1, $file2); # No need to check errors! + sub juggle { . . . } - import Fatal 'juggle'; + Fatal->import('juggle'); + +=head1 BEST PRACTICE + +B<Fatal has been obsoleted by the new L<autodie> pragma.> Please use +L<autodie> in preference to C<Fatal>. L<autodie> supports lexical scoping, +throws real exception objects, and provides much nicer error messages. + +The use of C<:void> with Fatal is discouraged. =head1 DESCRIPTION -C<Fatal> provides a way to conveniently replace functions which normally -return a false value when they fail with equivalents which raise exceptions -if they are not successful. This lets you use these functions without -having to test their return values explicitly on each call. Exceptions -can be caught using C<eval{}>. See L<perlfunc> and L<perlvar> for details. +C<Fatal> provides a way to conveniently replace +functions which normally return a false value when they fail with +equivalents which raise exceptions if they are not successful. This +lets you use these functions without having to test their return +values explicitly on each call. Exceptions can be caught using +C<eval{}>. See L<perlfunc> and L<perlvar> for details. The do-or-die equivalents are set up simply by calling Fatal's C<import> routine, passing it the names of the functions to be replaced. You may wrap both user-defined functions and overridable -CORE operators (except C<exec>, C<system> which cannot be expressed -via prototypes) in this way. +CORE operators (except C<exec>, C<system>, C<print>, or any other +built-in that cannot be expressed via prototypes) in this way. If the symbol C<:void> appears in the import list, then functions named later in that import list raise an exception only when these are called in void context--that is, when their return values are ignored. For example - use Fatal qw/:void open close/; + use Fatal qw/:void open close/; - # properly checked, so no exception raised on error - if(open(FH, "< /bogotic") { - warn "bogo file, dude: $!"; - } + # properly checked, so no exception raised on error + if (not open(my $fh, '<' '/bogotic') { + warn "Can't open /bogotic: $!"; + } - # not checked, so error raises an exception - close FH; + # not checked, so error raises an exception + close FH; + +The use of C<:void> is discouraged, as it can result in exceptions +not being thrown if you I<accidentally> call a method without +void context. Use L<autodie> instead if you need to be able to +disable autodying/Fatal behaviour for a small block of code. + +=head1 DIAGNOSTICS + +=over 4 + +=item Bad subroutine name for Fatal: %s + +You've called C<Fatal> with an argument that doesn't look like +a subroutine name, nor a switch that this version of Fatal +understands. + +=item %s is not a Perl subroutine + +You've asked C<Fatal> to try and replace a subroutine which does not +exist, or has not yet been defined. + +=item %s is neither a builtin, nor a Perl subroutine + +You've asked C<Fatal> to replace a subroutine, but it's not a Perl +built-in, and C<Fatal> couldn't find it as a regular subroutine. +It either doesn't exist or has not yet been defined. + +=item Cannot make the non-overridable %s fatal + +You've tried to use C<Fatal> on a Perl built-in that can't be +overridden, such as C<print> or C<system>, which means that +C<Fatal> can't help you, although some other modules might. +See the L</"SEE ALSO"> section of this documentation. + +=item Internal error: %s + +You've found a bug in C<Fatal>. Please report it using +the C<perlbug> command. + +=back =head1 BUGS -You should not fatalize functions that are called in list context, because this -module tests whether a function has failed by testing the boolean truth of its -return value in scalar context. +C<Fatal> clobbers the context in which a function is called and always +makes it a scalar context, except when the C<:void> tag is used. +This problem does not exist in L<autodie>. =head1 AUTHOR -Lionel Cons (CERN). +Original module by Lionel Cons (CERN). Prototype updates by Ilya Zakharevich <ilya@math.ohio-state.edu>. +L<autodie> support, bugfixes, extended diagnostics, C<system> +support, and major overhauling by Paul Fenwick <pjf@perltraining.com.au> + +=head1 LICENSE + +This module is free software, you may distribute it under the +same terms as Perl itself. + +=head1 SEE ALSO + +L<autodie> for a nicer way to use lexical Fatal. + +L<IPC::System::Simple> for a similar idea for calls to C<system()> +and backticks. + =cut diff --git a/lib/autodie.pm b/lib/autodie.pm new file mode 100644 index 0000000000..38c12f9e8e --- /dev/null +++ b/lib/autodie.pm @@ -0,0 +1,355 @@ +package autodie; +use 5.008; +use strict; +use warnings; + +use Fatal (); +our @ISA = qw(Fatal); +our $VERSION; + +BEGIN { + $VERSION = "1.997"; +} + +use constant ERROR_WRONG_FATAL => q{ +Incorrect version of Fatal.pm loaded by autodie. + +The autodie pragma uses an updated version of Fatal to do its +heavy lifting. We seem to have loaded Fatal version %s, which is +probably the version that came with your version of Perl. However +autodie needs version %s, which would have come bundled with +autodie. + +You may be able to solve this problem by adding the following +line of code to your main program, before any use of Fatal or +autodie. + + use lib "%s"; + +}; + +# We have to check we've got the right version of Fatal before we +# try to compile the rest of our code, lest we use a constant +# that doesn't exist. + +BEGIN { + + # If we have the wrong Fatal, then we've probably loaded the system + # one, not our own. Complain, and give a useful hint. ;) + + if ($Fatal::VERSION ne $VERSION) { + my $autodie_path = $INC{'autodie.pm'}; + + $autodie_path =~ s/autodie\.pm//; + + require Carp; + + Carp::croak sprintf( + ERROR_WRONG_FATAL, $Fatal::VERSION, $VERSION, $autodie_path + ); + } +} + +# When passing args to Fatal we want to keep the first arg +# (our package) in place. Hence the splice. + +sub import { + splice(@_,1,0,Fatal::LEXICAL_TAG); + goto &Fatal::import; +} + +sub unimport { + splice(@_,1,0,Fatal::LEXICAL_TAG); + goto &Fatal::unimport; +} + +1; + +__END__ + +=head1 NAME + +autodie - Replace functions with ones that succeed or die with lexical scope + +=head1 SYNOPSIS + + use autodie; # Recommended, implies 'use autodie qw(:default)' + + use autodie qw(open close); # open/close succeed or die + + open(my $fh, "<", $filename); # No need to check! + + { + no autodie qw(open); # open failures won't die + open(my $fh, "<", $filename); # Could fail silently! + no autodie; # disable all autodies + } + +=head1 DESCRIPTION + + bIlujDI' yIchegh()Qo'; yIHegh()! + + It is better to die() than to return() in failure. + + -- Klingon programming proverb. + +The C<autodie> pragma provides a convenient way to replace functions +that normally return false on failure with equivalents that throw +an exception on failure. + +The C<autodie> pragma has I<lexical scope>, meaning that functions +and subroutines altered with C<autodie> will only change their behaviour +until the end of the enclosing block, file, or C<eval>. + +If C<system> is specified as an argument to C<autodie>, then it +uses L<IPC::System::Simple> to do the heavy lifting. See the +description of that module for more information. + +=head1 EXCEPTIONS + +Exceptions produced by the C<autodie> pragma are members of the +L<autodie::exception> class. The preferred way to work with +these exceptions under Perl 5.10 is as follows: + + use feature qw(switch); + + eval { + use autodie; + + open(my $fh, '<', $some_file); + + my @records = <$fh>; + + # Do things with @records... + + close($fh); + + }; + + given ($@) { + when (undef) { say "No error"; } + when ('open') { say "Error from open"; } + when (':io') { say "Non-open, IO error."; } + when (':all') { say "All other autodie errors." } + default { say "Not an autodie error at all." } + } + +Under Perl 5.8, the C<given/when> structure is not available, so the +following structure may be used: + + eval { + use autodie; + + open(my $fh, '<', $some_file); + + my @records = <$fh>; + + # Do things with @records... + + close($fh); + }; + + if ($@ and $@->isa('autodie::exception')) { + if ($@->matches('open')) { print "Error from open\n"; } + if ($@->matches(':io' )) { print "Non-open, IO error."; } + } elsif ($@) { + # A non-autodie exception. + } + +See L<autodie::exception> for further information on interrogating +exceptions. + +=head1 CATEGORIES + +Autodie uses a simple set of categories to group together similar +built-ins. Requesting a category type (starting with a colon) will +enable autodie for all built-ins beneath that category. For example, +requesting C<:file> will enable autodie for C<close>, C<fcntl>, +C<fileno>, C<open> and C<sysopen>. + +The categories are currently: + + :all + :default + :io + read + seek + sysread + sysseek + syswrite + :dbm + dbmclose + dbmopen + :file + binmode + close + fcntl + fileno + flock + ioctl + open + sysopen + truncate + :filesys + chdir + closedir + opendir + link + mkdir + readlink + rename + rmdir + symlink + unlink + :ipc + pipe + :msg + msgctl + msgget + msgrcv + msgsnd + :semaphore + semctl + semget + semop + :shm + shmctl + shmget + shmread + :socket + accept + bind + connect + getsockopt + listen + recv + send + setsockopt + shutdown + socketpair + :threads + fork + :system + system + exec + + +Note that while the above category system is presently a strict +hierarchy, this should not be assumed. + +A plain C<use autodie> implies C<use autodie qw(:default)>. Note that +C<system> and C<exec> are not enabled by default. C<system> requires +the optional L<IPC::System::Simple> module to be installed, and enabling +C<system> or C<exec> will invalidate their exotic forms. See L</BUGS> +below for more details. + +The syntax: + + use autodie qw(:1.994); + +allows the C<:default> list from a particular version to be used. This +provides the convenience of using the default methods, but the surity +that no behavorial changes will occur if the C<autodie> module is +upgraded. + +=head1 FUNCTION SPECIFIC NOTES + +=head2 flock + +It is not considered an error for C<flock> to return false if it fails +to an C<EWOULDBLOCK> (or equivalent) condition. This means one can +still use the common convention of testing the return value of +C<flock> when called with the C<LOCK_NB> option: + + use autodie; + + if ( flock($fh, LOCK_EX | LOCK_NB) ) { + # We have a lock + } + +Autodying C<flock> will generate an exception if C<flock> returns +false with any other error. + +=head2 system/exec + +Applying C<autodie> to C<system> or C<exec> causes the exotic +forms C<system { $cmd } @args > or C<exec { $cmd } @args> +to be considered a syntax error until the end of the lexical scope. +If you really need to use the exotic form, you can call C<CORE::system> +or C<CORE::exec> instead, or use C<no autodie qw(system exec)> before +calling the exotic form. + +=head1 GOTCHAS + +Functions called in list context are assumed to have failed if they +return an empty list, or a list consisting only of a single undef +element. + +=head1 DIAGNOSTICS + +=over 4 + +=item :void cannot be used with lexical scope + +The C<:void> option is supported in L<Fatal>, but not +C<autodie>. However you can explicitly disable autodie +end the end of the current block with C<no autodie>. +To disable autodie for only a single function (eg, open) +use or C<no autodie qw(open)>. + +=back + +See also L<Fatal/DIAGNOSTICS>. + +=head1 BUGS + +"Used only once" warnings can be generated when C<autodie> or C<Fatal> +is used with package filehandles (eg, C<FILE>). It's strongly recommended +you use scalar filehandles instead. + +When using C<autodie> or C<Fatal> with user subroutines, the +declaration of those subroutines must appear before the first use of +C<Fatal> or C<autodie>, or have been exported from a module. +Attempting to ue C<Fatal> or C<autodie> on other user subroutines will +result in a compile-time error. + +=head2 REPORTING BUGS + +Please report bugs via the CPAN Request Tracker at +L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=autodie>. + +=head1 FEEDBACK + +If you find this module useful, please consider rating it on the +CPAN Ratings service at +L<http://cpanratings.perl.org/rate?distribution=autodie> . + +The module author loves to hear how C<autodie> has made your life +better (or worse). Feedback can be sent to +E<lt>pjf@perltraining.com.auE<gt>. + +=head1 AUTHOR + +Copyright 2008, Paul Fenwick E<lt>pjf@perltraining.com.auE<gt> + +=head1 LICENSE + +This module is free software. You may distribute it under the +same terms as Perl itself. + +=head1 SEE ALSO + +L<Fatal>, L<autodie::exception>, L<IPC::System::Simple> + +I<Perl tips, autodie> at +L<http://perltraining.com.au/tips/2008-08-20.html> + +=head1 ACKNOWLEDGEMENTS + +Mark Reed and Roland Giersig -- Klingon translators. + +See the F<AUTHORS> file for full credits. The latest version of this +file can be found at +L<http://github.com/pfenwick/autodie/tree/AUTHORS> . + +=cut diff --git a/lib/autodie/exception.pm b/lib/autodie/exception.pm new file mode 100644 index 0000000000..43f50fc9ed --- /dev/null +++ b/lib/autodie/exception.pm @@ -0,0 +1,665 @@ +package autodie::exception; +use 5.008; +use strict; +use warnings; +use Carp qw(croak); + +our $DEBUG = 0; + +use overload + q{""} => "stringify" +; + +# Overload smart-match only if we're using 5.10 + +use if ($] >= 5.010), overload => '~~' => "matches"; + +our $VERSION = '1.997'; + +my $PACKAGE = __PACKAGE__; # Useful to have a scalar for hash keys. + +=head1 NAME + +autodie::exception - Exceptions from autodying functions. + +=head1 SYNOPSIS + + eval { + use autodie; + + open(my $fh, '<', 'some_file.txt'); + + ... + }; + + if (my $E = $@) { + say "Ooops! ",$E->caller," had problems: $@"; + } + + +=head1 DESCRIPTION + +When an L<autodie> enabled function fails, it generates an +C<autodie::exception> object. This can be interrogated to +determine further information about the error that occurred. + +This document is broken into two sections; those methods that +are most useful to the end-developer, and those methods for +anyone wishing to subclass or get very familiar with +C<autodie::exception>. + +=head2 Common Methods + +These methods are intended to be used in the everyday dealing +of exceptions. + +The following assume that the error has been copied into +a separate scalar: + + if ($E = $@) { + ... + } + +This is not required, but is recommended in case any code +is called which may reset or alter C<$@>. + +=cut + +=head3 args + + my $array_ref = $E->args; + +Provides a reference to the arguments passed to the subroutine +that died. + +=cut + +sub args { return $_[0]->{$PACKAGE}{args}; } + +=head3 function + + my $sub = $E->function; + +The subroutine (including package) that threw the exception. + +=cut + +sub function { return $_[0]->{$PACKAGE}{function}; } + +=head3 file + + my $file = $E->file; + +The file in which the error occurred (eg, C<myscript.pl> or +C<MyTest.pm>). + +=cut + +sub file { return $_[0]->{$PACKAGE}{file}; } + +=head3 package + + my $package = $E->package; + +The package from which the exceptional subroutine was called. + +=cut + +sub package { return $_[0]->{$PACKAGE}{package}; } + +=head3 caller + + my $caller = $E->caller; + +The subroutine that I<called> the exceptional code. + +=cut + +sub caller { return $_[0]->{$PACKAGE}{caller}; } + +=head3 line + + my $line = $E->line; + +The line in C<< $E->file >> where the exceptional code was called. + +=cut + +sub line { return $_[0]->{$PACKAGE}{line}; } + +=head3 errno + + my $errno = $E->errno; + +The value of C<$!> at the time when the exception occurred. + +B<NOTE>: This method will leave the main C<autodie::exception> class +and become part of a role in the future. You should only call +C<errno> for exceptions where C<$!> would reasonably have been +set on failure. + +=cut + +# TODO: Make errno part of a role. It doesn't make sense for +# everything. + +sub errno { return $_[0]->{$PACKAGE}{errno}; } + +=head3 matches + + if ( $e->matches('open') ) { ... } + + if ( $e ~~ 'open' ) { ... } + +C<matches> is used to determine whether a +given exception matches a particular role. On Perl 5.10, +using smart-match (C<~~>) with an C<autodie::exception> object +will use C<matches> underneath. + +An exception is considered to match a string if: + +=over 4 + +=item * + +For a string not starting with a colon, the string exactly matches the +package and subroutine that threw the exception. For example, +C<MyModule::log>. If the string does not contain a package name, +C<CORE::> is assumed. + +=item * + +For a string that does start with a colon, if the subroutine +throwing the exception I<does> that behaviour. For example, the +C<CORE::open> subroutine does C<:file>, C<:io> and C<:all>. + +See L<autodie/CATEGORIES> for futher information. + +=back + +=cut + +{ + my (%cache); + + sub matches { + my ($this, $that) = @_; + + # XXX - Handle references + croak "UNIMPLEMENTED" if ref $that; + + my $sub = $this->function; + + if ($DEBUG) { + my $sub2 = $this->function; + warn "Smart-matching $that against $sub / $sub2\n"; + } + + # Direct subname match. + return 1 if $that eq $sub; + return 1 if $that !~ /:/ and "CORE::$that" eq $sub; + return 0 if $that !~ /^:/; + + # Cached match / check tags. + require Fatal; + + if (exists $cache{$sub}{$that}) { + return $cache{$sub}{$that}; + } + + # This rather awful looking line checks to see if our sub is in the + # list of expanded tags, caches it, and returns the result. + + return $cache{$sub}{$that} = grep { $_ eq $sub } @{ $this->_expand_tag($that) }; + } +} + +# This exists primarily so that child classes can override or +# augment it if they wish. + +sub _expand_tag { + my ($this, @args) = @_; + + return Fatal->_expand_tag(@args); +} + +=head2 Advanced methods + +The following methods, while usable from anywhere, are primarily +intended for developers wishing to subclass C<autodie::exception>, +write code that registers custom error messages, or otherwise +work closely with the C<autodie::exception> model. + +=cut + +# The table below records customer formatters. +# TODO - Should this be a package var instead? +# TODO - Should these be in a completely different file, or +# perhaps loaded on demand? Most formatters will never +# get used in most programs. + +my %formatter_of = ( + 'CORE::close' => \&_format_close, + 'CORE::open' => \&_format_open, + 'CORE::dbmopen' => \&_format_dbmopen, + 'CORE::flock' => \&_format_flock, +); + +# TODO: Our tests only check LOCK_EX | LOCK_NB is properly +# formatted. Try other combinations and ensure they work +# correctly. + +sub _format_flock { + my ($this) = @_; + + require Fcntl; + + my $filehandle = $this->args->[0]; + my $raw_mode = $this->args->[1]; + + my $mode_type; + my $lock_unlock; + + if ($raw_mode & Fcntl::LOCK_EX() ) { + $lock_unlock = "lock"; + $mode_type = "for exclusive access"; + } + elsif ($raw_mode & Fcntl::LOCK_SH() ) { + $lock_unlock = "lock"; + $mode_type = "for shared access"; + } + elsif ($raw_mode & Fcntl::LOCK_UN() ) { + $lock_unlock = "unlock"; + $mode_type = ""; + } + else { + # I've got no idea what they're trying to do. + $lock_unlock = "lock"; + $mode_type = "with mode $raw_mode"; + } + + my $cooked_filehandle; + + if ($filehandle and not ref $filehandle) { + + # A package filehandle with a name! + + $cooked_filehandle = " $filehandle"; + } + else { + # Otherwise we have a scalar filehandle. + + $cooked_filehandle = ''; + + } + + local $! = $this->errno; + + return "Can't $lock_unlock filehandle$cooked_filehandle $mode_type: $!"; + +} + +# Default formatter for CORE::dbmopen +sub _format_dbmopen { + my ($this) = @_; + my @args = @{$this->args}; + + # TODO: Presently, $args flattens out the (usually empty) hash + # which is passed as the first argument to dbmopen. This is + # a bug in our args handling code (taking a reference to it would + # be better), but for the moment we'll just examine the end of + # our arguments list for message formatting. + + my $mode = $args[-1]; + my $file = $args[-2]; + + # If we have a mask, then display it in octal, not decimal. + # We don't do this if it already looks octalish, or doesn't + # look like a number. + + if ($mode =~ /^[^\D0]\d+$/) { + $mode = sprintf("0%lo", $mode); + }; + + local $! = $this->errno; + + return "Can't dbmopen(%hash, '$file', $mode): '$!'"; +} + +# Default formatter for CORE::close + +sub _format_close { + my ($this) = @_; + my $close_arg = $this->args->[0]; + + local $! = $this->errno; + + # If we've got an old-style filehandle, mention it. + if ($close_arg and not ref $close_arg) { + return "Can't close filehandle '$close_arg': '$!'"; + } + + # TODO - This will probably produce an ugly error. Test and fix. + return "Can't close($close_arg) filehandle: '$!'"; + +} + +# Default formatter for CORE::open + +use constant _FORMAT_OPEN => "Can't open '%s' for %s: '%s'"; + +sub _format_open_with_mode { + my ($this, $mode, $file, $error) = @_; + + my $wordy_mode; + + if ($mode eq '<') { $wordy_mode = 'reading'; } + elsif ($mode eq '>') { $wordy_mode = 'writing'; } + elsif ($mode eq '>>') { $wordy_mode = 'appending'; } + + return sprintf _FORMAT_OPEN, $file, $wordy_mode, $error if $wordy_mode; + + Carp::confess("Internal autodie::exception error: Don't know how to format mode '$mode'."); + +} + +sub _format_open { + my ($this) = @_; + + my @open_args = @{$this->args}; + + # Use the default formatter for single-arg and many-arg open + if (@open_args <= 1 or @open_args >= 4) { + return $this->format_default; + } + + # For two arg open, we have to extract the mode + if (@open_args == 2) { + my ($fh, $file) = @open_args; + + if (ref($fh) eq "GLOB") { + $fh = '$fh'; + } + + my ($mode) = $file =~ m{ + ^\s* # Spaces before mode + ( + (?> # Non-backtracking subexp. + < # Reading + |>>? # Writing/appending + ) + ) + [^&] # Not an ampersand (which means a dup) + }x; + + # Have a funny mode? Use the default format. + return $this->format_default if not defined $mode; + + # Localising $! means perl make make it a pretty error for us. + local $! = $this->errno; + + return $this->_format_open_with_mode($mode, $file, $!); + } + + # Here we must be using three arg open. + + my $file = $open_args[2]; + + local $! = $this->errno; + + my $mode = $open_args[1]; + + local $@; + + my $msg = eval { $this->_format_open_with_mode($mode, $file, $!); }; + + return $msg if $msg; + + # Default message (for pipes and odd things) + + return "Can't open '$file' with mode '$open_args[1]': '$!'"; +} + +=head3 register + + autodie::exception->register( 'CORE::open' => \&mysub ); + +The C<register> method allows for the registration of a message +handler for a given subroutine. The full subroutine name including +the package should be used. + +Registered message handlers will receive the C<autodie::exception> +object as the first parameter. + +=cut + +sub register { + my ($class, $symbol, $handler) = @_; + + croak "Incorrect call to autodie::register" if @_ != 3; + + $formatter_of{$symbol} = $handler; + +} + +=head3 add_file_and_line + + say "Problem occurred",$@->add_file_and_line; + +Returns the string C< at %s line %d>, where C<%s> is replaced with +the filename, and C<%d> is replaced with the line number. + +Primarily intended for use by format handlers. + +=cut + +# Simply produces the file and line number; intended to be added +# to the end of error messages. + +sub add_file_and_line { + my ($this) = @_; + + return sprintf(" at %s line %d\n", $this->file, $this->line); +} + +=head3 stringify + + say "The error was: ",$@->stringify; + +Formats the error as a human readable string. Usually there's no +reason to call this directly, as it is used automatically if an +C<autodie::exception> object is ever used as a string. + +Child classes can override this method to change how they're +stringified. + +=cut + +sub stringify { + my ($this) = @_; + + my $call = $this->function; + + if ($DEBUG) { + my $dying_pkg = $this->package; + my $sub = $this->function; + my $caller = $this->caller; + warn "Stringifing exception for $dying_pkg :: $sub / $caller / $call\n"; + } + + # TODO - This isn't using inheritance. Should it? + if ( my $sub = $formatter_of{$call} ) { + return $sub->($this) . $this->add_file_and_line; + } + + return $this->format_default; + +} + +=head3 format_default + + my $error_string = $E->format_default; + +This produces the default error string for the given exception, +I<without using any registered message handlers>. It is primarily +intended to be called from a message handler when they have +been passed an exception they don't want to format. + +Child classes can override this method to change how default +messages are formatted. + +=cut + +# TODO: This produces ugly errors. Is there any way we can +# dig around to find the actual variable names? I know perl 5.10 +# does some dark and terrible magicks to find them for undef warnings. + +sub format_default { + my ($this) = @_; + + my $call = $this->function; + + local $! = $this->errno; + + # TODO: This is probably a good idea for CORE, is it + # a good idea for other subs? + + # Trim package name off dying sub for error messages. + $call =~ s/.*:://; + + # Walk through all our arguments, and... + # + # * Replace undef with the word 'undef' + # * Replace globs with the string '$fh' + # * Quote all other args. + + my @args = @{ $this->args() }; + + foreach my $arg (@args) { + if (not defined($arg)) { $arg = 'undef' } + elsif (ref($arg) eq "GLOB") { $arg = '$fh' } + else { $arg = qq{'$arg'} } + } + + # Format our beautiful error. + + return "Can't $call(". join(q{, }, @args) . "): $!" . + $this->add_file_and_line; + + # TODO - Handle user-defined errors from hash. + + # TODO - Handle default error messages. + +} + +=head3 new + + my $error = autodie::exception->new( + args => \@_, + function => "CORE::open", + errno => $!, + ); + + +Creates a new C<autodie::exception> object. Normally called +directly from an autodying function. The C<function> argument +is required, its the function we were trying to call that +generated the exception. The C<args> parameter is optional. + +The C<errno> value is optional. In versions of C<autodie::exception> +1.99 and earlier the code would try to automatically use the +current value of C<$!>, but this was unreliable and is no longer +supported. + +Atrributes such as package, file, and caller are determined +automatically, and cannot be specified. + +=cut + +sub new { + my ($class, @args) = @_; + + my $this = {}; + + bless($this,$class); + + # I'd love to use EVERY here, but it causes our code to die + # because it wants to stringify our objects before they're + # initialised, causing everything to explode. + + $this->_init(@args); + + return $this; +} + +sub _init { + + my ($this, %args) = @_; + + # Capturing errno here is not necessarily reliable. + my $original_errno = $!; + + our $init_called = 1; + + my $class = ref $this; + + # We're going to walk up our call stack, looking for the + # first thing that doesn't look like our exception + # code, autodie/Fatal, or some whacky eval. + + my ($package, $file, $line, $sub); + + my $depth = 0; + + while (1) { + $depth++; + + ($package, $file, $line, $sub) = CORE::caller($depth); + + # Skip up the call stack until we find something outside + # of the Fatal/autodie/eval space. + + next if $package->isa('Fatal'); + next if $package->isa($class); + next if $package->isa(__PACKAGE__); + next if $file =~ /^\(eval\s\d+\)$/; + + last; + + } + + $this->{$PACKAGE}{package} = $package; + $this->{$PACKAGE}{file} = $file; + $this->{$PACKAGE}{line} = $line; + $this->{$PACKAGE}{caller} = $sub; + $this->{$PACKAGE}{package} = $package; + + $this->{$PACKAGE}{errno} = $args{errno} || 0; + + $this->{$PACKAGE}{args} = $args{args} || []; + $this->{$PACKAGE}{function}= $args{function} or + croak("$class->new() called without function arg"); + + return $this; + +} + +1; + +__END__ + +=head1 SEE ALSO + +L<autodie>, L<autodie::exception::system> + +=head1 LICENSE + +Copyright (C)2008 Paul Fenwick + +This is free software. You may modify and/or redistribute this +code under the same terms as Perl 5.10 itself, or, at your option, +any later version of Perl 5. + +=head1 AUTHOR + +Paul Fenwick E<lt>pjf@perltraining.com.auE<gt> diff --git a/lib/autodie/exception/system.pm b/lib/autodie/exception/system.pm new file mode 100644 index 0000000000..e286b51a6e --- /dev/null +++ b/lib/autodie/exception/system.pm @@ -0,0 +1,81 @@ +package autodie::exception::system; +use 5.008; +use strict; +use warnings; +use base 'autodie::exception'; +use Carp qw(croak); + +our $VERSION = '1.997'; + +my $PACKAGE = __PACKAGE__; + +=head1 NAME + +autodie::exception::system - Exceptions from autodying system(). + +=head1 SYNOPSIS + + eval { + use autodie; + + system($cmd, @args); + + }; + + if (my $E = $@) { + say "Ooops! ",$E->caller," had problems: $@"; + } + + +=head1 DESCRIPTION + +This is a L<autodie::exception> class for failures from the +C<system> command. + +Presently there is no way to interrogate an C<autodie::exception::system> +object for the command, exit status, and other information you'd expect +such an object to hold. The interface will be expanded to accommodate +this in the future. + +=cut + +sub _init { + my ($this, %args) = @_; + + $this->{$PACKAGE}{message} = $args{message} + || croak "'message' arg not supplied to autodie::exception::system->new"; + + return $this->SUPER::_init(%args); + +} + +=head2 stringify + +When stringified, C<autodie::exception::system> objects currently +use the message generated by L<IPC::System::Simple>. + +=cut + +sub stringify { + + my ($this) = @_; + + return $this->{$PACKAGE}{message} . $this->add_file_and_line; + +} + +1; + +__END__ + +=head1 LICENSE + +Copyright (C)2008 Paul Fenwick + +This is free software. You may modify and/or redistribute this +code under the same terms as Perl 5.10 itself, or, at your option, +any later version of Perl 5. + +=head1 AUTHOR + +Paul Fenwick E<lt>pjf@perltraining.com.auE<gt> diff --git a/pod/perlmodlib.pod b/pod/perlmodlib.pod index c0a447ee77..291361655c 100644 --- a/pod/perlmodlib.pod +++ b/pod/perlmodlib.pod @@ -55,6 +55,10 @@ Get/set subroutine or variable attributes Set/get attributes of a subroutine (deprecated) +=item autodie + +Replace functions with ones that succeed or die with lexical scope + =item autouse Postpone load of modules until a function is used diff --git a/t/lib/autodie/00-load.t b/t/lib/autodie/00-load.t new file mode 100644 index 0000000000..d07fcaefbe --- /dev/null +++ b/t/lib/autodie/00-load.t @@ -0,0 +1,9 @@ +#!perl -T + +use Test::More tests => 1; + +BEGIN { + use_ok( 'Fatal' ); +} + +# diag( "Testing Fatal $Fatal::VERSION, Perl $], $^X" ); diff --git a/t/lib/autodie/Fatal.t b/t/lib/autodie/Fatal.t new file mode 100644 index 0000000000..a291837d13 --- /dev/null +++ b/t/lib/autodie/Fatal.t @@ -0,0 +1,36 @@ +#!/usr/bin/perl -w +use strict; + +use constant NO_SUCH_FILE => "this_file_or_dir_had_better_not_exist_XYZZY"; + +use Test::More tests => 17; + +use Fatal qw(open close :void opendir); + +eval { open FOO, "<".NO_SUCH_FILE }; # Two arg open +like($@, qr/^Can't open/, q{Package Fatal::open}); +is(ref $@, "", "Regular fatal throws a string"); + +my $foo = 'FOO'; +for ('$foo', "'$foo'", "*$foo", "\\*$foo") { + eval qq{ open $_, '<$0' }; + + is($@,"", "Open using filehandle named - $_"); + + like(scalar(<$foo>), qr{^#!.*/perl}, "File contents using - $_"); + eval qq{ close FOO }; + + is($@,"", "Close filehandle using - $_"); +} + +eval { opendir FOO, NO_SUCH_FILE }; +like($@, qr{^Can't open}, "Package :void Fatal::opendir"); + +eval { my $a = opendir FOO, NO_SUCH_FILE }; +is($@, "", "Package :void Fatal::opendir in scalar context"); + +eval { Fatal->import(qw(print)) }; +like( + $@, qr{Cannot make the non-overridable builtin print fatal}, + "Can't override print" +); diff --git a/t/lib/autodie/autodie.t b/t/lib/autodie/autodie.t new file mode 100644 index 0000000000..c528a160a4 --- /dev/null +++ b/t/lib/autodie/autodie.t @@ -0,0 +1,103 @@ +#!/usr/bin/perl -w +use strict; + +use constant NO_SUCH_FILE => 'this_file_had_so_better_not_be_here'; + +use Test::More tests => 19; + +{ + + use autodie qw(open); + + eval { open(my $fh, '<', NO_SUCH_FILE); }; + like($@,qr{Can't open},"autodie qw(open) in lexical scope"); + + no autodie qw(open); + + eval { open(my $fh, '<', NO_SUCH_FILE); }; + is($@,"","no autodie qw(open) in lexical scope"); + + use autodie qw(open); + eval { open(my $fh, '<', NO_SUCH_FILE); }; + like($@,qr{Can't open},"autodie qw(open) in lexical scope 2"); + + no autodie; # Should turn off all autodying subs + eval { open(my $fh, '<', NO_SUCH_FILE); }; + is($@,"","no autodie in lexical scope 2"); + + # Turn our pragma on one last time, so we can verify that + # falling out of this block reverts it back to previous + # behaviour. + use autodie qw(open); + eval { open(my $fh, '<', NO_SUCH_FILE); }; + like($@,qr{Can't open},"autodie qw(open) in lexical scope 3"); + +} + +eval { open(my $fh, '<', NO_SUCH_FILE); }; +is($@,"","autodie open outside of lexical scope"); + +eval { + use autodie; # Should turn on everything + open(my $fh, '<', NO_SUCH_FILE); +}; + +like($@, qr{Can't open}, "vanilla use autodie turns on everything."); + +eval { open(my $fh, '<', NO_SUCH_FILE); }; +is($@,"","vanilla autodie cleans up"); + +{ + use autodie qw(:io); + + eval { open(my $fh, '<', NO_SUCH_FILE); }; + like($@,qr{Can't open},"autodie q(:io) makes autodying open"); + + no autodie qw(:io); + + eval { open(my $fh, '<', NO_SUCH_FILE); }; + is($@,"", "no autodie qw(:io) disabled autodying open"); +} + +{ + package Testing_autodie; + + use Test::More; + + use constant NO_SUCH_FILE => ::NO_SUCH_FILE(); + + use Fatal qw(open); + + eval { open(my $fh, '<', NO_SUCH_FILE); }; + + like($@, qr{Can't open}, "Package fatal working"); + is(ref $@,"","Old Fatal throws strings"); + + { + use autodie qw(open); + + ok(1,"use autodie allowed with Fatal"); + + eval { open(my $fh, '<', NO_SUCH_FILE); }; + like($@, qr{Can't open}, "autodie and Fatal works"); + isa_ok($@, "autodie::exception"); # autodie throws real exceptions + + } + + eval { open(my $fh, '<', NO_SUCH_FILE); }; + + like($@, qr{Can't open}, "Package fatal working after autodie"); + is(ref $@,"","Old Fatal throws strings after autodie"); + + eval " no autodie qw(open); "; + + ok($@,"no autodie on Fataled sub an error."); + + eval " + no autodie qw(close); + use Fatal 'close'; + "; + + like($@, qr{not allowed}, "Using fatal after autodie is an error."); +} + diff --git a/t/lib/autodie/autodie_test_module.pm b/t/lib/autodie/autodie_test_module.pm new file mode 100644 index 0000000000..e8e824c522 --- /dev/null +++ b/t/lib/autodie/autodie_test_module.pm @@ -0,0 +1,18 @@ +package main; +use strict; +use warnings; + +# Calls open, while still in the main package. This shouldn't +# be autodying. +sub leak_test { + return open(my $fh, '<', $_[0]); +} + +package autodie_test_module; + +# This should be calling CORE::open +sub your_open { + return open(my $fh, '<', $_[0]); +} + +1; diff --git a/t/lib/autodie/backcompat.t b/t/lib/autodie/backcompat.t new file mode 100644 index 0000000000..acb81245b8 --- /dev/null +++ b/t/lib/autodie/backcompat.t @@ -0,0 +1,14 @@ +#!/usr/bin/perl -w +use strict; +use Fatal qw(open); +use Test::More tests => 2; +use constant NO_SUCH_FILE => "xyzzy_this_file_is_not_here"; + +eval { + open(my $fh, '<', NO_SUCH_FILE); +}; + +my $old_msg = qr{Can't open\(GLOB\(0x[0-9a-f]+\), <, xyzzy_this_file_is_not_here\): .* at \(eval \d+\)(?:\[.*?\])? line \d+\s+main::__ANON__\('GLOB\(0x[0-9a-f]+\)',\s*'<',\s*'xyzzy_this_file_is_not_here'\) called at \S+ line \d+\s+eval \Q{...}\E called at \S+ line \d+}; + +like($@,$old_msg,"Backwards compat ugly messages"); +is(ref($@),"", "Exception is a string, not an object"); diff --git a/t/lib/autodie/basic_exceptions.t b/t/lib/autodie/basic_exceptions.t new file mode 100644 index 0000000000..0981e8d6ef --- /dev/null +++ b/t/lib/autodie/basic_exceptions.t @@ -0,0 +1,27 @@ +#!/usr/bin/perl -w +use strict; + +use Test::More tests => 13; + +use constant NO_SUCH_FILE => "this_file_had_better_not_exist"; + +eval { + use autodie ':io'; + open(my $fh, '<', NO_SUCH_FILE); +}; + +like($@, qr/Can't open '\w+' for reading: /, "Prety printed open msg"); +like($@, qr{\Q$0\E}, "Our file mention in error message"); + +like($@, qr{for reading: '.+'}, "Error should be in single-quotes"); +like($@->errno,qr/./, "Errno should not be empty"); + +like($@, qr{\n$}, "Errors should end with a newline"); +is($@->file, $0, "Correct file"); +is($@->function, 'CORE::open', "Correct dying sub"); +is($@->package, __PACKAGE__, "Correct package"); +is($@->caller,__PACKAGE__."::__ANON__", "Correct caller"); +is($@->args->[1], '<', 'Correct mode arg'); +is($@->args->[2], NO_SUCH_FILE, 'Correct filename arg'); +ok($@->matches('open'), 'Looks like an error from open'); +ok($@->matches(':io'), 'Looks like an error from :io'); diff --git a/t/lib/autodie/binmode.t b/t/lib/autodie/binmode.t new file mode 100644 index 0000000000..317a41303c --- /dev/null +++ b/t/lib/autodie/binmode.t @@ -0,0 +1,33 @@ +#!/usr/bin/perl -w +use strict; +use Test::More 'no_plan'; + +# These are a bunch of general tests for working with files and +# filehandles. + +my $r = "default"; + +eval { + no warnings; + $r = binmode(FOO); +}; + +is($@,"","Sanity: binmode(FOO) doesn't usually throw exceptions"); +is($r,undef,"Sanity: binmode(FOO) returns undef"); + +eval { + use autodie qw(binmode); + no warnings; + binmode(FOO); +}; + +ok($@, "autodie qw(binmode) should cause failing binmode to die."); +isa_ok($@,"autodie::exception", "binmode exceptions are in autodie::exception"); + +eval { + use autodie; + no warnings; + binmode(FOO); +}; + +ok($@, "autodie (default) should cause failing binmode to die."); diff --git a/t/lib/autodie/context.t b/t/lib/autodie/context.t new file mode 100644 index 0000000000..39b86497c6 --- /dev/null +++ b/t/lib/autodie/context.t @@ -0,0 +1,66 @@ +#!/usr/bin/perl -w +use strict; + +use Test::More; + +plan 'no_plan'; + +sub list_return { + return if @_; + return qw(foo bar baz); +} + +sub list_return2 { + return if @_; + return qw(foo bar baz); +} + +# Returns a list presented to it, but also returns a single +# undef if given a list of a single undef. This mimics the +# behaviour of many user-defined subs and built-ins (eg: open) that +# always return undef regardless of context. + +sub list_mirror { + return undef if (@_ == 1 and not defined $_[0]); + return @_; + +} + +use Fatal qw(list_return); +use Fatal qw(:void list_return2); + +TODO: { + + # Clobbering context was documented as a bug in the original + # Fatal, so we'll still consider it a bug here. + + local $TODO = "Fatal clobbers context, just like it always has."; + + my @list = list_return(); + + is_deeply(\@list,[qw(foo bar baz)],'fatal sub works in list context'); +} + +eval { + my @line = list_return(1); # Should die +}; + +ok($@,"List return fatalised"); + +### Tests where we've fatalised our function with :void ### + +my @list2 = list_return2(); + +is_deeply(\@list2,[qw(foo bar baz)],'fatal sub works in list context'); + +eval { + my @line = list_return2(1); # Shouldn't die +}; + +ok(! $@,"void List return fatalised survives when non-void"); + +eval { + list_return2(1); +}; + +ok($@,"void List return fatalised"); diff --git a/t/lib/autodie/context_lexical.t b/t/lib/autodie/context_lexical.t new file mode 100644 index 0000000000..eeb1a547d2 --- /dev/null +++ b/t/lib/autodie/context_lexical.t @@ -0,0 +1,80 @@ +#!/usr/bin/perl -w +use strict; + +use Test::More; + +plan 'no_plan'; + +# Returns a list presented to it, but also returns a single +# undef if given a list of a single undef. This mimics the +# behaviour of many user-defined subs and built-ins (eg: open) that +# always return undef regardless of context. + +sub list_mirror { + return undef if (@_ == 1 and not defined $_[0]); + return @_; + +} + +### autodie clobbering tests ### + +eval { + list_mirror(); +}; + +is($@, "", "No autodie, no fatality"); + +eval { + use autodie qw(list_mirror); + list_mirror(); +}; + +ok($@, "Autodie fatality for empty return in void context"); + +eval { + list_mirror(); +}; + +is($@, "", "No autodie, no fatality (after autodie used)"); + +eval { + use autodie qw(list_mirror); + list_mirror(undef); +}; + +ok($@, "Autodie fatality for undef return in void context"); + +eval { + use autodie qw(list_mirror); + my @list = list_mirror(); +}; + +ok($@,"Autodie fatality for empty list return"); + +eval { + use autodie qw(list_mirror); + my @list = list_mirror(undef); +}; + +ok($@,"Autodie fatality for undef list return"); + +eval { + use autodie qw(list_mirror); + my @list = list_mirror("tada"); +}; + +ok(! $@,"No Autodie fatality for defined list return"); + +eval { + use autodie qw(list_mirror); + my $single = list_mirror("tada"); +}; + +ok(! $@,"No Autodie fatality for defined scalar return"); + +eval { + use autodie qw(list_mirror); + my $single = list_mirror(undef); +}; + +ok($@,"Autodie fatality for undefined scalar return"); diff --git a/t/lib/autodie/crickey.t b/t/lib/autodie/crickey.t new file mode 100644 index 0000000000..91a7d7837a --- /dev/null +++ b/t/lib/autodie/crickey.t @@ -0,0 +1,27 @@ +#!/usr/bin/perl -w +use strict; +use FindBin; +use Test::More 'no_plan'; + +use lib "$FindBin::Bin/lib"; + +use constant NO_SUCH_FILE => "crickey_mate_this_file_isnt_here_either"; + +use autodie::test::au qw(open); + +eval { + open(my $fh, '<', NO_SUCH_FILE); +}; + +ok(my $e = $@, 'Strewth! autodie::test::au should throw an exception on failure'); + +isa_ok($e, 'autodie::test::au::exception', + 'Yeah mate, that should be our test exception.'); + +like($e, qr/time for a beer/, "Time for a beer mate?"); + +like( eval { $e->time_for_a_beer; }, + qr/time for a beer/, "It's always a good time for a beer." +); + +ok($e->matches('open'), "Should be a fair dinkum error from open"); diff --git a/t/lib/autodie/dbmopen.t b/t/lib/autodie/dbmopen.t new file mode 100644 index 0000000000..31698e65be --- /dev/null +++ b/t/lib/autodie/dbmopen.t @@ -0,0 +1,36 @@ +#!/usr/bin/perl -w +use strict; +use Test::More qw(no_plan); + +use constant ERROR_REGEXP => qr{Can't dbmopen\(%hash, 'foo/bar/baz', 0666\):}; + +my $return = "default"; + +eval { + $return = dbmopen(my %foo, "foo/bar/baz", 0666); +}; + +ok(!$return, "Sanity: dbmopen usually returns false on failure"); +ok(!$@, "Sanity: dbmopen doesn't usually throw exceptions"); + +eval { + use autodie; + + dbmopen(my %foo, "foo/bar/baz", 0666); +}; + +ok($@, "autodie allows dbmopen to throw errors."); +isa_ok($@, "autodie::exception", "... errors are of the correct type"); + +like($@, ERROR_REGEXP, "Message should include number in octal, not decimal"); + +eval { + use autodie; + + my %bar = ( foo => 1, bar => 2 ); + + dbmopen(%bar, "foo/bar/baz", 0666); +}; + +like($@, ERROR_REGEXP, "Correct formatting even with non-empty dbmopen hash"); + diff --git a/t/lib/autodie/exception_class.t b/t/lib/autodie/exception_class.t new file mode 100644 index 0000000000..127893bcbf --- /dev/null +++ b/t/lib/autodie/exception_class.t @@ -0,0 +1,57 @@ +#!/usr/bin/perl -w +use strict; + +use FindBin; +use Test::More 'no_plan'; + +use lib "$FindBin::Bin/lib"; + +use constant NO_SUCH_FILE => "this_file_had_better_not_exist_xyzzy"; + +### Tests with non-existent exception class. + +my $open_success = eval { + use autodie::test::missing qw(open); # Uses non-existent exceptions + open(my $fh, '<', NO_SUCH_FILE); + 1; +}; + +is($open_success,undef,"Open should fail"); + +isnt($@,"",'$@ should not be empty'); + +is(ref($@),"",'$@ should not be a reference or object'); + +like($@, qr/Failed to load/, '$@ should contain bad exception class msg'); + +#### Tests with malformed exception class. + +my $open_success2 = eval { + use autodie::test::badname qw(open); + open(my $fh, '<', NO_SUCH_FILE); + 1; +}; + +is($open_success2,undef,"Open should fail"); + +isnt($@,"",'$@ should not be empty'); + +is(ref($@),"",'$@ should not be a reference or object'); + +like($@, qr/Bad exception class/, '$@ should contain bad exception class msg'); + +### Tests with well-formed exception class (in Klingon) + +my $open_success3 = eval { + use pujHa'ghach qw(open); #' <-- this makes my editor happy + open(my $fh, '<', NO_SUCH_FILE); + 1; +}; + +is($open_success3,undef,"Open should fail"); + +isnt("$@","",'$@ should not be empty'); + +isa_ok($@, "pujHa'ghach::Dotlh", '$@ should be a Klingon exception'); + +like($@, qr/lujqu'/, '$@ should contain Klingon text'); diff --git a/t/lib/autodie/exceptions.t b/t/lib/autodie/exceptions.t new file mode 100644 index 0000000000..2f8c2382fc --- /dev/null +++ b/t/lib/autodie/exceptions.t @@ -0,0 +1,45 @@ +#!/usr/bin/perl -w +use strict; +use Test::More; + +BEGIN { plan skip_all => "Perl 5.10 only tests" if $] < 5.010; } + +# These are tests that depend upon 5.10 (eg, smart-match). +# Basic tests should go in basic_exceptions.t + +use 5.010; +use constant NO_SUCH_FILE => 'this_file_had_better_not_exist_xyzzy'; + +plan 'no_plan'; + +eval { + use autodie ':io'; + open(my $fh, '<', NO_SUCH_FILE); +}; + +ok($@, "Exception thrown" ); +ok($@ ~~ 'open', "Exception from open" ); +ok($@ ~~ ':file', "Exception from open / class :file" ); +ok($@ ~~ ':io', "Exception from open / class :io" ); +ok($@ ~~ ':all', "Exception from open / class :all" ); + +eval { + no warnings 'once'; # To prevent the following close from complaining. + close(THIS_FILEHANDLE_AINT_OPEN); +}; + +ok(! $@, "Close without autodie should fail silent"); + +eval { + use autodie ':io'; + close(THIS_FILEHANDLE_AINT_OPEN); +}; + +like($@, qr{Can't close filehandle 'THIS_FILEHANDLE_AINT_OPEN'},"Nice msg from close"); + +ok($@, "Exception thrown" ); +ok($@ ~~ 'close', "Exception from close" ); +ok($@ ~~ ':file', "Exception from close / class :file" ); +ok($@ ~~ ':io', "Exception from close / class :io" ); +ok($@ ~~ ':all', "Exception from close / class :all" ); + diff --git a/t/lib/autodie/exec.t b/t/lib/autodie/exec.t new file mode 100644 index 0000000000..0d4439a8c1 --- /dev/null +++ b/t/lib/autodie/exec.t @@ -0,0 +1,12 @@ +#!/usr/bin/perl -w +use strict; +use Test::More tests => 3; + +eval { + use autodie qw(exec); + exec("this_command_had_better_not_exist", 1); +}; + +isa_ok($@,"autodie::exception", "failed execs should die"); +ok($@->matches('exec'), "exception should match exec"); +ok($@->matches(':system'), "exception should match :system"); diff --git a/t/lib/autodie/filehandles.t b/t/lib/autodie/filehandles.t new file mode 100644 index 0000000000..5bdf732e2c --- /dev/null +++ b/t/lib/autodie/filehandles.t @@ -0,0 +1,60 @@ +#!/usr/bin/perl -w + +package main; + +use strict; +use Test::More; + +# We may see failures with package filehandles if Fatal/autodie +# incorrectly pulls out a cached subroutine from a different package. + +# We're using Fatal because package filehandles are likely to +# see more use with Fatal than autodie. + +use Fatal qw(open); + +eval { + open(FILE, '<', $0); +}; + + +if ($@) { + # Holy smokes! We couldn't even open our own file, bail out... + + plan skip_all => q{Can't open $0 for filehandle tests} +} + +plan tests => 4; + +my $line = <FILE>; + +like($line, qr{perl}, 'Looks like we opened $0 correctly'); + +close(FILE); + +package autodie::test; +use Test::More; + +use Fatal qw(open); + +eval { + open(FILE2, '<', $0); +}; + +is($@,"",'Opened $0 in autodie::test'); + +my $line2 = <FILE2>; + +like($line2, qr{perl}, '...and we can read from $0 fine'); + +close(FILE2); + +package main; + +# This shouldn't read anything, because FILE2 should be inside +# autodie::test + +no warnings; # Otherwise we see problems with FILE2 +my $wrong_line = <FILE2>; + +ok(! defined($wrong_line),q{Filehandles shouldn't leak between packages}); diff --git a/t/lib/autodie/fileno.t b/t/lib/autodie/fileno.t new file mode 100644 index 0000000000..2b9c2598e7 --- /dev/null +++ b/t/lib/autodie/fileno.t @@ -0,0 +1,35 @@ +#!/usr/bin/perl -w +use strict; +use Test::More tests => 8; + +# Basic sanity tests. +is(fileno(STDIN), 0, "STDIN fileno looks sane"); +is(fileno(STDOUT),1, "STDOUT looks sane"); + +my $dummy = "foo"; + +ok(!defined(fileno($dummy)), "Non-filehandles shouldn't be defined."); + + +my $fileno = eval { + use autodie qw(fileno); + fileno(STDIN); +}; + +is($@,"","fileno(STDIN) shouldn't die"); +is($fileno,0,"autodying fileno(STDIN) should be 0"); + +$fileno = eval { + use autodie qw(fileno); + fileno(STDOUT); +}; + +is($@,"","fileno(STDOUT) shouldn't die"); +is($fileno,1,"autodying fileno(STDOUT) should be 1"); + +$fileno = eval { + use autodie qw(fileno); + fileno($dummy); +}; + +isa_ok($@,"autodie::exception", 'autodying fileno($dummy) should die'); diff --git a/t/lib/autodie/flock.t b/t/lib/autodie/flock.t new file mode 100644 index 0000000000..8b2a168398 --- /dev/null +++ b/t/lib/autodie/flock.t @@ -0,0 +1,90 @@ +#!/usr/bin/perl -w +use strict; +use Test::More; +use Fcntl qw(:flock); +use POSIX qw(EWOULDBLOCK); + +require Fatal; + +my $EWOULDBLOCK = eval { EWOULDBLOCK() } + || $Fatal::_EWOULDBLOCK{$^O} + || plan skip_all => "EWOULDBLOCK not defined on this system"; + +my ($self_fh, $self_fh2); + +eval { + use autodie; + open($self_fh, '<', $0); + open($self_fh2, '<', $0); + open(SELF, '<', $0); +}; + +if ($@) { + plan skip_all => "Cannot lock this test on this system."; +} + +my $flock_return = flock($self_fh, LOCK_EX | LOCK_NB); + +if (not $flock_return) { + plan skip_all => "flock on my own test not supported on this system."; +} + +my $flock_return2 = flock($self_fh2, LOCK_EX | LOCK_NB); + +if ($flock_return2) { + plan skip_all => "this test requires locking a file twice with ". + "different filehandles to fail"; +} + +$flock_return = flock($self_fh, LOCK_UN); + +if (not $flock_return) { + plan skip_all => "Odd, I can't unlock a file with flock on this system."; +} + +# If we're here, then we can lock and unlock our own file. + +plan 'no_plan'; + +ok( flock($self_fh, LOCK_EX | LOCK_NB), "Test file locked"); + +my $return; + +eval { + use autodie qw(flock); + $return = flock($self_fh2, LOCK_EX | LOCK_NB); +}; + +is($!+0, $EWOULDBLOCK, "Double-flocking should be EWOULDBLOCK"); +ok(!$return, "flocking a file twice should fail"); +is($@, "", "Non-blocking flock should not fail on EWOULDBLOCK"); + +__END__ + +# These are old tests which I'd love to resurrect, but they need +# a reliable way of getting flock to throw exceptions but with +# minimal blocking. They may turn into author tests. + +eval { + use autodie; + flock($self_fh2, LOCK_EX | LOCK_NB); +}; + +ok($@, "Locking a file twice throws an exception with vanilla autodie"); +isa_ok($@, "autodie::exception", "Exception is from autodie::exception"); + +like($@, qr/LOCK_EX/, "error message contains LOCK_EX switch"); +like($@, qr/LOCK_NB/, "error message contains LOCK_NB switch"); +unlike($@, qr/GLOB/ , "error doesn't include ugly GLOB mention"); + +eval { + use autodie; + flock(SELF, LOCK_EX | LOCK_NB); +}; + +ok($@, "Locking a package filehanlde twice throws exception with vanilla autodie"); +isa_ok($@, "autodie::exception", "Exception is from autodie::exception"); + +like($@, qr/LOCK_EX/, "error message contains LOCK_EX switch"); +like($@, qr/LOCK_NB/, "error message contains LOCK_NB switch"); +like($@, qr/SELF/ , "error mentions actual filehandle name."); diff --git a/t/lib/autodie/internal.t b/t/lib/autodie/internal.t new file mode 100644 index 0000000000..c1189444cb --- /dev/null +++ b/t/lib/autodie/internal.t @@ -0,0 +1,33 @@ +#!/usr/bin/perl -w +use strict; + +use constant NO_SUCH_FILE => "this_file_or_dir_had_better_not_exist_XYZZY"; + +use Test::More tests => 6; + +# Lexical tests using the internal interface. + +eval { Fatal->import(qw(:lexical :void)) }; +like($@, qr{:void cannot be used with lexical}, ":void can't be used with :lexical"); + +eval { Fatal->import(qw(open close :lexical)) }; +like($@, qr{:lexical must be used as first}, ":lexical must come first"); + +{ + use Fatal qw(:lexical chdir); + + eval { chdir(NO_SUCH_FILE); }; + like ($@, qr/^Can't chdir/, "Lexical fatal chdir"); + + no Fatal qw(:lexical chdir); + + eval { chdir(NO_SUCH_FILE); }; + is ($@, "", "No lexical fatal chdir"); + +} + +eval { chdir(NO_SUCH_FILE); }; +is($@, "", "Lexical chdir becomes non-fatal out of scope."); + +eval { Fatal->import('2+2'); }; +like($@,qr{Bad subroutine name},"Can't use fatal with invalid sub names"); diff --git a/t/lib/autodie/lethal.t b/t/lib/autodie/lethal.t new file mode 100644 index 0000000000..244d2f82b2 --- /dev/null +++ b/t/lib/autodie/lethal.t @@ -0,0 +1,17 @@ +#!/usr/bin/perl -w +use strict; +use FindBin; +use Test::More tests => 4; +use lib "$FindBin::Bin/lib"; +use lethal qw(open); + +use constant NO_SUCH_FILE => "this_file_had_better_not_exist"; + +eval { + open(my $fh, '<', NO_SUCH_FILE); +}; + +ok($@, "lethal throws an exception"); +isa_ok($@, 'autodie::exception','...which is the correct class'); +ok($@->matches('open'), "...which matches open"); +is($@->file,__FILE__, "...which reports the correct file"); diff --git a/t/lib/autodie/lib/autodie/test/au.pm b/t/lib/autodie/lib/autodie/test/au.pm new file mode 100644 index 0000000000..7a50e8f101 --- /dev/null +++ b/t/lib/autodie/lib/autodie/test/au.pm @@ -0,0 +1,14 @@ +package autodie::test::au; +use strict; +use warnings; + +use base qw(autodie); + +use autodie::test::au::exception; + +sub throw { + my ($this, @args) = @_; + return autodie::test::au::exception->new(@args); +} + +1; diff --git a/t/lib/autodie/lib/autodie/test/au/exception.pm b/t/lib/autodie/lib/autodie/test/au/exception.pm new file mode 100644 index 0000000000..5811fc1ea6 --- /dev/null +++ b/t/lib/autodie/lib/autodie/test/au/exception.pm @@ -0,0 +1,19 @@ +package autodie::test::au::exception; +use strict; +use warnings; + +use base qw(autodie::exception); + +sub time_for_a_beer { + return "Now's a good time for a beer."; +} + +sub stringify { + my ($this) = @_; + + my $base_str = $this->SUPER::stringify; + + return "$base_str\n" . $this->time_for_a_beer; +} + +1; diff --git a/t/lib/autodie/lib/autodie/test/badname.pm b/t/lib/autodie/lib/autodie/test/badname.pm new file mode 100644 index 0000000000..2a621a9112 --- /dev/null +++ b/t/lib/autodie/lib/autodie/test/badname.pm @@ -0,0 +1,8 @@ +package autodie::test::badname; +use base qw(autodie); + +sub exception_class { + return 'autodie::test::badname::$@#%'; # Doesn't exist! +} + +1; diff --git a/t/lib/autodie/lib/autodie/test/missing.pm b/t/lib/autodie/lib/autodie/test/missing.pm new file mode 100644 index 0000000000..b6166a53a4 --- /dev/null +++ b/t/lib/autodie/lib/autodie/test/missing.pm @@ -0,0 +1,8 @@ +package autodie::test::missing; +use base qw(autodie); + +sub exception_class { + return "autodie::test::missing::exception"; # Doesn't exist! +} + +1; diff --git a/t/lib/autodie/lib/lethal.pm b/t/lib/autodie/lib/lethal.pm new file mode 100644 index 0000000000..a49600a58a --- /dev/null +++ b/t/lib/autodie/lib/lethal.pm @@ -0,0 +1,8 @@ +package lethal; + +# A dummy package showing how we can trivially subclass autodie +# to our tastes. + +use base qw(autodie); + +1; diff --git a/t/lib/autodie/lib/pujHa/ghach.pm b/t/lib/autodie/lib/pujHa/ghach.pm new file mode 100644 index 0000000000..a55164b1a2 --- /dev/null +++ b/t/lib/autodie/lib/pujHa/ghach.pm @@ -0,0 +1,26 @@ +package pujHa'ghach; + +# Translator notes: reH Hegh is Kligon for "always dying". +# It was the original name for this testing pragma, but +# it lacked an apostrophe, which better shows how Perl is +# useful in Klingon naming schemes. + +# The new name is pujHa'ghach is "thing which is not weak". +# puj -> be weak (verb) +# -Ha' -> not +# ghach -> normalise -Ha' verb into noun. +# +# I'm not use if -wI' should be used here. pujwI' is "thing which +# is weak". One could conceivably use "pujHa'wI'" for "thing which +# is not weak". + +use strict; +use warnings; + +use base qw(autodie); + +sub exception_class { + return "pujHa'ghach::Dotlh"; # Dotlh - status +} + +1; diff --git a/t/lib/autodie/lib/pujHa/ghach/Dotlh.pm b/t/lib/autodie/lib/pujHa/ghach/Dotlh.pm new file mode 100644 index 0000000000..c7bbf8b1f6 --- /dev/null +++ b/t/lib/autodie/lib/pujHa/ghach/Dotlh.pm @@ -0,0 +1,59 @@ +package pujHa'ghach::Dotlh; + +# Translator notes: Dotlh = status + +# Ideally this should be le'wI' - Thing that is exceptional. ;) +# Unfortunately that results in a file called .pm, which may cause +# problems on some filesystems. + +use strict; +use warnings; + +use base qw(autodie::exception); + +sub stringify { + my ($this) = @_; + + my $error = $this->SUPER::stringify; + + return "QaghHommeyHeylIjmo':\n" . # Due to your apparent minor errors + "$error\n" . + "lujqu'"; # Epic fail + + +} + +1; + +__END__ + +# The following was a really neat idea, but currently autodie +# always pushes values in $! to format them, which loses the +# Klingon translation. + +use Errno qw(:POSIX); +use Scalar::Util qw(dualvar); + +my %translation_for = ( + EPERM() => q{Dachaw'be'}, # You do not have permission + ENOENT() => q{De' vItu'laHbe'}, # I cannot find this information. +); + +sub errno { + my ($this) = @_; + + my $errno = int $this->SUPER::errno; + + warn "In tlhIngan errno - $errno\n"; + + if ( my $tlhIngan = $translation_for{ $errno } ) { + return dualvar( $errno, $tlhIngan ); + } + + return $!; + +} + +1; + + diff --git a/t/lib/autodie/mkdir.t b/t/lib/autodie/mkdir.t new file mode 100644 index 0000000000..7bd6529086 --- /dev/null +++ b/t/lib/autodie/mkdir.t @@ -0,0 +1,69 @@ +#!/usr/bin/perl -w +use strict; +use Test::More; +use FindBin qw($Bin); +use constant TMPDIR => "$Bin/mkdir_test_delete_me"; + +# Delete our directory if it's there +rmdir TMPDIR; + +# See if we can create directories and remove them +mkdir TMPDIR or plan skip_all => "Failed to make test directory"; + +# Test the directory was created +-d TMPDIR or plan skip_all => "Failed to make test directory"; + +# Try making it a second time (this should fail) +if(mkdir TMPDIR) { plan skip_all => "Attempt to remake a directory succeeded";} + +# See if we can remove the directory +rmdir TMPDIR or plan skip_all => "Failed to remove directory"; + +# Check that the directory was removed +if(-d TMPDIR) { plan skip_all => "Failed to delete test directory"; } + +# Try to delete second time +if(rmdir TMPDIR) { plan skip_all => "Able to rmdir directory twice"; } + +plan tests => 12; + +# Create a directory (this should succeed) +eval { + use autodie; + + mkdir TMPDIR; +}; +is($@, "", "mkdir returned success"); +ok(-d TMPDIR, "Successfully created test directory"); + +# Try to create it again (this should fail) +eval { + use autodie; + + mkdir TMPDIR; +}; +ok($@, "Re-creating directory causes failure."); +isa_ok($@, "autodie::exception", "... errors are of the correct type"); +ok($@->matches("mkdir"), "... it's also a mkdir object"); +ok($@->matches(":filesys"), "... and a filesys object"); + +# Try to delete directory (this should succeed) +eval { + use autodie; + + rmdir TMPDIR; +}; +is($@, "", "rmdir returned success"); +ok(! -d TMPDIR, "Successfully removed test directory"); + +# Try to delete directory again (this should fail) +eval { + use autodie; + + rmdir TMPDIR; +}; +ok($@, "Re-deleting directory causes failure."); +isa_ok($@, "autodie::exception", "... errors are of the correct type"); +ok($@->matches("rmdir"), "... it's also a rmdir object"); +ok($@->matches(":filesys"), "... and a filesys object"); + diff --git a/t/lib/autodie/open.t b/t/lib/autodie/open.t new file mode 100644 index 0000000000..3a2d493c49 --- /dev/null +++ b/t/lib/autodie/open.t @@ -0,0 +1,18 @@ +#!/usr/bin/perl -w +use strict; + +use Test::More 'no_plan'; + +use constant NO_SUCH_FILE => "this_file_had_better_not_exist"; + +use autodie; + +eval { open(my $fh, '<', NO_SUCH_FILE); }; +ok($@, "3-arg opening non-existent file fails"); +like($@, qr/for reading/, "Well-formatted 3-arg open failure"); + +eval { open(my $fh, "< ".NO_SUCH_FILE) }; +ok($@, "2-arg opening non-existent file fails"); + +like($@, qr/for reading/, "Well-formatted 2-arg open failure"); +unlike($@, qr/GLOB\(0x/, "No ugly globs in 2-arg open messsage"); diff --git a/t/lib/autodie/recv.t b/t/lib/autodie/recv.t new file mode 100644 index 0000000000..cfaa679144 --- /dev/null +++ b/t/lib/autodie/recv.t @@ -0,0 +1,60 @@ +#!/usr/bin/perl -w +use strict; +use Test::More tests => 8; +use Socket; +use autodie qw(socketpair); + +# All of this code is based around recv returning an empty +# string when it gets data from a local machine (using AF_UNIX), +# but returning an undefined value on error. Fatal/autodie +# should be able to tell the difference. + +$SIG{PIPE} = 'IGNORE'; + +my ($sock1, $sock2); +socketpair($sock1, $sock2, AF_UNIX, SOCK_STREAM, PF_UNSPEC); + +my $buffer; +send($sock1, "xyz", 0); +my $ret = recv($sock2, $buffer, 2, 0); + +use autodie qw(recv); + +SKIP: { + + skip('recv() never returns empty string with socketpair emulation',4) + if ($ret); + + is($buffer,'xy',"recv() operational without autodie"); + + # Read the last byte from the socket. + eval { $ret = recv($sock2, $buffer, 1, 0); }; + + is($@, "", "recv should not die on returning an emtpy string."); + + is($buffer,"z","recv() operational with autodie"); + is($ret,"","recv returns undying empty string for local sockets"); + +} + +eval { + # STDIN isn't a socket, so this should fail. + recv(STDIN,$buffer,1,0); +}; + +ok($@,'recv dies on returning undef'); +isa_ok($@,'autodie::exception'); + +$buffer = "# Not an empty string\n"; + +# Terminate writing for $sock1 +shutdown($sock1, 1); + +eval { + use autodie qw(send); + # Writing to a socket terminated for writing should fail. + send($sock1,$buffer,0); +}; + +ok($@,'send dies on returning undef'); +isa_ok($@,'autodie::exception'); diff --git a/t/lib/autodie/repeat.t b/t/lib/autodie/repeat.t new file mode 100644 index 0000000000..5f85f1218c --- /dev/null +++ b/t/lib/autodie/repeat.t @@ -0,0 +1,19 @@ +#!/usr/bin/perl -w +use strict; +use Test::More 'no_plan'; +use constant NO_SUCH_FILE => "this_file_had_better_not_exist"; + +eval { + use autodie qw(open open open); + open(my $fh, '<', NO_SUCH_FILE); +}; + +isa_ok($@,q{autodie::exception}); +ok($@->matches('open'),"Exception from open"); + +eval { + open(my $fh, '<', NO_SUCH_FILE); +}; + +is($@,"","Repeated autodie should not leak"); + diff --git a/t/lib/autodie/scope_leak.t b/t/lib/autodie/scope_leak.t new file mode 100644 index 0000000000..3d7b55510c --- /dev/null +++ b/t/lib/autodie/scope_leak.t @@ -0,0 +1,37 @@ +#!/usr/bin/perl -w +use strict; +use FindBin; + +# Check for %^H leaking across file boundries. Many thanks +# to chocolateboy for pointing out this can be a problem. + +use lib $FindBin::Bin; + +use Test::More 'no_plan'; + +use constant NO_SUCH_FILE => 'this_file_had_better_not_exist'; +use autodie qw(open); + +eval { + open(my $fh, '<', NO_SUCH_FILE); +}; + +ok($@, "basic autodie test"); + +use autodie_test_module; + +# If things don't work as they should, then the file we've +# just loaded will still have an autodying main::open (although +# its own open should be unaffected). + +eval { + leak_test(NO_SUCH_FILE); +}; + +is($@,"","autodying main::open should not leak to other files"); + +eval { + autodie_test_module::your_open(NO_SUCH_FILE); +}; + +is($@,"","Other package open should be unaffected"); diff --git a/t/lib/autodie/sysopen.t b/t/lib/autodie/sysopen.t new file mode 100644 index 0000000000..ab489b7830 --- /dev/null +++ b/t/lib/autodie/sysopen.t @@ -0,0 +1,23 @@ +#!/usr/bin/perl -w +use strict; +use Test::More 'no_plan'; +use Fcntl; + +use autodie qw(sysopen); + +use constant NO_SUCH_FILE => "this_file_had_better_not_be_here_at_all"; + +my $fh; +eval { + sysopen($fh, $0, O_RDONLY); +}; + +is($@, "", "sysopen can open files that exist"); + +like(scalar( <$fh> ), qr/perl/, "Data in file read"); + +eval { + sysopen(my $fh2, NO_SUCH_FILE, O_RDONLY); +}; + +isa_ok($@, 'autodie::exception', 'Opening a bad file fails with sysopen'); diff --git a/t/lib/autodie/truncate.t b/t/lib/autodie/truncate.t new file mode 100644 index 0000000000..c99f500c2a --- /dev/null +++ b/t/lib/autodie/truncate.t @@ -0,0 +1,52 @@ +#!/usr/bin/perl -w +use strict; + +use Test::More; +use File::Temp qw(tempfile); +use IO::Handle; + +my $tmpfh = tempfile(); + +eval { + truncate($tmpfh, 0); +}; + +if ($@) { + plan skip_all => 'Truncate not implemented on this system'; +} + +plan tests => 3; + +SKIP: { + my $can_truncate_stdout = truncate(\*STDOUT,0); + + if ($can_truncate_stdout) { + skip("This system thinks we can truncate STDOUT. Suuure!", 1); + } + + eval { + use autodie; + truncate(\*STDOUT,0); + }; + + isa_ok($@, 'autodie::exception', "Truncating STDOUT should throw an exception"); + +} + +eval { + use autodie; + no warnings 'once'; + truncate(\*FOO, 0); +}; + +isa_ok($@, 'autodie::exception', "Truncating an unopened file is wrong."); + +$tmpfh->print("Hello World"); +$tmpfh->flush; + +eval { + use autodie; + truncate($tmpfh, 0); +}; + +is($@, "", "Truncating a normal file should be fine"); diff --git a/t/lib/autodie/unlink.t b/t/lib/autodie/unlink.t new file mode 100644 index 0000000000..f301500fda --- /dev/null +++ b/t/lib/autodie/unlink.t @@ -0,0 +1,52 @@ +#!/usr/bin/perl -w +use strict; +use Test::More; +use FindBin qw($Bin); +use constant TMPFILE => "$Bin/unlink_test_delete_me"; + +# Create a file to practice unlinking +open(my $fh, ">", TMPFILE) + or plan skip_all => "Unable to create test file: $!"; +print {$fh} "Test\n"; +close $fh; + +# Check that file now exists +-e TMPFILE or plan skip_all => "Failed to create test file"; + +# Check we can unlink +unlink TMPFILE; + +# Check it's gone +if(-e TMPFILE) {plan skip_all => "Failed to delete test file: $!";} + +# Re-create file +open(my $fh2, ">", TMPFILE) + or plan skip_all => "Unable to create test file: $!"; +print {$fh2} "Test\n"; +close $fh2; + +# Check that file now exists +-e TMPFILE or plan skip_all => "Failed to create test file"; + +plan tests => 6; + +# Try to delete directory (this should succeed) +eval { + use autodie; + + unlink TMPFILE; +}; +is($@, "", "Unlink appears to have been successful"); +ok(! -e TMPFILE, "File does not exist"); + +# Try to delete file again (this should fail) +eval { + use autodie; + + unlink TMPFILE; +}; +ok($@, "Re-unlinking file causes failure."); +isa_ok($@, "autodie::exception", "... errors are of the correct type"); +ok($@->matches("unlink"), "... it's also a unlink object"); +ok($@->matches(":filesys"), "... and a filesys object"); + diff --git a/t/lib/autodie/usersub.t b/t/lib/autodie/usersub.t new file mode 100644 index 0000000000..7e15576abf --- /dev/null +++ b/t/lib/autodie/usersub.t @@ -0,0 +1,64 @@ +#!/usr/bin/perl -w +use strict; + +use Test::More 'no_plan'; + +sub mytest { + return $_[0]; +} + +is(mytest(q{foo}),q{foo},"Mytest returns input"); + +my $return = eval { mytest(undef); }; + +ok(!defined($return), "mytest returns undef without autodie"); +is($@,"","Mytest doesn't throw an exception without autodie"); + +$return = eval { + use autodie qw(mytest); + + mytest('foo'); +}; + +is($return,'foo',"Mytest returns input with autodie"); + +$return = eval { + use autodie qw(mytest); + + mytest(undef); +}; + +isa_ok($@,'autodie::exception',"autodie mytest/undef throws exception"); + +# We set initial values here because we're expecting $data to be +# changed to undef later on. Having it as undef to begin with means +# we can't see mytest(undef) working correctly. + +my ($data, $data2) = (1,1); + +eval { + use autodie qw(mytest); + + { + no autodie qw(mytest); + + $data = mytest(undef); + $data2 = mytest('foo'); + } +}; + +is($@,"","no autodie can counter use autodie for user subs"); +ok(!defined($data), "mytest(undef) should return undef"); +is($data2, "foo", "mytest(foo) should return foo"); + +eval { + mytest(undef); +}; + +is($@,"","No lingering failure effects"); + +$return = eval { + mytest("bar"); +}; + +is($return,"bar","No lingering return effects"); diff --git a/t/lib/autodie/version.t b/t/lib/autodie/version.t new file mode 100644 index 0000000000..7a68f7f0fc --- /dev/null +++ b/t/lib/autodie/version.t @@ -0,0 +1,17 @@ +#!/usr/bin/perl -w +use strict; +use Test::More tests => 3; + +# For the moment, we'd like all our versions to be the same. +# In order to play nicely with some code scanners, they need to be +# hard-coded into the files, rather than just nicking the version +# from autodie::exception at run-time. + +require Fatal; +require autodie; +require autodie::exception; +require autodie::exception::system; + +is($Fatal::VERSION, $autodie::VERSION); +is($autodie::VERSION, $autodie::exception::VERSION); +is($autodie::exception::VERSION, $autodie::exception::system::VERSION); diff --git a/t/lib/autodie/version_tag.t b/t/lib/autodie/version_tag.t new file mode 100644 index 0000000000..7cb533329e --- /dev/null +++ b/t/lib/autodie/version_tag.t @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w +use strict; +use warnings; +use Test::More tests => 3; + +eval { + use autodie qw(:1.994); + + open(my $fh, '<', 'this_file_had_better_not_exist.txt'); +}; + +isa_ok($@, 'autodie::exception', "Basic version tags work"); + + +# Expanding :1.00 should fail, there was no autodie :1.00 +eval { my $foo = autodie->_expand_tag(":1.00"); }; + +isnt($@,"","Expanding :1.00 should fail"); + +my $version = $autodie::VERSION; + +# Expanding our current version should work! +eval { my $foo = autodie->_expand_tag(":$version"); }; + +is($@,"","Expanding :$version should succeed"); + |