b3ea68948b
Go 1.19 will forecefully format all your doc strings. To get this out of the way, here is one big commit with all the changes gofmt wants to make.
290 lines
8.5 KiB
Go
290 lines
8.5 KiB
Go
package structs
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPeriodicConfig_DSTChange_Transitions(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
locName := "America/Los_Angeles"
|
|
loc, err := time.LoadLocation(locName)
|
|
require.NoError(t, err)
|
|
|
|
cases := []struct {
|
|
name string
|
|
pattern string
|
|
initTime time.Time
|
|
expected []time.Time
|
|
}{
|
|
{
|
|
"normal time",
|
|
"0 2 * * * 2019",
|
|
time.Date(2019, time.February, 7, 1, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.February, 7, 2, 0, 0, 0, loc),
|
|
time.Date(2019, time.February, 8, 2, 0, 0, 0, loc),
|
|
time.Date(2019, time.February, 9, 2, 0, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Spring forward but not in switch time",
|
|
"0 4 * * * 2019",
|
|
time.Date(2019, time.March, 9, 1, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.March, 9, 4, 0, 0, 0, loc),
|
|
time.Date(2019, time.March, 10, 4, 0, 0, 0, loc),
|
|
time.Date(2019, time.March, 11, 4, 0, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Spring forward at a skipped time odd",
|
|
"2 2 * * * 2019",
|
|
time.Date(2019, time.March, 9, 1, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.March, 9, 2, 2, 0, 0, loc),
|
|
// no time in March 10!
|
|
time.Date(2019, time.March, 11, 2, 2, 0, 0, loc),
|
|
time.Date(2019, time.March, 12, 2, 2, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Spring forward at a skipped time",
|
|
"1 2 * * * 2019",
|
|
time.Date(2019, time.March, 9, 1, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.March, 9, 2, 1, 0, 0, loc),
|
|
// no time in March 8!
|
|
time.Date(2019, time.March, 11, 2, 1, 0, 0, loc),
|
|
time.Date(2019, time.March, 12, 2, 1, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Spring forward at a skipped time boundary",
|
|
"0 2 * * * 2019",
|
|
time.Date(2019, time.March, 9, 1, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.March, 9, 2, 0, 0, 0, loc),
|
|
// no time in March 8!
|
|
time.Date(2019, time.March, 11, 2, 0, 0, 0, loc),
|
|
time.Date(2019, time.March, 12, 2, 0, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Spring forward at a boundary of repeating time",
|
|
"0 1 * * * 2019",
|
|
time.Date(2019, time.March, 9, 0, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.March, 9, 1, 0, 0, 0, loc),
|
|
time.Date(2019, time.March, 10, 0, 0, 0, 0, loc).Add(1 * time.Hour),
|
|
time.Date(2019, time.March, 11, 1, 0, 0, 0, loc),
|
|
time.Date(2019, time.March, 12, 1, 0, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: before transition",
|
|
"30 0 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 0, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 4, 0, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 0, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 0, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: after transition",
|
|
"30 3 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 4, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 3, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: after transition starting in repeated span before",
|
|
"30 3 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 10, 0, 0, loc).Add(1 * time.Hour),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 4, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 3, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: after transition starting in repeated span after",
|
|
"30 3 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 10, 0, 0, loc).Add(2 * time.Hour),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 4, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 3, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 3, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: in repeated region",
|
|
"30 1 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(1 * time.Hour),
|
|
time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(2 * time.Hour),
|
|
time.Date(2019, time.November, 4, 1, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 1, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 1, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: in repeated region boundary",
|
|
"0 1 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc).Add(1 * time.Hour),
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc).Add(2 * time.Hour),
|
|
time.Date(2019, time.November, 4, 1, 0, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 1, 0, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 1, 0, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: in repeated region boundary 2",
|
|
"0 2 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc).Add(3 * time.Hour),
|
|
time.Date(2019, time.November, 4, 2, 0, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 2, 0, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 2, 0, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: in repeated region, starting from within region",
|
|
"30 1 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 40, 0, 0, loc).Add(1 * time.Hour),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(2 * time.Hour),
|
|
time.Date(2019, time.November, 4, 1, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 1, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 1, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: in repeated region, starting from within region 2",
|
|
"30 1 * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 40, 0, 0, loc).Add(2 * time.Hour),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 4, 1, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 1, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 1, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
{
|
|
"Fall back: wildcard",
|
|
"30 * * * * 2019",
|
|
time.Date(2019, time.November, 3, 0, 0, 0, 0, loc),
|
|
[]time.Time{
|
|
time.Date(2019, time.November, 3, 0, 30, 0, 0, loc),
|
|
time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(1 * time.Hour),
|
|
time.Date(2019, time.November, 3, 0, 30, 0, 0, loc).Add(2 * time.Hour),
|
|
time.Date(2019, time.November, 3, 2, 30, 0, 0, loc),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
p := &PeriodicConfig{
|
|
Enabled: true,
|
|
SpecType: PeriodicSpecCron,
|
|
Spec: c.pattern,
|
|
TimeZone: locName,
|
|
}
|
|
p.Canonicalize()
|
|
|
|
starting := c.initTime
|
|
for _, next := range c.expected {
|
|
n, err := p.Next(starting)
|
|
assert.NoError(t, err)
|
|
assert.Equalf(t, next, n, "next time of %v", starting)
|
|
|
|
starting = next
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPeriodConfig_DSTSprintForward_Property(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
locName := "America/Los_Angeles"
|
|
loc, err := time.LoadLocation(locName)
|
|
require.NoError(t, err)
|
|
|
|
cronExprs := []string{
|
|
"* * * * *",
|
|
"0 2 * * *",
|
|
"* 1 * * *",
|
|
}
|
|
|
|
times := []time.Time{
|
|
// spring forward
|
|
time.Date(2019, time.March, 11, 0, 0, 0, 0, loc),
|
|
time.Date(2019, time.March, 10, 0, 0, 0, 0, loc),
|
|
time.Date(2019, time.March, 11, 0, 0, 0, 0, loc),
|
|
|
|
// leap backwards
|
|
time.Date(2019, time.November, 4, 0, 0, 0, 0, loc),
|
|
time.Date(2019, time.November, 5, 0, 0, 0, 0, loc),
|
|
time.Date(2019, time.November, 6, 0, 0, 0, 0, loc),
|
|
}
|
|
|
|
testSpan := 4 * time.Hour
|
|
|
|
testCase := func(t *testing.T, cronExpr string, init time.Time) {
|
|
p := &PeriodicConfig{
|
|
Enabled: true,
|
|
SpecType: PeriodicSpecCron,
|
|
Spec: cronExpr,
|
|
TimeZone: "America/Los_Angeles",
|
|
}
|
|
p.Canonicalize()
|
|
|
|
lastNext := init
|
|
for start := init; start.Before(init.Add(testSpan)); start = start.Add(1 * time.Minute) {
|
|
next, err := p.Next(start)
|
|
require.NoError(t, err)
|
|
require.Truef(t, next.After(start),
|
|
"next(%v) = %v is not after init time", start, next)
|
|
|
|
if start.Before(lastNext) {
|
|
require.Equalf(t, lastNext, next, "next(%v) = %v is earlier than previously known next %v",
|
|
start, next, lastNext)
|
|
}
|
|
if strings.HasPrefix(cronExpr, "* * ") {
|
|
require.Equalf(t, next.Sub(start), 1*time.Minute,
|
|
"next(%v) = %v is the next minute", start, next)
|
|
}
|
|
|
|
lastNext = next
|
|
}
|
|
}
|
|
|
|
for _, cron := range cronExprs {
|
|
for _, startTime := range times {
|
|
t.Run(fmt.Sprintf("%v: %v", cron, startTime), func(t *testing.T) {
|
|
testCase(t, cron, startTime)
|
|
})
|
|
}
|
|
}
|
|
}
|