(travis) One more addition to the smoke-devrel family: 5.8.8 with long-doubles
[dbsrgits/DBIx-Class.git] / t / 00describe_environment.t
CommitLineData
cebc0cc8 1###
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
4###
5
3cd1b172 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
9my @initial_INC;
cebc0cc8 10BEGIN {
3cd1b172 11 @initial_INC = @INC;
12}
13
14BEGIN {
15 unshift @INC, 't/lib';
16
cebc0cc8 17 if ($] < 5.010) {
18
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
23 #
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.
30 #
31 # we want to do this here, in the very beginning, before even
32 # warnings/strict are loaded
33
cebc0cc8 34 require DBICTest::Util::OverrideRequire;
35
36 DBICTest::Util::OverrideRequire::override_global_require( sub {
37 my $res = eval { $_[0]->() };
38 if ($@ ne '') {
39 delete $INC{$_[1]};
40 die $@;
41 }
42 return $res;
43 } );
44 }
45}
46
cebc0cc8 47use strict;
48use warnings;
49
50use Test::More 'no_plan';
51use Config;
52use File::Find 'find';
3cd1b172 53use Digest::MD5 ();
54use Cwd 'abs_path';
c6b73be9 55use File::Spec;
843b427a 56use List::Util 'max';
cebc0cc8 57use ExtUtils::MakeMaker;
cebc0cc8 58
c6b73be9 59use DBICTest::RunMode;
60use DBICTest::Util 'visit_namespaces';
61use DBIx::Class::Optional::Dependencies;
cebc0cc8 62
c6b73be9 63my $known_paths = {
55c6fb91 64 SA => {
65 config_key => 'sitearch',
66 },
67 SL => {
68 config_key => 'sitelib',
69 },
c6b73be9 70 SS => {
71 config_key => 'sitelib_stem',
72 match_order => 1,
73 },
74 SP => {
75 config_key => 'siteprefix',
76 match_order => 2,
77 },
55c6fb91 78 VA => {
79 config_key => 'vendorarch',
80 },
81 VL => {
82 config_key => 'vendorlib',
83 },
c6b73be9 84 VS => {
85 config_key => 'vendorlib_stem',
86 match_order => 3,
87 },
88 VP => {
89 config_key => 'vendorprefix',
90 match_order => 4,
91 },
55c6fb91 92 PA => {
93 config_key => 'archlib',
94 },
95 PL => {
96 config_key => 'privlib',
97 },
c6b73be9 98 PP => {
99 config_key => 'prefix',
100 match_order => 5,
101 },
102 BLA => {
103 rel_path => './blib/arch',
104 skip_unversioned_modules => 1,
105 },
106 BLL => {
107 rel_path => './blib/lib',
108 skip_unversioned_modules => 1,
109 },
55c6fb91 110 INC => {
c6b73be9 111 rel_path => './inc',
55c6fb91 112 },
113 LIB => {
c6b73be9 114 rel_path => './lib',
115 skip_unversioned_modules => 1,
55c6fb91 116 },
3cd1b172 117 T => {
c6b73be9 118 rel_path => './t',
119 skip_unversioned_modules => 1,
120 },
121 CWD => {
122 rel_path => '.',
3cd1b172 123 },
55c6fb91 124 HOME => {
c6b73be9 125 rel_path => '~',
126 abs_unix_path => abs_unix_path (
55c6fb91 127 eval { require File::HomeDir and File::HomeDir->my_home }
128 ||
c6b73be9 129 $ENV{USERPROFILE}
130 ||
55c6fb91 131 $ENV{HOME}
132 ||
133 glob('~')
134 ),
135 },
cebc0cc8 136};
137
c6b73be9 138for my $k (keys %$known_paths) {
139 my $v = $known_paths->{$k};
4bfba4ee 140
55c6fb91 141 # never use home as a found-in-dir marker - it is too broad
142 # HOME is only used by the shortener
3cd1b172 143 $v->{marker} = $k unless $k eq 'HOME';
4bfba4ee 144
c6b73be9 145 unless ( $v->{abs_unix_path} ) {
146 if ( $v->{rel_path} ) {
147 $v->{abs_unix_path} = abs_unix_path( $v->{rel_path} );
55c6fb91 148 }
149 elsif ( $Config{ $v->{config_key} || '' } ) {
c6b73be9 150 $v->{abs_unix_path} = abs_unix_path (
55c6fb91 151 $Config{"$v->{config_key}exp"} || $Config{$v->{config_key}}
152 );
153 }
154 }
cebc0cc8 155
c6b73be9 156 delete $known_paths->{$k} unless $v->{abs_unix_path} and -d $v->{abs_unix_path};
cebc0cc8 157}
c6b73be9 158my $seen_markers = {};
cebc0cc8 159
55c6fb91 160# first run through lib/ and *try* to load anything we can find
cebc0cc8 161# within our own project
162find({
163 wanted => sub {
164 -f $_ or return;
165
166 # can't just `require $fn`, as we need %INC to be
167 # populated properly
168 my ($mod) = $_ =~ /^ lib [\/\\] (.+) \.pm $/x
169 or return;
170
55c6fb91 171 try_module_require(join ('::', File::Spec->splitdir($mod)) )
cebc0cc8 172 },
173 no_chdir => 1,
174}, 'lib' );
175
55c6fb91 176
177
cebc0cc8 178# now run through OptDeps and attempt loading everything else
179#
180# some things needs to be sorted before other things
181# positive - load first
182# negative - load last
183my $load_weights = {
184 # Make sure oracle is tried last - some clients (e.g. 10.2) have symbol
185 # clashes with libssl, and will segfault everything coming after them
186 "DBD::Oracle" => -999,
187};
3cd1b172 188
189my @known_modules = sort
cebc0cc8 190 { ($load_weights->{$b}||0) <=> ($load_weights->{$a}||0) }
191 keys %{
192 DBIx::Class::Optional::Dependencies->req_list_for([
250d9e55 193 grep
194 # some DBDs are notoriously problematic to load
195 # hence only show stuff based on test_rdbms which will
196 # take into account necessary ENVs
55c6fb91 197 { $_ !~ /^ (?: rdbms | dist )_ /x }
250d9e55 198 keys %{DBIx::Class::Optional::Dependencies->req_group_list}
cebc0cc8 199 ])
200 }
201;
202
3cd1b172 203try_module_require($_) for @known_modules;
cebc0cc8 204
55c6fb91 205my $has_versionpm = eval { require version };
3cd1b172 206
207
208# At this point we've loaded everything we ever could, but some modules
209# (understandably) crapped out. For an even more thorough report, note
210# everthing present in @INC we excplicitly know about (via OptDeps)
211# *even though* it didn't load
212my $known_failed_loads;
213
214for my $mod (@known_modules) {
215 my $inc_key = module_notional_filename($mod);
216 next if defined $INC{$inc_key};
217
218 if (defined( my $idx = module_found_at_inc_index( $mod, \@INC ) ) ) {
c6b73be9 219 $known_failed_loads->{$mod} = abs_unix_path( "$INC[$idx]/$inc_key" );
3cd1b172 220 }
221
222}
223
224my $perl = 'perl';
225
226# This is a cool idea, but the line is too long even with shortening :(
227#
228#for my $i ( 1 .. $Config{config_argc} ) {
229# my $conf_arg = $Config{"config_arg$i"};
230# $conf_arg =~ s!
231# \= (.+)
232# !
c6b73be9 233# '=' . shorten_fn($1)
3cd1b172 234# !ex;
235#
236# $perl .= " $conf_arg";
237#}
238
239my $interesting_modules = {
240 # pseudo module
241 $perl => {
242 version => $],
c6b73be9 243 abs_unix_path => $^X,
3cd1b172 244 }
55c6fb91 245};
3cd1b172 246
247
248# drill through the *ENTIRE* symtable and build a map of intereseting modules
cebc0cc8 249visit_namespaces( action => sub {
4bfba4ee 250 no strict 'refs';
cebc0cc8 251 my $pkg = shift;
252
253 # keep going, but nothing to see here
254 return 1 if $pkg eq 'main';
255
4bfba4ee 256 # private - not interested, including no further descent
cebc0cc8 257 return 0 if $pkg =~ / (?: ^ | :: ) _ /x;
258
3cd1b172 259 my $inc_key = module_notional_filename($pkg);
cebc0cc8 260
c6b73be9 261 my $abs_unix_path = (
3cd1b172 262 $INC{$inc_key}
4bfba4ee 263 and
3cd1b172 264 -f $INC{$inc_key}
4bfba4ee 265 and
3cd1b172 266 -r $INC{$inc_key}
55c6fb91 267 and
c6b73be9 268 abs_unix_path($INC{$inc_key})
3cd1b172 269 );
cebc0cc8 270
3cd1b172 271 # handle versions first (not interested in synthetic classes)
cebc0cc8 272 if (
3cd1b172 273 defined ${"${pkg}::VERSION"}
cebc0cc8 274 and
3cd1b172 275 ${"${pkg}::VERSION"} !~ /\Qset by base.pm/
cebc0cc8 276 ) {
cebc0cc8 277
3cd1b172 278 # make sure a version can be extracted, be noisy when it doesn't work
279 # do this even if we are throwing away the result below in lieu of EUMM
280 my $mod_ver = eval { $pkg->VERSION };
281
282 if (my $err = $@) {
283 $err =~ s/^/ /mg;
284 say_err (
285 "Calling `$pkg->VERSION` resulted in an exception, which should never "
286 . "happen - please file a bug with the distribution containing $pkg. "
287 . "Complete exception text below:\n\n$err"
288 );
289 }
290 elsif( ! defined $mod_ver or ! length $mod_ver ) {
291 my $ret = defined $mod_ver
292 ? "the empty string ''"
293 : "'undef'"
294 ;
295
296 say_err (
297 "Calling `$pkg->VERSION` returned $ret, even though \$${pkg}::VERSION "
298 . "is defined, which should never happen - please file a bug with the "
299 . "distribution containing $pkg."
300 );
301
302 undef $mod_ver;
303 }
304
305 if (
c6b73be9 306 $abs_unix_path
3cd1b172 307 and
c6b73be9 308 defined ( my $eumm_ver = eval { MM->parse_version( $abs_unix_path ) } )
3cd1b172 309 ) {
310
311 # can only run the check reliably if v.pm is there
312 if (
313 $has_versionpm
314 and
315 defined $mod_ver
316 and
317 $eumm_ver ne $mod_ver
318 and
319 (
320 ( eval { version->parse( do { (my $v = $eumm_ver) =~ s/_//g; $v } ) } || 0 )
321 !=
322 ( eval { version->parse( do { (my $v = $mod_ver) =~ s/_//g; $v } ) } || 0 )
323 )
324 ) {
325 say_err (
326 "Mismatch of versions '$mod_ver' and '$eumm_ver', obtained respectively "
c6b73be9 327 . "via `$pkg->VERSION` and parsing the version out of @{[ shorten_fn( $abs_unix_path ) ]} "
3cd1b172 328 . "with ExtUtils::MakeMaker\@@{[ ExtUtils::MakeMaker->VERSION ]}. "
329 . "This should never happen - please check whether this is still present "
330 . "in the latest version, and then file a bug with the distribution "
331 . "containing $pkg."
332 );
333 }
334
335 $interesting_modules->{$pkg}{version} = $eumm_ver;
336 }
337 elsif( defined $mod_ver ) {
338
339 $interesting_modules->{$pkg}{version} = $mod_ver;
340 }
cebc0cc8 341 }
c6b73be9 342 elsif ( $abs_unix_path = $known_failed_loads->{$pkg} ) {
343 $interesting_modules->{$pkg}{version} = '!! LOAD FAIL !!';
55c6fb91 344 }
345
c6b73be9 346 if ($abs_unix_path) {
347 my ($marker, $initial_inc_idx);
348
349 my $current_inc_idx = module_found_at_inc_index($pkg, \@INC);
350 my $p = subpath_of_known_path( $abs_unix_path );
351
352 if (
353 defined $current_inc_idx
354 and
355 $p->{marker}
356 and
357 abs_unix_path($INC[$current_inc_idx]) eq $p->{abs_unix_path}
358 ) {
359 $marker = $p->{marker};
3cd1b172 360 }
c6b73be9 361 elsif (defined ( $initial_inc_idx = module_found_at_inc_index($pkg, \@initial_INC) ) ) {
362 $marker = "\$INC[$initial_inc_idx]";
3cd1b172 363 }
55c6fb91 364
3cd1b172 365 # we are only interested if there was a declared version already above
c6b73be9 366 # OR if the module came from somewhere other than skip_unversioned_modules
3cd1b172 367 if (
368 $marker
369 and
370 (
371 $interesting_modules->{$pkg}
372 or
c6b73be9 373 !$p->{skip_unversioned_modules}
3cd1b172 374 )
375 ) {
376 $interesting_modules->{$pkg}{source_marker} = $marker;
c6b73be9 377 $seen_markers->{$marker} = 1;
55c6fb91 378 }
3cd1b172 379
380 # at this point only fill in the path (md5 calc) IFF it is interesting
381 # in any respect
c6b73be9 382 $interesting_modules->{$pkg}{abs_unix_path} = $abs_unix_path
3cd1b172 383 if $interesting_modules->{$pkg};
cebc0cc8 384 }
385
386 1;
387});
388
c6b73be9 389# compress identical versions sourced from ./blib, ./lib and ./t as close to the root
3cd1b172 390# of a namespace as we can
c6b73be9 391purge_identically_versioned_submodules_with_markers([ map {
392 ( $_->{skip_unversioned_modules} && $_->{marker} ) || ()
393} values %$known_paths ]);
cebc0cc8 394
3cd1b172 395ok 1, (scalar keys %$interesting_modules) . " distinctly versioned modules found";
cebc0cc8 396
397# do not announce anything under ci - we are watching for STDERR silence
3cd1b172 398exit 0 if DBICTest::RunMode->is_ci;
cebc0cc8 399
cebc0cc8 400
55c6fb91 401# diag the result out
402my $max_ver_len = max map
403 { length "$_" }
3cd1b172 404 ( 'xxx.yyyzzz_bbb', map { $_->{version} || '' } values %$interesting_modules )
55c6fb91 405;
c6b73be9 406my $max_marker_len = max map { length $_ } ( '$INC[999]', keys %$seen_markers );
55c6fb91 407
408my $discl = <<'EOD';
409
c6b73be9 410List of loadable modules within both the core and *OPTIONAL* dependency
411chains present on this system (modules sourced from ./blib, ./lib and ./t
412with versions identical to their parent namespace were omitted for brevity)
413
414 *** Note that *MANY* of these modules will *NEVER* be loaded ***
415 *** during normal operation of DBIx::Class ***
55c6fb91 416EOD
cebc0cc8 417
3cd1b172 418# pre-assemble everything and print it in one shot
419# makes it less likely for parallel test execution to insert bogus lines
420my $final_out = "\n$discl\n";
cebc0cc8 421
c6b73be9 422$final_out .= "\@INC at startup (does not reflect manipulation at runtime):\n";
cebc0cc8 423
c6b73be9 424my $in_inc_skip;
425for (0.. $#initial_INC) {
426
427 my $path = shorten_fn( $initial_INC[$_] );
428
429 # when *to* print
430 if (
431 ! $ENV{AUTOMATED_TESTING}
432 or
433 @initial_INC < 11
434 or
435 $seen_markers->{"\$INC[$_]"}
436 or
437 ! -e $path
438 or
439 ! File::Spec->file_name_is_absolute($path)
440 ) {
441 $in_inc_skip = 0;
442 $final_out .= sprintf ( "% 3s: %s\n",
443 $_,
444 $path
445 );
446 }
447 elsif(! $in_inc_skip++) {
448 $final_out .= " ...\n";
449 }
450}
451
452$final_out .= "\n";
453
454if (my @seen_known_paths = grep { $known_paths->{$_} } keys %$seen_markers) {
3cd1b172 455
456 $final_out .= join "\n", 'Sourcing markers:', (map
457 {
458 sprintf "%*s: %s",
459 $max_marker_len => $_->{marker},
c6b73be9 460 ($_->{config_key} ? "\$Config{$_->{config_key}}" : "$_->{rel_path}/" )
3cd1b172 461 }
462 sort
55c6fb91 463 {
3cd1b172 464 !!$b->{config_key} cmp !!$a->{config_key}
465 or
466 ( $a->{marker}||'') cmp ($b->{marker}||'')
cebc0cc8 467 }
c6b73be9 468 @{$known_paths}{@seen_known_paths}
3cd1b172 469 ), '', '';
cebc0cc8 470
cebc0cc8 471}
472
3cd1b172 473$final_out .= "=============================\n";
cebc0cc8 474
3cd1b172 475$final_out .= join "\n", (map
476 { sprintf (
843b427a 477 "%*s %*s %*s%s",
3cd1b172 478 $max_marker_len => $interesting_modules->{$_}{source_marker} || '',
479 $max_ver_len => ( defined $interesting_modules->{$_}{version}
480 ? $interesting_modules->{$_}{version}
481 : ''
482 ),
c6b73be9 483 -78 => $_,
484 ($interesting_modules->{$_}{abs_unix_path}
485 ? " [ MD5: @{[ get_md5( $interesting_modules->{$_}{abs_unix_path} ) ]} ]"
486 : "! -f \$INC{'@{[ module_notional_filename($_) ]}'}"
3cd1b172 487 ),
488 ) }
489 sort { lc($a) cmp lc($b) } keys %$interesting_modules
490), '';
491
492$final_out .= "=============================\n$discl\n\n";
4bfba4ee 493
3cd1b172 494diag $final_out;
4bfba4ee 495
55c6fb91 496exit 0;
4bfba4ee 497
4bfba4ee 498
4bfba4ee 499
c6b73be9 500sub say_err { print STDERR "\n", @_, "\n\n" };
cebc0cc8 501
55c6fb91 502# do !!!NOT!!! use Module::Runtime's require_module - it breaks CORE::require
503sub try_module_require {
504 # trap deprecation warnings and whatnot
505 local $SIG{__WARN__} = sub {};
506 local $@;
507 eval "require $_[0]";
508}
cebc0cc8 509
c6b73be9 510sub abs_unix_path {
511 return '' unless (
512 defined $_[0]
513 and
514 ( -e $_[0] or File::Spec->file_name_is_absolute($_[0]) )
515 );
55c6fb91 516
3cd1b172 517 # File::Spec's rel2abs does not resolve symlinks
518 # we *need* to look at the filesystem to be sure
c6b73be9 519 my $abs_fn = abs_path($_[0]);
55c6fb91 520
c6b73be9 521 if ( $^O eq 'MSWin32' and $abs_fn ) {
55c6fb91 522
523 # sometimes we can get a short/longname mix, normalize everything to longnames
c6b73be9 524 $abs_fn = Win32::GetLongPathName($abs_fn);
55c6fb91 525
526 # Fixup (native) slashes in Config not matching (unixy) slashes in INC
c6b73be9 527 $abs_fn =~ s|\\|/|g;
55c6fb91 528 }
cebc0cc8 529
c6b73be9 530 $abs_fn;
cebc0cc8 531}
532
55c6fb91 533sub shorten_fn {
534 my $fn = shift;
535
c6b73be9 536 my $abs_fn = abs_unix_path($fn);
55c6fb91 537
c6b73be9 538 if (my $p = subpath_of_known_path( $fn ) ) {
539 $abs_fn =~ s| (?<! / ) $|/|x
540 if -d $abs_fn;
541
542 if ($p->{rel_path}) {
543 $abs_fn =~ s!\Q$p->{abs_unix_path}!$p->{rel_path}!
544 and return $abs_fn;
545 }
546 elsif ($p->{config_key}) {
547 $abs_fn =~ s!\Q$p->{abs_unix_path}!<<$p->{marker}>>!
548 and
549 $seen_markers->{$p->{marker}} = 1
550 and
551 return $abs_fn;
552 }
55c6fb91 553 }
554
c6b73be9 555 # we got so far - not a known path
556 # return the unixified version it if was absolute, leave as-is otherwise
557 return ( $abs_fn and File::Spec->file_name_is_absolute( $fn ) )
558 ? $abs_fn
559 : $fn
560 ;
55c6fb91 561}
562
c6b73be9 563sub subpath_of_known_path {
564 my $abs_fn = abs_unix_path( $_[0] )
55c6fb91 565 or return '';
566
c6b73be9 567 for my $p (
568 sort {
569 length( $b->{abs_unix_path} ) <=> length( $a->{abs_unix_path} )
570 or
571 ( $a->{match_order} || 0 ) <=> ( $b->{match_order} || 0 )
572 }
573 values %$known_paths
55c6fb91 574 ) {
3cd1b172 575 # run through the matcher twice - first always append a /
576 # then try without
577 # important to avoid false positives
578 for my $suff ( '/', '' ) {
c6b73be9 579 return { %$p } if 0 == index( $abs_fn, "$p->{abs_unix_path}$suff" );
3cd1b172 580 }
55c6fb91 581 }
582}
583
3cd1b172 584sub module_found_at_inc_index {
c6b73be9 585 my ($mod, $inc_dirs) = @_;
586
587 return undef unless @$inc_dirs;
3cd1b172 588
589 my $fn = module_notional_filename($mod);
590
c6b73be9 591 for my $i ( 0 .. $#$inc_dirs ) {
592
3cd1b172 593 # searching from here on out won't mean anything
c6b73be9 594 # FIXME - there is actually a way to interrogate this safely, but
595 # that's a fight for another day
596 return undef if length ref $inc_dirs->[$i];
3cd1b172 597
598 if (
c6b73be9 599 -d $inc_dirs->[$i]
3cd1b172 600 and
c6b73be9 601 -f "$inc_dirs->[$i]/$fn"
3cd1b172 602 and
c6b73be9 603 -r "$inc_dirs->[$i]/$fn"
3cd1b172 604 ) {
605 return $i;
606 }
607 }
608
609 return undef;
610}
611
612sub purge_identically_versioned_submodules_with_markers {
613 my $markers = shift;
614
615 return unless @$markers;
616
617 for my $mod ( sort { length($b) <=> length($a) } keys %$interesting_modules ) {
618
619 next unless defined $interesting_modules->{$mod}{version};
620
621 my $marker = $interesting_modules->{$mod}{source_marker}
622 or next;
623
624 next unless grep { $marker eq $_ } @$markers;
625
626 my $parent = $mod;
627
628 while ( $parent =~ s/ :: (?: . (?! :: ) )+ $ //x ) {
629 $interesting_modules->{$parent}
630 and
631 ($interesting_modules->{$parent}{version}||'') eq $interesting_modules->{$mod}{version}
632 and
633 ($interesting_modules->{$parent}{source_marker}||'') eq $interesting_modules->{$mod}{source_marker}
634 and
635 delete $interesting_modules->{$mod}
636 and
637 last
638 }
639 }
640}
641
642sub module_notional_filename {
643 (my $fn = $_[0] . '.pm') =~ s|::|/|g;
644 $fn;
645}
646
55c6fb91 647sub get_md5 {
648 # we already checked for -r/-f, just bail if can't open
649 open my $fh, '<:raw', $_[0] or return '';
55c6fb91 650 Digest::MD5->new->addfile($fh)->hexdigest;
651}