github.com/songzhibin97/gkit@v1.2.13/distributed/schedule/parser_test.go (about)

     1  package schedule
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  )
     9  
    10  func TestRange(t *testing.T) {
    11  	zero := uint64(0)
    12  	ranges := []struct {
    13  		expr     string
    14  		min, max uint
    15  		expected uint64
    16  		err      string
    17  	}{
    18  		{"5", 0, 7, 1 << 5, ""},
    19  		{"0", 0, 7, 1 << 0, ""},
    20  		{"7", 0, 7, 1 << 7, ""},
    21  
    22  		{"5-5", 0, 7, 1 << 5, ""},
    23  		{"5-6", 0, 7, 1<<5 | 1<<6, ""},
    24  		{"5-7", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
    25  
    26  		{"5-6/2", 0, 7, 1 << 5, ""},
    27  		{"5-7/2", 0, 7, 1<<5 | 1<<7, ""},
    28  		{"5-7/1", 0, 7, 1<<5 | 1<<6 | 1<<7, ""},
    29  
    30  		{"*", 1, 3, 1<<1 | 1<<2 | 1<<3 | starBit, ""},
    31  		{"*/2", 1, 3, 1<<1 | 1<<3, ""},
    32  
    33  		{"5--5", 0, 0, zero, "too many hyphens"},
    34  		{"jan-x", 0, 0, zero, "failed to parse int from"},
    35  		{"2-x", 1, 5, zero, "failed to parse int from"},
    36  		{"*/-12", 0, 0, zero, "negative number"},
    37  		{"*//2", 0, 0, zero, "too many slashes"},
    38  		{"1", 3, 5, zero, "below minimum"},
    39  		{"6", 3, 5, zero, "above maximum"},
    40  		{"5-3", 3, 5, zero, "beyond end of range"},
    41  		{"*/0", 0, 0, zero, "should be a positive number"},
    42  	}
    43  
    44  	for _, c := range ranges {
    45  		actual, err := getRange(c.expr, bounds{c.min, c.max, nil})
    46  		if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
    47  			t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
    48  		}
    49  		if len(c.err) == 0 && err != nil {
    50  			t.Errorf("%s => unexpected error %v", c.expr, err)
    51  		}
    52  		if actual != c.expected {
    53  			t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
    54  		}
    55  	}
    56  }
    57  
    58  func TestField(t *testing.T) {
    59  	fields := []struct {
    60  		expr     string
    61  		min, max uint
    62  		expected uint64
    63  	}{
    64  		{"5", 1, 7, 1 << 5},
    65  		{"5,6", 1, 7, 1<<5 | 1<<6},
    66  		{"5,6,7", 1, 7, 1<<5 | 1<<6 | 1<<7},
    67  		{"1,5-7/2,3", 1, 7, 1<<1 | 1<<5 | 1<<7 | 1<<3},
    68  	}
    69  
    70  	for _, c := range fields {
    71  		actual, _ := getField(c.expr, bounds{c.min, c.max, nil})
    72  		if actual != c.expected {
    73  			t.Errorf("%s => expected %d, got %d", c.expr, c.expected, actual)
    74  		}
    75  	}
    76  }
    77  
    78  func TestAll(t *testing.T) {
    79  	allBits := []struct {
    80  		r        bounds
    81  		expected uint64
    82  	}{
    83  		{minutes, 0xfffffffffffffff}, // 0-59: 60 ones
    84  		{hours, 0xffffff},            // 0-23: 24 ones
    85  		{dom, 0xfffffffe},            // 1-31: 31 ones, 1 zero
    86  		{months, 0x1ffe},             // 1-12: 12 ones, 1 zero
    87  		{dow, 0x7f},                  // 0-6: 7 ones
    88  	}
    89  
    90  	for _, c := range allBits {
    91  		actual := all(c.r) // all() adds the starBit, so compensate for that..
    92  		if c.expected|starBit != actual {
    93  			t.Errorf("%d-%d/%d => expected %b, got %b",
    94  				c.r.min, c.r.max, 1, c.expected|starBit, actual)
    95  		}
    96  	}
    97  }
    98  
    99  func TestBits(t *testing.T) {
   100  	bits := []struct {
   101  		min, max, step uint
   102  		expected       uint64
   103  	}{
   104  		{0, 0, 1, 0x1},
   105  		{1, 1, 1, 0x2},
   106  		{1, 5, 2, 0x2a}, // 101010
   107  		{1, 4, 2, 0xa},  // 1010
   108  	}
   109  
   110  	for _, c := range bits {
   111  		actual := getBits(c.min, c.max, c.step)
   112  		if c.expected != actual {
   113  			t.Errorf("%d-%d/%d => expected %b, got %b",
   114  				c.min, c.max, c.step, c.expected, actual)
   115  		}
   116  	}
   117  }
   118  
   119  func TestParseScheduleErrors(t *testing.T) {
   120  	var tests = []struct{ expr, err string }{
   121  		{"* 5 j * * *", "failed to parse int from"},
   122  		{"@every Xm", "failed to parse duration"},
   123  		{"@unrecognized", "unrecognized descriptor"},
   124  		{"* * * *", "expected 5 to 6 fields"},
   125  		{"", "empty spec string"},
   126  	}
   127  	for _, c := range tests {
   128  		actual, err := secondParser.Parse(c.expr)
   129  		if err == nil || !strings.Contains(err.Error(), c.err) {
   130  			t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
   131  		}
   132  		if actual != nil {
   133  			t.Errorf("expected nil schedule on error, got %v", actual)
   134  		}
   135  	}
   136  }
   137  
   138  func TestParseSchedule(t *testing.T) {
   139  	tokyo, _ := time.LoadLocation("Asia/Tokyo")
   140  	entries := []struct {
   141  		parser   Parser
   142  		expr     string
   143  		expected Schedule
   144  	}{
   145  		{secondParser, "0 5 * * * *", every5min(time.Local)},
   146  		{standardParser, "5 * * * *", every5min(time.Local)},
   147  		{secondParser, "CRON_TZ=UTC  0 5 * * * *", every5min(time.UTC)},
   148  		{standardParser, "CRON_TZ=UTC  5 * * * *", every5min(time.UTC)},
   149  		{secondParser, "CRON_TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)},
   150  		{secondParser, "@every 5m", ConstantDelaySchedule{5 * time.Minute}},
   151  		{secondParser, "@midnight", midnight(time.Local)},
   152  		{secondParser, "TZ=UTC  @midnight", midnight(time.UTC)},
   153  		{secondParser, "TZ=Asia/Tokyo @midnight", midnight(tokyo)},
   154  		{secondParser, "@yearly", annual(time.Local)},
   155  		{secondParser, "@annually", annual(time.Local)},
   156  		{
   157  			parser: secondParser,
   158  			expr:   "* 5 * * * *",
   159  			expected: &SpecSchedule{
   160  				Second:   all(seconds),
   161  				Minute:   1 << 5,
   162  				Hour:     all(hours),
   163  				Dom:      all(dom),
   164  				Month:    all(months),
   165  				Dow:      all(dow),
   166  				Location: time.Local,
   167  			},
   168  		},
   169  	}
   170  
   171  	for _, c := range entries {
   172  		actual, err := c.parser.Parse(c.expr)
   173  		if err != nil {
   174  			t.Errorf("%s => unexpected error %v", c.expr, err)
   175  		}
   176  		if !reflect.DeepEqual(actual, c.expected) {
   177  			t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
   178  		}
   179  	}
   180  }
   181  
   182  func TestOptionalSecondSchedule(t *testing.T) {
   183  	parser := NewParse(WithInterval(SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor))
   184  
   185  	entries := []struct {
   186  		expr     string
   187  		expected Schedule
   188  	}{
   189  		{"0 5 * * * *", every5min(time.Local)},
   190  		{"5 5 * * * *", every5min5s(time.Local)},
   191  		{"5 * * * *", every5min(time.Local)},
   192  	}
   193  
   194  	for _, c := range entries {
   195  		actual, err := parser.Parse(c.expr)
   196  		if err != nil {
   197  			t.Errorf("%s => unexpected error %v", c.expr, err)
   198  		}
   199  		if !reflect.DeepEqual(actual, c.expected) {
   200  			t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
   201  		}
   202  	}
   203  }
   204  
   205  func TestNormalizeFields(t *testing.T) {
   206  	tests := []struct {
   207  		name     string
   208  		input    []string
   209  		options  Interval
   210  		expected []string
   211  	}{
   212  		{
   213  			"AllFields_NoOptional",
   214  			[]string{"0", "5", "*", "*", "*", "*"},
   215  			Second | Minute | Hour | Dom | Month | Dow | Descriptor,
   216  			[]string{"0", "5", "*", "*", "*", "*"},
   217  		},
   218  		{
   219  			"AllFields_SecondOptional_Provided",
   220  			[]string{"0", "5", "*", "*", "*", "*"},
   221  			SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor,
   222  			[]string{"0", "5", "*", "*", "*", "*"},
   223  		},
   224  		{
   225  			"AllFields_SecondOptional_NotProvided",
   226  			[]string{"5", "*", "*", "*", "*"},
   227  			SecondOptional | Minute | Hour | Dom | Month | Dow | Descriptor,
   228  			[]string{"0", "5", "*", "*", "*", "*"},
   229  		},
   230  		{
   231  			"SubsetFields_NoOptional",
   232  			[]string{"5", "15", "*"},
   233  			Hour | Dom | Month,
   234  			[]string{"0", "0", "5", "15", "*", "*"},
   235  		},
   236  		{
   237  			"SubsetFields_DowOptional_Provided",
   238  			[]string{"5", "15", "*", "4"},
   239  			Hour | Dom | Month | DowOptional,
   240  			[]string{"0", "0", "5", "15", "*", "4"},
   241  		},
   242  		{
   243  			"SubsetFields_DowOptional_NotProvided",
   244  			[]string{"5", "15", "*"},
   245  			Hour | Dom | Month | DowOptional,
   246  			[]string{"0", "0", "5", "15", "*", "*"},
   247  		},
   248  		{
   249  			"SubsetFields_SecondOptional_NotProvided",
   250  			[]string{"5", "15", "*"},
   251  			SecondOptional | Hour | Dom | Month,
   252  			[]string{"0", "0", "5", "15", "*", "*"},
   253  		},
   254  	}
   255  
   256  	for _, test := range tests {
   257  		t.Run(test.name, func(t *testing.T) {
   258  			actual, err := normalizeFields(test.input, test.options)
   259  			if err != nil {
   260  				t.Errorf("unexpected error: %v", err)
   261  			}
   262  			if !reflect.DeepEqual(actual, test.expected) {
   263  				t.Errorf("expected %v, got %v", test.expected, actual)
   264  			}
   265  		})
   266  	}
   267  }
   268  
   269  func TestNormalizeFields_Errors(t *testing.T) {
   270  	tests := []struct {
   271  		name    string
   272  		input   []string
   273  		options Interval
   274  		err     string
   275  	}{
   276  		{
   277  			"TwoOptionals",
   278  			[]string{"0", "5", "*", "*", "*", "*"},
   279  			SecondOptional | Minute | Hour | Dom | Month | DowOptional,
   280  			"",
   281  		},
   282  		{
   283  			"TooManyFields",
   284  			[]string{"0", "5", "*", "*"},
   285  			SecondOptional | Minute | Hour,
   286  			"",
   287  		},
   288  		{
   289  			"NoFields",
   290  			[]string{},
   291  			SecondOptional | Minute | Hour,
   292  			"",
   293  		},
   294  		{
   295  			"TooFewFields",
   296  			[]string{"*"},
   297  			SecondOptional | Minute | Hour,
   298  			"",
   299  		},
   300  	}
   301  	for _, test := range tests {
   302  		t.Run(test.name, func(t *testing.T) {
   303  			actual, err := normalizeFields(test.input, test.options)
   304  			if err == nil {
   305  				t.Errorf("expected an error, got none. results: %v", actual)
   306  			}
   307  			if !strings.Contains(err.Error(), test.err) {
   308  				t.Errorf("expected error %q, got %q", test.err, err.Error())
   309  			}
   310  		})
   311  	}
   312  }
   313  
   314  func TestStandardSpecSchedule(t *testing.T) {
   315  	entries := []struct {
   316  		expr     string
   317  		expected Schedule
   318  		err      string
   319  	}{
   320  		{
   321  			expr:     "5 * * * *",
   322  			expected: &SpecSchedule{1 << seconds.min, 1 << 5, all(hours), all(dom), all(months), all(dow), time.Local},
   323  		},
   324  		{
   325  			expr:     "@every 5m",
   326  			expected: ConstantDelaySchedule{time.Duration(5) * time.Minute},
   327  		},
   328  		{
   329  			expr: "5 j * * *",
   330  			err:  "failed to parse int from",
   331  		},
   332  		{
   333  			expr: "* * * *",
   334  			err:  "expected exactly 5 fields",
   335  		},
   336  	}
   337  
   338  	for _, c := range entries {
   339  		actual, err := NewParseWithStandard(c.expr)
   340  		if len(c.err) != 0 && (err == nil || !strings.Contains(err.Error(), c.err)) {
   341  			t.Errorf("%s => expected %v, got %v", c.expr, c.err, err)
   342  		}
   343  		if len(c.err) == 0 && err != nil {
   344  			t.Errorf("%s => unexpected error %v", c.expr, err)
   345  		}
   346  		if !reflect.DeepEqual(actual, c.expected) {
   347  			t.Errorf("%s => expected %b, got %b", c.expr, c.expected, actual)
   348  		}
   349  	}
   350  }
   351  
   352  func TestNoDescriptorParser(t *testing.T) {
   353  	parser := NewParse(WithInterval(Minute | Hour))
   354  	_, err := parser.Parse("@every 1m")
   355  	if err == nil {
   356  		t.Error("expected an error, got none")
   357  	}
   358  }
   359  
   360  func every5min(loc *time.Location) *SpecSchedule {
   361  	return &SpecSchedule{1 << 0, 1 << 5, all(hours), all(dom), all(months), all(dow), loc}
   362  }
   363  
   364  func every5min5s(loc *time.Location) *SpecSchedule {
   365  	return &SpecSchedule{1 << 5, 1 << 5, all(hours), all(dom), all(months), all(dow), loc}
   366  }
   367  
   368  func midnight(loc *time.Location) *SpecSchedule {
   369  	return &SpecSchedule{1, 1, 1, all(dom), all(months), all(dow), loc}
   370  }
   371  
   372  func annual(loc *time.Location) *SpecSchedule {
   373  	return &SpecSchedule{
   374  		Second:   1 << seconds.min,
   375  		Minute:   1 << minutes.min,
   376  		Hour:     1 << hours.min,
   377  		Dom:      1 << dom.min,
   378  		Month:    1 << months.min,
   379  		Dow:      all(dow),
   380  		Location: loc,
   381  	}
   382  }