package ErrorLocation; use strict; use warnings; use Test::Builder; use Carp qw(croak); use Exporter 'import'; our @EXPORT = qw(location_ok); my $builder = Test::Builder->new; my $gen = 'A000'; sub location_ok ($$) { my ($code, $name) = @_; local $Test::Builder::Level = $Test::Builder::Level + 1; my ($pre) = $code =~ /\A(.*?)(?:## fail\n.*)?\n?\z/s; my $fail_line = 1 + $pre =~ tr/\n//; my $PACKAGE = "LocationTest::_".++$gen; my $sub = eval qq{ sub { package $PACKAGE; #line 1 LocationTestFile $code } }; my $full_trace; my $last_location; my $immediate; my $trace_capture = sub { my @c = caller; my ($location) = $_[0] =~ /^.* at (.*? line \d+)\.?$/; $location ||= sprintf "%s line %s", (caller(0))[1,2]; if (!$last_location || $last_location ne $location) { $last_location = $location; $immediate = $c[1] eq 'LocationTestFile'; { local %Carp::Internal; local %Carp::CarpInternal; $full_trace = Carp::longmess(''); } $full_trace =~ s/\A.*\n//; $full_trace =~ s/^\t//mg; $full_trace =~ s/^[^\n]+ called at ${\__FILE__} line [0-9]+\n.*//ms; if ($c[0] eq 'Carp') { $full_trace =~ s/.*?(^Carp::)/$1/ms; } else { my ($arg) = @_; $arg =~ s/\Q at $c[1] line $c[2]\E\.\n\z//; my $caller = 'CORE::die(' . Carp::format_arg($arg) . ") called at $location\n"; $full_trace =~ s/\A.*\n/$caller/; } $full_trace =~ s{^(.* called at )(\(eval [0-9]+\)(?:\[[^\]]*\])?) line ([0-9]+)\n}{ my ($prefix, $file, $line) = ($1, $2, $3); my $i = 0; while (my @c = caller($i++)) { if ($c[1] eq $file && $c[2] eq $line) { $file .= "[$c[0]]"; last; } } "$prefix$file line $line\n"; }meg; $full_trace =~ s/^/ /mg; } }; croak "$name - compile error: $@" if !$sub; local $@; eval { local $Carp::Verbose = 0; local $SIG{__WARN__}; local $SIG{__DIE__} = $trace_capture; $sub->(); 1; } and croak "$name - code did not fail!"; croak "died directly in test code: $@" if $immediate; delete $LocationTest::{"_$gen"}; my ($location) = $@ =~ /.* at (.*? line \d+)\.?$/; $builder->is_eq($location, "LocationTestFile line $fail_line", $name) or $builder->diag(" error:\n $@\n full trace:\n$full_trace"), return !1; } 1;