00001 package Doxygen::Test;
00002
00003 =head1 NAME
00004
00005 Doxygen::Test - Test module, used only in Doxygen:: test files (.t files).
00006
00007 =head1 SYNOPSIS
00008
00009 use Doxygen::Test path => 'Filter.pm',
00010 tests => 2;
00011
00012 hasComment('\file Test.pm');
00013 hasMacro;
00014
00015 =head1 DESCRIPTION
00016
00017 This module exists only to support automated testing of the other
00018 C<Doxygen> modules.
00019 It is used inside of C<t.#> files instead of C<Test::Simple> or
00020 C<Test::More>.
00021
00022 In addition to specifying the number of tests,
00023 instantiation specifies a source file to be processed.
00024 This is done using the normal C<Doxygen::Source> mechanism.
00025 The output is captured and stored in the object,
00026 so that subsequent C<like()> calls can match against it.
00027
00028 =head1 METHODS
00029
00030 =over
00031
00032 =cut
00033
00034 use 5.005; # just to pick something, but not really tested
00035 use strict;
00036 use warnings;
00037 use UNIVERSAL qw(isa);
00038
00039 use File::Basename;
00040 use Test::Builder;
00041
00042 use Doxygen::Source;
00043
00044 use constant TEST_FLAGS => qw(
00045 path
00046 );
00047
00048 require Exporter;
00049
00050 our @ISA = qw(Exporter);
00051 our @EXPORT = qw(
00052 ok like contains
00053 hasComment hasMacro includes nameSpace
00054 dumpGeneratedText);
00055 our $VERSION = '0.01';
00056
00057 our $global;
00058
00059 ###########################################################################
00060 ###########################################################################
00061
00062 =item C<new($class, %flags)>
00063
00064 Create new C<Doxygen::Text> item. Not called directly, a global object
00065 is instantiated via the C<import()> function.
00066
00067 =cut
00068
00069 sub new
00070 {
00071 my ($class, %flags) = @_;
00072 my $self = bless {
00073 builder => Test::Builder->new,
00074 ok => 0,
00075 failed => 0,
00076 map { $flags{$_} ? ( $_ => $flags{$_} ) : ( ) } TEST_FLAGS
00077 }, $class;
00078
00079 return $self
00080 unless $self->{ok} = isa $self->{builder}, 'Test::Builder';
00081
00082 delete $flags{$_}
00083 for TEST_FLAGS;
00084
00085 $flags{tests} += 3
00086 if defined $flags{tests};
00087
00088 $self->{builder}->plan(%flags);
00089
00090 $self->{path} = '<noPath>'
00091 unless $self->{path};
00092
00093 # test #1
00094 $self->{builder}->ok($self->{ok} = -f $self->{path}, $self->{path});
00095
00096 return $self
00097 unless $self->{ok};
00098
00099 $self->{source} = new Doxygen::Source(path => $self->{path}, info => 0, stats => 0);
00100 $self->{builder}->ok( # test #2
00101 $self->{ok} = isa($self->{source}, 'Doxygen::Source'),
00102 "create Doxygen::Source object");
00103
00104 return $self
00105 unless $self->{ok};
00106
00107 my $string;
00108
00109 open TMPOUT, ">&STDOUT";
00110 close STDOUT;
00111 open STDOUT, '>', \$string or die "*** Boo hoo!\n";
00112
00113 eval {
00114 $self->{source}->massage;
00115 $self->{source}->generate;
00116 };
00117
00118 my $err = $@;
00119
00120 close STDOUT;
00121 open STDOUT, ">&TMPOUT";
00122 close TMPOUT;
00123
00124 # test #3
00125 $self->{builder}->ok($self->{text} = $string, 'generated text');
00126
00127 warn "!!! Error generating text!\n!!! $err\n"
00128 if $err;
00129
00130 $self
00131 }
00132
00133 ###########################################################################
00134
00135 =item C<DESTROY($self)>
00136
00137 Destructor for object.
00138
00139 Checks to see if all tests appear to have passed.
00140 If not, dumps generated text to C<STDERR>.
00141
00142 =cut
00143
00144 sub DESTROY
00145 {
00146 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00147
00148 warn "<<<<<<<<<<<<<<<\n$self->{text}\n>>>>>>>>>>>>>>>\n"
00149 if $self->{ok}
00150 && $self->{failed};
00151 }
00152
00153 ###########################################################################
00154
00155 =item C<import(%self, %flags)>
00156
00157 The import protocol for the package is used to setup conditions
00158 for testing.
00159
00160 =cut
00161
00162 sub import ($%)
00163 {
00164 my $class = shift;
00165
00166 $global = $class->new(@_)
00167 unless isa $global, __PACKAGE__;
00168
00169 die "*** Unable to create @{[ __PACKAGE__ ]}\n"
00170 unless isa $global, __PACKAGE__;
00171
00172 $global->{builder}->exported_to(scalar(caller));
00173 $class->export_to_level(1, $class, @EXPORT);
00174 }
00175
00176 ###########################################################################
00177 ###########################################################################
00178
00179 =item C<check()>
00180
00181 Checks to see if the underlying Test::Builder has registered a failed test.
00182
00183 Seems clumsy that we have call this after changing the test object.
00184 Sadly, the test object disappears before we get called in C<DESTROY>.
00185 Thus we have to keep this updated the best we can.
00186
00187 =cut
00188
00189 sub check ()
00190 {
00191 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00192
00193 return
00194 unless isa $self->{builder}, 'Test::Builder';
00195
00196 unless (isa $self->{builder}, 'Test::Builder') {
00197 } elsif ($self->{builder}->can('is_passing')) {
00198 $self->{failed} = 1
00199 unless $self->{builder}->is_passing;
00200 } elsif (isa $self->{builder}->{Test_Results}, 'ARRAY') {
00201 for my $t (@{$self->{builder}->{Test_Results}}) {
00202 $self->{failed} = 1
00203 unless $t->{ok}
00204 }
00205 }
00206 }
00207
00208 ###########################################################################
00209
00210 =item C<comment($self, $match)>
00211
00212 Get generated comment starting with the specified match string.
00213 This allows pulling a specific comment from the generated text.
00214
00215 Comments are assumed to be in C++ multi-line format with a doubled asterisk
00216 on the opening of the comment:
00217
00222 Returns any subsequent matching text from the comment.
00223 The C<$match> is a string, not a regular expression.
00224
00225 =cut
00226
00227 sub comment
00228 {
00229 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00230
00231 return
00232 unless $self->{ok};
00233
00234 unless (isa $self->{comment}, 'HASH') {
00235 $self->{comment}->{$1} = $2
00236 while $self->{text}
00237 =~ m{/\*\*\s*\n[\*\s]+(.*?)\s*?\n[\*\s]*?(.*?)[\*\s]*\*/}gs;
00238 }
00239
00240 $self->{comment}->{$_[0]}
00241 }
00242
00243 ###########################################################################
00244 ###########################################################################
00245
00246 =item C<contains($text [, $name])>
00247
00248 Checks to see if the the generated text contains the specified text.
00249 If the C<$name> is not provided it is constructed.
00250
00251 =cut
00252
00253 sub contains ($;$)
00254 {
00255 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00256
00257 return
00258 unless $self->{ok};
00259
00260 my $text = shift;
00261 my $name = shift || qq{contains "$text"};
00262
00263 $self->{builder}->ok(index($self->{text}, $text) >= 0, $name);
00264 check;
00265 }
00266
00267 ###########################################################################
00268
00269 =item C<like($pattern, $name)>
00270
00271 Marks test based on the generated text matching the specified C<$pattern>.
00272 Much like C<Test::Simple::like()>, with the text target pre-specified.
00273
00274 =cut
00275
00276 sub like ($$)
00277 {
00278 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00279
00280 return
00281 unless $self->{ok};
00282
00283 $self->{builder}->like($self->{text}, @_);
00284 check
00285 }
00286
00287 ###########################################################################
00288
00289 =item C<ok($test, $name)>
00290
00291 Marks test based on boolean test result C<$test>.
00292 Just like C<Test::Simple::ok()>.
00293
00294 =cut
00295
00296 sub ok ($$)
00297 {
00298 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00299
00300 return
00301 unless $self->{ok};
00302
00303 $self->{builder}->ok(@_);
00304 check
00305 }
00306
00307 ###########################################################################
00308 ###########################################################################
00309
00310 =item C<hasComment($tag [, @matches ])>
00311
00312 Checks if generated text contains comment beginning with specified C<$tag>.
00313 This would be something like C<"\file Filter.pm">.
00314
00315 If this is true and subsequent C<@matches> are provided these will be
00316 tested against the body of the comment and the test will pass only if
00317 all of them match.
00318
00319 Note that for purposes of counting tests, this is one test for the
00320 C<$tag> and one test for each of the C<@matches> (may be zero).
00321
00322 =cut
00323
00324 sub hasComment ($;@)
00325 {
00326 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00327
00328 return
00329 unless $self->{ok};
00330
00331 my $tag = shift;
00332 my $cmnt = $self->comment($tag);
00333 my $name = "/** $tag */";
00334 my $ok = defined $cmnt;
00335
00336 $self->{builder}->ok($ok, $name);
00337
00338 for (@_) {
00339 my $fullname = qq{$name "$_"};
00340
00341 if ($ok) {
00342 $self->{builder}->ok(index($cmnt, $_) >= 0, $fullname);
00343 } else {
00344 $self->{builder}->skip($fullname);
00345 }
00346 }
00347
00348 check
00349 }
00350
00351 ###########################################################################
00352
00353 =item C<hasMacro()>
00354
00355 Checks if the redefinition-prevention macro is present in the file.
00356 This will look something like:
00357
00358 #ifndef DoxyFilt
00359 #define DoxyFilt
00360
00361 ...[snip]...
00362
00363 #endif DoxyFilt
00364
00365 =cut
00366
00367 sub hasMacro ()
00368 {
00369 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00370
00371 return
00372 unless $self->{ok};
00373
00374 my $macro = basename $self->{path};
00375
00376 # this matches Doxygen::Item::File:
00377 $macro =~ s|^.*:[/\\]||;
00378 $macro =~ s|\W|_|g;
00379
00380 $self->{builder}->like(
00381 $self->{text},
00382 qr((?:^|\n)
00383 \s* #ifndef \s+ $macro \s* \n
00384 \s* #define \s+ $macro \s* \n
00385 .*? \s* \n
00386 \s* #endif \s+ $macro \s*
00387 (?:\n|$))sx,
00388 "$macro re-definition macro");
00389 check
00390 }
00391
00392 ###########################################################################
00393
00394 =item C<includes($path)>
00395
00396 Checks for a C<#include> for the specified path.
00397
00398 =cut
00399
00400 sub includes ($)
00401 {
00402 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00403 my $path = shift;
00404
00405 return
00406 unless $self->{ok};
00407
00408 $self->{builder}->like(
00409 $self->{text},
00410 qr{\n#include\s+<$path>\s*\n},
00411 "#include <$path>");
00412
00413 check
00414 }
00415
00416 ###########################################################################
00417
00418 =item C<nameSpace($path)>
00419
00420 Check to see if specified namespace is defined.
00421
00422 =cut
00423
00424 sub nameSpace ($)
00425 {
00426 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00427
00428 return
00429 unless $self->{ok};
00430
00431 my $pattern = join '', map {
00432 '\n\s*namespace\s+' . $_ . '\s*\{\s*'
00433 } split /::/, $_[0];
00434
00435 $self->{builder}->like(
00436 $self->{text},
00437 qr($pattern)x,
00438 "namespace $_[0]");
00439 check
00440 }
00441
00442 ###########################################################################
00443 ###########################################################################
00444
00445 =item C<dumpGeneratedText()>
00446
00447 Bail out of test, incidentally causing generated text to be dumped.
00448
00449 =cut
00450
00451 sub dumpGeneratedText () {
00452 my $self = isa($_[0], __PACKAGE__) ? shift : $global;
00453
00454 $self->{failed} = 1;
00455 }
00456
00457 ###########################################################################
00458 ###########################################################################
00459
00460 1
00461
00462 __END__
00463
00464 =back
00465
00466 =head1 SEE ALSO
00467
00468 DoxyFilt.pl
00469
00470 =head1 AUTHOR
00471
00472 Marc M. Adkins, L<mailto:Perl@Doorways.org>
00473
00474 =head1 COPYRIGHT AND LICENSE
00475
00476 Copyright 2004-2010 by Marc M. Adkins
00477
00478 This library is free software; you can redistribute it and/or modify
00479 it under the same terms as Perl itself.
00480
00481 =cut