Skip Menu |

Preferred bug tracker

Please visit the preferred bug tracker to report your issue.

This queue is for tickets about the Test-Exception CPAN distribution.

Report information
The Basics
Id: 24678
Status: open
Priority: 0/
Queue: Test-Exception

People
Owner: Nobody in particular
Requestors: jjore [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in: (no value)
Fixed in: (no value)



Subject: $@ isn't kept safe from scope cleanup - some failures ignored
use Test::More tests => 3; use Test::Exception; sub A1::DESTROY { eval {} } dies_ok { my $obj = bless [], 'A1'; die } q[Exceptions aren't hidden by eval{} during scope cleanup]; sub A2::DESTROY { die 42 } throws_ok { my $obj = bless [], 'A2'; die 43 } qr/43/, q[Of multiple failures, the "primary" one is returned]; sub A3::DESTROY { die } dies_ok { my $obj = bless [], 'A3'; return 1 } q[Failure during scope cleanup is detected]; 1..3 not ok 1 - Exceptions aren't hidden by eval{} during scope cleanup # Failed test 'Exceptions aren't hidden by eval{} during scope cleanup' # at src/te line 5. ok 2 - Of multiple failures, the "primary" one is returned not ok 3 - Failure during scope cleanup is detected # Failed test 'Failure during scope cleanup is detected' # at src/te line 11. # Looks like you failed 2 tests of 3.
From: DAGOLDEN [...] cpan.org
This isn't just a bug in Test::Exception, but rather a bug in eval/DESTROY. Localizing $@ in DESTROY is the workaround: use Test::More tests => 2; sub A1::DESTROY{ eval {} } eval { my $obj = bless [], 'A1'; die }; ok $@, "eval caught an exception"; sub A2::DESTROY{ local $@; eval {} } eval { my $obj = bless [], 'A2'; die }; ok $@, "try eval caught an exception"; Result: 1..2 not ok 1 - eval caught an exception # Failed test 'eval caught an exception' # at evaleval.pl line 7. ok 2 - try eval caught an exception # Looks like you failed 1 test of 2.
From: JJORE [...] cpan.org
On Tue Jan 30 23:41:38 2007, DAGOLDEN wrote: Show quoted text
> This isn't just a bug in Test::Exception, but rather a bug in > eval/DESTROY. Localizing $@ in DESTROY is the workaround:
Yes, that's a workaround for proper user code that wants to prevent any propagation of exceptions. Anyhow, the attached patch covers all the pathological stuff I've been able to come up with right now: exception objects blessed into false classes, exception objects that look false, exception objects that clobber $@ when you look at them, destructors that clobber $@ during success or failure, destructors that also clobber $SIG{__DIE__} at the same time. If you planned to throw an exception during a destructor and wanted to hide it from your callers then you'd localize $SIG{__DIE__} and $@ and put it in an eval. Only the $SIG{__DIE__} is outside normal non-paranoid practice so I don't mind recommending it that way.
--- lib/Test/Exception.pm.~1~ 2006-10-07 01:42:36.000000000 -0700 +++ lib/Test/Exception.pm 2007-01-30 23:32:07.000000000 -0800 @@ -5,6 +5,7 @@ use Test::Builder; use Sub::Uplevel qw( uplevel ); use base qw( Exporter ); +use Scalar::Util 'blessed'; use Carp; our $VERSION = '0.24'; @@ -77,12 +78,28 @@ =cut - +our $DIED; +sub DIED { + + $DIED = 1; +} sub _try_as_caller { my $coderef = shift; - eval { uplevel 3, $coderef }; - return $@; + local $@; + local $SIG{__DIE__} = $SIG{__DIE__}; + $DIED = 0; + + my $failed = not defined eval { + $SIG{__DIE__} = \ &DIED; + uplevel 3, $coderef; + }; + + return defined( blessed( $@ ) ) ? $@ : + $@ ? $@ : + $DIED ? "Died\n" : + $failed ? "Died\n" : + ''; }; --- /dev/null 2006-10-26 22:58:30.000000000 -0700 +++ t/pathological.t 2007-01-30 23:46:21.000000000 -0800 @@ -0,0 +1,47 @@ +use strict; +use warnings; +use Test::More tests => 11; +use Test::Exception; + +sub A1::DESTROY {eval{}} +dies_ok { my $x = bless [], 'A1'; die } q[Unlocalized $@ for eval{} during DESTROY]; + +sub A2::DESTROY {die 43 } +throws_ok { my $x = bless [], 'A2'; die 42} qr/42.+43/s, q[Died with the primary and secondar errors]; + +{ + sub A3::DESTROY {die} + dies_ok { my $x = bless [], 'A3'; 1 } q[Death during destruction for success is noticed]; +} + + +sub A4::DESTROY {delete$SIG{__DIE__};eval{}} +dies_ok { my $x = bless [], 'A4'; die } q[Unlocalized $@ for eval{} during DESTROY]; + +sub A5::DESTROY {delete$SIG{__DIE__};die 43 } +throws_ok { my $x = bless [], 'A5'; die 42} qr/42.+43/s, q[Died with the primary and secondar errors]; + +TODO: { + local $TODO = q[No clue how to solve this one.]; + sub A6::DESTROY {delete$SIG{__DIE__};die} + dies_ok { my $x = bless [], 'A6'; 1 } q[Death during destruction for success is noticed]; +} + + +dies_ok { die bless [], 0 } q[Died with a "false" exception class]; +dies_ok { die bless [], "\0" } q[Died with a "false" exception class]; + +package A7; +use overload bool => sub { 0 }, '0+' => sub { 0 }, '""' => sub { '' }, fallback => 1; +package main; +dies_ok { die bless [], 'A7' } q[False overloaded exceptions are noticed]; + + +$main::{'0::'} = $main::{'A7::'}; +dies_ok { die bless [], 0 } q[Died a false death]; + + +package A8; +use overload bool => sub {eval{};0}, '0+' => sub{eval{};0}, '""' => sub { eval{}; '' }, fallback => 1; +package main; +dies_ok { die bless [], 'A8' } q[Evanescent exceptions are noticed];
From: PETEK [...] cpan.org
JJORE's patch also clears the issue that dies_ok does not insulate against $SIG{__DIE__} modifications, which I am running into at the current time. I would like to see the patch included in Test::Exception 0.26.
Attached is a version of Josh's patch, but against T::E 0.27. This version also handles the case where the block being tested returns undef without an exception.
--- lib/Test/Exception.pm.0.27 +++ lib/Test/Exception.pm @@ -5,6 +5,7 @@ use Test::Builder; use Sub::Uplevel qw( uplevel ); use base qw( Exporter ); +use Scalar::Util 'blessed'; use Carp; our $VERSION = '0.27'; @@ -94,6 +95,11 @@ } } +my $DIED; +sub _DIED { + $DIED = 1; +} + sub _try_as_caller { my $coderef = shift; @@ -101,8 +107,20 @@ local *CORE::GLOBAL::caller; { no warnings 'redefine'; *CORE::GLOBAL::caller = \&_quiet_caller; } - eval { uplevel 3, $coderef }; - return $@; + local $@; + local $SIG{__DIE__} = $SIG{__DIE__}; + $DIED = 0; + + my $failed = not defined eval { + $SIG{__DIE__} = \ &_DIED; + uplevel 3, $coderef; + }; + + return defined( blessed( $@ ) ) ? $@ : + $@ ? $@ : + $DIED ? "Died\n" : + $failed ? "Died\n" : + ''; };