File Coverage

blib/lib/Test/Metrics/Any.pm
Criterion Covered Total %
statement 55 55 100.0
branch 8 8 100.0
condition n/a
subroutine 16 16 100.0
pod 5 6 83.3
total 84 85 98.8


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2020 -- leonerd@leonerd.org.uk
5              
6             package Test::Metrics::Any;
7              
8 3     3   146336 use strict;
  3         17  
  3         86  
9 3     3   15 use warnings;
  3         5  
  3         80  
10 3     3   15 use base qw( Test::Builder::Module );
  3         5  
  3         399  
11              
12 3     3   1346 use Metrics::Any::Adapter 'Test';
  3         1068  
  3         16  
13 3     3   1458 use Metrics::Any::Adapter::Test; # Eager load
  3         2602  
  3         1962  
14              
15             our $VERSION = '0.01';
16              
17             our @EXPORT = qw(
18             is_metrics
19             is_metrics_from
20             );
21              
22             =head1 NAME
23              
24             C - assert that code produces metrics via L
25              
26             =head1 SYNOPSIS
27              
28             use Test::More;
29             use Test::Metrics::Any;
30              
31             use Module::Under::Test;
32              
33             is_metrics_from(
34             sub { Module::Under::Test::do_a_thing for 1 .. 5 },
35             {
36             things_done => 5,
37             time_taken => Test::Metrics::Any::positive,
38             },
39             'do_a_thing reported some metrics'
40             );
41              
42             done_testing;
43              
44             =head1 DESCRIPTION
45              
46             This test module helps write unit tests which assert that the code under test
47             reports metrics via L.
48              
49             Loading this module automatically sets the L type to
50             C.
51              
52             =cut
53              
54             =head1 FUNCTIONS
55              
56             =cut
57              
58             =head2 is_metrics
59              
60             is_metrics( \%metrics, $name )
61              
62             Asserts that the current value of every metric named in the given hash
63             reference is set to the value provided. Values can either be given as exact
64             numbers, or by one of the match functions mentioned in L.
65              
66             Key names in the given hash should match the name format used by
67             L. Name components are joined by underscores, and
68             any label tags are appended with spaces, as C.
69              
70             {
71             "a_basic_metric" => 123,
72             "a_labelled_metric label:here" => 456,
73             }
74              
75             This function only checks the values of metrics actually mentioned in the hash
76             given as its argument. It is not a failure for more metrics to have been
77             reported by the code under test than are mentioned in the hash. This helps to
78             ensure that new metrics added in code do not break existing tests that weren't
79             set up to expect them.
80              
81             =cut
82              
83             sub is_metrics
84             {
85 9     9 1 20 my ( $expect, $testname ) = @_;
86 9         35 my $tb = __PACKAGE__->builder;
87              
88 9         89 my %got = map { ( split m/\s*=\s*/, $_ )[0,1] } split m/\n/, Metrics::Any::Adapter::Test->metrics;
  9         195  
89              
90 9         33 foreach my $name ( sort keys %$expect ) {
91 9         27 my $expectval = $expect->{$name};
92              
93 9         15 my $gotval = $got{$name};
94 9 100       48 unless( defined $gotval ) {
95 1         4 my $ret = $tb->ok( 0, $testname );
96 1         1060 $tb->diag( "Expected a metric called '$name' but didn't find one" );
97 1         252 return $ret;
98             }
99              
100 8 100       28 if( ref $expectval eq "Test::Metrics::Any::_predicate" ) {
101 6 100       13 unless( $expectval->check( $gotval ) ) {
102 3         9 my $ret = $tb->ok( 0, $testname );
103 3         2992 $tb->diag( "Expected metric '$name' to be ${\$expectval->message} but got $gotval" );
  3         7  
104 3         692 return $ret;
105             }
106             }
107             else {
108 2 100       7 unless( $gotval == $expectval ) {
109 1         7 my $ret = $tb->ok( 0, $testname );
110 1         935 $tb->diag( "Expected metric '$name' to be $expectval but got $gotval" );
111 1         227 return $ret;
112             }
113             }
114             }
115              
116 4         20 return $tb->ok( 1, $testname );
117             }
118              
119             =head2 is_metrics_from
120              
121             is_metrics_from( $code, \%metrics, $name )
122              
123             Asserts the value of metrics reported by running the given piece of code.
124              
125             The metrics in the test adapter are cleared, then the code is invoked, then
126             any metrics are checked in the same manner as L.
127              
128             =cut
129              
130             sub is_metrics_from(&@)
131             {
132 9     9 1 6935 my ( $code, $expect, $testname ) = @_;
133              
134 9         40 Metrics::Any::Adapter::Test->clear;
135              
136 9         71 $code->();
137              
138 9         446 local $Test::Builder::Level = $Test::Builder::Level + 1;
139 9         24 return is_metrics( $expect, $testname );
140             }
141              
142             =head1 PREDICATES
143              
144             As an alternative to expecting exact values for metrics, the following test
145             functions can be provided instead to assert that the metric is behaving
146             sensibly without needing to be an exact value. This could be useful for
147             example when the exact number of bytes or timing measures can vary between
148             test runs or platforms.
149              
150             These predicates are not exported but must be invoked fully-qualified.
151              
152             =cut
153              
154 6     6 0 33 sub predicate { return bless [ @_ ], "Test::Metrics::Any::_predicate" }
155             {
156             package Test::Metrics::Any::_predicate;
157 6     6   9 sub check { my $self = shift; $self->[1]->( shift ) }
  6         33  
158 3     3   5 sub message { my $self = shift; $self->[0] }
  3         15  
159             }
160              
161             =head2 positive
162              
163             metric => Test::Metrics::Any::positive
164              
165             Asserts that the number is greater than zero. It must not be zero.
166              
167             =cut
168              
169 2     2 1 12 sub positive { predicate positive => sub { shift > 0 } }
  2     2   1588  
170              
171             =head2 at_least
172              
173             metric => Test::Metrics::Any::at_least( $n )
174              
175             Asserts that the number at least that given - it can be equal or greater.
176              
177             =cut
178              
179 2     2 1 7 sub at_least { my ($n) = @_; predicate "at least $n" => sub { shift >= $n } }
  2     2   2371  
  2         8  
180              
181             =head2 greater_than
182              
183             metric => Test::Metrics::Any::greater_than( $n )
184              
185             Asserts that the number is greater than that given - it must not be equal.
186              
187             =cut
188              
189 2     2 1 8 sub greater_than { my ($n) = @_; predicate "greater than $n" => sub { shift > $n } }
  2     2   2369  
  2         9  
190              
191             =head1 AUTHOR
192              
193             Paul Evans
194              
195             =cut
196              
197             0x55AA;