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  }