| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
=head1 NAME |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Test::Approx - compare two things for approximate equality |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use Test::Approx 'no_plan'; |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
is_approx( 'abcd', 'abcd', 'equal strings' ); |
|
10
|
|
|
|
|
|
|
is_approx( 1234, 1234, 'equal integers' ); |
|
11
|
|
|
|
|
|
|
is_approx( 1.234, 1.234, 'equal decimal numbers' ); |
|
12
|
|
|
|
|
|
|
is_approx( '1.234000', '1.234', 'equal decimal numbers, extra zeros' ); |
|
13
|
|
|
|
|
|
|
is_approx( 1.0, 1, 'equal decimal number & integer' ); |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
is_approx( 'abcdefgh', 'abcdefg', 'approx strings' ); |
|
16
|
|
|
|
|
|
|
is_approx( 1, 1.001, 'approx given decimal number & integer' ); |
|
17
|
|
|
|
|
|
|
is_approx( 51.60334, 51.603335, 'approx decimal numbers' ); |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# default Levenshtein edit tolerance is 5% of avg string length: |
|
20
|
|
|
|
|
|
|
is_approx( 'abcdefg', 'abcgfe', 'str tolerance' ); # fail |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# default difference tolerance is 5% of first number: |
|
23
|
|
|
|
|
|
|
is_approx( 1, 1.04, 'num tolerance' ); # fail |
|
24
|
|
|
|
|
|
|
is_approx( 1, 1.05, 'num tolerance' ); # fail |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# default difference tolerance is 5% of first integer, or 1: |
|
27
|
|
|
|
|
|
|
is_approx( 1, 2, 'int tolerance' ); # pass |
|
28
|
|
|
|
|
|
|
is_approx( 100, 105, 'int tolerance' ); # pass |
|
29
|
|
|
|
|
|
|
is_approx( 100, 106, 'int tolerance' ); # fail |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# you can set the tolerance yourself: |
|
32
|
|
|
|
|
|
|
is_approx( 'abcdefg', 'abcgfe', 'diff strings', '50%' ); # pass |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# you can set tolerance as a number too: |
|
35
|
|
|
|
|
|
|
is_approx( 'abcdefg', 'abcgfe', 'diff strings', 6 ); |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# you can force compare as string, number, or integer: |
|
38
|
|
|
|
|
|
|
is_approx_str( '1.001', '1.901', 'pass as string' ); |
|
39
|
|
|
|
|
|
|
is_approx_num( '1.001', '1.901', 'fail as num' ); |
|
40
|
|
|
|
|
|
|
is_approx_int( '1.001', '1.901', 'pass as int' ); # not rounded! |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=cut |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
package Test::Approx; |
|
45
|
|
|
|
|
|
|
|
|
46
|
1
|
|
|
1
|
|
1975
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
44
|
|
|
47
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
37
|
|
|
48
|
|
|
|
|
|
|
|
|
49
|
1
|
|
|
1
|
|
3171
|
use POSIX qw( strtod strtol ); |
|
|
1
|
|
|
|
|
34874
|
|
|
|
1
|
|
|
|
|
10
|
|
|
50
|
1
|
|
|
1
|
|
2341
|
use Text::LevenshteinXS qw(distance); |
|
|
1
|
|
|
|
|
4337
|
|
|
|
1
|
|
|
|
|
77
|
|
|
51
|
1
|
|
|
1
|
|
8
|
use Test::Builder; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
29
|
|
|
52
|
|
|
|
|
|
|
|
|
53
|
1
|
|
|
1
|
|
5
|
use base 'Exporter'; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
1240
|
|
|
54
|
|
|
|
|
|
|
our @EXPORT = qw( is_approx is_approx_str is_approx_num is_approx_int ); |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
our $VERSION = 0.03; |
|
57
|
|
|
|
|
|
|
our %DEFAULT_TOLERANCE = ( |
|
58
|
|
|
|
|
|
|
str => '5%', |
|
59
|
|
|
|
|
|
|
num => '5%', |
|
60
|
|
|
|
|
|
|
int => '5%', |
|
61
|
|
|
|
|
|
|
); |
|
62
|
|
|
|
|
|
|
our $Test = Test::Builder->new; |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
sub import { |
|
65
|
1
|
|
|
1
|
|
8
|
my $self = shift; |
|
66
|
1
|
|
|
|
|
3
|
my $pack = caller; |
|
67
|
|
|
|
|
|
|
|
|
68
|
1
|
|
|
|
|
7
|
$Test->exported_to($pack); |
|
69
|
1
|
50
|
|
|
|
11
|
$Test->plan(@_) if (@_); |
|
70
|
|
|
|
|
|
|
|
|
71
|
1
|
|
|
|
|
140
|
$self->export_to_level(1, $self, @EXPORT); |
|
72
|
|
|
|
|
|
|
} |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
sub check_type { |
|
75
|
32
|
|
|
32
|
0
|
40
|
my $arg = shift; |
|
76
|
|
|
|
|
|
|
|
|
77
|
32
|
|
|
|
|
89
|
local $! = 0; |
|
78
|
32
|
|
|
|
|
186
|
my ($num, $unparsed) = strtod( $arg ); |
|
79
|
32
|
100
|
66
|
|
|
262
|
return 'str' if (($arg eq '') || ($unparsed != 0) || $!); |
|
|
|
|
66
|
|
|
|
|
|
80
|
22
|
100
|
|
|
|
141
|
return 'num' if $num =~ /\.\d*\z/; |
|
81
|
|
|
|
|
|
|
|
|
82
|
14
|
|
|
|
|
41
|
return 'int'; |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub is_approx { |
|
86
|
16
|
|
|
16
|
1
|
21179
|
my ($arg1, $arg2, $msg, $tolerance) = @_; |
|
87
|
|
|
|
|
|
|
|
|
88
|
16
|
|
|
|
|
29
|
local $Test::Builder::Level = $Test::Builder::Level + 1; |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# clean input & avoid warnings |
|
91
|
16
|
50
|
|
|
|
39
|
$arg1 = '' unless defined $arg1; |
|
92
|
16
|
50
|
|
|
|
35
|
$arg2 = '' unless defined $arg2; |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# check inputs types and call appropriate sub |
|
95
|
16
|
|
|
|
|
36
|
my $arg1_type = check_type( $arg1 ); |
|
96
|
16
|
|
|
|
|
34
|
my $arg2_type = check_type( $arg2 ); |
|
97
|
|
|
|
|
|
|
|
|
98
|
16
|
100
|
|
|
|
63
|
if ($arg1_type eq 'int') { |
|
|
|
100
|
|
|
|
|
|
|
99
|
7
|
100
|
|
|
|
31
|
return is_approx_int( @_ ) if $arg2_type eq 'int'; |
|
100
|
1
|
50
|
|
|
|
7
|
return is_approx_num( @_ ) if $arg2_type eq 'num'; |
|
101
|
|
|
|
|
|
|
} elsif ($arg1_type eq 'num') { |
|
102
|
4
|
50
|
66
|
|
|
42
|
return is_approx_num( @_ ) |
|
103
|
|
|
|
|
|
|
if ($arg2_type eq 'int') or ($arg2_type eq 'num'); |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# default behaviour, compare as strings: |
|
107
|
5
|
|
|
|
|
12
|
return is_approx_str( @_ ); |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub is_approx_str { |
|
111
|
7
|
|
|
7
|
1
|
1643
|
my ($str1, $str2, $msg, $tolerance) = @_; |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# clean input & avoid warnings |
|
114
|
7
|
50
|
|
|
|
20
|
$str1 = '' unless defined $str1; |
|
115
|
7
|
50
|
|
|
|
16
|
$str2 = '' unless defined $str2; |
|
116
|
7
|
100
|
|
|
|
22
|
$tolerance = $DEFAULT_TOLERANCE{str} unless defined( $tolerance ); |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
# build some diagnostics info |
|
119
|
7
|
100
|
|
|
|
42
|
my $short1 = length($str1) > 8 ? substr($str1, 0, 5) . '...' : $str1; |
|
120
|
7
|
100
|
|
|
|
16
|
my $short2 = length($str2) > 8 ? substr($str2, 0, 5) . '...' : $str2; |
|
121
|
7
|
|
|
|
|
19
|
my $msg2 = "'$short1' =~ '$short2'"; |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# set default message |
|
124
|
7
|
50
|
|
|
|
16
|
$msg = $msg2 unless defined($msg); |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# figure out what to use as the threshold |
|
127
|
7
|
|
|
|
|
9
|
my $threshold; |
|
128
|
7
|
50
|
|
|
|
33
|
if ($tolerance =~ /^(.+)%$/) { |
|
129
|
|
|
|
|
|
|
# tolerance is a percentage |
|
130
|
7
|
|
|
|
|
21
|
my $percent = $1 / 100; |
|
131
|
|
|
|
|
|
|
# calculate threshold from a percentage: |
|
132
|
|
|
|
|
|
|
# x% of average string length, or 1 |
|
133
|
7
|
|
100
|
|
|
37
|
$threshold = int(( (length($str1)+length($str2))/2 )*$percent) || 1; |
|
134
|
|
|
|
|
|
|
} else { |
|
135
|
|
|
|
|
|
|
# tolerance is already a threshold |
|
136
|
0
|
|
|
|
|
0
|
$threshold = $tolerance; |
|
137
|
|
|
|
|
|
|
} |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# we've got a threshold, now do the test: |
|
141
|
7
|
|
|
|
|
64305
|
my $dist = distance($str1, $str2); |
|
142
|
7
|
100
|
|
|
|
36
|
unless ($Test->ok($dist <= $threshold, $msg)) { |
|
143
|
3
|
50
|
|
|
|
1617
|
$Test->diag(" test: $msg2") if ($msg ne $msg2); |
|
144
|
3
|
|
|
|
|
207
|
$Test->diag(" error: edit distance ($dist) was greater than threshold ($threshold)"); |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub is_approx_num { |
|
149
|
13
|
|
|
13
|
1
|
12417
|
my ($num1, $num2, $msg, $tolerance) = @_; |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# clean input & avoid warnings |
|
152
|
13
|
50
|
|
|
|
270
|
$num1 = strtod( defined $num1 ? $num1 : '' ); # ignore any errors |
|
153
|
13
|
50
|
|
|
|
76
|
$num2 = strtod( defined $num2 ? $num2 : '' ); # ignore any errors |
|
154
|
13
|
100
|
|
|
|
45
|
$tolerance = $DEFAULT_TOLERANCE{num} unless defined( $tolerance ); |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
# build some diagnostics info |
|
157
|
13
|
50
|
|
|
|
254
|
my $short1 = length($num1) > 8 ? substr($num1, 0, 5) . '...' : $num1; |
|
158
|
13
|
100
|
|
|
|
51
|
my $short2 = length($num2) > 8 ? substr($num2, 0, 5) . '...' : $num2; |
|
159
|
13
|
|
|
|
|
221
|
my $msg2 = "'$short1' =~ '$short2'"; |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# set default message |
|
162
|
13
|
50
|
|
|
|
31
|
$msg = $msg2 unless defined($msg); |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# figure out what to use as the threshold |
|
165
|
13
|
|
|
|
|
16
|
my $threshold; |
|
166
|
13
|
100
|
|
|
|
227
|
if ($tolerance =~ /^(.+)%$/) { |
|
167
|
|
|
|
|
|
|
# tolerance is a percentage |
|
168
|
10
|
|
|
|
|
28
|
my $percent = $1 / 100; |
|
169
|
|
|
|
|
|
|
# calculate threshold from a percentage: x% of num1 |
|
170
|
|
|
|
|
|
|
# strtod() to get around weird bug: |
|
171
|
|
|
|
|
|
|
# $dist = 0.05; $threshold = 0.05; $dist <= $threshold; # false ??!? |
|
172
|
10
|
|
|
|
|
227
|
$threshold = strtod( abs( $num1 * $percent ) ); |
|
173
|
|
|
|
|
|
|
} else { |
|
174
|
|
|
|
|
|
|
# tolerance is already a threshold |
|
175
|
3
|
|
|
|
|
5
|
$threshold = $tolerance; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# we've got a threshold, now do the test: |
|
179
|
|
|
|
|
|
|
# strtod() to get around weird bug: |
|
180
|
|
|
|
|
|
|
# $dist = 0.05; $threshold = 0.05; $dist <= $threshold; # false ??!? |
|
181
|
13
|
|
|
|
|
71
|
my $dist = strtod( abs($num2 - $num1) ); |
|
182
|
13
|
100
|
|
|
|
223
|
unless ($Test->ok($dist <= $threshold, $msg)) { |
|
183
|
3
|
50
|
|
|
|
1719
|
$Test->diag(" test: $msg2") if ($msg ne $msg2); |
|
184
|
3
|
|
|
|
|
237
|
$Test->diag(" error: distance ($dist) was greater than threshold ($threshold)"); |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub is_approx_int { |
|
189
|
14
|
|
|
14
|
1
|
3094
|
my ($int1, $int2, $msg, $tolerance) = @_; |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# clean input & avoid warnings |
|
192
|
14
|
50
|
|
|
|
9474
|
$int1 = strtol( defined $int1 ? $int1 : '' ); # ignore any errors |
|
193
|
14
|
50
|
|
|
|
67
|
$int2 = strtol( defined $int2 ? $int2 : '' ); # ignore any errors |
|
194
|
14
|
100
|
|
|
|
51
|
$tolerance = $DEFAULT_TOLERANCE{int} unless defined( $tolerance ); |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# build some diagnostics info |
|
197
|
14
|
50
|
|
|
|
40
|
my $short1 = length($int1) > 8 ? substr($int1, 0, 5) . '...' : $int1; |
|
198
|
14
|
50
|
|
|
|
29
|
my $short2 = length($int2) > 8 ? substr($int2, 0, 5) . '...' : $int2; |
|
199
|
14
|
|
|
|
|
34
|
my $msg2 = "'$short1' =~ '$short2'"; |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# set default message |
|
202
|
14
|
50
|
|
|
|
32
|
$msg = $msg2 unless defined($msg); |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# figure out what to use as the threshold |
|
205
|
14
|
|
|
|
|
15
|
my $threshold; |
|
206
|
14
|
100
|
|
|
|
74
|
if ($tolerance =~ /^(.+)%$/) { |
|
207
|
|
|
|
|
|
|
# tolerance is a percentage |
|
208
|
13
|
|
|
|
|
37
|
my $percent = $1 / 100; |
|
209
|
|
|
|
|
|
|
# calculate threshold from a percentage: x% of num1 || 1 |
|
210
|
|
|
|
|
|
|
# strtod() to get around weird bug: |
|
211
|
|
|
|
|
|
|
# $dist = 0.05; $threshold = 0.05; $dist <= $threshold; # false ??!? |
|
212
|
13
|
|
100
|
|
|
107
|
$threshold = strtod( abs( int( $int1 * $percent ) ) ) || 1; |
|
213
|
|
|
|
|
|
|
} else { |
|
214
|
|
|
|
|
|
|
# tolerance is already a threshold |
|
215
|
1
|
|
|
|
|
5
|
$threshold = $tolerance; |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# we've got a threshold, now do the test: |
|
219
|
|
|
|
|
|
|
# strtod() to get around weird bug: |
|
220
|
|
|
|
|
|
|
# $dist = 0.05; $threshold = 0.05; $dist <= $threshold; # false ??!? |
|
221
|
14
|
|
|
|
|
59
|
my $dist = strtod( abs($int2 - $int1) ); |
|
222
|
14
|
100
|
|
|
|
74
|
unless ($Test->ok($dist <= $threshold, $msg)) { |
|
223
|
3
|
50
|
|
|
|
1570
|
$Test->diag(" test: $msg2") if ($msg ne $msg2); |
|
224
|
3
|
|
|
|
|
209
|
$Test->diag(" error: distance ($dist) was greater than threshold ($threshold)"); |
|
225
|
|
|
|
|
|
|
} |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
1; |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
__END__ |