github.com/isyscore/isc-gobase@v1.5.3-0.20231218061332-cbc7451899e9/cron/test/cront_test.go (about)

     1  package test
     2  
     3  import (
     4  	"fmt"
     5  	cron2 "github.com/isyscore/isc-gobase/cron"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  )
    10  
    11  // Many tests schedule a job for every second, and then wait at most a second
    12  // for it to run.  This amount is just slightly larger than 1 second to
    13  // compensate for a few milliseconds of runtime.
    14  const OneSecond = 1*time.Second + 10*time.Millisecond
    15  
    16  func TestFuncPanicRecovery(t *testing.T) {
    17  	cron := cron2.New()
    18  	cron.Start()
    19  	defer cron.Stop()
    20  	_ = cron.AddFunc("* * * * * ?", func() { panic("YOLO") })
    21  
    22  	select {
    23  	case <-time.After(OneSecond):
    24  		return
    25  	}
    26  }
    27  
    28  type DummyJob struct{}
    29  
    30  func (d DummyJob) Run() {
    31  	panic("YOLO")
    32  }
    33  
    34  func TestJobPanicRecovery(t *testing.T) {
    35  	var job DummyJob
    36  
    37  	cron := cron2.New()
    38  	cron.Start()
    39  	defer cron.Stop()
    40  	_ = cron.AddJob("* * * * * ?", job)
    41  
    42  	select {
    43  	case <-time.After(OneSecond):
    44  		return
    45  	}
    46  }
    47  
    48  // Start and stop cron with no entries.
    49  func TestNoEntries(t *testing.T) {
    50  	cron := cron2.New()
    51  	cron.Start()
    52  
    53  	select {
    54  	case <-time.After(OneSecond):
    55  		t.Fatal("expected cron will be stopped immediately")
    56  	case <-stop(cron):
    57  	}
    58  }
    59  
    60  // Start, stop, then add an entry. Verify entry doesn't run.
    61  func TestStopCausesJobsToNotRun(t *testing.T) {
    62  	wg := &sync.WaitGroup{}
    63  	wg.Add(1)
    64  
    65  	cron := cron2.New()
    66  	cron.Start()
    67  	cron.Stop()
    68  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
    69  
    70  	select {
    71  	case <-time.After(OneSecond):
    72  		// No job ran!
    73  	case <-wait(wg):
    74  		t.Fatal("expected stopped cron does not run any job")
    75  	}
    76  }
    77  
    78  // Add a job, start cron, expect it runs.
    79  func TestAddBeforeRunning(t *testing.T) {
    80  	wg := &sync.WaitGroup{}
    81  	wg.Add(1)
    82  
    83  	cron := cron2.New()
    84  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
    85  	cron.Start()
    86  	defer cron.Stop()
    87  
    88  	// Give cron 2 seconds to run our job (which is always activated).
    89  	select {
    90  	case <-time.After(OneSecond):
    91  		t.Fatal("expected job runs")
    92  	case <-wait(wg):
    93  	}
    94  }
    95  
    96  // Start cron, add a job, expect it runs.
    97  func TestAddWhileRunning(t *testing.T) {
    98  	wg := &sync.WaitGroup{}
    99  	wg.Add(1)
   100  
   101  	cron := cron2.New()
   102  	cron.Start()
   103  	defer cron.Stop()
   104  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
   105  
   106  	select {
   107  	case <-time.After(OneSecond):
   108  		t.Fatal("expected job runs")
   109  	case <-wait(wg):
   110  	}
   111  }
   112  
   113  // Test for #34. Adding a job after calling start results in multiple job invocations
   114  func TestAddWhileRunningWithDelay(t *testing.T) {
   115  	cron := cron2.New()
   116  	cron.Start()
   117  	defer cron.Stop()
   118  	time.Sleep(5 * time.Second)
   119  	var calls = 0
   120  	_ = cron.AddFunc("* * * * * *", func() { calls += 1 })
   121  
   122  	<-time.After(OneSecond)
   123  	if calls != 1 {
   124  		t.Errorf("called %d times, expected 1\n", calls)
   125  	}
   126  }
   127  
   128  // Test timing with Entries.
   129  func TestSnapshotEntries(t *testing.T) {
   130  	wg := &sync.WaitGroup{}
   131  	wg.Add(1)
   132  
   133  	cron := cron2.New()
   134  	_ = cron.AddFunc("@every 2s", func() { wg.Done() })
   135  	cron.Start()
   136  	defer cron.Stop()
   137  
   138  	// Cron should fire in 2 seconds. After 1 second, call Entries.
   139  	select {
   140  	case <-time.After(OneSecond):
   141  		cron.Entries()
   142  	}
   143  
   144  	// Even though Entries was called, the cron should fire at the 2 second mark.
   145  	select {
   146  	case <-time.After(OneSecond):
   147  		t.Error("expected job runs at 2 second mark")
   148  	case <-wait(wg):
   149  	}
   150  
   151  }
   152  
   153  // Test that the entries are correctly sorted.
   154  // Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
   155  // that the immediate entry runs immediately.
   156  // Also: Test that multiple jobs run in the same instant.
   157  func TestMultipleEntries(t *testing.T) {
   158  	wg := &sync.WaitGroup{}
   159  	wg.Add(2)
   160  
   161  	cron := cron2.New()
   162  	_ = cron.AddFunc("0 0 0 1 1 ?", func() {})
   163  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
   164  	_ = cron.AddFunc("0 0 0 31 12 ?", func() {})
   165  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
   166  
   167  	cron.Start()
   168  	defer cron.Stop()
   169  
   170  	select {
   171  	case <-time.After(OneSecond):
   172  		t.Error("expected job run in proper order")
   173  	case <-wait(wg):
   174  	}
   175  }
   176  
   177  // Test running the same job twice.
   178  func TestRunningJobTwice(t *testing.T) {
   179  	wg := &sync.WaitGroup{}
   180  	wg.Add(2)
   181  
   182  	cron := cron2.New()
   183  	_ = cron.AddFunc("0 0 0 1 1 ?", func() {})
   184  	_ = cron.AddFunc("0 0 0 31 12 ?", func() {})
   185  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
   186  
   187  	cron.Start()
   188  	defer cron.Stop()
   189  
   190  	select {
   191  	case <-time.After(2 * OneSecond):
   192  		t.Error("expected job fires 2 times")
   193  	case <-wait(wg):
   194  	}
   195  }
   196  
   197  func TestRunningMultipleSchedules(t *testing.T) {
   198  	wg := &sync.WaitGroup{}
   199  	wg.Add(2)
   200  
   201  	cron := cron2.New()
   202  	_ = cron.AddFunc("0 0 0 1 1 ?", func() {})
   203  	_ = cron.AddFunc("0 0 0 31 12 ?", func() {})
   204  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
   205  	cron.Schedule(cron2.Every(time.Minute), cron2.FuncJob(func() {}))
   206  	cron.Schedule(cron2.Every(time.Second), cron2.FuncJob(func() { wg.Done() }))
   207  	cron.Schedule(cron2.Every(time.Hour), cron2.FuncJob(func() {}))
   208  
   209  	cron.Start()
   210  	defer cron.Stop()
   211  
   212  	select {
   213  	case <-time.After(2 * OneSecond):
   214  		t.Error("expected job fires 2 times")
   215  	case <-wait(wg):
   216  	}
   217  }
   218  
   219  // Test that the cron is run in the local time zone (as opposed to UTC).
   220  func TestLocalTimezone(t *testing.T) {
   221  	wg := &sync.WaitGroup{}
   222  	wg.Add(2)
   223  
   224  	now := time.Now()
   225  	spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
   226  		now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
   227  
   228  	cron := cron2.New()
   229  	_ = cron.AddFunc(spec, func() { wg.Done() })
   230  	cron.Start()
   231  	defer cron.Stop()
   232  
   233  	select {
   234  	case <-time.After(OneSecond * 2):
   235  		t.Error("expected job fires 2 times")
   236  	case <-wait(wg):
   237  	}
   238  }
   239  
   240  // Test that the cron is run in the given time zone (as opposed to local).
   241  func TestNonLocalTimezone(t *testing.T) {
   242  	wg := &sync.WaitGroup{}
   243  	wg.Add(2)
   244  
   245  	loc, err := time.LoadLocation("Atlantic/Cape_Verde")
   246  	if err != nil {
   247  		fmt.Printf("Failed to load time zone Atlantic/Cape_Verde: %+v", err)
   248  		t.Fail()
   249  	}
   250  
   251  	now := time.Now().In(loc)
   252  	spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
   253  		now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
   254  
   255  	cron := cron2.NewWithLocation(loc)
   256  	_ = cron.AddFunc(spec, func() { wg.Done() })
   257  	cron.Start()
   258  	defer cron.Stop()
   259  
   260  	select {
   261  	case <-time.After(OneSecond * 2):
   262  		t.Error("expected job fires 2 times")
   263  	case <-wait(wg):
   264  	}
   265  }
   266  
   267  // Test that calling stop before start silently returns without
   268  // blocking the stop channel.
   269  func TestStopWithoutStart(t *testing.T) {
   270  	cron := cron2.New()
   271  	cron.Stop()
   272  }
   273  
   274  type testJob struct {
   275  	wg   *sync.WaitGroup
   276  	name string
   277  }
   278  
   279  func (t testJob) Run() {
   280  	t.wg.Done()
   281  }
   282  
   283  // Test that adding an invalid job spec returns an error
   284  func TestInvalidJobSpec(t *testing.T) {
   285  	cron := cron2.New()
   286  	err := cron.AddJob("this will not parse", nil)
   287  	if err == nil {
   288  		t.Errorf("expected an error with invalid spec, got nil")
   289  	}
   290  }
   291  
   292  // Test blocking run method behaves as Start()
   293  func TestBlockingRun(t *testing.T) {
   294  	wg := &sync.WaitGroup{}
   295  	wg.Add(1)
   296  
   297  	cron := cron2.New()
   298  	_ = cron.AddFunc("* * * * * ?", func() { wg.Done() })
   299  
   300  	var unblockChan = make(chan struct{})
   301  
   302  	go func() {
   303  		cron.Run()
   304  		close(unblockChan)
   305  	}()
   306  	defer cron.Stop()
   307  
   308  	select {
   309  	case <-time.After(OneSecond):
   310  		t.Error("expected job fires")
   311  	case <-unblockChan:
   312  		t.Error("expected that Run() blocks")
   313  	case <-wait(wg):
   314  	}
   315  }
   316  
   317  // Test that double-running is a no-op
   318  func TestStartNoop(t *testing.T) {
   319  	var tickChan = make(chan struct{}, 2)
   320  
   321  	cron := cron2.New()
   322  	_ = cron.AddFunc("* * * * * ?", func() {
   323  		tickChan <- struct{}{}
   324  	})
   325  
   326  	cron.Start()
   327  	defer cron.Stop()
   328  
   329  	// Wait for the first firing to ensure the runner is going
   330  	<-tickChan
   331  
   332  	cron.Start()
   333  
   334  	<-tickChan
   335  
   336  	// Fail if this job fires again in a short period, indicating a double-run
   337  	select {
   338  	case <-time.After(time.Millisecond):
   339  	case <-tickChan:
   340  		t.Error("expected job fires exactly twice")
   341  	}
   342  }
   343  
   344  // Simple test using Runnables.
   345  func TestJob(t *testing.T) {
   346  	wg := &sync.WaitGroup{}
   347  	wg.Add(1)
   348  
   349  	cron := cron2.New()
   350  	_ =cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"})
   351  	_ = cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"})
   352  	_ = cron.AddJob("* * * * * ?", testJob{wg, "job2"})
   353  	_ = cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"})
   354  	cron.Schedule(cron2.Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
   355  	cron.Schedule(cron2.Every(5*time.Minute), testJob{wg, "job5"})
   356  
   357  	cron.Start()
   358  	defer cron.Stop()
   359  
   360  	select {
   361  	case <-time.After(OneSecond):
   362  		t.FailNow()
   363  	case <-wait(wg):
   364  	}
   365  
   366  	// Ensure the entries are in the right order.
   367  	expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
   368  
   369  	var actuals []string
   370  	for _, entry := range cron.Entries() {
   371  		actuals = append(actuals, entry.Job.(testJob).name)
   372  	}
   373  
   374  	for i, expected := range expecteds {
   375  		if actuals[i] != expected {
   376  			t.Fatalf("Jobs not in the right order.  (expected) %s != %s (actual)", expecteds, actuals)
   377  		}
   378  	}
   379  }
   380  
   381  type ZeroSchedule struct{}
   382  
   383  func (*ZeroSchedule) Next(time.Time) time.Time {
   384  	return time.Time{}
   385  }
   386  
   387  // Tests that job without time does not run
   388  func TestJobWithZeroTimeDoesNotRun(t *testing.T) {
   389  	cron := cron2.New()
   390  	calls := 0
   391  	_ = cron.AddFunc("* * * * * *", func() { calls += 1 })
   392  	cron.Schedule(new(ZeroSchedule), cron2.FuncJob(func() { t.Error("expected zero task will not run") }))
   393  	cron.Start()
   394  	defer cron.Stop()
   395  	<-time.After(OneSecond)
   396  	if calls != 1 {
   397  		t.Errorf("called %d times, expected 1\n", calls)
   398  	}
   399  }
   400  
   401  func wait(wg *sync.WaitGroup) chan bool {
   402  	ch := make(chan bool)
   403  	go func() {
   404  		wg.Wait()
   405  		ch <- true
   406  	}()
   407  	return ch
   408  }
   409  
   410  func stop(cron *cron2.Cron) chan bool {
   411  	ch := make(chan bool)
   412  	go func() {
   413  		cron.Stop()
   414  		ch <- true
   415  	}()
   416  	return ch
   417  }