File Coverage

blib/lib/Test/C2FIT/Fixture.pm
Criterion Covered Total %
statement 104 194 53.6
branch 14 38 36.8
condition 1 3 33.3
subroutine 26 50 52.0
pod 3 25 12.0
total 148 310 47.7


line stmt bran cond sub pod time code
1             # $Id: Fixture.pm,v 1.18 2006/06/16 15:20:56 tonyb Exp $
2             #
3             # Copyright (c) 2002-2005 Cunningham & Cunningham, Inc.
4             # Released under the terms of the GNU General Public License version 2 or later.
5             #
6             # Perl translation by Dave W. Smith
7             # Modified by Tony Byrne
8              
9             package Test::C2FIT::Fixture;
10              
11 3     3   1508 use strict;
  3         8  
  3         110  
12 3     3   14 use Error qw( :try );
  3         24  
  3         19  
13              
14             my %summary;
15              
16             our $yellow = '#ffffcf';
17             our $green = '#cfffcf';
18             our $red = '#ffcfcf';
19             our $gray = '#808080';
20             our $ignore = '#efefef';
21             our $info = $gray;
22             our $label = '#c08080';
23              
24             sub new {
25 4     4 0 39 my $pkg = shift;
26 4         22 my $self = bless { counts => Test::C2FIT::Counts->new(), @_ }, $pkg;
27              
28             #
29             # TypeAdapter support in perl: the following hashes can contain a field/method name
30             # to Adapter mapping. Key is the columnName, value is a fully qualified package name
31             # of a TypeAdapter to use
32             #
33 4 50       43 $self->{fieldColumnTypeMap} = {} unless exists $self->{fieldColumnTypeMap};
34 4 50       15 $self->{methodColumnTypeMap} = {} unless exists $self->{fieldColumnTypeMap};
35 4 50       21 $self->{methodSetterTypeMap} = {}
36             unless exists $self->{methodSetterTypeMap}; # see actionFixture
37 4         13 return $self;
38             }
39              
40             sub counts {
41 15     15 0 5120 my $self = shift;
42 15 100       36 $self->{'counts'} = $_[0] if @_;
43 15         98 return $self->{'counts'};
44             }
45              
46             sub doTables {
47 2     2 0 14 my $self = shift;
48 2         4 my ($tables) = @_;
49              
50 2         338 $Test::C2FIT::Fixture::summary{'run date'} = scalar localtime( time() );
51 2         18 $Test::C2FIT::Fixture::summary{'run elapsed time'} =
52             Test::C2FIT::Runtime->new();
53              
54 2         56 while ($tables) {
55 2         13 my $heading = $tables->at( 0, 0, 0 );
56 2 50       10 if ($heading) {
57             try {
58 2     2   55 my $pkg = $heading->text();
59 2         14 my $fixture = $self->loadFixture($pkg);
60 2         7 $fixture->counts( $self->counts() );
61 2         17 $fixture->doTable($tables);
62             }
63             otherwise {
64 0     0   0 my $e = shift;
65 0         0 $self->exception( $heading, $e );
66 2         38 };
67             }
68 2         53 $tables = $tables->more();
69             }
70             }
71              
72             sub doTable {
73 2     2 0 6 my $self = shift;
74 2         3 my ($table) = @_;
75 2         9 $self->doRows( $table->parts()->more() );
76             }
77              
78             sub doRows {
79 1     1 0 2 my $self = shift;
80 1         2 my ($rows) = @_;
81 1         4 while ($rows) {
82 2         7 my $more = $rows->more();
83 2         12 $self->doRow($rows);
84 2         28 $rows = $more;
85             }
86             }
87              
88             sub doRow {
89 2     2 0 4 my $self = shift;
90 2         3 my ($row) = @_;
91 2         14 $self->doCells( $row->parts() );
92             }
93              
94             sub doCells {
95 2     2 0 4 my $self = shift;
96 2         3 my ($cells) = @_;
97 2         2 my $columnNumber = 0;
98              
99 2         6 while ($cells) {
100             try {
101 8     8   137 $self->doCell( $cells, $columnNumber );
102             }
103             otherwise {
104 0     0   0 my $e = shift;
105 0         0 $self->exception( $cells, $e );
106 8         45 };
107 8         105 $cells = $cells->more();
108 8         24 ++$columnNumber;
109             }
110             }
111              
112             sub doCell {
113 0     0 0 0 my $self = shift;
114 0         0 my ( $cell, $columnNumber ) = @_;
115 0         0 $self->ignore($cell);
116             }
117              
118             # Annotations
119              
120             sub right {
121 6     6 0 14 my $self = shift;
122 6         10 my ($cell) = @_;
123 6         28 $cell->addToTag(qq| bgcolor="$green"|);
124 6         83 $self->counts()->{'right'} += 1;
125             }
126              
127             sub wrong {
128 0     0 0 0 my $self = shift;
129 0         0 my ( $cell, $actual ) = @_;
130 0         0 $cell->addToTag(qq| bgcolor="$red"|);
131 0         0 $cell->{'body'} = $self->escape( $cell->text() );
132 0 0       0 $cell->addToBody( $self->label("expected") . "
"
133             . $self->escape($actual)
134             . $self->label("actual") )
135             if defined($actual);
136 0         0 $self->counts()->{'wrong'} += 1;
137             }
138              
139             sub ignore {
140 0     0 0 0 my $self = shift;
141 0         0 my ($cell) = @_;
142 0         0 $cell->addToTag(qq| bgcolor="$ignore"|);
143 0         0 $self->counts()->{'ignores'} += 1;
144             }
145              
146             sub error {
147 0     0 0 0 my $self = shift;
148 0         0 my ( $cell, $message ) = @_;
149 0         0 $cell->{'body'} = $self->escape( $cell->text() );
150 0         0 $cell->addToBody( "
" . $self->escape($message) . "
" );
151 0         0 $cell->addToTag( ' bgcolor="' . $yellow . '"' );
152 0         0 $self->counts()->{'exceptions'}++;
153             }
154              
155             sub info {
156 0     0 0 0 my $self = shift;
157 0         0 my ( $cell, $message );
158 0 0       0 if ( scalar @_ == 2 ) {
159 0         0 ( $cell, $message ) = @_;
160 0         0 $cell->addToBody( $self->info($message) );
161             }
162             else {
163 0         0 $message = shift;
164 0         0 return qq| |
165             . $self->escape($message)
166             . qq||;
167             }
168             }
169              
170             sub exception {
171 0     0 0 0 my $self = shift;
172 0         0 my ( $cell, $exception ) = @_;
173              
174             #TBD include a stack trace: (impl. should be the same as under java)
175             #
176             # perl does not support this directly. One solution might be using own
177             # $SIG{'__DIE__'} handler. Unfortunately, this may confuse other error
178             # handling routines - those from the Error-module or those from
179             # the "system under test"
180             #
181              
182             # $cell->addToTag(' bgcolor="ffffcf"');
183             # $cell->addToBody('
' . 
184             # $exception .
185             # "");
186             # $self->counts()->{'exceptions'} += 1;
187 0         0 $self->error( $cell, $exception );
188             }
189              
190             # Utilities
191              
192             sub label {
193 2     2 0 4 my $self = shift;
194 2         4 my ($string) = @_;
195 2 50       6 return '' unless $string;
196 2         10 return qq| $string|;
197             }
198              
199             sub gray {
200 0     0 0 0 my $self = shift;
201 0         0 my ($string) = @_;
202 0 0       0 return '' unless $string;
203 0         0 return qq|$string|;
204             }
205              
206             sub escape {
207 0     0 0 0 my $self = shift;
208 0         0 my ($string) = @_;
209              
210 0 0       0 return $string unless $string;
211              
212 0         0 $string =~ s/\&/&/g;
213 0         0 $string =~ s/
214              
215 0         0 $string =~ s/ /  /g;
216 0         0 $string =~ s|\r\n|
|g;
217 0         0 $string =~ s|\r|
|g;
218 0         0 $string =~ s|\n|
|g;
219 0         0 return $string;
220             }
221              
222             sub camel {
223 6     6 0 12 my ( $pkg, $string ) = @_;
224 6         15 $string =~ s/\s+$//s;
225 6         24 $string =~ s/\s(\S)/uc($1)/eg;
  2         12  
226 6         42 return $string;
227             }
228              
229             sub parse {
230 0     0 0 0 my $self = shift;
231 0         0 my ( $string, $type ) = @_;
232 0 0       0 throw Test::C2FIT::Exception("can't yet parse $type\n")
233             if $type ne "generic";
234 0         0 return $string;
235             }
236              
237             sub check {
238 6     6 0 10 my $self = shift;
239 6         8 my ( $cell, $adapter ) = @_;
240              
241 6         17 my $text = $cell->text();
242 6 50 33     55 if ( !defined($text) || $text eq "" ) {
    50          
    50          
243             try {
244 0     0   0 $self->info( $cell, $adapter->toString( $adapter->get() ) );
245             }
246             otherwise {
247 0     0   0 my $e = shift;
248 0         0 $self->info( $cell, "error" );
249 0         0 };
250             }
251             elsif ( not defined($adapter) ) {
252 0         0 $self->ignore($cell);
253             }
254             elsif ( $text eq "error" ) {
255             try {
256 0     0   0 my $result = $adapter->invoke();
257 0         0 $self->wrong( $cell, $adapter->toString($result) );
258             }
259             otherwise {
260              
261             #TBD The Java source distinguishes between illegal access
262             # and "normal" exceptions.
263 0     0   0 $self->right($cell);
264 0         0 };
265             }
266             else {
267             try {
268 6     6   122 my $result = $adapter->get();
269 6 50       25 if ( $adapter->equals( $adapter->parse($text), $result ) ) {
270 6         39 $self->right($cell);
271             }
272             else {
273 0         0 $self->wrong( $cell, $adapter->toString($result) );
274             }
275             }
276             otherwise {
277 0     0   0 my $e = shift;
278 0         0 $self->exception( $cell, $e );
279 6         54 };
280             }
281             }
282              
283             sub fixtureName {
284 0     0 0 0 my $self = shift;
285 0         0 my $tables = shift;
286 0         0 return $tables->at( 0, 0, 0 );
287             }
288              
289             sub loadFixture {
290 2     2 0 7 my $self = shift;
291 2         4 my $fixtureName = shift;
292              
293 2         40 my $foundButNotFixture =
294             qq|"$fixtureName" was found, but it's not a fixture.\n|;
295              
296 2         14 my $fixture = $self->_createNewInstance($fixtureName);
297              
298 2 50       28 throw Test::C2FIT::Exception($foundButNotFixture)
299             unless UNIVERSAL::isa( $fixture, 'Test::C2FIT::Fixture' );
300              
301 2         6 return $fixture;
302             }
303              
304             #
305             # creates a new Instance of a Package.
306             # - cares about java/perl notation
307             # - mangles full qualified package name for fit/fat/eg
308             #
309             # - should be the only code creating instances of user specific packages
310             #
311             sub _createNewInstance {
312 2     2   5 my ( $self, $name ) = @_;
313              
314 2         12 my $perlPackageName = $self->_java2PerlFixtureName($name);
315 2         4 my $instance;
316 2         9 my $notFound = qq|The fixture "$name" was not found.\n|;
317              
318             try {
319 2     2   70 $instance = $perlPackageName->new();
320             }
321 2     0   20 otherwise {};
  0         0  
322 2 50       39 if ( !ref($instance) ) {
323             try {
324 0     0   0 eval "use $perlPackageName;";
325 0 0       0 warn 1, " Result of use pgkName: $@" if $@;
326 0         0 $instance = $perlPackageName->new();
327             }
328             otherwise {
329 0     0   0 my $e = shift;
330 0         0 warn 1, " Error Instantiating a Package: $e";
331              
332 0         0 throw Test::C2FIT::Exception($notFound);
333 0         0 };
334             }
335              
336 2 50       7 throw Test::C2FIT::Exception( "$perlPackageName - instantiation error"
337             ) # if new does not return a ref...
338             unless ref($instance);
339              
340 2         6 return $instance;
341             }
342              
343             sub _java2PerlFixtureName {
344 8     8   8429 my ( $self, $fixtureName ) = @_;
345 8         19 $fixtureName =~ s/^fit\./Test\.C2FIT\./;
346              
347             # Need this because example and fat packages are in our namespace - prevents
348             # creation of top level namespace, frowned upon by CPAN indexer.
349 8         16 $fixtureName =~ s/^eg\./Test\.C2FIT\.eg\./;
350 8         12 $fixtureName =~ s/^fat\./Test\.C2FIT\.fat\./;
351              
352 8         20 $fixtureName =~ s/\./::/g;
353 8         32 return $fixtureName;
354             }
355              
356             #
357             # rules for determination of the TypeAdapter to be uses for a column
358             #
359             # 1. suggestFieldType / suggestMethodResultType returns the
360             # fully qualified package name of the TypeAdapter (inherits from Test::C2FIT::TypeAdapter).
361             #
362             # 2. (when 1. returned undef)
363             # Default behavior, i.e. Test::C2FIT::GenericAdapter for methods,
364             # Test::C2FIT::GenericArrayAdapter for array-ref-fields or
365             # Test::C2FIT::GenericAdapter for fields
366             #
367              
368             sub suggestFieldType
369             { # fields in ColumnFixture, RowFixture and setter parameter in ActionFixtures
370 5     5 1 10 my ( $self, $fieldColumnName ) = @_;
371 5         15 return $self->{fieldColumnTypeMap}->{$fieldColumnName};
372             }
373              
374             sub suggestMethodResultType { # method return values in all Fixtures
375 1     1 1 3 my ( $self, $methodColumnName ) = @_;
376 1         4 return $self->{methodColumnTypeMap}->{$methodColumnName};
377             }
378              
379             sub suggestMethodParamType { # method param - see ActionFixture and TypeAdapter
380 0     0 1 0 my ( $self, $methodName ) = @_;
381 0         0 return $self->{methodSetterTypeMap}->{$methodName};
382             }
383              
384             package Test::C2FIT::Counts;
385              
386             sub new {
387 4     4   9 my $pkg = shift;
388 4         45 bless {
389             right => 0,
390             wrong => 0,
391             ignores => 0,
392             exceptions => 0
393             }, $pkg;
394             }
395              
396             sub toString {
397 0     0   0 my $self = shift;
398 0         0 join( ", ",
399 0         0 map { $self->{$_} . " " . $_ } qw(right wrong ignores exceptions) );
400             }
401              
402             sub tally {
403 0     0   0 my $self = shift;
404 0         0 my ($counts) = @_;
405              
406 0         0 $self->{'right'} += $counts->{'right'};
407 0         0 $self->{'wrong'} += $counts->{'wrong'};
408 0         0 $self->{'ignores'} += $counts->{'ignores'};
409 0         0 $self->{'exceptions'} += $counts->{'exceptions'};
410             }
411              
412             package Test::C2FIT::Runtime;
413              
414 3     3   9849 use overload '""' => \&toString;
  3         13  
  3         36  
415              
416             sub new {
417 3     3   3421 use Benchmark;
  3         25933  
  3         24  
418 2     2   5 my $pkg = shift;
419 2         19 bless { start => new Benchmark() }, $pkg;
420             }
421              
422             sub toString {
423 0     0     my $self = shift;
424 0           my $end = new Benchmark();
425 0           my $timeDiff = timediff( $end, $self->{start} );
426 0           my $timeStr = timestr($timeDiff);
427 0           return $timeStr;
428             }
429              
430             1;
431              
432             =pod
433              
434             =head1 NAME
435              
436             Test::C2FIT::Fixture - Base class of all fixtures. A fixture checks examples in a table (of the
437             input document) by running the actual program. Typically you neither use this class directly, nor
438             subclass it directly.
439              
440              
441             =head1 SYNOPSIS
442              
443              
444             =head1 DESCRIPTION
445              
446              
447             When your data is not stored as string, then you'll propably need an TypeAdapter. Either you
448             fill an appropriate hash while instantiating a Fixture, or you overload an appropriate method.
449              
450             =head1 METHODS
451              
452             =over 4
453              
454             =item B
455              
456             Returns a fully qualified package/classname of a TypeAdapter suitable for parsing/checking of cell entries
457             of the column named "$columnName".
458              
459             Default implementation uses a lookup in the instance's fieldColumnTypeMap hash.
460             Will be used in ColumnFixture, RowFixture and setter parameter of an ActionFixture.
461              
462             =item B
463              
464             Used in all Fixtures. Returns a fully qualified package/classname of a TypeAdapter suitable for parsing
465             cell entries of the column named "$methodName" and checking them to return values of the method $methodName().
466              
467             =item B
468              
469             Used in ActionFixture for setter-type methods. Returns a fully qualified
470             package/classname of a TypeAdapter suitable for parsing
471             cell entries following a cell with the content of $methodName.
472              
473              
474             =back
475              
476             =head1 SEE ALSO
477              
478             Extensive and up-to-date documentation on FIT can be found at:
479             http://fit.c2.com/
480              
481              
482             =cut
483              
484             __END__