File Coverage

inc/Test/Differences.pm
Criterion Covered Total %
statement 53 112 47.3
branch 14 76 18.4
condition 7 22 31.8
subroutine 11 15 73.3
pod 3 3 100.0
total 88 228 38.6


line stmt bran cond sub pod time code
1             #line 1
2             package Test::Differences;
3              
4             #line 201
5              
6             $VERSION = 0.47;
7              
8             use Exporter;
9              
10             @ISA = qw( Exporter );
11             @EXPORT = qw( eq_or_diff eq_or_diff_text eq_or_diff_data );
12              
13             use strict;
14              
15             use Carp;
16             use Text::Diff;
17              
18             sub _isnt_ARRAY_of_scalars {
19             return 1 if ref ne "ARRAY";
20             return scalar grep ref, @$_;
21             }
22              
23              
24             sub _isnt_HASH_of_scalars {
25             return 1 if ref ne "HASH";
26             return scalar grep ref, keys %$_;
27             }
28              
29             use constant ARRAY_of_scalars => "ARRAY of scalars";
30             use constant ARRAY_of_ARRAYs_of_scalars => "ARRAY of ARRAYs of scalars";
31             use constant ARRAY_of_HASHes_of_scalars => "ARRAY of HASHes of scalars";
32              
33              
34             sub _grok_type {
35             local $_ = shift if @_;
36             return "SCALAR" unless ref ;
37             if ( ref eq "ARRAY" ) {
38             return undef unless @$_;
39             return ARRAY_of_scalars unless
40             _isnt_ARRAY_of_scalars;
41             return ARRAY_of_ARRAYs_of_scalars
42             unless grep _isnt_ARRAY_of_scalars, @$_;
43             return ARRAY_of_HASHes_of_scalars
44             unless grep _isnt_HASH_of_scalars, @$_;
45             return 0;
46             }
47             }
48              
49              
50             ## Flatten any acceptable data structure in to an array of lines.
51             sub _flatten {
52             my $type = shift;
53             local $_ = shift if @_;
54              
55             return [ split /^/m ] unless ref;
56              
57             croak "Can't flatten $_" unless $type ;
58              
59             ## Copy the top level array so we don't trash the originals
60             my @recs = @$_;
61              
62             if ( $type eq ARRAY_of_ARRAYs_of_scalars ) {
63             ## Also copy the inner arrays if need be
64             $_ = [ @$_ ] for @recs;
65             }
66              
67              
68             if ( $type eq ARRAY_of_HASHes_of_scalars ) {
69             my %headings;
70             for my $rec ( @recs ) {
71             $headings{$_} = 1 for keys %$rec;
72             }
73             my @headings = sort keys %headings;
74              
75             ## Convert all hashes in to arrays.
76             for my $rec ( @recs ) {
77             $rec = [ map $rec->{$_}, @headings ],
78             }
79              
80             unshift @recs, \@headings;
81              
82             $type = ARRAY_of_ARRAYs_of_scalars;
83             }
84              
85             if ( $type eq ARRAY_of_ARRAYs_of_scalars ) {
86             ## Convert undefs
87             for my $rec ( @recs ) {
88             for ( @$rec ) {
89             $_ = "" unless defined;
90             }
91             $rec = join ",", @$rec;
92             }
93             }
94              
95             return \@recs;
96             }
97              
98              
99             sub _identify_callers_test_package_of_choice {
100             ## This is called at each test in case Test::Differences was used before
101             ## the base testing modules.
102             ## First see if %INC tells us much of interest.
103             my $has_builder_pm = grep $_ eq "Test/Builder.pm", keys %INC;
104             my $has_test_pm = grep $_ eq "Test.pm", keys %INC;
105              
106             return "Test" if $has_test_pm && ! $has_builder_pm;
107             return "Test::Builder" if ! $has_test_pm && $has_builder_pm;
108              
109             if ( $has_test_pm && $has_builder_pm ) {
110             ## TODO: Look in caller's namespace for hints. For now, assume Builder.
111             ## This should only ever be an issue if multiple test suites end
112             ## up in memory at once.
113             return "Test::Builder";
114             }
115             }
116              
117              
118             my $warned_of_unknown_test_lib;
119              
120             sub eq_or_diff_text { $_[3] = { data_type => "text" }; goto &eq_or_diff; }
121             sub eq_or_diff_data { $_[3] = { data_type => "data" }; goto &eq_or_diff; }
122              
123             ## This string is a cheat: it's used to see if the two arrays of values
124             ## are identical. The stringified values are joined using this joint
125             ## and compared using eq. This is a deep equality comparison for
126             ## references and a shallow one for scalars.
127             my $joint = chr( 0 ) . "A" . chr( 1 );
128              
129             sub eq_or_diff {
130             my ( @vals, $name, $options );
131             $options = pop if @_ > 2 && ref $_[-1];
132             ( $vals[0], $vals[1], $name ) = @_;
133              
134             my $data_type;
135             $data_type = $options->{data_type} if $options;
136             $data_type ||= "text" unless ref $vals[0] || ref $vals[1];
137             $data_type ||= "data";
138              
139             my @widths;
140              
141             my @types = map _grok_type, @vals;
142              
143             my $dump_it = !$types[0] || !$types[1];
144              
145             if ( $dump_it ) {
146             require Data::Dumper;
147             local $Data::Dumper::Indent = 1;
148             local $Data::Dumper::Sortkeys = 1;
149             local $Data::Dumper::Purity = 0;
150             local $Data::Dumper::Terse = 1;
151             local $Data::Dumper::Deepcopy = 1;
152             local $Data::Dumper::Quotekeys = 0;
153             @vals = map
154             [ split /^/, Data::Dumper::Dumper( $_ ) ],
155             @vals;
156             }
157             else {
158             @vals = (
159             _flatten( $types[0], $vals[0] ),
160             _flatten( $types[1], $vals[1] )
161             );
162             }
163              
164             my $caller = caller;
165              
166             my $passed = join( $joint, @{$vals[0]} ) eq
167             join( $joint, @{$vals[1]} );
168              
169             my $diff;
170             unless ( $passed ) {
171             my $context;
172              
173             $context = $options->{context}
174             if exists $options->{context};
175              
176             $context = $dump_it ? 2**31 : grep( @$_ > 25, @vals ) ? 3 : 25
177             unless defined $context;
178              
179             confess "context must be an integer: '$context'\n"
180             unless $context =~ /\A\d+\z/;
181              
182             $diff = diff @vals, {
183             CONTEXT => $context,
184             STYLE => "Table",
185             FILENAME_A => "Got",
186             FILENAME_B => "Expected",
187             OFFSET_A => $data_type eq "text" ? 1 : 0,
188             OFFSET_B => $data_type eq "text" ? 1 : 0,
189             INDEX_LABEL => $data_type eq "text" ? "Ln" : "Elt",
190             };
191             chomp $diff;
192             $diff .= "\n";
193             }
194              
195             my $which = _identify_callers_test_package_of_choice;
196              
197             if ( $which eq "Test" ) {
198             @_ = $passed
199             ? ( "", "", $name )
200             : ( "\n$diff", "No differences", $name );
201             goto &Test::ok;
202             }
203             elsif ( $which eq "Test::Builder" ) {
204 1     1   1685 my $test = Test::Builder->new;
  1         2  
  1         70  
205             ## TODO: Call exported_to here? May not need to because the caller
206             ## should have imported something based on Test::Builder already.
207             $test->ok( $passed, $name );
208             $test->diag( $diff ) unless $passed;
209 1     1   6 }
  1         2  
  1         34  
210             else {
211 1     1   5 unless ( $warned_of_unknown_test_lib ) {
  1         1  
  1         71  
212 1     1   77622 Carp::cluck
  1         193194  
  1         276  
213             "Can't identify test lib in use, doesn't seem to be Test.pm or Test::Builder based\n";
214             $warned_of_unknown_test_lib = 1;
215 0 0   0   0 }
216 0         0 ## Play dumb and hope nobody notices the fool drooling in the corner
217             if ( $passed ) {
218             print "ok\n";
219             }
220             else {
221 0 0   0   0 $diff =~ s/^/# /gm;
222 0         0 print "not ok\n", $diff;
223             }
224             }
225 1     1   13 }
  1         2  
  1         51  
226 1     1   4  
  1         1  
  1         45  
227 1     1   10  
  1         3  
  1         7273  
228             #line 482
229              
230              
231 4 50   4   12 1;