readme.dev: s/needed/required/ packages
[pve-common.git] / test / calendar_event_test.pl
1 #!/usr/bin/perl
2
3 use lib '../src';
4 use strict;
5 use warnings;
6 use POSIX ();
7 use Data::Dumper;
8 use Time::Local;
9 use Test::More;
10
11 use PVE::CalendarEvent;
12
13 # Time tests should run in a controlled setting
14 $ENV{TZ} = 'UTC';
15 POSIX::tzset();
16
17 my $alldays = [0,1,2,3,4,5,6];
18 my $tests = [
19     [
20      '*',
21      { h => '*', m => '*', dow => $alldays },
22      [
23       [0, 60],
24       [30, 60],
25       [59, 60],
26       [60, 120],
27      ]
28     ],
29     [
30      '*/10',
31      { h => '*', m => [0, 10, 20, 30, 40, 50], dow => $alldays },
32      [
33       [0, 600],
34       [599, 600],
35       [600, 1200],
36       [50*60, 60*60]
37      ]
38     ],
39     [
40      '*/12:0' ,
41      { h => [0, 12], m => [0], dow => $alldays },
42      [
43       [ 10, 43200],
44       [ 13*3600, 24*3600],
45      ]
46     ],
47     [
48      '1/12:0/15' ,
49      { h => [1, 13], m => [0, 15, 30, 45], dow => $alldays },
50      [
51       [0, 3600],
52       [3600, 3600+15*60],
53       [3600+16*60, 3600+30*60 ],
54       [3600+30*60, 3600+45*60 ],
55       [3600+45*60, 3600+12*3600],
56       [13*3600 + 1, 13*3600+15*60],
57       [13*3600 + 15*60, 13*3600+30*60],
58       [13*3600 + 30*60, 13*3600+45*60],
59       [13*3600 + 45*60, 25*3600],
60      ],
61     ],
62     [
63      '1,4,6',
64      { h => '*', m => [1, 4, 6], dow => $alldays},
65      [
66       [0, 60],
67       [60, 4*60],
68       [4*60+60, 6*60],
69       [6*60, 3600+60],
70      ]
71     ],
72     [
73      '0..3',
74      { h => '*', m => [ 0, 1, 2, 3 ], dow => $alldays },
75     ],
76     [
77      '23..23:0..3',
78      { h => [ 23 ], m => [ 0, 1, 2, 3 ], dow => $alldays },
79     ],
80     [
81      'Mon',
82      { h => [0], m => [0], dow => [1] },
83      [
84       [0, 4*86400], # Note: Epoch 0 is Thursday, 1. January 1970
85       [4*86400, 11*86400],
86       [11*86400, 18*86400],
87      ],
88     ],
89     [
90      'sat..sun',
91      { h => [0], m => [0], dow => [0, 6] },
92      [
93       [0, 2*86400],
94       [2*86400, 3*86400],
95       [3*86400, 9*86400],
96      ]
97     ],
98     [
99      'sun..sat',
100      { h => [0], m => [0], dow => $alldays },
101     ],
102     [
103      'Fri..Mon',
104      { error => "wrong order in range 'Fri..Mon'" },
105     ],
106     [
107      'wed,mon..tue,fri',
108      { h => [0], m => [0], dow => [ 1, 2, 3, 5] },
109     ],
110     [
111      'mon */15',
112      { h => '*', m =>  [0, 15, 30, 45], dow => [1]},
113     ],
114     [
115     '22/1:0',
116      { h => [22, 23], m => [0], dow => $alldays },
117      [
118         [0, 22*60*60],
119         [22*60*60, 23*60*60],
120         [22*60*60 + 59*60, 23*60*60]
121      ],
122     ],
123     [
124      '*/2:*',
125      { h => [0,2,4,6,8,10,12,14,16,18,20,22], m => '*', dow => $alldays },
126      [
127         [0, 60],
128         [60*60, 2*60*60],
129         [2*60*60, 2*60*60 + 60]
130      ]
131     ],
132     [
133      '20..22:*/30',
134      { h => [20,21,22], m => [0,30], dow => $alldays },
135      [
136         [0, 20*60*60],
137         [20*60*60, 20*60*60 + 30*60],
138         [22*60*60 + 30*60, 44*60*60]
139      ]
140     ],
141     [
142      '61',
143      { error => "value '61' out of range" },
144     ],
145     [
146      '*/61',
147      { error => "repetition '61' out of range" },
148     ],
149     [
150      '0..80',
151      { error => "range end '80' out of range" },
152     ],
153     [
154      ' mon 0 0 0',
155      { error => "unable to parse calendar event - unused parts" },
156     ],
157     [
158      '',
159      { error => "unable to parse calendar event - event is empty" },
160     ],
161     [
162      ' mon 0 0',
163      { error => "unable to parse calendar event - unused parts" },
164     ],
165     [
166      '0,1,3..5',
167      { h => '*', m => [0,1,3,4,5], dow => $alldays },
168      [
169         [0, 60],
170         [60, 3*60],
171         [5*60, 60*60]
172      ]
173     ],
174     [
175      '2,4:0,1,3..5',
176      { h => [2,4], m => [0,1,3,4,5], dow => $alldays },
177      [
178         [0, 2*60*60],
179         [2*60*60 + 60, 2*60*60 + 3*60],
180         [2*60*60 + 5*60, 4*60*60]
181      ]
182     ],
183 ];
184
185 foreach my $test (@$tests) {
186     my ($t, $expect, $nextsync) = @$test;
187
188     my $timespec;
189     eval { $timespec = PVE::CalendarEvent::parse_calendar_event($t); };
190     my $err = $@;
191     delete $timespec->{utc};
192
193     if ($expect->{error}) {
194         chomp $err if $err;
195         $timespec = { error => $err } if $err;
196         is_deeply($timespec, $expect, "expect parse error on '$t' - $expect->{error}");
197         die "unable to execute nextsync tests" if $nextsync;
198     } else {
199         is_deeply($timespec, $expect, "parse '$t'");
200     }
201
202     next if !$nextsync;
203
204     foreach my $nt (@$nextsync) {
205         my ($last, $expect_next) = @$nt;
206         my $msg = "next event '$t' $last => ${expect_next}";
207         $timespec->{utc} = 1;
208         my $next = PVE::CalendarEvent::compute_next_event($timespec, $last);
209         is($next, $expect_next, $msg);
210     }
211 };
212
213 sub tztest {
214     my ($calspec, $last) = @_;
215     my $spec = PVE::CalendarEvent::parse_calendar_event($calspec);
216     return PVE::CalendarEvent::compute_next_event($spec, $last);
217 }
218
219 # Test loop termination at CEST/CET switch (cannot happen here in UTC)
220 is(tztest('mon..fri', timelocal(0, 0, 0, 28, 9, 2018)),
221                       timelocal(0, 0, 0, 29, 9, 2018));
222 is(tztest('mon..fri UTC', timelocal(0, 0, 0, 28, 9, 2018)),
223                           timelocal(0, 0, 0, 29, 9, 2018));
224
225 # Now in the affected time zone
226 $ENV{TZ} = ':Europe/Vienna';
227 POSIX::tzset();
228 is(tztest('mon..fri', timelocal(0, 0, 0, 28, 9, 2018)),
229                       timelocal(0, 0, 0, 29, 9, 2018));
230 # Specifically requesting UTC in the calendar spec means the resulting output
231 # time as seen locally (timelocal() as opposed to timegm()) is shifted by 1
232 # hour.
233 is(tztest('mon..fri UTC', timelocal(0, 0, 0, 28, 9, 2018)),
234                           timelocal(0, 0, 1, 29, 9, 2018));
235 $ENV{TZ} = 'UTC';
236 POSIX::tzset();
237
238 done_testing();