Abstract away the CORE::GLOBAL::require override code, foolproof tests
Peter Rabbitson [Thu, 24 Nov 2011 17:38:09 +0000 (18:38 +0100)]
Rewrite prompted by some weirdness in Package::Stash::XS and require overrides
on older perls < 5.8.7. Besides the cleaner code add a kick-ass require
tracer.

Changes
t/53lean_startup.t
t/55namespaces_cleaned.t
t/lib/DBICTest/Util/OverrideRequire.pm [new file with mode: 0644]

diff --git a/Changes b/Changes
index 4e06e66..c899cc4 100644 (file)
--- a/Changes
+++ b/Changes
@@ -14,6 +14,7 @@ Revision history for DBIx::Class
           nasty memleak with version.pm on multiple ->VERSION invocations)
         - The internal carp module now correctly skips CAG frames when
           reporting a callsite
+        - Fix test failures on perl < 5.8.7 and new Package::Stash::XS
 
     * Misc
         - No longer depend on Variable::Magic now that a pure-perl
index 81f9bca..072eac3 100644 (file)
@@ -1,14 +1,17 @@
 # Use a require override instead of @INC munging (less common)
 # Do the override as early as possible so that CORE::require doesn't get compiled away
-# We will replace $req_override in a bit
+# We will add the hook in a bit, got to load some regular stuff
 
 my $test_hook;
 BEGIN {
-  $test_hook = sub {}; # noop at first
-  *CORE::GLOBAL::require = sub {
-    $test_hook->(@_);
-    CORE::require($_[0]);
-  };
+  unshift @INC, 't/lib';
+  require DBICTest::Util::OverrideRequire;
+
+  DBICTest::Util::OverrideRequire::override_global_require( sub {
+    my $res = $_[0]->();
+    $test_hook->($_[1]) if $test_hook;
+    return $res;
+  });
 }
 
 use strict;
@@ -16,6 +19,12 @@ use warnings;
 use Test::More;
 use Data::Dumper;
 
+# Package::Stash::XS is silly and fails if a require hook contains regular
+# expressions on perl < 5.8.7. Load the damned thing if the case
+BEGIN {
+  require Package::Stash if $] < 5.008007;
+}
+
 my $expected_core_modules;
 
 BEGIN {
@@ -112,7 +121,7 @@ for (keys %$expected_core_modules) {
   $mod =~ s/::/\//g;
   unless ($INC{$mod}) {
     my $err = sprintf "Expected DBIC core module %s never loaded - %s needs adjustment", $_, __FILE__;
-    if (DBICTest::RunMode->is_smoker) {
+    if (DBICTest::RunMode->is_smoker or DBICTest::RunMode->is_author) {
       fail ($err)
     }
     else {
index 8a9e337..6706966 100644 (file)
@@ -1,57 +1,32 @@
-# Pre-5.10 perls pollute %INC on unsuccesfull module
-# require, making it appear as if the module is already
-# loaded on subsequent require()s
-# Can't seem to find the exact RT/perldelta entry
-#
-# we want to do this here, in the very beginning, before even
-# warnings/strict are loaded
 BEGIN {
   if ($] < 5.010) {
 
-    # All of this almost verbatim copied from Lexical::SealRequireHints
-    # Zefram++
-
-    # a potential caller() in $next_require must see the correct
-    # immediate frame caller
-    my $caller = caller(0);
-
-    our $next_require = defined(&CORE::GLOBAL::require)
-      ? \&CORE::GLOBAL::require
-      : sub {
-        my ($arg) = @_;
-
-        # The shenanigans with $CORE::GLOBAL::{require}
-        # are required because if there's a
-        # &CORE::GLOBAL::require when the eval is
-        # executed then the CORE::require in there is
-        # interpreted as plain require on some Perl
-        # versions, leading to recursion.
-        my $grequire = delete $CORE::GLOBAL::{require};
-
-        my $result = eval sprintf '
-          local $SIG{__DIE__};
-          $CORE::GLOBAL::{require} = $grequire;
-          package %s;
-          CORE::require($arg);
-        ', $caller;
-
-        die $@ if $@ ne '';
-        return $result;
-      }
-    ;
-
-    *CORE::GLOBAL::require = sub {
-      die "wrong number of arguments to require\n"
-        unless @_ == 1;
-
-      my $res = eval "package $caller; \$next_require->(\@_)";
+    # Pre-5.10 perls pollute %INC on unsuccesfull module
+    # require, making it appear as if the module is already
+    # loaded on subsequent require()s
+    # Can't seem to find the exact RT/perldelta entry
+    #
+    # The reason we can't just use a sane, clean loader, is because
+    # if a Module require()s another module the %INC will still
+    # get filled with crap and we are back to square one. A global
+    # fix is really the only way for this test, as we try to load
+    # each available module separately, and have no control (nor
+    # knowledge) over their common dependencies.
+    #
+    # we want to do this here, in the very beginning, before even
+    # warnings/strict are loaded
+
+    unshift @INC, 't/lib';
+    require DBICTest::Util::OverrideRequire;
+
+    DBICTest::Util::OverrideRequire::override_global_require( sub {
+      my $res = eval { $_[0]->() };
       if ($@ ne '') {
-        delete $INC{$_[0]};
+        delete $INC{$_[1]};
         die $@;
       }
-
-      $res;
-    };
+      return $res;
+    } );
   }
 }
 
@@ -211,5 +186,4 @@ sub find_modules {
   return sort @modules;
 }
 
-
 done_testing;
diff --git a/t/lib/DBICTest/Util/OverrideRequire.pm b/t/lib/DBICTest/Util/OverrideRequire.pm
new file mode 100644 (file)
index 0000000..d776267
--- /dev/null
@@ -0,0 +1,127 @@
+package DBICTest::Util::OverrideRequire;
+
+# no use/require of any kind - work bare
+
+BEGIN {
+  # 0 - no trace
+  # 1 - just requires and return values
+  # 2 - full stacktrace
+  *TRACE = sub () { 0 };
+}
+
+# Takes a single coderef and replaces CORE::GLOBAL::require with it.
+#
+# On subsequent require() calls, the coderef will be invoked with
+# two arguments - ($next_require, $module_name_copy)
+#
+# $next_require is a coderef closing over the module name. It needs
+# to be invoked at some point without arguments for the actual
+# require to take place (this way your coderef in essence becomes an
+# around modifier)
+#
+# $module_name_copy is a string-copy of what $next_require is closing
+# over. The reason for the copy is that you may trigger a side effect
+# on magical values, and subsequently abort the require (e.g.
+# require v.5.8.8 magic)
+#
+# All of this almost verbatim copied from Lexical::SealRequireHints
+# Zefram++
+sub override_global_require (&) {
+  my $override_cref = shift;
+
+  our $next_require = defined(&CORE::GLOBAL::require)
+    ? \&CORE::GLOBAL::require
+    : sub {
+
+      my ($arg) = @_;
+
+      # The shenanigans with $CORE::GLOBAL::{require}
+      # are required because if there's a
+      # &CORE::GLOBAL::require when the eval is
+      # executed then the CORE::require in there is
+      # interpreted as plain require on some Perl
+      # versions, leading to recursion.
+      my $grequire = delete $CORE::GLOBAL::{require};
+
+      my $res = eval sprintf '
+        local $SIG{__DIE__};
+        $CORE::GLOBAL::{require} = $grequire;
+        package %s;
+        CORE::require($arg);
+      ', scalar caller(0);  # the caller already had its package replaced
+
+      my $err = $@ if $@ ne '';
+
+      if( TRACE ) {
+        if (TRACE == 1) {
+          printf STDERR "Require of '%s' (returned: '%s')\n",
+            (my $m_copy = $arg),
+            (my $r_copy = $res),
+          ;
+        }
+        else {
+          my ($fr_num, @fr, @tr, $excise);
+          while (@fr = caller($fr_num++)) {
+
+            # Package::Stash::XS is a cock and gets mightily confused if one
+            # uses a regex in the require hook - go figure
+
+            if (index($fr[1], '(eval ') != 0 and index($fr[1], __FILE__) != 0) {
+              push @tr, [@fr]
+            }
+
+            if ($fr[3] eq 'DBICTest::Util::OverrideRequire::__ANON__') {
+              $excise ||= $tr[-2];
+            }
+          }
+
+          my @stack =
+            map { "$_->[1], line $_->[2]" }
+            grep { not ($_->[1] eq $excise->[1] and $_->[2] eq $_->[2]) }
+            @tr
+          ;
+
+          printf STDERR "Require of '%s' (returned: '%s')\n%s\n\n",
+            (my $m_copy = $arg),
+            (my $r_copy = $res||''),
+            join "\n", (map { "    $_" } @stack)
+          ;
+        }
+      }
+
+      die $err if defined $err;
+
+      return $res;
+    }
+  ;
+
+  # Need to suppress the redefinition warning, without
+  # invoking warnings.pm.
+  BEGIN { ${^WARNING_BITS} = ""; }
+
+  *CORE::GLOBAL::require = sub {
+    die "wrong number of arguments to require\n"
+      unless @_ == 1;
+
+    # the copy is to prevent accidental overload firing (e.g. require v5.8.8)
+    my ($arg_copy) = our ($arg) = @_;
+
+    return $override_cref->(sub {
+      die "The require delegate takes no arguments\n"
+        if @_;
+
+      my $res = eval sprintf '
+        local $SIG{__DIE__};
+        package %s;
+        $next_require->($arg);
+      ', scalar caller(2);  # 2 for the indirection of the $override_cref around
+
+      die $@ if $@ ne '';
+
+      return $res;
+
+    }, $arg_copy);
+  }
+}
+
+1;