git-flavoured autodie 1.997 patch
Paul Fenwick [Sat, 20 Dec 2008 13:21:02 +0000 (22:21 +0900)]
G'day p5p,

Since we've moved over to git, attached is a git-friendly patch of autodie
1.997 against the current blead.  It's no different to the older 1.997
patch[1], but contains all the meta-info that git likes to have so that you
can use 'git am' to apply the changes.

All the very best,

Paul

[1] Okay, there's one or two non-significant whitespace changes.

--
Paul Fenwick <pjf@perltraining.com.au> | http://perltraining.com.au/
Director of Training                   | Ph:  +61 3 9354 6001
Perl Training Australia                | Fax: +61 3 9354 2681

>From b0dc5ff6b006a9df2a67b886e5e0d0d168c1245e Mon Sep 17 00:00:00 2001
From: Paul Fenwick <pjf@perltraining.com.au>
Date: Sun, 21 Dec 2008 00:17:28 +1100
Subject: [PATCH] Autodie 1.997

43 files changed:
MANIFEST
lib/Fatal.pm
lib/autodie.pm [new file with mode: 0644]
lib/autodie/exception.pm [new file with mode: 0644]
lib/autodie/exception/system.pm [new file with mode: 0644]
pod/perlmodlib.pod
t/lib/autodie/00-load.t [new file with mode: 0644]
t/lib/autodie/Fatal.t [new file with mode: 0644]
t/lib/autodie/autodie.t [new file with mode: 0644]
t/lib/autodie/autodie_test_module.pm [new file with mode: 0644]
t/lib/autodie/backcompat.t [new file with mode: 0644]
t/lib/autodie/basic_exceptions.t [new file with mode: 0644]
t/lib/autodie/binmode.t [new file with mode: 0644]
t/lib/autodie/context.t [new file with mode: 0644]
t/lib/autodie/context_lexical.t [new file with mode: 0644]
t/lib/autodie/crickey.t [new file with mode: 0644]
t/lib/autodie/dbmopen.t [new file with mode: 0644]
t/lib/autodie/exception_class.t [new file with mode: 0644]
t/lib/autodie/exceptions.t [new file with mode: 0644]
t/lib/autodie/exec.t [new file with mode: 0644]
t/lib/autodie/filehandles.t [new file with mode: 0644]
t/lib/autodie/fileno.t [new file with mode: 0644]
t/lib/autodie/flock.t [new file with mode: 0644]
t/lib/autodie/internal.t [new file with mode: 0644]
t/lib/autodie/lethal.t [new file with mode: 0644]
t/lib/autodie/lib/autodie/test/au.pm [new file with mode: 0644]
t/lib/autodie/lib/autodie/test/au/exception.pm [new file with mode: 0644]
t/lib/autodie/lib/autodie/test/badname.pm [new file with mode: 0644]
t/lib/autodie/lib/autodie/test/missing.pm [new file with mode: 0644]
t/lib/autodie/lib/lethal.pm [new file with mode: 0644]
t/lib/autodie/lib/pujHa/ghach.pm [new file with mode: 0644]
t/lib/autodie/lib/pujHa/ghach/Dotlh.pm [new file with mode: 0644]
t/lib/autodie/mkdir.t [new file with mode: 0644]
t/lib/autodie/open.t [new file with mode: 0644]
t/lib/autodie/recv.t [new file with mode: 0644]
t/lib/autodie/repeat.t [new file with mode: 0644]
t/lib/autodie/scope_leak.t [new file with mode: 0644]
t/lib/autodie/sysopen.t [new file with mode: 0644]
t/lib/autodie/truncate.t [new file with mode: 0644]
t/lib/autodie/unlink.t [new file with mode: 0644]
t/lib/autodie/usersub.t [new file with mode: 0644]
t/lib/autodie/version.t [new file with mode: 0644]
t/lib/autodie/version_tag.t [new file with mode: 0644]

index 52dd3c1..11d0570 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -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
index 0b4bf9b..0f7ef8f 100644 (file)
 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 (file)
index 0000000..38c12f9
--- /dev/null
@@ -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 (file)
index 0000000..43f50fc
--- /dev/null
@@ -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 (file)
index 0000000..e286b51
--- /dev/null
@@ -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>
index c0a447e..2913616 100644 (file)
@@ -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 (file)
index 0000000..d07fcae
--- /dev/null
@@ -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 (file)
index 0000000..a291837
--- /dev/null
@@ -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 (file)
index 0000000..c528a16
--- /dev/null
@@ -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 (file)
index 0000000..e8e824c
--- /dev/null
@@ -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 (file)
index 0000000..acb8124
--- /dev/null
@@ -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 (file)
index 0000000..0981e8d
--- /dev/null
@@ -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 (file)
index 0000000..317a413
--- /dev/null
@@ -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 (file)
index 0000000..39b8649
--- /dev/null
@@ -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 (file)
index 0000000..eeb1a54
--- /dev/null
@@ -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 (file)
index 0000000..91a7d78
--- /dev/null
@@ -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 (file)
index 0000000..31698e6
--- /dev/null
@@ -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 (file)
index 0000000..127893b
--- /dev/null
@@ -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 (file)
index 0000000..2f8c238
--- /dev/null
@@ -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 (file)
index 0000000..0d4439a
--- /dev/null
@@ -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 (file)
index 0000000..5bdf732
--- /dev/null
@@ -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 (file)
index 0000000..2b9c259
--- /dev/null
@@ -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 (file)
index 0000000..8b2a168
--- /dev/null
@@ -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 (file)
index 0000000..c118944
--- /dev/null
@@ -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 (file)
index 0000000..244d2f8
--- /dev/null
@@ -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 (file)
index 0000000..7a50e8f
--- /dev/null
@@ -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 (file)
index 0000000..5811fc1
--- /dev/null
@@ -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 (file)
index 0000000..2a621a9
--- /dev/null
@@ -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 (file)
index 0000000..b6166a5
--- /dev/null
@@ -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 (file)
index 0000000..a49600a
--- /dev/null
@@ -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 (file)
index 0000000..a55164b
--- /dev/null
@@ -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 (file)
index 0000000..c7bbf8b
--- /dev/null
@@ -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 (file)
index 0000000..7bd6529
--- /dev/null
@@ -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 (file)
index 0000000..3a2d493
--- /dev/null
@@ -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 (file)
index 0000000..cfaa679
--- /dev/null
@@ -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 (file)
index 0000000..5f85f12
--- /dev/null
@@ -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 (file)
index 0000000..3d7b555
--- /dev/null
@@ -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 (file)
index 0000000..ab489b7
--- /dev/null
@@ -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 (file)
index 0000000..c99f500
--- /dev/null
@@ -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 (file)
index 0000000..f301500
--- /dev/null
@@ -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 (file)
index 0000000..7e15576
--- /dev/null
@@ -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 (file)
index 0000000..7a68f7f
--- /dev/null
@@ -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 (file)
index 0000000..7cb5333
--- /dev/null
@@ -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");
+