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  }