File Coverage

blib/lib/Test/Approximate.pm
Criterion Covered Total %
statement 69 73 94.5
branch 19 26 73.0
condition 8 15 53.3
subroutine 14 14 100.0
pod 2 3 66.6
total 112 131 85.5


line stmt bran cond sub pod time code
1             package Test::Approximate;
2             # ABSTRACT: Test and deeply test two number is approximate equality
3 3     3   40136 use strict;
  3         6  
  3         112  
4 3     3   13 use warnings;
  3         5  
  3         128  
5              
6             our $VERSION = 0.009;
7              
8 3     3   1406 use POSIX qw( strtod );
  3         15413  
  3         14  
9 3     3   2421 use Test::Builder;
  3         5  
  3         102  
10             our $Test = Test::Builder->new;
11              
12 3     3   9 use base 'Exporter';
  3         3  
  3         1985  
13             our @EXPORT = qw( is_approx approx );
14              
15             our $DEFAULT_TOLERANCE = '1%';
16              
17             sub is_approx {
18 6     6 1 2093 my ( $got, $expected, $msg, $tolerance ) = @_;
19              
20 6   33     12 $tolerance //= $DEFAULT_TOLERANCE;
21              
22             # build some diagnostics info
23 6 50       23 my $short1 = length($got) > 12 ? substr($got, 0, 8) . '...' : $got;
24 6 50       13 my $short2 = length($expected) > 12 ? substr($expected, 0, 8) . '...' : $expected;
25 6         16 my $msg2 = "'$short1' =~ '$short2'";
26              
27             #set default message
28 6 50       12 $msg = $msg2 unless defined($msg);
29              
30 6 100       7 unless ( $Test->ok(_is_approx($got, $expected, $tolerance), $msg) ) {
31 1         374 $Test->diag(" test: $msg");
32              
33 1 50 33     50 if ( check_type($got) eq 'str' or check_type($expected) eq 'str' ) {
34 0         0 $Test->diag(" error: diff between string\n got: $got\nexpected: $expected");
35 0         0 return;
36             }
37 1         3 my $diff = $got - $expected;
38 1 50       5 if ( $tolerance =~ /^(.+)%$/ ) {
39 1         4 my $percentage = ( $diff / $expected ) * 100;
40 1         9 $Test->diag(" error: diff $percentage% is not under tolerance $tolerance");
41             }
42             else {
43 0         0 $Test->diag(" error: diff $diff is not under tolerance $tolerance");
44             }
45             }
46              
47             }
48              
49             sub _is_approx {
50 27     27   29 my ( $num1, $num2, $tolerance ) = @_;
51              
52             # borrowed form Test::Approx
53 27         36 my $num1_type = check_type($num1);
54 27         41 my $num2_type = check_type($num2);
55              
56 27 100 66     86 if ( $num1_type eq 'str' or $num2_type eq 'str' ) {
57 3         10 return $num1 eq $num2;
58             }
59             # figure out what to use as the threshold
60 24         16 my $threshold;
61 24 50       109 if ( $tolerance =~ /^(.+)%$/ ) {
62 24         47 my $percent = $1 / 100;
63              
64 24         130 $threshold = strtod( abs( $num1 * $percent ) );
65             } else {
66 0         0 $threshold = $tolerance;
67             }
68              
69 24         81 my $dist = strtod( abs($num2 - $num1) );
70 24 100       79 return $dist <= $threshold ? 1 : 0;
71             }
72              
73             # borrowed from Test::Approx
74             sub check_type {
75 56     56 0 42 my $arg = shift;
76              
77 56         97 local $! = 0;
78 56         216 my ( $num, $unparsed ) = strtod($arg);
79 56 100 66     299 return 'str' if ( ($arg eq '') || ($unparsed != 0) || $! );
      66        
80 50         87 return 'num';
81             }
82              
83             # deeply test approx
84             sub approx {
85 29     29 1 618 my ( $structure, $torlerance ) = @_;
86              
87 29 100       78 if ( ref $structure eq '' ) { # value
    100          
    50          
88 22         62 return Test::Deep::Approximate->new($structure, $torlerance);
89             }
90             elsif ( ref $structure eq ref {} ) { # hash
91              
92 2         4 my $hash = {};
93 2         8 foreach my $key ( keys %$structure ) {
94 6         43 $hash->{$key} = approx($structure->{$key}, $torlerance);
95             }
96 2         15 return $hash;
97             }
98             elsif ( ref $structure eq ref [] ) { # array
99              
100 5         6 my $array = [];
101 5         10 for my $item ( @$structure ) {
102 18         61 push @$array, approx($item, $torlerance);
103             }
104 5         36 return $array;
105             }
106             }
107              
108             {
109             package Test::Deep::Approximate;
110 3     3   1329 use Test::Deep::Cmp;
  3         3672  
  3         11  
111              
112             sub init {
113 22     22   104 my ( $self, $expect, $torlerance ) = @_;
114              
115 22         85 $self->{expect} = $expect;
116 22         38 $self->{torlerance} = $torlerance;
117             }
118              
119             sub _is_approx {
120 21     21   15 shift;
121 21         29 return Test::Approximate::_is_approx(@_);
122             }
123              
124             sub descend {
125 21     21   15213 my ( $self, $got ) = @_;
126              
127 21         42 return $self->_is_approx($got, $self->{expect}, $self->{torlerance});
128             }
129              
130             sub diagnostics {
131 2     2   926 my ( $self, $where, $last ) = @_;
132              
133 2         3 my $got = $last->{got};
134 2         9 my $diag = <
135             Comparing $where
136             got : $got
137             expected : $self->{expect}
138             EOM
139 2         4 return $diag;
140             }
141              
142             }
143              
144             1;
145              
146             __END__