open-nomad/nomad/structs/structs_periodic_test.go
2023-04-10 15:36:59 +00:00

293 lines
8.6 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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)
})
}
}
}