File Coverage

blib/lib/Test/Recent.pm
Criterion Covered Total %
statement 74 75 98.6
branch 25 28 89.2
condition 7 10 70.0
subroutine 14 14 100.0
pod 2 2 100.0
total 122 129 94.5


line stmt bran cond sub pod time code
1             package Test::Recent;
2 3     3   87123 use 5.006;
  3         11  
  3         124  
3              
4 3     3   16 use base qw(Exporter);
  3         6  
  3         335  
5              
6 3     3   15 use strict;
  3         9  
  3         117  
7 3     3   3376 use Test::Builder::Tester;
  3         13016  
  3         20  
8              
9 3     3   5154 use DateTime;
  3         571625  
  3         277  
10 3     3   4273 use Time::Duration::Parse qw(parse_duration);
  3         9114  
  3         24  
11 3     3   3833 use DateTime::Format::ISO8601;
  3         182192  
  3         248  
12 3     3   44 use Scalar::Util qw(blessed);
  3         5  
  3         220  
13 3     3   21 use Carp qw(croak);
  3         8  
  3         167  
14              
15 3     3   37 use vars qw(@EXPORT_OK $VERSION $OverridedNowForTesting $RelativeTo);
  3         89  
  3         2895  
16              
17             $VERSION = "2.50";
18              
19             my $tester = Test::Builder->new();
20              
21             # utility regex
22             my $YMD = qr/[0-9]{4}-[0-9]{2}-[0-9]{2}/x;
23             my $HMS = qr/[0-9]{2}:[0-9]{2}:[0-9]{2}/x;
24             my $SUBSEC = qr/[0-9]+/x;
25             my $TZ = qr/[+-][0-9]{2}/x;
26             my $EPOCH = qr/\A \d+ (?:\.\d+)? \z/x;
27              
28             $Test::Recent::future_duration = DateTime::Duration->new( seconds => 0 );
29              
30             # convert anything that's passed to us into a DateTime object
31             sub _datetime($) {
32 57     57   100 my $str = shift;
33 57 50       104 return unless defined $str;
34 57 100 66     3620 return $str if blessed $str && $str->isa("DateTime");
35              
36             ###
37             # is this epoch seconds?
38             ###
39              
40 26 100       238 if ($str =~ $EPOCH) {
41 5         29 return DateTime->from_epoch( epoch => $str );
42             }
43              
44             ###
45             # munge common extra formats into ISO8601
46             ###
47              
48             # postgres
49 21         326 $str =~ s<\A ($YMD) [ ] ($HMS) [.] $SUBSEC ($TZ) \z><$1T$2$3>x;
50              
51 21         35 return eval { DateTime::Format::ISO8601->parse_datetime( $str ) }; ## no critic (RequireCheckingReturnValueOfEval)
  21         107  
52             }
53              
54             # work out what the time is now
55             sub _now() {
56             # people can override time!
57 53     53   73 my $now = $RelativeTo;
58 53 100       203 if (defined $now) {
59 14         27 $now = _datetime($now);
60 14 50       710 unless (defined $now) {
61 0         0 croak "\$Test::Recent::RelativeTo isn't parsable by Test::Recent";
62             }
63             }
64              
65             # historically we allowed $OverridedNowForTesting to be used to override
66             # the sense of time. If some muppet is still using this, let them
67 53 100       117 $now = $OverridedNowForTesting unless defined $now;
68              
69 53 50       101 $now = DateTime->now() unless defined $now;
70              
71 53         88 return $now;
72             }
73              
74             sub occured_within_ago($$) {
75 44     44 1 5554 my $value = shift;
76 44 100       126 return unless defined $value;
77              
78 43         108 my $time = _datetime($value);
79 43 100       9969 return unless defined $time;
80              
81             # forget the nanoseconds in the time passed to us. This is necessary
82             # because DateTime->now() doesn't return nanoseconds, so if we don't
83             # forget nanoseconds what is passed in might actually be mistaken
84             # for something in the future
85 42         148 $time = $time->clone->set_nanosecond(0);
86              
87 42         13433 my $durations = shift;
88 42         57 my ($past_duration, $future_duration);
89 42 100       114 if (ref $durations eq "ARRAY") {
90 4         5 ($past_duration, $future_duration) = @{ $durations };
  4         10  
91             } else {
92 38         64 ($past_duration, $future_duration)
93             = ($durations, $Test::Recent::future_duration);
94             }
95              
96 42         73 foreach my $duration ($past_duration, $future_duration) {
97 84 100 66     3769 unless (blessed $duration && $duration->isa("DateTime::Duration")) {
98 39         128 $duration = DateTime::Duration->new(
99             seconds => parse_duration($duration)
100             );
101             }
102             }
103              
104 42         528 my $now = _now;
105 42         155 my $ago = $now - $past_duration;
106 42         26157 my $ahead = $now + $future_duration;
107              
108 42 100       5443 return if $ahead < $time;
109 32 100       2888 return if $time < $ago;
110 26         2154 return 1;
111             }
112             push @EXPORT_OK, "occured_within_ago";
113              
114             sub recent ($;$$) {
115 11     11 1 7162 my $time = shift;
116 11   50     35 my $desc = pop || "recent time";
117 11   100     103 my $duration = shift || "10s";
118              
119             # work out when now is and "freeze it"
120 11         26 local $RelativeTo = _now; ## no critic (ProhibitMixedCaseVars)
121              
122 11         26 my $ok = occured_within_ago($time, $duration);
123 11         357 $tester->ok($ok, $desc);
124 11 100       3880 return 1 if $ok;
125 4         15 $tester->diag("$time not recent to $RelativeTo");
126 4         410 return;
127             }
128             push @EXPORT_OK, "recent";
129              
130             1;
131              
132             __END__