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  }