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