github.com/hernad/nomad@v1.6.112/nomad/structs/structs_periodic_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package structs 5 6 import ( 7 "fmt" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/hernad/nomad/ci" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func TestPeriodicConfig_DSTChange_Transitions(t *testing.T) { 18 ci.Parallel(t) 19 20 locName := "America/Los_Angeles" 21 loc, err := time.LoadLocation(locName) 22 require.NoError(t, err) 23 24 cases := []struct { 25 name string 26 pattern string 27 initTime time.Time 28 expected []time.Time 29 }{ 30 { 31 "normal time", 32 "0 2 * * * 2019", 33 time.Date(2019, time.February, 7, 1, 0, 0, 0, loc), 34 []time.Time{ 35 time.Date(2019, time.February, 7, 2, 0, 0, 0, loc), 36 time.Date(2019, time.February, 8, 2, 0, 0, 0, loc), 37 time.Date(2019, time.February, 9, 2, 0, 0, 0, loc), 38 }, 39 }, 40 { 41 "Spring forward but not in switch time", 42 "0 4 * * * 2019", 43 time.Date(2019, time.March, 9, 1, 0, 0, 0, loc), 44 []time.Time{ 45 time.Date(2019, time.March, 9, 4, 0, 0, 0, loc), 46 time.Date(2019, time.March, 10, 4, 0, 0, 0, loc), 47 time.Date(2019, time.March, 11, 4, 0, 0, 0, loc), 48 }, 49 }, 50 { 51 "Spring forward at a skipped time odd", 52 "2 2 * * * 2019", 53 time.Date(2019, time.March, 9, 1, 0, 0, 0, loc), 54 []time.Time{ 55 time.Date(2019, time.March, 9, 2, 2, 0, 0, loc), 56 // no time in March 10! 57 time.Date(2019, time.March, 11, 2, 2, 0, 0, loc), 58 time.Date(2019, time.March, 12, 2, 2, 0, 0, loc), 59 }, 60 }, 61 { 62 "Spring forward at a skipped time", 63 "1 2 * * * 2019", 64 time.Date(2019, time.March, 9, 1, 0, 0, 0, loc), 65 []time.Time{ 66 time.Date(2019, time.March, 9, 2, 1, 0, 0, loc), 67 // no time in March 8! 68 time.Date(2019, time.March, 11, 2, 1, 0, 0, loc), 69 time.Date(2019, time.March, 12, 2, 1, 0, 0, loc), 70 }, 71 }, 72 { 73 "Spring forward at a skipped time boundary", 74 "0 2 * * * 2019", 75 time.Date(2019, time.March, 9, 1, 0, 0, 0, loc), 76 []time.Time{ 77 time.Date(2019, time.March, 9, 2, 0, 0, 0, loc), 78 // no time in March 8! 79 time.Date(2019, time.March, 11, 2, 0, 0, 0, loc), 80 time.Date(2019, time.March, 12, 2, 0, 0, 0, loc), 81 }, 82 }, 83 { 84 "Spring forward at a boundary of repeating time", 85 "0 1 * * * 2019", 86 time.Date(2019, time.March, 9, 0, 0, 0, 0, loc), 87 []time.Time{ 88 time.Date(2019, time.March, 9, 1, 0, 0, 0, loc), 89 time.Date(2019, time.March, 10, 0, 0, 0, 0, loc).Add(1 * time.Hour), 90 time.Date(2019, time.March, 11, 1, 0, 0, 0, loc), 91 time.Date(2019, time.March, 12, 1, 0, 0, 0, loc), 92 }, 93 }, 94 { 95 "Fall back: before transition", 96 "30 0 * * * 2019", 97 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc), 98 []time.Time{ 99 time.Date(2019, time.November, 3, 0, 30, 0, 0, loc), 100 time.Date(2019, time.November, 4, 0, 30, 0, 0, loc), 101 time.Date(2019, time.November, 5, 0, 30, 0, 0, loc), 102 time.Date(2019, time.November, 6, 0, 30, 0, 0, loc), 103 }, 104 }, 105 { 106 "Fall back: after transition", 107 "30 3 * * * 2019", 108 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc), 109 []time.Time{ 110 time.Date(2019, time.November, 3, 3, 30, 0, 0, loc), 111 time.Date(2019, time.November, 4, 3, 30, 0, 0, loc), 112 time.Date(2019, time.November, 5, 3, 30, 0, 0, loc), 113 time.Date(2019, time.November, 6, 3, 30, 0, 0, loc), 114 }, 115 }, 116 { 117 "Fall back: after transition starting in repeated span before", 118 "30 3 * * * 2019", 119 time.Date(2019, time.November, 3, 0, 10, 0, 0, loc).Add(1 * time.Hour), 120 []time.Time{ 121 time.Date(2019, time.November, 3, 3, 30, 0, 0, loc), 122 time.Date(2019, time.November, 4, 3, 30, 0, 0, loc), 123 time.Date(2019, time.November, 5, 3, 30, 0, 0, loc), 124 time.Date(2019, time.November, 6, 3, 30, 0, 0, loc), 125 }, 126 }, 127 { 128 "Fall back: after transition starting in repeated span after", 129 "30 3 * * * 2019", 130 time.Date(2019, time.November, 3, 0, 10, 0, 0, loc).Add(2 * time.Hour), 131 []time.Time{ 132 time.Date(2019, time.November, 3, 3, 30, 0, 0, loc), 133 time.Date(2019, time.November, 4, 3, 30, 0, 0, loc), 134 time.Date(2019, time.November, 5, 3, 30, 0, 0, loc), 135 time.Date(2019, time.November, 6, 3, 30, 0, 0, loc), 136 }, 137 }, 138 { 139 "Fall back: in repeated region", 140 "30 1 * * * 2019", 141 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc), 142 []time.Time{ 143 time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(1 * time.Hour), 144 time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(2 * time.Hour), 145 time.Date(2019, time.November, 4, 1, 30, 0, 0, loc), 146 time.Date(2019, time.November, 5, 1, 30, 0, 0, loc), 147 time.Date(2019, time.November, 6, 1, 30, 0, 0, loc), 148 }, 149 }, 150 { 151 "Fall back: in repeated region boundary", 152 "0 1 * * * 2019", 153 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc), 154 []time.Time{ 155 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc).Add(1 * time.Hour), 156 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc).Add(2 * time.Hour), 157 time.Date(2019, time.November, 4, 1, 0, 0, 0, loc), 158 time.Date(2019, time.November, 5, 1, 0, 0, 0, loc), 159 time.Date(2019, time.November, 6, 1, 0, 0, 0, loc), 160 }, 161 }, 162 { 163 "Fall back: in repeated region boundary 2", 164 "0 2 * * * 2019", 165 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc), 166 []time.Time{ 167 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc).Add(3 * time.Hour), 168 time.Date(2019, time.November, 4, 2, 0, 0, 0, loc), 169 time.Date(2019, time.November, 5, 2, 0, 0, 0, loc), 170 time.Date(2019, time.November, 6, 2, 0, 0, 0, loc), 171 }, 172 }, 173 { 174 "Fall back: in repeated region, starting from within region", 175 "30 1 * * * 2019", 176 time.Date(2019, time.November, 3, 0, 40, 0, 0, loc).Add(1 * time.Hour), 177 []time.Time{ 178 time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(2 * time.Hour), 179 time.Date(2019, time.November, 4, 1, 30, 0, 0, loc), 180 time.Date(2019, time.November, 5, 1, 30, 0, 0, loc), 181 time.Date(2019, time.November, 6, 1, 30, 0, 0, loc), 182 }, 183 }, 184 { 185 "Fall back: in repeated region, starting from within region 2", 186 "30 1 * * * 2019", 187 time.Date(2019, time.November, 3, 0, 40, 0, 0, loc).Add(2 * time.Hour), 188 []time.Time{ 189 time.Date(2019, time.November, 4, 1, 30, 0, 0, loc), 190 time.Date(2019, time.November, 5, 1, 30, 0, 0, loc), 191 time.Date(2019, time.November, 6, 1, 30, 0, 0, loc), 192 }, 193 }, 194 { 195 "Fall back: wildcard", 196 "30 * * * * 2019", 197 time.Date(2019, time.November, 3, 0, 0, 0, 0, loc), 198 []time.Time{ 199 time.Date(2019, time.November, 3, 0, 30, 0, 0, loc), 200 time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(1 * time.Hour), 201 time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(2 * time.Hour), 202 time.Date(2019, time.November, 3, 2, 30, 0, 0, loc), 203 }, 204 }, 205 } 206 207 for _, c := range cases { 208 t.Run(c.name, func(t *testing.T) { 209 p := &PeriodicConfig{ 210 Enabled: true, 211 SpecType: PeriodicSpecCron, 212 Spec: c.pattern, 213 TimeZone: locName, 214 } 215 p.Canonicalize() 216 217 starting := c.initTime 218 for _, next := range c.expected { 219 n, err := p.Next(starting) 220 assert.NoError(t, err) 221 assert.Equalf(t, next, n, "next time of %v", starting) 222 223 starting = next 224 } 225 }) 226 } 227 } 228 229 func TestPeriodConfig_DSTSprintForward_Property(t *testing.T) { 230 ci.Parallel(t) 231 232 locName := "America/Los_Angeles" 233 loc, err := time.LoadLocation(locName) 234 require.NoError(t, err) 235 236 cronExprs := []string{ 237 "* * * * *", 238 "0 2 * * *", 239 "* 1 * * *", 240 } 241 242 times := []time.Time{ 243 // spring forward 244 time.Date(2019, time.March, 11, 0, 0, 0, 0, loc), 245 time.Date(2019, time.March, 10, 0, 0, 0, 0, loc), 246 time.Date(2019, time.March, 11, 0, 0, 0, 0, loc), 247 248 // leap backwards 249 time.Date(2019, time.November, 4, 0, 0, 0, 0, loc), 250 time.Date(2019, time.November, 5, 0, 0, 0, 0, loc), 251 time.Date(2019, time.November, 6, 0, 0, 0, 0, loc), 252 } 253 254 testSpan := 4 * time.Hour 255 256 testCase := func(t *testing.T, cronExpr string, init time.Time) { 257 p := &PeriodicConfig{ 258 Enabled: true, 259 SpecType: PeriodicSpecCron, 260 Spec: cronExpr, 261 TimeZone: "America/Los_Angeles", 262 } 263 p.Canonicalize() 264 265 lastNext := init 266 for start := init; start.Before(init.Add(testSpan)); start = start.Add(1 * time.Minute) { 267 next, err := p.Next(start) 268 require.NoError(t, err) 269 require.Truef(t, next.After(start), 270 "next(%v) = %v is not after init time", start, next) 271 272 if start.Before(lastNext) { 273 require.Equalf(t, lastNext, next, "next(%v) = %v is earlier than previously known next %v", 274 start, next, lastNext) 275 } 276 if strings.HasPrefix(cronExpr, "* * ") { 277 require.Equalf(t, next.Sub(start), 1*time.Minute, 278 "next(%v) = %v is the next minute", start, next) 279 } 280 281 lastNext = next 282 } 283 } 284 285 for _, cron := range cronExprs { 286 for _, startTime := range times { 287 t.Run(fmt.Sprintf("%v: %v", cron, startTime), func(t *testing.T) { 288 testCase(t, cron, startTime) 289 }) 290 } 291 } 292 }