#!/usr/bin/perl # Unit testing for Class::Inspector # Do all the tests on ourself, where possible, as we know we will be loaded. use strict; BEGIN { $| = 1; $^W = 1; # $DB::single = 1; } use Test::More tests => 54; use Class::Inspector (); # To make maintaining this a little faster, # CI is defined as Class::Inspector, and # BAD for a class we know doesn't exist. use constant CI => 'Class::Inspector'; use constant BAD => 'Class::Inspector::Nonexistant'; # How many functions and public methods are there in Class::Inspector my $base_functions = 17; my $base_public = 12; my $base_private = $base_functions - $base_public; ##################################################################### # Begin Tests # Check the good/bad class code ok( CI->_class( CI ), 'Class validator works for known valid' ); ok( CI->_class( BAD ), 'Class validator works for correctly formatted, but not installed' ); ok( CI->_class( 'A::B::C::D::E' ), 'Class validator works for long classes' ); ok( CI->_class( '::' ), 'Class validator allows main' ); ok( CI->_class( '::Blah' ), 'Class validator works for main aliased' ); ok( ! CI->_class(), 'Class validator failed for missing class' ); ok( ! CI->_class( '4teen' ), 'Class validator fails for number starting class' ); ok( ! CI->_class( 'Blah::%f' ), 'Class validator catches bad characters' ); # Check the loaded method ok( CI->loaded( CI ), "->loaded detects loaded" ); ok( ! CI->loaded( BAD ), "->loaded detects not loaded" ); # Check the file name methods my $filename = CI->filename( CI ); ok( $filename eq File::Spec->catfile( "Class", "Inspector.pm" ), "->filename works correctly" ); my $inc_filename = CI->_inc_filename( CI ); ok( $inc_filename eq "Class/Inspector.pm", "->_inc_filename works correctly" ); ok( index( CI->loaded_filename(CI), $filename ) >= 0, "->loaded_filename works" ); ok( ($filename eq $inc_filename or index( CI->loaded_filename(CI), $inc_filename ) == -1), "->loaded_filename works" ); ok( index( CI->resolved_filename(CI), $filename ) >= 0, "->resolved_filename works" ); ok( ($filename eq $inc_filename or index( CI->resolved_filename(CI), $inc_filename ) == -1), "->resolved_filename works" ); # Check the installed stuff ok( CI->installed( CI ), "->installed detects installed" ); ok( ! CI->installed( BAD ), "->installed detects not installed" ); # Check the functions my $functions = CI->functions( CI ); ok( (ref($functions) eq 'ARRAY' and $functions->[0] eq '_class' and scalar @$functions == $base_functions), "->functions works correctly" ); ok( ! CI->functions( BAD ), "->functions fails correctly" ); # Check function refs $functions = CI->function_refs( CI ); ok( (ref($functions) eq 'ARRAY' and ref $functions->[0] and ref($functions->[0]) eq 'CODE' and scalar @$functions == $base_functions), "->function_refs works correctly" ); ok( ! CI->functions( BAD ), "->function_refs fails correctly" ); # Check function_exists ok( CI->function_exists( CI, 'installed' ), "->function_exists detects function that exists" ); ok( ! CI->function_exists( CI, 'nsfladf' ), "->function_exists fails for bad function" ); ok( ! CI->function_exists( CI ), "->function_exists fails for missing function" ); ok( ! CI->function_exists( BAD, 'function' ), "->function_exists fails for bad class" ); # Check the methods method. # First, defined a new subclass of Class::Inspector with some additional methods CLASS: { package Class::Inspector::Dummy; use strict; BEGIN { require Class::Inspector; @Class::Inspector::Dummy::ISA = 'Class::Inspector'; } sub _a_first { 1; } sub adummy1 { 1; } sub _dummy2 { 1; } sub dummy3 { 1; } sub installed { 1; } } package main; my $methods = CI->methods( CI ); ok( ( ref($methods) eq 'ARRAY' and $methods->[0] eq '_class' and scalar @$methods == $base_functions), "->methods works for non-inheriting class" ); $methods = CI->methods( 'Class::Inspector::Dummy' ); ok( (ref($methods) eq 'ARRAY' and $methods->[0] eq '_a_first' and scalar @$methods == ($base_functions + 4) and scalar( grep { /dummy/ } @$methods ) == 3), "->methods works for inheriting class" ); ok( ! CI->methods( BAD ), "->methods fails correctly" ); # Check the variety of different possible ->methods options # Public option $methods = CI->methods( CI, 'public' ); ok( (ref($methods) eq 'ARRAY' and $methods->[0] eq 'children' and scalar @$methods == $base_public), "Public ->methods works for non-inheriting class" ); $methods = CI->methods( 'Class::Inspector::Dummy', 'public' ); ok( (ref($methods) eq 'ARRAY' and $methods->[0] eq 'adummy1' and scalar @$methods == ($base_public + 2) and scalar( grep { /dummy/ } @$methods ) == 2), "Public ->methods works for inheriting class" ); ok( ! CI->methods( BAD ), "Public ->methods fails correctly" ); # Private option $methods = CI->methods( CI, 'private' ); ok( (ref($methods) eq 'ARRAY' and $methods->[0] eq '_class' and scalar @$methods == $base_private), "Private ->methods works for non-inheriting class" ); $methods = CI->methods( 'Class::Inspector::Dummy', 'private' ); ok( (ref($methods) eq 'ARRAY' and $methods->[0] eq '_a_first' and scalar @$methods == ($base_private + 2) and scalar( grep { /dummy/ } @$methods ) == 1), "Private ->methods works for inheriting class" ); ok( ! CI->methods( BAD ), "Private ->methods fails correctly" ); # Full option $methods = CI->methods( CI, 'full' ); ok( (ref($methods) eq 'ARRAY' and $methods->[0] eq 'Class::Inspector::_class' and scalar @$methods == $base_functions), "Full ->methods works for non-inheriting class" ); $methods = CI->methods( 'Class::Inspector::Dummy', 'full' ); ok( (ref($methods) eq 'ARRAY' and $methods->[0] eq 'Class::Inspector::Dummy::_a_first' and scalar @$methods == ($base_functions + 4) and scalar( grep { /dummy/ } @$methods ) == 3), "Full ->methods works for inheriting class" ); ok( ! CI->methods( BAD ), "Full ->methods fails correctly" ); # Expanded option $methods = CI->methods( CI, 'expanded' ); ok( (ref($methods) eq 'ARRAY' and ref($methods->[0]) eq 'ARRAY' and $methods->[0]->[0] eq 'Class::Inspector::_class' and $methods->[0]->[1] eq 'Class::Inspector' and $methods->[0]->[2] eq '_class' and ref($methods->[0]->[3]) eq 'CODE' and scalar @$methods == $base_functions), "Expanded ->methods works for non-inheriting class" ); $methods = CI->methods( 'Class::Inspector::Dummy', 'expanded' ); ok( (ref($methods) eq 'ARRAY' and ref($methods->[0]) eq 'ARRAY' and $methods->[0]->[0] eq 'Class::Inspector::Dummy::_a_first' and $methods->[0]->[1] eq 'Class::Inspector::Dummy' and $methods->[0]->[2] eq '_a_first' and ref($methods->[0]->[3]) eq 'CODE' and scalar @$methods == ($base_functions + 4) and scalar( grep { /dummy/ } map { $_->[2] } @$methods ) == 3), "Expanded ->methods works for inheriting class" ); ok( ! CI->methods( BAD ), "Expanded ->methods fails correctly" ); # Check clashing between options ok( ! CI->methods( CI, 'public', 'private' ), "Public and private ->methods clash correctly" ); ok( ! CI->methods( CI, 'private', 'public' ), "Public and private ->methods clash correctly" ); ok( ! CI->methods( CI, 'full', 'expanded' ), "Full and expanded ->methods class correctly" ); ok( ! CI->methods( CI, 'expanded', 'full' ), "Full and expanded ->methods class correctly" ); # Check combining options $methods = CI->methods( CI, 'public', 'expanded' ); ok( (ref($methods) eq 'ARRAY' and ref($methods->[0]) eq 'ARRAY' and $methods->[0]->[0] eq 'Class::Inspector::children' and $methods->[0]->[1] eq 'Class::Inspector' and $methods->[0]->[2] eq 'children' and ref($methods->[0]->[3]) eq 'CODE' and scalar @$methods == $base_public), "Public + Expanded ->methods works for non-inheriting class" ); $methods = CI->methods( 'Class::Inspector::Dummy', 'public', 'expanded' ); ok( (ref($methods) eq 'ARRAY' and ref($methods->[0]) eq 'ARRAY' and $methods->[0]->[0] eq 'Class::Inspector::Dummy::adummy1' and $methods->[0]->[1] eq 'Class::Inspector::Dummy' and $methods->[0]->[2] eq 'adummy1' and ref($methods->[0]->[3]) eq 'CODE' and scalar @$methods == ($base_public + 2) and scalar( grep { /dummy/ } map { $_->[2] } @$methods ) == 2), "Public + Expanded ->methods works for inheriting class" ); ok( ! CI->methods( BAD ), "Expanded ->methods fails correctly" ); ##################################################################### # Search Tests # Create the classes to use CLASSES: { package Foo; sub foo { 1 }; package Foo::Subclass; @Foo::Subclass::ISA = 'Foo'; package Bar; @Bar::ISA = 'Foo'; package This; sub isa { $_[1] eq 'Foo' ? 1 : undef } 1; } # Check trivial ->find cases SCOPE: { is( CI->subclasses( '' ), undef, '->subclasses(bad) returns undef' ); is( CI->subclasses( BAD ), '', '->subclasses(none) returns false' ); my $rv = CI->subclasses( CI ); is_deeply( $rv, [ 'Class::Inspector::Dummy' ], '->subclasses(CI) returns just itself' ); # Check non-trivial ->subclasses cases $rv = CI->subclasses( 'Foo' ); is_deeply( $rv, [ 'Bar', 'Foo::Subclass', 'This' ], '->subclasses(nontrivial) returns the expected class list' ); } ##################################################################### # Regression Tests # Discovered in 1.06, fixed in 1.07 # In some cases, spurious empty GLOB entries can be created in a package. # These contain no actual symbols, but were causing ->loaded to return true. # An empty namespace with a single spurious empty glob entry (although # created in this test with a scalar) should return FALSE for ->loaded $Class::Inspector::SpuriousPackage::something = 1; $Class::Inspector::SpuriousPackage::something = 1; # Avoid a warning ok( ! Class::Inspector->loaded('Class::Inspector::SpuriousPackage'), '->loaded returns false for spurious glob in package' ); # Discovered in 1.11, fixed in 1.12 # With the introduction of ->subclasses, we exposed ourselves to # non-local problems with ->isa method implementations. PACKAGES: { # The busted package package Class::Inspector::BrokenISA; use vars qw{&isa $VERSION}; $VERSION = '0.01'; # The test packages package My::Foo; use vars qw{$VERSION}; $VERSION = '0.01'; package My::Bar; use vars qw{$VERSION @ISA}; $VERSION = '0.01'; @ISA = 'My::Foo'; } TESTS: { my $rv = Class::Inspector->subclasses( 'My::Foo' ); is_deeply( $rv, [ 'My::Bar' ], '->subclasses in the presence of an evil ->isa does not crash' ); }