This file is indexed.

/usr/share/perl5/Test/ClassAPI.pm is in libtest-classapi-perl 1.06-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package Test::ClassAPI;

# Allows us to test class APIs in a simplified manner.
# Implemented as a wrapper around Test::More, Class::Inspector and Config::Tiny.

use 5.006;
use strict;
use File::Spec       0.83 ();
use Test::More       0.47 ();
use Config::Tiny     2.00 ();
use Class::Inspector 1.12 ();
use Params::Util     1.00 '_INSTANCE';

use vars qw{$VERSION $CONFIG $SCHEDULE $EXECUTED %IGNORE *DATA};
BEGIN {
	$VERSION = '1.06';

	# Config starts empty
	$CONFIG   = undef;
	$SCHEDULE = undef;

	# We only execute once
	$EXECUTED = '';

	# When looking for method that arn't described in the class
	# description, we ignore anything from UNIVERSAL.
	%IGNORE = map { $_, 1 } qw{isa can};
}

# Get the super path ( not including UNIVERSAL )
# Rather than using Class::ISA, we'll use an inlined version
# that implements the same basic algorithm, but faster.
sub _super_path($) {
	my $class = shift;
	my @path  = ();
	my @queue = ( $class );
	my %seen  = ( $class => 1 );
	while ( my $cl = shift @queue ) {
		no strict 'refs';
		push @path, $cl;
		unshift @queue, grep { ! $seen{$_}++ }
			map { s/^::/main::/; s/\'/::/g; $_ }
			( @{"${cl}::ISA"} );
	}

	@path;
}





#####################################################################
# Main Methods

# Initialise the Configuration
sub init {
	my $class = shift;

	# Use the script's DATA handle or one passed
	*DATA = ref($_[0]) eq 'GLOB' ? shift : *main::DATA;
 
	# Read in all the data, and create the config object
	local $/ = undef;
	$CONFIG = Config::Tiny->read_string( <DATA> )
		or die 'Failed to load test configuration: '
			. Config::Tiny->errstr;
	$SCHEDULE = delete $CONFIG->{_}
		or die 'Config does not have a schedule defined';

	# Add implied schedule entries
	foreach my $tclass ( keys %$CONFIG ) {
		$SCHEDULE->{$tclass} ||= 'class';
		foreach my $test ( keys %{$CONFIG->{$tclass}} ) {
			next unless $CONFIG->{$tclass}->{$test} eq 'implements';
			$SCHEDULE->{$test} ||= 'interface';
		}
	}
	

	# Check the schedule information
	foreach my $tclass ( keys %$SCHEDULE ) {
		my $value = $SCHEDULE->{$tclass};
		unless ( $value =~ /^(?:class|abstract|interface)$/ ) {
			die "Invalid schedule option '$value' for class '$tclass'";
		}
		unless ( $CONFIG->{$tclass} ) {
			die "No section '[$tclass]' defined for schedule class";
		}
	}

	1;
}

# Find and execute the tests
sub execute {
	my $class = shift;
	if ( $EXECUTED ) {
		die 'You can only execute once, use another test script';
	}
	$class->init unless $CONFIG;

	# Handle options
	my @options = map { lc $_ } @_;
	my $CHECK_UNKNOWN_METHODS     = !! grep { $_ eq 'complete'   } @options;
	my $CHECK_FUNCTION_COLLISIONS = !! grep { $_ eq 'collisions' } @options;

	# Set the plan of no plan if we don't have a plan
	unless ( Test::More->builder->has_plan ) {
		Test::More::plan( 'no_plan' );
	}

	# Determine the list of classes to test
	my @classes = sort keys %$SCHEDULE;
	@classes = grep { $SCHEDULE->{$_} ne 'interface' } @classes;

	# Check that all the classes/abstracts are loaded
	foreach my $class ( @classes ) {
		Test::More::ok( Class::Inspector->loaded( $class ), "Class '$class' is loaded" );
	}

	# Check that all the full classes match all the required interfaces
	@classes = grep { $SCHEDULE->{$_} eq 'class' } @classes;
	foreach my $class ( @classes ) {
		# Find all testable parents
		my @path = grep { $SCHEDULE->{$_} } _super_path($class);

		# Iterate over the testable entries
		my %known_methods = ();
		my @implements = ();
		foreach my $parent ( @path ) {
			foreach my $test ( sort keys %{$CONFIG->{$parent}} ) {
				my $type = $CONFIG->{$parent}->{$test};

				# Does the class have a named method
				if ( $type eq 'method' ) {
					$known_methods{$test}++;
					Test::More::can_ok( $class, $test );
					next;
				}

				# Does the class inherit from a named parent
				if ( $type eq 'isa' ) {
					Test::More::ok( $class->isa($test), "$class isa $test" );
					next;
				}

				unless ( $type eq 'implements' ) {
					print "# Warning: Unknown test type '$type'";
					next;
				}
				
				# When we 'implement' a class or interface,
				# we need to check the 'method' tests within
				# it, but not anything else. So we will add
				# the class name to a seperate queue to be
				# processed afterwards, ONLY if it is not
				# already in the normal @path, or already
				# on the seperate queue.
				next if grep { $_ eq $test } @path;
				next if grep { $_ eq $test } @implements;
				push @implements, $test;
			}
		}

		# Now, if it had any, go through and check the classes added
		# because of any 'implements' tests
		foreach my $parent ( @implements ) {
			foreach my $test ( keys %{$CONFIG->{$parent}} ) {
				my $type = $CONFIG->{$parent}->{$test};
				if ( $type eq 'method' ) {
					# Does the class have a method
					$known_methods{$test}++;
					Test::More::can_ok( $class, $test );
				}
			}
		}

		if ( $CHECK_UNKNOWN_METHODS ) {
			# Check for unknown public methods
			my $methods = Class::Inspector->methods( $class, 'public', 'expanded' )
				or die "Failed to find public methods for class '$class'";
			@$methods = grep { $_->[2] !~ /^[A-Z_]+$/ } # Internals stuff
				grep { $_->[1] ne 'Exporter' } # Ignore Exporter methods we don't overload
				grep { ! ($known_methods{$_->[2]} or $IGNORE{$_->[2]}) } @$methods;
			if ( @$methods ) {
				print STDERR join '', map { "# Found undocumented method '$_->[2]' defined at '$_->[0]'\n" } @$methods;
			}
			Test::More::is( scalar(@$methods), 0, "No unknown public methods in '$class'" );
		}

		if ( $CHECK_FUNCTION_COLLISIONS ) {
			# Check for methods collisions.
			# A method collision is where
			#
			#     Foo::Bar->method
			#
			# is actually interpreted as
			#
			#     &Foo::Bar()->method
			#
			no strict 'refs';
			my @collisions = ();
			foreach my $symbol ( sort keys %{"${class}::"} ) {
				next unless $symbol =~ s/::$//;
				next unless defined *{"${class}::${symbol}"}{CODE};
				print STDERR "Found function collision: ${class}->${symbol} clashes with ${class}::${symbol}\n";
				push @collisions, $symbol;
			}
			Test::More::is( scalar(@collisions), 0, "No function/class collisions in '$class'" );
		}
	}

	1;
}

1;

__END__

=head1 NAME

Test::ClassAPI - Provides basic first-pass API testing for large class trees

=head1 DESCRIPTION

For many APIs with large numbers of classes, it can be very useful to be able
to do a quick once-over to make sure that classes, methods, and inheritance
is correct, before doing more comprehensive testing. This module aims to
provide such a capability.

=head2 Using Test::ClassAPI

Test::ClassAPI is used with a fairly standard looking test script, with the
API description contained in a __DATA__ section at the end of the script.

  #!/usr/bin/perl
  
  # Test the API for Foo::Bar
  use strict;
  use Test::More 'tests' => 123; # Optional
  use Test::ClassAPI;
  
  # Load the API to test
  use Foo::Bar;
  
  # Execute the tests
  Test::ClassAPI->execute;
  
  __DATA__
  
  Foo::Bar::Thing=interface
  Foo::Bar::Object=abstract
  Foo::Bar::Planet=class
  
  [Foo::Bar::Thing]
  foo=method
  
  [Foo::Bar::Object]
  bar=method
  whatsit=method
  
  [Foo::Bar::Planet]
  Foo::Bar::Object=isa
  Foo::Bar::Thing=isa
  blow_up=method
  freeze=method
  thaw=method

Looking at the test script, the code itself is fairly simple. We first load
Test::More and Test::ClassAPI. The loading and specification of a test plan
is optional, Test::ClassAPI will provide a plan automatically if needed.

This is followed by a compulsory __DATA__ section, containing the API
description. This description is in provided in the general form of a Windows
style .ini file and is structured as follows.

=head2 Class Manifest

At the beginning of the file, in the root section of the config file, is a
list of entries where the key represents a class name, and the value is one
of either 'class', 'abstract', or 'interface'.

The 'class' entry indicates a fully fledged class. That is, the class is
tested to ensure it has been loaded, and the existance of every method listed
in the section ( and its superclasses ) is tested for.

The 'abstract' entry indicates an abstract class, one which is part of our
class tree, and needs to exist, but is never instantiated directly, and thus
does not have to itself implement all of the methods listed for it. Generally,
many individual 'class' entries will inherit from an 'abstract', and thus a
method listed in the abstract's section will be tested for in all the 
subclasses of it.

The 'interface' entry indicates an external interface that is not part of
our class tree, but is inherited from by one or more of our classes, and thus
the methods listed in the interface's section are tested for in all the 
classes that inherit from it. For example, if a class inherits from, and
implements, the File::Handle interface, a C<File::Handle=interface> entry
could be added, with the C<[File::Handle]> section listing all the methods
in File::Handle that our class tree actually cares about. No tests, for class
or method existance, are done on the interface itself.

=head2 Class Sections

Every class listed in the class manifest B<MUST> have an individual section,
indicated by C<[Class::Name]> and containing a set of entries where the key
is the name of something to test, and the value is the type of test for it.

The 'isa' test checks inheritance, to make sure that the class the section is
for is (by some path) a sub-class of something else. This does not have to be
an immediate sub-class. Any class refered to (recursively) in a 'isa' test
will have its 'method' test entries applied to the class as well.

The 'method' test is a simple method existance test, using C<UNIVERSAL::can>
to make sure that the method exists in the class.

=head1 METHODS

=head2 execute

The C<Test::ClassAPI> has a single method, C<execute> which is used to start
the testing process. It accepts a single option argument, 'complete', which
indicates to the testing process that the API listed should be considered a
complete list of the entire API. This enables an additional test for each
class to ensure that B<every> public method in the class is detailed in the
API description, and that nothing has been "missed".

=head1 SUPPORT

Bugs should be submitted via the CPAN bug tracker, located at

L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-ClassAPI>

For other issues, or commercial enhancement or support, contact the author.

=head1 AUTHOR

Adam Kennedy E<lt>adamk@cpan.orgE<gt>

=head1 COPYRIGHT

Copyright 2002 - 2009 Adam Kennedy.

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the
LICENSE file included with this module.

=cut