go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/scheduler/appengine/schedule/schedule_test.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package schedule 16 17 import ( 18 "testing" 19 "time" 20 21 . "github.com/smartystreets/goconvey/convey" 22 ) 23 24 var ( 25 epoch = parseTime("2015-09-14 22:42:00 +0000 UTC") 26 closestMidnight = parseTime("2015-09-15 00:00:00 +0000 UTC") 27 ) 28 29 func parseTime(t string) time.Time { 30 tm, err := time.Parse("2006-01-02 15:04:05.999 -0700 MST", t) 31 if err != nil { 32 panic(err) 33 } 34 return tm 35 } 36 37 func timeTable(cron string, now time.Time, count int) (out []time.Time) { 38 s, err := Parse(cron, 0) 39 if err != nil { 40 panic(err) 41 } 42 prev := time.Time{} 43 for ; count > 0; count-- { 44 next := s.Next(now, prev) 45 out = append(out, next) 46 prev = next 47 now = next.Add(time.Millisecond) 48 } 49 return 50 } 51 52 func TestAbsoluteSchedule(t *testing.T) { 53 t.Parallel() 54 55 Convey("Parsing success", t, func() { 56 sched, err := Parse("* * * * * *", 0) 57 So(err, ShouldBeNil) 58 So(sched.String(), ShouldEqual, "* * * * * *") 59 }) 60 61 Convey("Parsing error", t, func() { 62 sched, err := Parse("not a schedule", 0) 63 So(err, ShouldNotBeNil) 64 So(sched, ShouldBeNil) 65 }) 66 67 Convey("Next works", t, func() { 68 sched, _ := Parse("*/15 * * * * * *", 0) 69 So(sched.IsAbsolute(), ShouldBeTrue) 70 So(sched.Next(epoch, time.Time{}), ShouldResemble, epoch.Add(15*time.Second)) 71 So(sched.Next(epoch.Add(15*time.Second), epoch), ShouldResemble, epoch.Add(30*time.Second)) 72 }) 73 74 Convey("Each 3 hours time table", t, func() { 75 So(timeTable("0 */3 * * * *", epoch, 4), ShouldResemble, []time.Time{ 76 closestMidnight, 77 closestMidnight.Add(3 * time.Hour), 78 closestMidnight.Add(6 * time.Hour), 79 closestMidnight.Add(9 * time.Hour), 80 }) 81 So(timeTable("0 1/3 * * * *", epoch, 4), ShouldResemble, []time.Time{ 82 closestMidnight.Add(1 * time.Hour), 83 closestMidnight.Add(4 * time.Hour), 84 closestMidnight.Add(7 * time.Hour), 85 closestMidnight.Add(10 * time.Hour), 86 }) 87 }) 88 89 Convey("Trailing stars are optional", t, func() { 90 // Exact same time tables. 91 So(timeTable("0 */3 * * *", epoch, 4), ShouldResemble, timeTable("0 */3 * * * *", epoch, 4)) 92 }) 93 94 Convey("List of hours", t, func() { 95 So(timeTable("0 2,10,18 * * *", epoch, 4), ShouldResemble, []time.Time{ 96 closestMidnight.Add(2 * time.Hour), 97 closestMidnight.Add(10 * time.Hour), 98 closestMidnight.Add(18 * time.Hour), 99 closestMidnight.Add(26 * time.Hour), 100 }) 101 }) 102 103 Convey("Once a day", t, func() { 104 So(timeTable("0 7 * * *", epoch, 4), ShouldResemble, []time.Time{ 105 closestMidnight.Add(7 * time.Hour), 106 closestMidnight.Add(31 * time.Hour), 107 closestMidnight.Add(55 * time.Hour), 108 closestMidnight.Add(79 * time.Hour), 109 }) 110 }) 111 } 112 113 func TestRelativeSchedule(t *testing.T) { 114 t.Parallel() 115 116 Convey("Parsing success", t, func() { 117 sched, err := Parse("with 15s interval", 0) 118 So(err, ShouldBeNil) 119 So(sched.String(), ShouldEqual, "with 15s interval") 120 }) 121 122 Convey("Parsing error", t, func() { 123 sched, err := Parse("with bladasdafier", 0) 124 So(err, ShouldNotBeNil) 125 So(sched, ShouldBeNil) 126 127 sched, err = Parse("with -1s interval", 0) 128 So(err, ShouldNotBeNil) 129 So(sched, ShouldBeNil) 130 131 sched, err = Parse("with NaNs interval", 0) 132 So(err, ShouldNotBeNil) 133 So(sched, ShouldBeNil) 134 }) 135 136 Convey("Next works", t, func() { 137 sched, _ := Parse("with 15s interval", 0) 138 So(sched.IsAbsolute(), ShouldBeFalse) 139 140 // First tick is pseudorandom. 141 So(sched.Next(epoch, time.Time{}), ShouldResemble, epoch.Add(14*time.Second+177942239*time.Nanosecond)) 142 143 // Next tick is 15s from prev one, or now if it's too late. 144 So(sched.Next(epoch.Add(16*time.Second), epoch.Add(15*time.Second)), ShouldResemble, epoch.Add(30*time.Second)) 145 So(sched.Next(epoch.Add(31*time.Second), epoch.Add(15*time.Second)), ShouldResemble, epoch.Add(31*time.Second)) 146 }) 147 }