File Coverage

blib/lib/Test/C2FIT/RowFixture.pm
Criterion Covered Total %
statement 104 148 70.2
branch 12 28 42.8
condition 1 3 33.3
subroutine 16 18 88.8
pod 0 12 0.0
total 133 209 63.6


line stmt bran cond sub pod time code
1             # $Id: RowFixture.pm,v 1.6 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::RowFixture;
10              
11 2     2   1651 use base qw(Test::C2FIT::ColumnFixture);
  2         5  
  2         691  
12              
13 2     2   12 use strict;
  2         3  
  2         56  
14 2     2   9 use Test::C2FIT::TypeAdapter;
  2         3  
  2         37  
15 2     2   8 use Error qw( :try );
  2         4  
  2         9  
16              
17             sub new {
18 1     1 0 3 my $pkg = shift;
19 1         20 return $pkg->SUPER::new(
20             results => [],
21             missing => [],
22             surplus => [],
23             @_
24             );
25             }
26              
27             sub doRows {
28 1     1 0 2 my $self = shift;
29 1         1 my ($rows) = @_;
30             try {
31 1     1   15 $self->bind( $rows->parts() );
32 1         5 $self->{'results'} = $self->query();
33 1         13 $self->match( $self->rowsToArray( $rows->more() ),
34             $self->{'results'}, 0 );
35 1         6 my $last = $rows->last();
36 1         9 $last->more( $self->buildRows( $self->{'surplus'} ) );
37 1         4 $self->markRows( $last->more(), "surplus" );
38 1         9 $self->markList( $self->{'missing'}, "missing" );
39             }
40             otherwise {
41 0     0   0 my $e = shift;
42 0         0 $self->exception( $rows->leaf(), $e );
43 1         9 };
44             }
45              
46             sub match {
47 1     1 0 3 my $self = shift;
48 1         3 my ( $expected, $computed, $col ) = @_;
49              
50 1         1 my $ncols = @{ $self->{'columnBindings'} };
  1         3  
51 1 50       9 if ( $col >= $ncols ) {
    50          
52 0         0 $self->checkLists( $expected, $computed );
53             }
54             elsif ( not defined( $self->{'columnBindings'}->[$col] ) ) {
55 0         0 $self->match( $expected, $computed, $col + 1 );
56             }
57             else {
58 1         7 my $eMap = $self->eSort( $expected, $col );
59 1         9 my $cMap = $self->cSort( $computed, $col );
60 1         8 my $keys = $self->union( keys %$eMap, keys %$cMap );
61 1         4 foreach my $key (@$keys) {
62 2         3 my $eList = $$eMap{$key};
63 2         4 my $cList = $$cMap{$key};
64 2 50 33     18 if ( !$eList ) {
    50          
    50          
65 0         0 push @{ $self->{'surplus'} }, @$cList;
  0         0  
66             }
67             elsif ( !$cList ) {
68 0         0 push @{ $self->{'missing'} }, @$eList;
  0         0  
69             }
70             elsif ( 1 == @$eList && 1 == @$cList ) {
71 2         19 $self->checkLists( $eList, $cList );
72             }
73             else {
74 0         0 $self->match( $eList, $cList, $col + 1 );
75             }
76             }
77             }
78             }
79              
80             sub rowsToArray {
81 1     1 0 2 my $self = shift;
82 1         2 my ($rows) = @_;
83 1         2 my @results = ();
84 1         4 while ($rows) {
85 2         3 push @results, $rows;
86 2         8 $rows = $rows->more();
87             }
88 1         9 return \@results;
89             }
90              
91             sub eSort {
92 1     1 0 3 my $self = shift;
93 1         1 my ( $list, $col ) = @_;
94              
95 1         3 my $adapter = $self->{'columnBindings'}->[$col];
96 1         2 my %result = ();
97              
98 1         3 foreach my $row (@$list) {
99 2         8 my $cell = $row->parts()->at($col);
100 2         3 eval {
101 2         6 my $key = $adapter->parse( $cell->text() );
102 2         5 push @{ $result{$key} }, $row;
  2         7  
103             };
104 2 50       6 if ($@) {
105 0         0 $self->exception( $cell, $@ );
106 0         0 while ( $cell = $cell->more() ) {
107 0         0 $self->ignore($cell);
108             }
109             }
110             }
111              
112 1         3 return \%result;
113             }
114              
115             sub cSort {
116 1     1 0 3 my $self = shift;
117 1         2 my ( $list, $col ) = @_;
118              
119 1         3 my $adapter = $self->{'columnBindings'}->[$col];
120 1         3 my %result = ();
121 1         2 foreach my $row (@$list) {
122 2         4 eval {
123 2         10 $adapter->target($row);
124 2         10 my $key = $adapter->get();
125 2         4 push @{ $result{$key} }, $row;
  2         7  
126             };
127 2 50       14 if ($@) {
128 0         0 push @{ $self->{'surplus'} }, $row;
  0         0  
129             }
130             }
131 1         3 return \%result;
132             }
133              
134             sub union {
135 1     1 0 3 my $self = shift;
136 1         2 my %merged = ();
137 1         6 $merged{$_}++ foreach @_;
138 1         6 return [ keys %merged ];
139             }
140              
141             sub checkLists {
142 4     4 0 5 my $self = shift;
143 4         5 my ( $eList, $cList ) = @_;
144              
145 4 100       12 if ( 0 == @$eList ) {
146 2         3 push @{ $self->{'surplus'} }, @$cList;
  2         5  
147 2         12 return;
148             }
149 2 50       12 if ( 0 == @$cList ) {
150 0         0 push @{ $self->{'missing'} }, @$eList;
  0         0  
151 0         0 return;
152             }
153 2         3 my $row = shift @$eList;
154 2         6 my $cell = $row->parts();
155 2         4 my $obj = shift @$cList;
156 2         2 foreach my $adapter ( @{ $self->{'columnBindings'} } ) {
  2         6  
157 4 50       9 last if not defined($cell);
158 4 50       11 if ($adapter) {
159 4         10 $adapter->target($obj);
160             }
161 4         16 $self->check( $cell, $adapter );
162 4         76 $cell = $cell->more();
163             }
164 2         10 $self->checkLists( $eList, $cList );
165             }
166              
167             sub markRows {
168 1     1 0 2 my $self = shift;
169 1         2 my ( $rows, $message ) = @_;
170              
171 1         8 my $annotation = Test::C2FIT::Fixture->label($message);
172 1         6 while ($rows) {
173 0         0 $self->wrong( $rows->parts() );
174 0         0 $rows->parts()->addToBody($annotation);
175 0         0 $rows = $rows->more();
176             }
177             }
178              
179             sub markList {
180 1     1 0 2 my $self = shift;
181 1         2 my ( $rows, $message ) = @_;
182 1         5 my $annotation = Test::C2FIT::Fixture->label($message);
183 1         5 foreach my $row (@$rows) {
184 0         0 $self->wrong( $row->parts() );
185 0         0 $row->parts()->addToBody($annotation);
186             }
187             }
188              
189             sub buildRows {
190 1     1 0 2 my $self = shift;
191 1         2 my ($rowsref) = @_;
192              
193 1         8 my $root = Test::C2FIT::Parse->from( "", undef, undef, undef );
194 1         3 my $next = $root;
195 1         3 foreach my $row (@$rowsref) {
196 0         0 $next = $next->more(
197             Test::C2FIT::Parse->from(
198             "tr", undef, $self->buildCells($row), undef
199             )
200             );
201             }
202 1         5 return $root->more();
203             }
204              
205             sub buildCells {
206 0     0 0   my $self = shift;
207 0           my ($row) = @_;
208 0           my $ncols = @{ $self->{'columnBindings'} };
  0            
209              
210 0 0         if ( !$row ) {
211 0           my $nil = Test::C2FIT::Parse->from( "td", "nul", undef, undef );
212 0           $nil->addToTag(" colspan=$ncols");
213 0           return $nil;
214             }
215 0           my $root = Test::C2FIT::Parse->from( "", undef, undef, undef );
216 0           my $next = $root;
217 0           foreach my $adapter ( @{ $self->{'columnBindings'} } ) {
  0            
218 0           $next =
219             $next->more(
220             Test::C2FIT::Parse->from( "td", " ", undef, undef ) );
221 0 0         if ( !$adapter ) {
222 0           $self->ignore($next);
223             }
224             else {
225 0           eval {
226 0           $adapter->target($row);
227 0           $self->info( $next, $adapter->toString( $adapter->get() ) );
228             };
229 0 0         if ($@) {
230 0           $self->exception( $next, $@ );
231             }
232             }
233             }
234 0           return $root->more();
235             }
236              
237             1;
238              
239             =pod
240              
241             =head1 NAME
242              
243             Test::C2FIT::RowFixture - A RowFixture compares rows in the test data to objects
244             in the system under test. Methods are invoked on the objects and returned values
245             compared to those in the table. An algorithm matches rows with objects based on
246             one or more keys. Objects may be missing or in surplus and are so noted.
247              
248             =head1 SYNOPSIS
249              
250             Normally, you subclass RowFixture.
251              
252             package MyColumnFixture;
253             use base 'Test::C2FIT::ColumnFixture;'
254              
255             sub query {
256             my $self = shift;
257             return [ ];
258             }
259              
260             =head1 DESCRIPTION
261              
262             query() should return an arrayref consisting of either blessed objects (fields and methods are used) or
263             unbessed hashrefs (only fields are used).
264              
265              
266             When your data is not stored as string, then you'll propably need an TypeAdapter. See more in L.
267              
268             =head1 METHODS
269              
270             =over 4
271              
272             =item B
273              
274             query() should return an arrayref consisting of either blessed objects (fields and methods are used) or
275             unbessed hashrefs (only fields are used).
276              
277             =back
278              
279             =head1 SEE ALSO
280              
281             Extensive and up-to-date documentation on FIT can be found at:
282             http://fit.c2.com/
283              
284              
285             =cut
286              
287             __END__