2 ### This version is rather 5.8-centric, because DBIC itself is 5.8
3 ### It certainly can be rewritten to degrade well on 5.6
6 # Very important to grab the snapshot early, as we will be reporting
7 # the INC indices from the POV of whoever ran the script, *NOT* from
8 # the POV of the internals
14 BEGIN { do "./t/lib/ANFANG.pm" or die ( $@ || $! ) }
19 # Pre-5.10 perls pollute %INC on unsuccesfull module
20 # require, making it appear as if the module is already
21 # loaded on subsequent require()s
22 # Can't seem to find the exact RT/perldelta entry
24 # The reason we can't just use a sane, clean loader, is because
25 # if a Module require()s another module the %INC will still
26 # get filled with crap and we are back to square one. A global
27 # fix is really the only way for this test, as we try to load
28 # each available module separately, and have no control (nor
29 # knowledge) over their common dependencies.
31 # we want to do this here, in the very beginning, before even
32 # warnings/strict are loaded
34 require DBICTest::Util::OverrideRequire;
36 DBICTest::Util::OverrideRequire::override_global_require( sub {
37 my $res = eval { $_[0]->() };
50 use Test::More 'no_plan';
52 use File::Find 'find';
57 use ExtUtils::MakeMaker;
59 use DBICTest::RunMode;
60 use DBIx::Class::_Util 'visit_namespaces';
61 use DBIx::Class::Optional::Dependencies;
65 config_key => 'sitearch',
68 config_key => 'sitelib',
71 config_key => 'sitelib_stem',
75 config_key => 'siteprefix',
79 config_key => 'vendorarch',
82 config_key => 'vendorlib',
85 config_key => 'vendorlib_stem',
89 config_key => 'vendorprefix',
93 config_key => 'archlib',
96 config_key => 'privlib',
99 config_key => 'prefix',
103 rel_path => './blib/arch',
104 skip_unversioned_modules => 1,
107 rel_path => './blib/lib',
108 skip_unversioned_modules => 1,
115 skip_unversioned_modules => 1,
119 skip_unversioned_modules => 1,
123 skip_unversioned_modules => 1,
130 abs_unix_path => abs_unix_path (
131 eval { require File::HomeDir and File::HomeDir->my_home }
142 for my $k (keys %$known_paths) {
143 my $v = $known_paths->{$k};
145 # never use home as a found-in-dir marker - it is too broad
146 # HOME is only used by the shortener
147 $v->{marker} = $k unless $k eq 'HOME';
149 unless ( $v->{abs_unix_path} ) {
150 if ( $v->{rel_path} ) {
151 $v->{abs_unix_path} = abs_unix_path( $v->{rel_path} );
153 elsif ( $Config{ $v->{config_key} || '' } ) {
154 $v->{abs_unix_path} = abs_unix_path (
155 $Config{"$v->{config_key}exp"} || $Config{$v->{config_key}}
160 delete $known_paths->{$k} unless $v->{abs_unix_path} and -d $v->{abs_unix_path};
162 my $seen_markers = {};
164 # first run through lib/ and *try* to load anything we can find
165 # within our own project
170 $_ =~ m|lib/DBIx/Class/_TempExtlib| and return;
172 # can't just `require $fn`, as we need %INC to be
174 my ($mod) = $_ =~ /^ lib [\/\\] (.+) \.pm $/x
177 try_module_require(join ('::', File::Spec->splitdir($mod)) )
184 # now run through OptDeps and attempt loading everything else
186 # some things needs to be sorted before other things
187 # positive - load first
188 # negative - load last
190 # Make sure oracle is tried last - some clients (e.g. 10.2) have symbol
191 # clashes with libssl, and will segfault everything coming after them
192 "DBD::Oracle" => -999,
195 my @known_modules = sort
196 { ($load_weights->{$b}||0) <=> ($load_weights->{$a}||0) }
199 DBIx::Class::Optional::Dependencies->req_list_for([
201 # some DBDs are notoriously problematic to load
202 # hence only show stuff based on test_rdbms which will
203 # take into account necessary ENVs
204 { $_ !~ /^ (?: rdbms | dist )_ /x }
205 keys %{DBIx::Class::Optional::Dependencies->req_group_list}
210 try_module_require($_) for @known_modules;
212 my $has_versionpm = eval { require version };
215 # At this point we've loaded everything we ever could, but some modules
216 # (understandably) crapped out. For an even more thorough report, note
217 # everthing present in @INC we excplicitly know about (via OptDeps)
218 # *even though* it didn't load
219 my $known_failed_loads;
221 for my $mod (@known_modules) {
222 my $inc_key = module_notional_filename($mod);
223 next if defined $INC{$inc_key};
225 if (defined( my $idx = module_found_at_inc_index( $mod, \@INC ) ) ) {
226 $known_failed_loads->{$mod} = abs_unix_path( "$INC[$idx]/$inc_key" );
233 # This is a cool idea, but the line is too long even with shortening :(
235 #for my $i ( 1 .. $Config{config_argc} ) {
236 # my $conf_arg = $Config{"config_arg$i"};
240 # '=' . shorten_fn($1)
243 # $perl .= " $conf_arg";
246 my $interesting_modules = {
250 abs_unix_path => abs_unix_path($^X),
255 # drill through the *ENTIRE* symtable and build a map of interesting modules
256 visit_namespaces( action => sub {
260 # keep going, but nothing to see here
261 return 1 if $pkg eq 'main';
263 # private - not interested, including no further descent
264 return 0 if $pkg =~ / (?: ^ | :: ) _ /x;
266 my $inc_key = module_notional_filename($pkg);
268 my $abs_unix_path = (
275 abs_unix_path($INC{$inc_key})
278 # handle versions first (not interested in synthetic classes)
280 defined ${"${pkg}::VERSION"}
282 ${"${pkg}::VERSION"} !~ /\Qset by base.pm/
285 # make sure a version can be extracted, be noisy when it doesn't work
286 # do this even if we are throwing away the result below in lieu of EUMM
287 my $mod_ver = eval { $pkg->VERSION };
292 "Calling `$pkg->VERSION` resulted in an exception, which should never "
293 . "happen - please file a bug with the distribution containing $pkg. "
294 . "Complete exception text below:\n\n$err"
297 elsif( ! defined $mod_ver or ! length $mod_ver ) {
298 my $ret = defined $mod_ver
299 ? "the empty string ''"
304 "Calling `$pkg->VERSION` returned $ret, even though \$${pkg}::VERSION "
305 . "is defined, which should never happen - please file a bug with the "
306 . "distribution containing $pkg."
315 defined ( my $eumm_ver = eval { MM->parse_version( $abs_unix_path ) } )
318 # can only run the check reliably if v.pm is there
324 $eumm_ver ne $mod_ver
327 ( eval { version->parse( do { (my $v = $eumm_ver) =~ s/_//g; $v } ) } || 0 )
329 ( eval { version->parse( do { (my $v = $mod_ver) =~ s/_//g; $v } ) } || 0 )
333 "Mismatch of versions '$mod_ver' and '$eumm_ver', obtained respectively "
334 . "via `$pkg->VERSION` and parsing the version out of @{[ shorten_fn( $abs_unix_path ) ]} "
335 . "with ExtUtils::MakeMaker\@@{[ ExtUtils::MakeMaker->VERSION ]}. "
336 . "This should never happen - please check whether this is still present "
337 . "in the latest version, and then file a bug with the distribution "
342 $interesting_modules->{$pkg}{version} = $eumm_ver;
344 elsif( defined $mod_ver ) {
346 $interesting_modules->{$pkg}{version} = $mod_ver;
349 elsif ( $known_failed_loads->{$pkg} ) {
350 $abs_unix_path = $known_failed_loads->{$pkg};
351 $interesting_modules->{$pkg}{version} = '!! LOAD FAIL !!';
354 if ($abs_unix_path) {
355 my ($marker, $initial_inc_idx);
357 my $current_inc_idx = module_found_at_inc_index($pkg, \@INC);
358 my $p = subpath_of_known_path( $abs_unix_path );
361 defined $current_inc_idx
365 abs_unix_path($INC[$current_inc_idx]) eq $p->{abs_unix_path}
367 $marker = $p->{marker};
369 elsif (defined ( $initial_inc_idx = module_found_at_inc_index($pkg, \@initial_INC) ) ) {
370 $marker = "\$INC[$initial_inc_idx]";
373 # we are only interested if there was a declared version already above
374 # OR if the module came from somewhere other than skip_unversioned_modules
379 $interesting_modules->{$pkg}
381 !$p->{skip_unversioned_modules}
384 $interesting_modules->{$pkg}{source_marker} = $marker;
385 $seen_markers->{$marker} = 1;
388 # at this point only fill in the path (md5 calc) IFF it is interesting
390 $interesting_modules->{$pkg}{abs_unix_path} = $abs_unix_path
391 if $interesting_modules->{$pkg};
397 # compress identical versions sourced from ./blib, ./lib, ./t and ./xt
398 # as close to the root of a namespace as we can
399 purge_identically_versioned_submodules_with_markers([ map {
400 ( $_->{skip_unversioned_modules} && $_->{marker} ) || ()
401 } values %$known_paths ]);
403 ok 1, (scalar keys %$interesting_modules) . " distinctly versioned modules found";
405 # do not announce anything under ci - we are watching for STDERR silence
406 exit 0 if DBICTest::RunMode->is_ci;
409 # diag the result out
410 my $max_ver_len = max map
412 ( 'xxx.yyyzzz_bbb', map { $_->{version} || '' } values %$interesting_modules )
414 my $max_marker_len = max map { length $_ } ( '$INC[999]', keys %$seen_markers );
418 List of loadable modules within both the core and *OPTIONAL* dependency chains
419 present on this system (modules sourced from ./blib, ./lib, ./t, and ./xt
420 with versions identical to their parent namespace were omitted for brevity)
422 *** Note that *MANY* of these modules will *NEVER* be loaded ***
423 *** during normal operation of DBIx::Class ***
426 # pre-assemble everything and print it in one shot
427 # makes it less likely for parallel test execution to insert bogus lines
428 my $final_out = "\n$discl\n";
430 $final_out .= "\@INC at startup (does not reflect manipulation at runtime):\n";
433 for (0.. $#initial_INC) {
435 my $shortname = shorten_fn( $initial_INC[$_] );
437 # when *to* print a line of INC
439 ! $ENV{AUTOMATED_TESTING}
443 $seen_markers->{"\$INC[$_]"}
447 ! File::Spec->file_name_is_absolute($shortname)
450 $final_out .= sprintf ( "% 3s: %s\n",
455 elsif(! $in_inc_skip++) {
456 $final_out .= " ...\n";
462 if (my @seen_known_paths = grep { $known_paths->{$_} } keys %$seen_markers) {
464 $final_out .= join "\n", 'Sourcing markers:', (map
467 $max_marker_len => $_->{marker},
468 ($_->{config_key} ? "\$Config{$_->{config_key}}" : "$_->{rel_path}/" )
472 !!$b->{config_key} cmp !!$a->{config_key}
474 ( $a->{marker}||'') cmp ($b->{marker}||'')
476 @{$known_paths}{@seen_known_paths}
481 $final_out .= "=============================\n";
483 $final_out .= join "\n", (map
486 $max_marker_len => $interesting_modules->{$_}{source_marker} || '',
487 $max_ver_len => ( defined $interesting_modules->{$_}{version}
488 ? $interesting_modules->{$_}{version}
492 ($interesting_modules->{$_}{abs_unix_path}
493 ? " [ MD5: @{[ get_md5( $interesting_modules->{$_}{abs_unix_path} ) ]} ]"
494 : "! -f \$INC{'@{[ module_notional_filename($_) ]}'}"
497 sort { lc($a) cmp lc($b) } keys %$interesting_modules
500 $final_out .= "=============================\n$discl\n\n";
508 sub say_err { print STDERR "\n", @_, "\n\n" };
510 # do !!!NOT!!! use Module::Runtime's require_module - it breaks CORE::require
511 sub try_module_require {
512 # trap deprecation warnings and whatnot
513 local $SIG{__WARN__} = sub {};
515 eval "require $_[0]";
522 ( -e $_[0] or File::Spec->file_name_is_absolute($_[0]) )
525 # File::Spec's rel2abs does not resolve symlinks
526 # we *need* to look at the filesystem to be sure
527 my $abs_fn = abs_path($_[0]);
529 if ( $^O eq 'MSWin32' and $abs_fn ) {
531 # sometimes we can get a short/longname mix, normalize everything to longnames
532 $abs_fn = Win32::GetLongPathName($abs_fn);
534 # Fixup (native) slashes in Config not matching (unixy) slashes in INC
544 my $abs_fn = abs_unix_path($fn);
546 if (my $p = subpath_of_known_path( $fn ) ) {
547 $abs_fn =~ s| (?<! / ) $|/|x
550 if ($p->{rel_path}) {
551 $abs_fn =~ s!\Q$p->{abs_unix_path}!$p->{rel_path}!
554 elsif ($p->{config_key}) {
555 $abs_fn =~ s!\Q$p->{abs_unix_path}!<<$p->{marker}>>!
557 $seen_markers->{$p->{marker}} = 1
563 # we got so far - not a known path
564 # return the unixified version it if was absolute, leave as-is otherwise
565 my $rv = ( $abs_fn and File::Spec->file_name_is_absolute( $fn ) )
570 $rv = "( ! -e ) $rv" unless -e $rv;
575 sub subpath_of_known_path {
576 my $abs_fn = abs_unix_path( $_[0] )
581 length( $b->{abs_unix_path} ) <=> length( $a->{abs_unix_path} )
583 ( $a->{match_order} || 0 ) <=> ( $b->{match_order} || 0 )
587 # run through the matcher twice - first always append a /
589 # important to avoid false positives
590 for my $suff ( '/', '' ) {
591 return { %$p } if 0 == index( $abs_fn, "$p->{abs_unix_path}$suff" );
596 sub module_found_at_inc_index {
597 my ($mod, $inc_dirs) = @_;
599 return undef unless @$inc_dirs;
601 my $fn = module_notional_filename($mod);
603 # trust INC if it specifies an existing path
604 if( -f ( my $existing_path = abs_unix_path( $INC{$fn} ) ) ) {
605 for my $i ( 0 .. $#$inc_dirs ) {
607 # searching from here on out won't mean anything
608 # FIXME - there is actually a way to interrogate this safely, but
609 # that's a fight for another day
610 return undef if length ref $inc_dirs->[$i];
613 if 0 == index( $existing_path, abs_unix_path( $inc_dirs->[$i] ) . '/' );
617 for my $i ( 0 .. $#$inc_dirs ) {
622 -f "$inc_dirs->[$i]/$fn"
624 -r "$inc_dirs->[$i]/$fn"
633 sub purge_identically_versioned_submodules_with_markers {
636 return unless @$markers;
638 for my $mod ( sort { length($b) <=> length($a) } keys %$interesting_modules ) {
640 next unless defined $interesting_modules->{$mod}{version};
642 my $marker = $interesting_modules->{$mod}{source_marker}
645 next unless grep { $marker eq $_ } @$markers;
649 while ( $parent =~ s/ :: (?: . (?! :: ) )+ $ //x ) {
650 $interesting_modules->{$parent}
652 ($interesting_modules->{$parent}{version}||'') eq $interesting_modules->{$mod}{version}
654 ($interesting_modules->{$parent}{source_marker}||'') eq $interesting_modules->{$mod}{source_marker}
656 delete $interesting_modules->{$mod}
663 sub module_notional_filename {
664 (my $fn = $_[0] . '.pm') =~ s|::|/|g;
669 # we already checked for -r/-f, just bail if can't open
670 open my $fh, '<:raw', $_[0] or return '';
671 Digest::MD5->new->addfile($fh)->hexdigest;