github.com/rigado/snapd@v2.42.5-go-mod+incompatible/timeutil/schedule_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package timeutil_test 21 22 import ( 23 "strings" 24 "testing" 25 "time" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/timeutil" 30 ) 31 32 func Test(t *testing.T) { TestingT(t) } 33 34 type timeutilSuite struct{} 35 36 var _ = Suite(&timeutilSuite{}) 37 38 func (ts *timeutilSuite) TestClock(c *C) { 39 td := timeutil.Clock{Hour: 23, Minute: 59} 40 c.Check(td.Add(time.Minute), Equals, timeutil.Clock{Hour: 0, Minute: 0}) 41 42 td = timeutil.Clock{Hour: 5, Minute: 34} 43 c.Check(td.Add(time.Minute), Equals, timeutil.Clock{Hour: 5, Minute: 35}) 44 45 td = timeutil.Clock{Hour: 10, Minute: 1} 46 c.Check(td.Sub(timeutil.Clock{Hour: 10, Minute: 0}), Equals, time.Minute) 47 48 td = timeutil.Clock{Hour: 23, Minute: 0} 49 c.Check(td.Add(time.Hour), Equals, timeutil.Clock{Hour: 0, Minute: 0}) 50 c.Check(td.Add(2*time.Hour), Equals, timeutil.Clock{Hour: 1, Minute: 0}) 51 c.Check(td.Sub(timeutil.Clock{Hour: 1, Minute: 0}), Equals, 22*time.Hour) 52 c.Check(td.Sub(timeutil.Clock{Hour: 0, Minute: 0}), Equals, 23*time.Hour) 53 54 td = timeutil.Clock{Hour: 1, Minute: 0} 55 c.Check(td.Sub(timeutil.Clock{Hour: 23, Minute: 0}), Equals, -2*time.Hour) 56 c.Check(td.Sub(timeutil.Clock{Hour: 1, Minute: 0}), Equals, time.Duration(0)) 57 58 td = timeutil.Clock{Hour: 0, Minute: 0} 59 c.Check(td.Sub(timeutil.Clock{Hour: 23, Minute: 0}), Equals, -1*time.Hour) 60 c.Check(td.Sub(timeutil.Clock{Hour: 1, Minute: 0}), Equals, -23*time.Hour) 61 } 62 63 func (ts *timeutilSuite) TestParseClock(c *C) { 64 for _, t := range []struct { 65 timeStr string 66 hour, minute int 67 errStr string 68 }{ 69 {"8:59", 8, 59, ""}, 70 {"08:59", 8, 59, ""}, 71 {"12:00", 12, 0, ""}, 72 {"xx", 0, 0, `cannot parse "xx"`}, 73 {"11:61", 0, 0, `cannot parse "11:61"`}, 74 {"25:00", 0, 0, `cannot parse "25:00"`}, 75 } { 76 ti, err := timeutil.ParseClock(t.timeStr) 77 if t.errStr != "" { 78 c.Check(err, ErrorMatches, t.errStr) 79 } else { 80 c.Check(err, IsNil) 81 c.Check(ti.Hour, Equals, t.hour) 82 c.Check(ti.Minute, Equals, t.minute) 83 } 84 } 85 } 86 87 func (ts *timeutilSuite) TestScheduleString(c *C) { 88 for _, t := range []struct { 89 sched timeutil.Schedule 90 str string 91 }{ 92 { 93 timeutil.Schedule{ 94 ClockSpans: []timeutil.ClockSpan{ 95 {Start: timeutil.Clock{Hour: 13, Minute: 41}, End: timeutil.Clock{Hour: 14, Minute: 59}}}, 96 }, 97 "13:41-14:59", 98 }, { 99 timeutil.Schedule{ 100 ClockSpans: []timeutil.ClockSpan{ 101 {Start: timeutil.Clock{Hour: 13, Minute: 41}, End: timeutil.Clock{Hour: 14, Minute: 59}}, 102 }, 103 WeekSpans: []timeutil.WeekSpan{ 104 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}}, 105 }, 106 "mon,13:41-14:59", 107 }, { 108 timeutil.Schedule{ 109 ClockSpans: []timeutil.ClockSpan{ 110 {Start: timeutil.Clock{Hour: 13, Minute: 41}, End: timeutil.Clock{Hour: 14, Minute: 59}, Spread: true}}, 111 }, 112 "13:41~14:59", 113 }, { 114 timeutil.Schedule{ 115 ClockSpans: []timeutil.ClockSpan{ 116 {Start: timeutil.Clock{Hour: 6}, End: timeutil.Clock{Hour: 6}}}, 117 WeekSpans: []timeutil.WeekSpan{ 118 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Friday}}}, 119 }, 120 "mon-fri,06:00", 121 }, { 122 timeutil.Schedule{ 123 ClockSpans: []timeutil.ClockSpan{ 124 {Start: timeutil.Clock{Hour: 6}, End: timeutil.Clock{Hour: 6}}, 125 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 14}, Spread: true, Split: 2}}, 126 WeekSpans: []timeutil.WeekSpan{ 127 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Friday}}, 128 {Start: timeutil.Week{Weekday: time.Saturday}, End: timeutil.Week{Weekday: time.Saturday}}}, 129 }, 130 "mon-fri,sat,06:00,09:00~14:00/2", 131 }, { 132 timeutil.Schedule{ 133 ClockSpans: []timeutil.ClockSpan{ 134 {Start: timeutil.Clock{Hour: 6}, End: timeutil.Clock{Hour: 6}}}, 135 WeekSpans: []timeutil.WeekSpan{ 136 {Start: timeutil.Week{Weekday: time.Monday, Pos: 1}, End: timeutil.Week{Weekday: time.Friday, Pos: 1}}}, 137 }, 138 "mon1-fri1,06:00", 139 }, { 140 timeutil.Schedule{ 141 ClockSpans: []timeutil.ClockSpan{ 142 {Start: timeutil.Clock{Hour: 6}, End: timeutil.Clock{Hour: 6}}}, 143 WeekSpans: []timeutil.WeekSpan{ 144 {Start: timeutil.Week{Weekday: time.Monday, Pos: 5}, 145 End: timeutil.Week{Weekday: time.Monday, Pos: 5}}}, 146 }, 147 "mon5,06:00", 148 }, { 149 timeutil.Schedule{ 150 WeekSpans: []timeutil.WeekSpan{ 151 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}}, 152 }, 153 "mon", 154 }, { 155 timeutil.Schedule{ 156 ClockSpans: []timeutil.ClockSpan{ 157 {Start: timeutil.Clock{Hour: 6}, End: timeutil.Clock{Hour: 9}, Spread: true, Split: 2}}, 158 }, 159 "06:00~09:00/2", 160 }, 161 } { 162 c.Check(t.sched.String(), Equals, t.str) 163 } 164 } 165 166 func (ts *timeutilSuite) TestParseLegacySchedule(c *C) { 167 for _, t := range []struct { 168 in string 169 expected []*timeutil.Schedule 170 errStr string 171 }{ 172 // invalid 173 {"", nil, `cannot parse "": not a valid interval`}, 174 {"invalid-11:00", nil, `cannot parse "invalid": not a valid time`}, 175 {"9:00-11:00/invalid", nil, `cannot parse "invalid": not a valid interval`}, 176 {"09:00-25:00", nil, `cannot parse "25:00": not a valid time`}, 177 {"09:00-24:30", nil, `cannot parse "24:30": not a valid time`}, 178 179 // valid 180 {"9:00-11:00", []*timeutil.Schedule{ 181 {ClockSpans: []timeutil.ClockSpan{ 182 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}, Spread: true}}}, 183 }, ""}, 184 {"9:00-11:00/20:00-22:00", []*timeutil.Schedule{ 185 {ClockSpans: []timeutil.ClockSpan{ 186 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}, Spread: true}}}, 187 {ClockSpans: []timeutil.ClockSpan{ 188 {Start: timeutil.Clock{Hour: 20}, End: timeutil.Clock{Hour: 22}, Spread: true}}}, 189 }, ""}, 190 } { 191 c.Logf("trying: %v", t) 192 schedule, err := timeutil.ParseLegacySchedule(t.in) 193 if t.errStr != "" { 194 c.Check(err, ErrorMatches, t.errStr, Commentf("%q returned unexpected error: %s", t.in, err)) 195 } else { 196 c.Check(err, IsNil, Commentf("%q returned error: %s", t.in, err)) 197 c.Check(schedule, DeepEquals, t.expected, Commentf("%q failed", t.in)) 198 } 199 200 } 201 } 202 203 func parse(c *C, s string) (time.Duration, time.Duration) { 204 l := strings.Split(s, "-") 205 c.Assert(l, HasLen, 2) 206 a, err := time.ParseDuration(l[0]) 207 c.Assert(err, IsNil) 208 b, err := time.ParseDuration(l[1]) 209 c.Assert(err, IsNil) 210 return a, b 211 } 212 213 const ( 214 maxDuration = 60 * 24 * time.Hour 215 ) 216 217 func (ts *timeutilSuite) TestLegacyScheduleNext(c *C) { 218 const shortForm = "2006-01-02 15:04" 219 220 for _, t := range []struct { 221 schedule string 222 last string 223 now string 224 next string 225 }{ 226 { 227 // daily schedule, missed one window 228 // -> run next daily window 229 schedule: "9:00-11:00/21:00-23:00", 230 last: "2017-02-05 22:00", 231 now: "2017-02-06 20:00", 232 next: "1h-3h", 233 }, 234 { 235 // daily schedule, used one window 236 // -> run next daily window 237 schedule: "9:00-11:00/21:00-23:00", 238 last: "2017-02-06 10:00", 239 now: "2017-02-06 20:00", 240 next: "1h-3h", 241 }, 242 { 243 // daily schedule, missed all todays windows 244 // run tomorrow 245 schedule: "9:00-11:00/21:00-22:00", 246 last: "2017-02-04 21:30", 247 now: "2017-02-06 23:00", 248 next: "10h-12h", 249 }, 250 { 251 // single daily schedule, already updated today 252 schedule: "9:00-11:00", 253 last: "2017-02-06 09:30", 254 now: "2017-02-06 10:00", 255 next: "23h-25h", 256 }, 257 { 258 // single daily schedule, already updated today 259 // (at exactly the edge) 260 schedule: "9:00-11:00", 261 last: "2017-02-06 09:00", 262 now: "2017-02-06 09:00", 263 next: "24h-26h", 264 }, 265 { 266 // single daily schedule, last update a day ago 267 // now is within the update window so randomize 268 // (run within remaining time delta) 269 schedule: "9:00-11:00", 270 last: "2017-02-05 09:30", 271 now: "2017-02-06 10:00", 272 next: "0-55m", 273 }, 274 { 275 // multi daily schedule, already updated today 276 schedule: "9:00-11:00/21:00-22:00", 277 last: "2017-02-06 21:30", 278 now: "2017-02-06 23:00", 279 next: "10h-12h", 280 }, 281 { 282 // daily schedule, very small window 283 schedule: "9:00-9:03", 284 last: "2017-02-05 09:02", 285 now: "2017-02-06 08:58", 286 next: "2m-5m", 287 }, 288 { 289 // daily schedule, zero window 290 schedule: "9:00-9:00", 291 last: "2017-02-05 09:02", 292 now: "2017-02-06 08:58", 293 next: "2m-2m", 294 }, 295 } { 296 last, err := time.ParseInLocation(shortForm, t.last, time.Local) 297 c.Assert(err, IsNil) 298 299 fakeNow, err := time.ParseInLocation(shortForm, t.now, time.Local) 300 c.Assert(err, IsNil) 301 restorer := timeutil.MockTimeNow(func() time.Time { 302 return fakeNow 303 }) 304 defer restorer() 305 306 sched, err := timeutil.ParseLegacySchedule(t.schedule) 307 c.Assert(err, IsNil) 308 minDist, maxDist := parse(c, t.next) 309 310 next := timeutil.Next(sched, last, maxDuration) 311 c.Check(next >= minDist && next <= maxDist, Equals, true, Commentf("invalid distance for schedule %q with last refresh %q, now %q, expected %v, got %v", t.schedule, t.last, t.now, t.next, next)) 312 } 313 314 } 315 316 func (ts *timeutilSuite) TestParseSchedule(c *C) { 317 for _, t := range []struct { 318 in string 319 expected []*timeutil.Schedule 320 errStr string 321 }{ 322 // invalid 323 {"", nil, `cannot parse "": not a valid fragment`}, 324 {"invalid-11:00", nil, `cannot parse "invalid-11:00": not a valid time`}, 325 {"9:00-11:00/invalid", nil, `cannot parse "9:00-11:00/invalid": not a valid interval`}, 326 {"9:00-11:00/0", nil, `cannot parse "9:00-11:00/0": not a valid interval`}, 327 {"09:00-25:00", nil, `cannot parse "09:00-25:00": not a valid time`}, 328 {"09:00-24:30", nil, `cannot parse "09:00-24:30": not a valid time`}, 329 {"mon-01:00", nil, `cannot parse "mon-01:00": not a valid time`}, 330 {"9:00-mon@11:00", nil, `cannot parse "9:00-mon@11:00": not a valid time`}, 331 {"9:00,mon", nil, `cannot parse "mon": invalid schedule fragment`}, 332 {"mon~wed", nil, `cannot parse "mon~wed": "mon~wed" is not a valid weekday`}, 333 {"mon--wed", nil, `cannot parse "mon--wed": invalid week span`}, 334 {"mon-wed/2,,9:00", nil, `cannot parse "mon-wed/2": "wed/2" is not a valid weekday`}, 335 {"mon..wed", nil, `cannot parse "mon..wed": "mon..wed" is not a valid weekday`}, 336 {"mon9,9:00", nil, `cannot parse "mon9": "mon9" is not a valid weekday`}, 337 {"mon0,9:00", nil, `cannot parse "mon0": "mon0" is not a valid weekday`}, 338 {"mon5-mon1,9:00", nil, `cannot parse "mon5-mon1": unsupported schedule`}, 339 {"mon-mon2,9:00", nil, `cannot parse "mon-mon2": week number must be present for both weekdays or neither`}, 340 {"mon%,9:00", nil, `cannot parse "mon%": "mon%" is not a valid weekday`}, 341 {"foo2,9:00", nil, `cannot parse "foo2": "foo2" is not a valid weekday`}, 342 {"9:00---11:00", nil, `cannot parse "9:00---11:00": not a valid time`}, 343 {"9:00-11:00/3/3/3", nil, `cannot parse "9:00-11:00/3/3/3": not a valid interval`}, 344 {"9:00-11:00///3", nil, `cannot parse "9:00-11:00///3": not a valid interval`}, 345 {"9:00-9:00-10:00/3", nil, `cannot parse "9:00-9:00-10:00/3": not a valid time`}, 346 {"9:00,,,9:00-10:00/3", nil, `cannot parse ",9:00-10:00/3": not a valid fragment`}, 347 {",,,", nil, `cannot parse "": not a valid fragment`}, 348 {",,", nil, `cannot parse "": not a valid fragment`}, 349 {":", nil, `cannot parse ":": not a valid time`}, 350 {"-", nil, `cannot parse "-": "" is not a valid weekday`}, 351 {"-/4", nil, `cannot parse "-/4": "" is not a valid weekday`}, 352 {"~/4", nil, `cannot parse "~/4": "~/4" is not a valid weekday`}, 353 // valid 354 { 355 in: "9:00-11:00", 356 expected: []*timeutil.Schedule{{ 357 ClockSpans: []timeutil.ClockSpan{ 358 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}}}}}, 359 }, { 360 in: "9:00-11:00/2", 361 expected: []*timeutil.Schedule{{ 362 ClockSpans: []timeutil.ClockSpan{ 363 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}, Split: 2}}}}, 364 }, { 365 in: "mon,9:00-11:00", 366 expected: []*timeutil.Schedule{{ 367 ClockSpans: []timeutil.ClockSpan{ 368 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}}}, 369 WeekSpans: []timeutil.WeekSpan{ 370 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}}, 371 }}, 372 }, { 373 in: "fri,mon,9:00-11:00", 374 expected: []*timeutil.Schedule{{ 375 ClockSpans: []timeutil.ClockSpan{ 376 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}}}, 377 WeekSpans: []timeutil.WeekSpan{ 378 {Start: timeutil.Week{Weekday: time.Friday}, End: timeutil.Week{Weekday: time.Friday}}, 379 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}}, 380 }}, 381 }, { 382 in: "9:00-11:00,,20:00-22:00", 383 expected: []*timeutil.Schedule{{ 384 ClockSpans: []timeutil.ClockSpan{ 385 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}}}, 386 }, { 387 ClockSpans: []timeutil.ClockSpan{ 388 {Start: timeutil.Clock{Hour: 20}, End: timeutil.Clock{Hour: 22}}}}, 389 }, 390 }, { 391 in: "mon,9:00-11:00,,wed,22:00-23:00", 392 expected: []*timeutil.Schedule{{ 393 ClockSpans: []timeutil.ClockSpan{ 394 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}}}, 395 WeekSpans: []timeutil.WeekSpan{ 396 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}}, 397 }, { 398 ClockSpans: []timeutil.ClockSpan{ 399 {Start: timeutil.Clock{Hour: 22}, End: timeutil.Clock{Hour: 23}}}, 400 WeekSpans: []timeutil.WeekSpan{ 401 {Start: timeutil.Week{Weekday: time.Wednesday}, End: timeutil.Week{Weekday: time.Wednesday}}}, 402 }}, 403 }, { 404 in: "mon,9:00,10:00,14:00,15:00", 405 expected: []*timeutil.Schedule{{ 406 ClockSpans: []timeutil.ClockSpan{ 407 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 9}}, 408 {Start: timeutil.Clock{Hour: 10}, End: timeutil.Clock{Hour: 10}}, 409 {Start: timeutil.Clock{Hour: 14}, End: timeutil.Clock{Hour: 14}}, 410 {Start: timeutil.Clock{Hour: 15}, End: timeutil.Clock{Hour: 15}}}, 411 WeekSpans: []timeutil.WeekSpan{ 412 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}}, 413 }}, 414 }, { 415 in: "mon,wed", 416 expected: []*timeutil.Schedule{{ 417 WeekSpans: []timeutil.WeekSpan{ 418 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}, 419 {Start: timeutil.Week{Weekday: time.Wednesday}, End: timeutil.Week{Weekday: time.Wednesday}}}, 420 }}, 421 }, { 422 // same as above 423 in: "mon,,wed", 424 expected: []*timeutil.Schedule{{ 425 WeekSpans: []timeutil.WeekSpan{ 426 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Monday}}}, 427 }, { 428 WeekSpans: []timeutil.WeekSpan{ 429 {Start: timeutil.Week{Weekday: time.Wednesday}, End: timeutil.Week{Weekday: time.Wednesday}}}}, 430 }, 431 }, { 432 // but not the same as this one 433 in: "mon-wed", 434 expected: []*timeutil.Schedule{{ 435 WeekSpans: []timeutil.WeekSpan{ 436 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Wednesday}}}, 437 }}, 438 }, { 439 in: "mon-wed,fri,9:00-11:00/2", 440 expected: []*timeutil.Schedule{{ 441 ClockSpans: []timeutil.ClockSpan{ 442 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}, Split: 2}, 443 }, 444 WeekSpans: []timeutil.WeekSpan{ 445 {Start: timeutil.Week{Weekday: time.Monday}, End: timeutil.Week{Weekday: time.Wednesday}}, 446 {Start: timeutil.Week{Weekday: time.Friday}, End: timeutil.Week{Weekday: time.Friday}}, 447 }, 448 }}, 449 }, { 450 in: "9:00~11:00", 451 expected: []*timeutil.Schedule{{ 452 ClockSpans: []timeutil.ClockSpan{ 453 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 11}, Spread: true}}, 454 }}, 455 }, { 456 in: "9:00", 457 expected: []*timeutil.Schedule{{ 458 ClockSpans: []timeutil.ClockSpan{ 459 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 9}}}, 460 }}, 461 }, { 462 in: "mon1,9:00", 463 expected: []*timeutil.Schedule{{ 464 ClockSpans: []timeutil.ClockSpan{ 465 {Start: timeutil.Clock{Hour: 9}, End: timeutil.Clock{Hour: 9}}}, 466 WeekSpans: []timeutil.WeekSpan{ 467 {Start: timeutil.Week{Weekday: time.Monday, Pos: 1}, End: timeutil.Week{Weekday: time.Monday, Pos: 1}}}, 468 }}, 469 }, { 470 in: "00:00-24:00", 471 expected: []*timeutil.Schedule{{ 472 ClockSpans: []timeutil.ClockSpan{ 473 {Start: timeutil.Clock{Hour: 0}, End: timeutil.Clock{Hour: 24}}}, 474 }}, 475 }, { 476 in: "23:00-01:00", 477 expected: []*timeutil.Schedule{{ 478 ClockSpans: []timeutil.ClockSpan{ 479 {Start: timeutil.Clock{Hour: 23}, End: timeutil.Clock{Hour: 1}}, 480 }, 481 }}, 482 }, { 483 in: "fri-mon", 484 expected: []*timeutil.Schedule{{ 485 WeekSpans: []timeutil.WeekSpan{ 486 {Start: timeutil.Week{Weekday: time.Friday}, End: timeutil.Week{Weekday: time.Monday}}}, 487 }}, 488 }, 489 } { 490 c.Logf("trying %+v", t) 491 schedule, err := timeutil.ParseSchedule(t.in) 492 if t.errStr != "" { 493 c.Check(err, ErrorMatches, t.errStr, Commentf("%q returned unexpected error: %s", t.in, err)) 494 } else { 495 c.Check(err, IsNil, Commentf("%q returned error: %s", t.in, err)) 496 c.Check(schedule, DeepEquals, t.expected, Commentf("%q failed", t.in)) 497 } 498 } 499 } 500 501 func (ts *timeutilSuite) TestScheduleNext(c *C) { 502 const shortForm = "2006-01-02 15:04" 503 504 for _, t := range []struct { 505 schedule string 506 last string 507 now string 508 next string 509 randomized bool 510 }{ 511 { 512 schedule: "mon,10:00,,fri,15:00", 513 // sun 22:00 514 last: "2017-02-05 22:00", 515 // mon 9:00 516 now: "2017-02-06 9:00", 517 next: "1h-1h", 518 }, { 519 // first monday of the month, at 10:00 520 schedule: "mon1,10:00", 521 // Sun 22:00 522 last: "2017-02-05 22:00", 523 // Mon 9:00 524 now: "2017-02-06 9:00", 525 next: "1h-1h", 526 }, { 527 // first Monday of the month, at 10:00 528 schedule: "mon1,10:00", 529 // first Monday of the month, 10:00 530 last: "2017-02-06 10:00", 531 // first Monday of the month, 11:00, right after 532 // 'previous first Monday' run 533 now: "2017-02-06 11:00", 534 // expecting March, 6th, 10:00, 27 days and 23 hours 535 // from now 536 next: "671h-671h", 537 }, { 538 // second Monday of the month, at 10:00 539 schedule: "mon2,10:00", 540 // first Monday of the month, 10:00 541 last: "2017-02-06 10:00", 542 // first Monday of the month, 11:00, right after 543 // 'previous first Monday' run 544 now: "2017-02-06 11:00", 545 // expecting February, 13, 10:00, 6 days and 23 hours 546 // from now 547 next: "167h-167h", 548 }, { 549 // last Monday of the month, at 10:00 550 schedule: "mon5,10:00", 551 // first Monday of the month, 10:00 552 last: "2017-02-06 10:00", 553 // first Monday of the month, 11:00, right after 554 // 'previous first Monday' run 555 now: "2017-02-06 11:00", 556 // expecting February, 27th, 10:00, 20 days and 23 hours 557 // from now 558 next: "503h-503h", 559 }, { 560 // from the first Monday of the month to the second Tuesday of 561 // the month, at 10:00 562 schedule: "mon1-tue2,10:00", 563 // Monday, 10:00 564 last: "2017-02-06 10:00", 565 // Tuesday, the day after the first Monday of the month 566 now: "2017-02-07 11:00", 567 // expecting to run the next day at 10:00 568 next: "23h-23h", 569 }, { 570 // from the first Monday of the month to the second Tuesday of 571 // the month, at 10:00 572 schedule: "mon1-tue2,10:00", 573 last: "2017-02-01 10:00", 574 // Sunday, 10:00 575 now: "2017-02-05 10:00", 576 // expecting to run the next day at 10:00 577 next: "24h-24h", 578 }, { 579 // from the first Monday of the month to the second Tuesday of 580 // the month, at 10:00 581 schedule: "mon1-tue2,10:00", 582 // Tuesday, 10:00 583 last: "2017-02-14 22:00", 584 // Thursday, 10:00 585 now: "2017-02-16 10:00", 586 // expecting to run in 18 days 587 next: "432h-432h", 588 }, { 589 // from the first Monday of the month to the second Tuesday of 590 // the month, at 10:00 591 schedule: "mon1-tue2,10:00", 592 // Sunday, 22:00 593 last: "2017-02-05 22:00", 594 // first Monday of the month 595 now: "2017-02-06 11:00", 596 // expecting to run the next day at 10:00 597 next: "23h-23h", 598 }, { 599 // from the first Monday of the month to the second Tuesday of 600 // the month, at 10:00 601 schedule: "mon1-tue2,10:00-12:00", 602 // Sunday, 22:00 603 last: "2017-02-05 22:00", 604 // first Monday of the month, within the update window 605 now: "2017-02-06 11:00", 606 // expecting to run now 607 next: "0h-0h", 608 }, { 609 // from the first Monday of the month to the second Tuesday of 610 // the month, at 10:00 611 schedule: "mon1-tue2,10:00~12:00", 612 // Sunday, 22:00 613 last: "2017-02-05 22:00", 614 // first Monday of the month, within the update window 615 now: "2017-02-06 11:00", 616 // expecting to run now 617 next: "0h-1h", 618 // since we're in update window we'll run now regardless 619 // of 'spreading' 620 randomized: false, 621 }, { 622 schedule: "mon,10:00~12:00,,fri,15:00", 623 last: "2017-02-05 22:00", 624 now: "2017-02-06 9:00", 625 next: "1h-3h", 626 randomized: true, 627 }, { 628 schedule: "mon,10:00-12:00,,fri,15:00", 629 last: "2017-02-06 12:00", 630 // tue 12:00 631 now: "2017-02-07 12:00", 632 // 3 days and 3 hours from now 633 next: "75h-75h", 634 }, { 635 // randomized between 10:00 and 12:00 636 schedule: "mon,10:00~12:00", 637 // sun 22:00 638 last: "2017-02-05 22:00", 639 // mon 9:00 640 now: "2017-02-06 9:00", 641 next: "1h-3h", 642 randomized: true, 643 }, { 644 // Friday to Monday, 10am 645 schedule: "fri-mon,10:00", 646 // sun 22:00 647 last: "2017-02-05 22:00", 648 // mon 9:00 649 now: "2017-02-06 9:00", 650 next: "1h-1h", 651 }, { 652 // Friday to Monday, 10am 653 schedule: "fri-mon,10:00", 654 // mon 10:00 655 last: "2017-02-06 10:00", 656 // mon 10:00 657 now: "2017-02-06 10:00", 658 // 4 days from now 659 next: "96h-96h", 660 }, { 661 // Wednesday to Friday, 10am 662 schedule: "wed-fri,10:00", 663 // mon 10:00 664 last: "2017-02-06 10:00", 665 // mon 10:00 666 now: "2017-02-06 10:00", 667 // 2 days from now 668 next: "48h-48h", 669 }, { 670 // randomized, once a day 671 schedule: "0:00~24:00", 672 // sun 22:00 673 last: "2017-02-05 22:00", 674 // mon 9:00 675 now: "2017-02-05 23:00", 676 next: "1h-25h", 677 randomized: true, 678 }, { 679 // randomized, once a day 680 schedule: "0:00~24:00", 681 // mon 10:00 682 last: "2017-02-06 10:00", 683 // mon 11:00 684 now: "2017-02-06 11:00", 685 // sometime the next day 686 next: "13h-37h", 687 randomized: true, 688 }, { 689 // during the night, 23:00-1:00 690 schedule: "23:00~1:00", 691 // mon 10:00 692 last: "2017-02-06 10:00", 693 // mon 11:00 694 now: "2017-02-06 22:00", 695 // sometime over the night 696 next: "1h-3h", 697 randomized: true, 698 }, { 699 // during the night, 23:00-1:00 700 schedule: "23:00~1:00", 701 // Mon 23:00 702 last: "2017-02-06 23:00", 703 // Tue 0:00 704 now: "2017-02-07 00:00", 705 // sometime over the night 706 next: "23h-25h", 707 randomized: true, 708 }, { 709 // twice between 9am and 11am 710 schedule: "9:00-11:00/2", 711 // last attempt at the beginning of window 712 last: "2017-02-06 9:00", 713 // sometime between 10am and 11am 714 now: "2017-02-06 9:30", 715 next: "30m-90m", 716 }, { 717 // 2 ranges 718 schedule: "9:00-10:00,10:00-11:00", 719 // last attempt at the beginning of window 720 last: "2017-02-06 9:01", 721 // next one at 10am 722 now: "2017-02-06 9:30", 723 next: "30m-30m", 724 }, { 725 // twice, at 9am and at 2pm 726 schedule: "9:00,14:00", 727 // last right after scheduled time window 728 last: "2017-02-06 9:01", 729 // next one at 2pm 730 now: "2017-02-06 9:30", 731 next: "270m-270m", 732 }, { 733 // 2 ranges, reversed order in spec 734 schedule: "10:00~11:00,9:00-10:00", 735 // last attempt at the beginning of window 736 last: "2017-02-06 9:01", 737 // sometime between 10am and 11am 738 now: "2017-02-06 9:30", 739 next: "30m-90m", 740 randomized: true, 741 }, { 742 // first Wednesday at 13:00 743 schedule: "wed1,13:00", 744 now: "2018-07-30 9:00", 745 // yesterday 746 last: "2018-07-29 13:00", 747 // next one on 2018-08-01 13:00 748 next: "52h-52h", 749 }, 750 } { 751 c.Logf("trying %+v", t) 752 753 last, err := time.ParseInLocation(shortForm, t.last, time.Local) 754 c.Assert(err, IsNil) 755 756 fakeNow, err := time.ParseInLocation(shortForm, t.now, time.Local) 757 c.Assert(err, IsNil) 758 restorer := timeutil.MockTimeNow(func() time.Time { 759 return fakeNow 760 }) 761 defer restorer() 762 763 sched, err := timeutil.ParseSchedule(t.schedule) 764 c.Assert(err, IsNil) 765 766 // keep track of previous result for tests where event time is 767 // randomized 768 previous := time.Duration(0) 769 calls := 2 770 771 for i := 0; i < calls; i++ { 772 next := timeutil.Next(sched, last, maxDuration) 773 if t.randomized { 774 c.Check(next, Not(Equals), previous) 775 } else if previous != 0 { 776 // not randomized and not the first run 777 c.Check(next, Equals, previous) 778 } 779 780 c.Logf("next: %v", next) 781 minDist, maxDist := parse(c, t.next) 782 783 c.Check(next >= minDist && next <= maxDist, 784 Equals, true, 785 Commentf("invalid distance for schedule %q with last refresh %q, now %q, expected %v, got %v, date %s", 786 t.schedule, t.last, t.now, t.next, next, fakeNow.Add(next))) 787 previous = next 788 } 789 } 790 } 791 792 func (ts *timeutilSuite) TestScheduleIncludes(c *C) { 793 const shortForm = "2006-01-02 15:04:05" 794 795 for _, t := range []struct { 796 schedule string 797 now string 798 expecting bool 799 }{ 800 { 801 schedule: "mon,10:00,,fri,15:00", 802 // mon 9:00 803 now: "2017-02-06 9:00:00", 804 expecting: false, 805 }, { 806 // first monday of the month, at 10:00 807 schedule: "mon1,10:00", 808 // Mon 10:00:00 809 now: "2017-02-06 10:00:00", 810 expecting: true, 811 }, { 812 // first monday of the month, at 10:00 813 schedule: "mon1,10:00", 814 // Mon 10:00:45 815 now: "2017-02-06 10:00:45", 816 expecting: true, 817 }, { 818 // first monday of the month, at 10:00 819 schedule: "mon1,10:00", 820 // Mon 10:01 821 now: "2017-02-06 10:01:00", 822 expecting: false, 823 }, { 824 // last Monday of the month, at 10:00 825 schedule: "mon5,10:00-11:00", 826 // first Monday of the month, 11:00, right after 827 // 'previous first Monday' run 828 now: "2017-02-27 10:59:20", 829 expecting: true, 830 }, { 831 // from first Monday of the month to the second Tuesday of 832 // the month, at 10:00 to 12:00 833 schedule: "mon1-tue2,10:00-12:00", 834 // Thursday, 11:10 835 now: "2017-02-09 11:10:00", 836 expecting: true, 837 }, { 838 // from first Monday of the month to the second Tuesday of 839 // the month, at 10:00 to 12:00 840 schedule: "mon1-tue2,10:00~12:00", 841 // Thursday, 11:10 842 now: "2017-02-02 11:10:00", 843 expecting: false, 844 }, { 845 // from first Monday of the month to the second Tuesday of 846 // the month, at 10:00 to 12:00 847 schedule: "mon1-tue2,10:00~12:00", 848 // Monday, 11:10 849 now: "2017-02-06 11:10:00", 850 expecting: true, 851 }, { 852 // from first Monday of the month to the second Tuesday of 853 // the month, at 10:00 to 12:00 854 schedule: "mon1-tue2,10:00~12:00", 855 // Thursday, 11:10 856 now: "2017-02-16 11:10:00", 857 expecting: false, 858 }, { 859 // from first Monday of the month to the second Tuesday of 860 // the month, at 10:00 to 12:00 861 schedule: "mon1-tue2,10:00~12:00", 862 // Thursday, 11:10 863 now: "2017-02-16 11:10:00", 864 expecting: false, 865 }, { 866 // from first Monday of the month to the second Tuesday of 867 // the month, at 10:00 to 12:00 868 schedule: "mon1-tue2,10:00~12:00", 869 // Thursday, 11:10 870 now: "2017-02-09 11:10:00", 871 expecting: true, 872 }, { 873 // from first Tuesday of the month to the second Monday of 874 // the month, at 10:00 to 12:00 875 schedule: "tue1-mon2,10:00~12:00", 876 // Thursday, 11:10 877 now: "2017-02-09 11:10:00", 878 expecting: true, 879 }, { 880 // from 4th Monday of the month to the last Wednesday of 881 // the month, at 10:00 to 12:00 882 schedule: "mon4-wed5,10:00~12:00", 883 // Schedule ends up being Feb 20 - Feb 22 2017 884 now: "2017-03-01 11:10:00", 885 expecting: false, 886 }, { 887 // from 4th Monday of the month to the last Wednesday of 888 // the month, at 10:00 to 12:00 889 schedule: "mon4-wed5,10:00~12:00", 890 // Schedule ends up being Feb 20 - Feb 22 2017 891 now: "2017-02-23 11:10:00", 892 expecting: false, 893 }, { 894 // from last Monday of the month to the second Tuesday of 895 // the month, at 10:00 896 schedule: "mon1-tue2,10:00~12:00", 897 // Sunday, 11:10 898 now: "2017-02-05 11:10:00", 899 expecting: false, 900 }, { 901 // twice between 9am and 11am 902 schedule: "9:00-11:00/2", 903 now: "2017-02-06 10:30:00", 904 expecting: true, 905 }, { 906 schedule: "9:00-10:00,10:00-11:00", 907 now: "2017-02-06 10:30:00", 908 expecting: true, 909 }, { 910 // every day, 23:59 911 schedule: "23:59", 912 now: "2017-02-06 23:59:59", 913 expecting: true, 914 }, { 915 // 2 ranges, reversed order in spec 916 schedule: "10:00~11:00,9:00-10:00", 917 // sometime between 10am and 11am 918 now: "2017-02-06 9:30:00", 919 expecting: true, 920 }, 921 } { 922 c.Logf("trying %+v", t) 923 924 now, err := time.ParseInLocation(shortForm, t.now, time.Local) 925 c.Assert(err, IsNil) 926 927 sched, err := timeutil.ParseSchedule(t.schedule) 928 c.Assert(err, IsNil) 929 930 c.Check(timeutil.Includes(sched, now), Equals, t.expecting, 931 Commentf("unexpected result for schedule %v and time %v", t.schedule, now)) 932 } 933 } 934 935 func (ts *timeutilSuite) TestClockSpans(c *C) { 936 const shortForm = "2006-01-02 15:04:05" 937 938 for _, t := range []struct { 939 clockspan string 940 flattenend []string 941 }{ 942 { 943 clockspan: "23:00-01:00/2", 944 flattenend: []string{"23:00-00:00", "00:00-01:00"}, 945 }, { 946 clockspan: "23:00-01:00/4", 947 flattenend: []string{"23:00-23:30", "23:30-00:00", "00:00-00:30", "00:30-01:00"}, 948 }, 949 } { 950 c.Logf("trying %+v", t) 951 spans, err := timeutil.ParseClockSpan(t.clockspan) 952 c.Assert(err, IsNil) 953 954 spanStrings := make([]string, len(t.flattenend)) 955 flattened := spans.ClockSpans() 956 c.Assert(flattened, HasLen, len(t.flattenend)) 957 for i := range flattened { 958 spanStrings[i] = flattened[i].String() 959 } 960 961 c.Assert(spanStrings, DeepEquals, t.flattenend) 962 } 963 } 964 965 func (ts *timeutilSuite) TestWeekSpans(c *C) { 966 const shortForm = "2006-01-02" 967 968 // July 2018 August 2018 969 // Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa 970 // 1 2 3 4 5 6 7 1 2 3 4 971 // 8 9 10 11 12 13 14 5 6 7 8 9 10 11 972 // 15 16 17 18 19 20 21 12 13 14 15 16 17 18 973 // 22 23 24 25 26 27 28 19 20 21 22 23 24 25 974 // 29 30 31 26 27 28 29 30 31 975 976 for _, t := range []struct { 977 week string 978 when string 979 match bool 980 }{ 981 { 982 // first Wednesday 983 week: "wed1", 984 when: "2018-08-01", 985 match: true, 986 }, { 987 // first Wednesday 988 week: "wed1", 989 // actually 2nd Wednesday 990 when: "2018-08-08", 991 match: false, 992 }, { 993 // second Wednesday 994 week: "wed2", 995 when: "2018-08-08", 996 match: true, 997 }, { 998 // first Tuesday 999 week: "tue1", 1000 when: "2018-08-07", 1001 match: true, 1002 }, { 1003 // first Sunday 1004 week: "sun1", 1005 when: "2018-07-01", 1006 match: true, 1007 }, { 1008 // last Tuesday 1009 week: "tue5", 1010 when: "2018-07-31", 1011 match: true, 1012 }, { 1013 // last Tuesday 1014 week: "tue5", 1015 when: "2018-07-24", 1016 match: false, 1017 }, { 1018 // last Thursday 1019 week: "thu5", 1020 when: "2018-07-26", 1021 match: true, 1022 }, 1023 } { 1024 c.Logf("trying %+v", t) 1025 ws, err := timeutil.ParseWeekSpan(t.week) 1026 c.Assert(err, IsNil) 1027 1028 when, err := time.ParseInLocation(shortForm, t.when, time.Local) 1029 c.Assert(err, IsNil) 1030 c.Logf("when: %v %s", when, when.Weekday()) 1031 1032 c.Check(ws.Match(when), Equals, t.match) 1033 } 1034 }