gitee.com/quant1x/gox@v1.21.2/cron/cron_test.go (about)

     1  package cron
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"gitee.com/quant1x/gox/runtime"
     8  	"log"
     9  	"strings"
    10  	"sync"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  )
    15  
    16  // Many tests schedule a job for every second, and then wait at most a second
    17  // for it to run.  This amount is just slightly larger than 1 second to
    18  // compensate for a few milliseconds of runtime.
    19  const OneSecond = 1*time.Second + 50*time.Millisecond
    20  
    21  type syncWriter struct {
    22  	wr bytes.Buffer
    23  	m  sync.Mutex
    24  }
    25  
    26  func (sw *syncWriter) Write(data []byte) (n int, err error) {
    27  	sw.m.Lock()
    28  	n, err = sw.wr.Write(data)
    29  	sw.m.Unlock()
    30  	return
    31  }
    32  
    33  func (sw *syncWriter) String() string {
    34  	sw.m.Lock()
    35  	defer sw.m.Unlock()
    36  	return sw.wr.String()
    37  }
    38  
    39  func newBufLogger(sw *syncWriter) Logger {
    40  	return PrintfLogger(log.New(sw, "", log.LstdFlags))
    41  }
    42  
    43  func TestFuncPanicRecovery(t *testing.T) {
    44  	var buf syncWriter
    45  	cron := New(WithParser(secondParser),
    46  		WithChain(Recover(newBufLogger(&buf))))
    47  	cron.Start()
    48  	defer cron.Stop()
    49  	cron.AddFunc("* * * * * ?", func() {
    50  		panic("YOLO")
    51  	})
    52  
    53  	select {
    54  	case <-time.After(OneSecond):
    55  		if !strings.Contains(buf.String(), "YOLO") {
    56  			t.Error("expected a panic to be logged, got none")
    57  		}
    58  		return
    59  	}
    60  }
    61  
    62  type DummyJob struct{}
    63  
    64  func (d DummyJob) Run() {
    65  	panic("YOLO")
    66  }
    67  
    68  func TestJobPanicRecovery(t *testing.T) {
    69  	var job DummyJob
    70  
    71  	var buf syncWriter
    72  	cron := New(WithParser(secondParser),
    73  		WithChain(Recover(newBufLogger(&buf))))
    74  	cron.Start()
    75  	defer cron.Stop()
    76  	cron.AddJob("* * * * * ?", job)
    77  
    78  	select {
    79  	case <-time.After(OneSecond):
    80  		if !strings.Contains(buf.String(), "YOLO") {
    81  			t.Error("expected a panic to be logged, got none")
    82  		}
    83  		return
    84  	}
    85  }
    86  
    87  // Start and stop cron with no entries.
    88  func TestNoEntries(t *testing.T) {
    89  	cron := newWithSeconds()
    90  	cron.Start()
    91  
    92  	select {
    93  	case <-time.After(OneSecond):
    94  		t.Fatal("expected cron will be stopped immediately")
    95  	case <-stop(cron):
    96  	}
    97  }
    98  
    99  // Start, stop, then add an entry. Verify entry doesn't run.
   100  func TestStopCausesJobsToNotRun(t *testing.T) {
   101  	wg := &sync.WaitGroup{}
   102  	wg.Add(1)
   103  
   104  	cron := newWithSeconds()
   105  	cron.Start()
   106  	cron.Stop()
   107  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   108  
   109  	select {
   110  	case <-time.After(OneSecond):
   111  		// No job ran!
   112  	case <-wait(wg):
   113  		t.Fatal("expected stopped cron does not run any job")
   114  	}
   115  }
   116  
   117  // Add a job, start cron, expect it runs.
   118  func TestAddBeforeRunning(t *testing.T) {
   119  	wg := &sync.WaitGroup{}
   120  	wg.Add(1)
   121  
   122  	cron := newWithSeconds()
   123  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   124  	cron.Start()
   125  	defer cron.Stop()
   126  
   127  	// Give cron 2 seconds to run our job (which is always activated).
   128  	select {
   129  	case <-time.After(OneSecond):
   130  		t.Fatal("expected job runs")
   131  	case <-wait(wg):
   132  	}
   133  }
   134  
   135  // Start cron, add a job, expect it runs.
   136  func TestAddWhileRunning(t *testing.T) {
   137  	wg := &sync.WaitGroup{}
   138  	wg.Add(1)
   139  
   140  	cron := newWithSeconds()
   141  	cron.Start()
   142  	defer cron.Stop()
   143  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   144  
   145  	select {
   146  	case <-time.After(OneSecond):
   147  		t.Fatal("expected job runs")
   148  	case <-wait(wg):
   149  	}
   150  }
   151  
   152  // Test for #34. Adding a job after calling start results in multiple job invocations
   153  func TestAddWhileRunningWithDelay(t *testing.T) {
   154  	cron := newWithSeconds()
   155  	cron.Start()
   156  	defer cron.Stop()
   157  	time.Sleep(5 * time.Second)
   158  	var calls int64
   159  	cron.AddFunc("* * * * * *", func() { atomic.AddInt64(&calls, 1) })
   160  
   161  	<-time.After(OneSecond)
   162  	if atomic.LoadInt64(&calls) != 1 {
   163  		t.Errorf("called %d times, expected 1\n", calls)
   164  	}
   165  }
   166  
   167  // Add a job, remove a job, start cron, expect nothing runs.
   168  func TestRemoveBeforeRunning(t *testing.T) {
   169  	wg := &sync.WaitGroup{}
   170  	wg.Add(1)
   171  
   172  	cron := newWithSeconds()
   173  	id, _ := cron.AddFunc("* * * * * ?", func() { wg.Done() })
   174  	cron.Remove(id)
   175  	cron.Start()
   176  	defer cron.Stop()
   177  
   178  	select {
   179  	case <-time.After(OneSecond):
   180  		// Success, shouldn't run
   181  	case <-wait(wg):
   182  		t.FailNow()
   183  	}
   184  }
   185  
   186  // Start cron, add a job, remove it, expect it doesn't run.
   187  func TestRemoveWhileRunning(t *testing.T) {
   188  	wg := &sync.WaitGroup{}
   189  	wg.Add(1)
   190  
   191  	cron := newWithSeconds()
   192  	cron.Start()
   193  	defer cron.Stop()
   194  	id, _ := cron.AddFunc("* * * * * ?", func() { wg.Done() })
   195  	cron.Remove(id)
   196  
   197  	select {
   198  	case <-time.After(OneSecond):
   199  	case <-wait(wg):
   200  		t.FailNow()
   201  	}
   202  }
   203  
   204  // Test timing with Entries.
   205  func TestSnapshotEntries(t *testing.T) {
   206  	wg := &sync.WaitGroup{}
   207  	wg.Add(1)
   208  
   209  	cron := New()
   210  	cron.AddFunc("@every 2s", func() { wg.Done() })
   211  	cron.Start()
   212  	defer cron.Stop()
   213  
   214  	// Cron should fire in 2 seconds. After 1 second, call Entries.
   215  	select {
   216  	case <-time.After(OneSecond):
   217  		cron.Entries()
   218  	}
   219  
   220  	// Even though Entries was called, the cron should fire at the 2 second mark.
   221  	select {
   222  	case <-time.After(OneSecond):
   223  		t.Error("expected job runs at 2 second mark")
   224  	case <-wait(wg):
   225  	}
   226  }
   227  
   228  // Test that the entries are correctly sorted.
   229  // Add a bunch of long-in-the-future entries, and an immediate entry, and ensure
   230  // that the immediate entry runs immediately.
   231  // Also: Test that multiple jobs run in the same instant.
   232  func TestMultipleEntries(t *testing.T) {
   233  	wg := &sync.WaitGroup{}
   234  	wg.Add(2)
   235  
   236  	cron := newWithSeconds()
   237  	cron.AddFunc("0 0 0 1 1 ?", func() {})
   238  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   239  	id1, _ := cron.AddFunc("* * * * * ?", func() { t.Fatal() })
   240  	id2, _ := cron.AddFunc("* * * * * ?", func() { t.Fatal() })
   241  	cron.AddFunc("0 0 0 31 12 ?", func() {})
   242  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   243  
   244  	cron.Remove(id1)
   245  	cron.Start()
   246  	cron.Remove(id2)
   247  	defer cron.Stop()
   248  
   249  	select {
   250  	case <-time.After(OneSecond):
   251  		t.Error("expected job run in proper order")
   252  	case <-wait(wg):
   253  	}
   254  }
   255  
   256  // Test running the same job twice.
   257  func TestRunningJobTwice(t *testing.T) {
   258  	wg := &sync.WaitGroup{}
   259  	wg.Add(2)
   260  
   261  	cron := newWithSeconds()
   262  	cron.AddFunc("0 0 0 1 1 ?", func() {})
   263  	cron.AddFunc("0 0 0 31 12 ?", func() {})
   264  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   265  
   266  	cron.Start()
   267  	defer cron.Stop()
   268  
   269  	select {
   270  	case <-time.After(2 * OneSecond):
   271  		t.Error("expected job fires 2 times")
   272  	case <-wait(wg):
   273  	}
   274  }
   275  
   276  func TestRunningMultipleSchedules(t *testing.T) {
   277  	wg := &sync.WaitGroup{}
   278  	wg.Add(2)
   279  
   280  	cron := newWithSeconds()
   281  	cron.AddFunc("0 0 0 1 1 ?", func() {})
   282  	cron.AddFunc("0 0 0 31 12 ?", func() {})
   283  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   284  	cron.Schedule(Every(time.Minute), FuncJob(func() {}))
   285  	cron.Schedule(Every(time.Second), FuncJob(func() { wg.Done() }))
   286  	cron.Schedule(Every(time.Hour), FuncJob(func() {}))
   287  
   288  	cron.Start()
   289  	defer cron.Stop()
   290  
   291  	select {
   292  	case <-time.After(2 * OneSecond):
   293  		t.Error("expected job fires 2 times")
   294  	case <-wait(wg):
   295  	}
   296  }
   297  
   298  // Test that the cron is run in the local time zone (as opposed to UTC).
   299  func TestLocalTimezone(t *testing.T) {
   300  	wg := &sync.WaitGroup{}
   301  	wg.Add(2)
   302  
   303  	now := time.Now()
   304  	// FIX: Issue #205
   305  	// This calculation doesn't work in seconds 58 or 59.
   306  	// Take the easy way out and sleep.
   307  	if now.Second() >= 58 {
   308  		time.Sleep(2 * time.Second)
   309  		now = time.Now()
   310  	}
   311  	spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
   312  		now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
   313  
   314  	cron := newWithSeconds()
   315  	cron.AddFunc(spec, func() { wg.Done() })
   316  	cron.Start()
   317  	defer cron.Stop()
   318  
   319  	select {
   320  	case <-time.After(OneSecond * 2):
   321  		t.Error("expected job fires 2 times")
   322  	case <-wait(wg):
   323  	}
   324  }
   325  
   326  // Test that the cron is run in the given time zone (as opposed to local).
   327  func TestNonLocalTimezone(t *testing.T) {
   328  	wg := &sync.WaitGroup{}
   329  	wg.Add(2)
   330  
   331  	loc, err := time.LoadLocation("Atlantic/Cape_Verde")
   332  	if err != nil {
   333  		fmt.Printf("Failed to load time zone Atlantic/Cape_Verde: %+v", err)
   334  		t.Fail()
   335  	}
   336  
   337  	now := time.Now().In(loc)
   338  	// FIX: Issue #205
   339  	// This calculation doesn't work in seconds 58 or 59.
   340  	// Take the easy way out and sleep.
   341  	if now.Second() >= 58 {
   342  		time.Sleep(2 * time.Second)
   343  		now = time.Now().In(loc)
   344  	}
   345  	spec := fmt.Sprintf("%d,%d %d %d %d %d ?",
   346  		now.Second()+1, now.Second()+2, now.Minute(), now.Hour(), now.Day(), now.Month())
   347  
   348  	cron := New(WithLocation(loc), WithParser(secondParser))
   349  	cron.AddFunc(spec, func() { wg.Done() })
   350  	cron.Start()
   351  	defer cron.Stop()
   352  
   353  	select {
   354  	case <-time.After(OneSecond * 2):
   355  		t.Error("expected job fires 2 times")
   356  	case <-wait(wg):
   357  	}
   358  }
   359  
   360  // Test that calling stop before start silently returns without
   361  // blocking the stop channel.
   362  func TestStopWithoutStart(t *testing.T) {
   363  	cron := New()
   364  	cron.Stop()
   365  }
   366  
   367  type testJob struct {
   368  	wg   *sync.WaitGroup
   369  	name string
   370  }
   371  
   372  func (t testJob) Run() {
   373  	t.wg.Done()
   374  }
   375  
   376  // Test that adding an invalid job spec returns an error
   377  func TestInvalidJobSpec(t *testing.T) {
   378  	cron := New()
   379  	_, err := cron.AddJob("this will not parse", nil)
   380  	if err == nil {
   381  		t.Errorf("expected an error with invalid spec, got nil")
   382  	}
   383  }
   384  
   385  // Test blocking run method behaves as Start()
   386  func TestBlockingRun(t *testing.T) {
   387  	wg := &sync.WaitGroup{}
   388  	wg.Add(1)
   389  
   390  	cron := newWithSeconds()
   391  	cron.AddFunc("* * * * * ?", func() { wg.Done() })
   392  
   393  	var unblockChan = make(chan struct{})
   394  
   395  	go func() {
   396  		cron.Run()
   397  		close(unblockChan)
   398  	}()
   399  	defer cron.Stop()
   400  
   401  	select {
   402  	case <-time.After(OneSecond):
   403  		t.Error("expected job fires")
   404  	case <-unblockChan:
   405  		t.Error("expected that Run() blocks")
   406  	case <-wait(wg):
   407  	}
   408  }
   409  
   410  // Test that double-running is a no-op
   411  func TestStartNoop(t *testing.T) {
   412  	var tickChan = make(chan struct{}, 2)
   413  
   414  	cron := newWithSeconds()
   415  	cron.AddFunc("* * * * * ?", func() {
   416  		tickChan <- struct{}{}
   417  	})
   418  
   419  	cron.Start()
   420  	defer cron.Stop()
   421  
   422  	// Wait for the first firing to ensure the runner is going
   423  	<-tickChan
   424  
   425  	cron.Start()
   426  
   427  	<-tickChan
   428  
   429  	// Fail if this job fires again in a short period, indicating a double-run
   430  	select {
   431  	case <-time.After(time.Millisecond):
   432  	case <-tickChan:
   433  		t.Error("expected job fires exactly twice")
   434  	}
   435  }
   436  
   437  // Simple test using Runnables.
   438  func TestJob(t *testing.T) {
   439  	wg := &sync.WaitGroup{}
   440  	wg.Add(1)
   441  
   442  	cron := newWithSeconds()
   443  	cron.AddJob("0 0 0 30 Feb ?", testJob{wg, "job0"})
   444  	cron.AddJob("0 0 0 1 1 ?", testJob{wg, "job1"})
   445  	job2, _ := cron.AddJob("* * * * * ?", testJob{wg, "job2"})
   446  	cron.AddJob("1 0 0 1 1 ?", testJob{wg, "job3"})
   447  	cron.Schedule(Every(5*time.Second+5*time.Nanosecond), testJob{wg, "job4"})
   448  	job5 := cron.Schedule(Every(5*time.Minute), testJob{wg, "job5"})
   449  
   450  	// Test getting an Entry pre-Start.
   451  	if actualName := cron.Entry(job2).Job.(testJob).name; actualName != "job2" {
   452  		t.Error("wrong job retrieved:", actualName)
   453  	}
   454  	if actualName := cron.Entry(job5).Job.(testJob).name; actualName != "job5" {
   455  		t.Error("wrong job retrieved:", actualName)
   456  	}
   457  
   458  	cron.Start()
   459  	defer cron.Stop()
   460  
   461  	select {
   462  	case <-time.After(OneSecond):
   463  		t.FailNow()
   464  	case <-wait(wg):
   465  	}
   466  
   467  	// Ensure the entries are in the right order.
   468  	expecteds := []string{"job2", "job4", "job5", "job1", "job3", "job0"}
   469  
   470  	var actuals []string
   471  	for _, entry := range cron.Entries() {
   472  		actuals = append(actuals, entry.Job.(testJob).name)
   473  	}
   474  
   475  	for i, expected := range expecteds {
   476  		if actuals[i] != expected {
   477  			t.Fatalf("Jobs not in the right order.  (expected) %s != %s (actual)", expecteds, actuals)
   478  		}
   479  	}
   480  
   481  	// Test getting Entries.
   482  	if actualName := cron.Entry(job2).Job.(testJob).name; actualName != "job2" {
   483  		t.Error("wrong job retrieved:", actualName)
   484  	}
   485  	if actualName := cron.Entry(job5).Job.(testJob).name; actualName != "job5" {
   486  		t.Error("wrong job retrieved:", actualName)
   487  	}
   488  }
   489  
   490  // Issue #206
   491  // Ensure that the next run of a job after removing an entry is accurate.
   492  func TestScheduleAfterRemoval(t *testing.T) {
   493  	var wg1 sync.WaitGroup
   494  	var wg2 sync.WaitGroup
   495  	wg1.Add(1)
   496  	wg2.Add(1)
   497  
   498  	// The first time this job is run, set a timer and remove the other job
   499  	// 750ms later. Correct behavior would be to still run the job again in
   500  	// 250ms, but the bug would cause it to run instead 1s later.
   501  
   502  	var calls int
   503  	var mu sync.Mutex
   504  
   505  	cron := newWithSeconds()
   506  	hourJob := cron.Schedule(Every(time.Hour), FuncJob(func() {}))
   507  	cron.Schedule(Every(time.Second), FuncJob(func() {
   508  		mu.Lock()
   509  		defer mu.Unlock()
   510  		switch calls {
   511  		case 0:
   512  			wg1.Done()
   513  			calls++
   514  		case 1:
   515  			time.Sleep(750 * time.Millisecond)
   516  			cron.Remove(hourJob)
   517  			calls++
   518  		case 2:
   519  			calls++
   520  			wg2.Done()
   521  		case 3:
   522  			panic("unexpected 3rd call")
   523  		}
   524  	}))
   525  
   526  	cron.Start()
   527  	defer cron.Stop()
   528  
   529  	// the first run might be any length of time 0 - 1s, since the schedule
   530  	// rounds to the second. wait for the first run to true up.
   531  	wg1.Wait()
   532  
   533  	select {
   534  	case <-time.After(2 * OneSecond):
   535  		t.Error("expected job fires 2 times")
   536  	case <-wait(&wg2):
   537  	}
   538  }
   539  
   540  type ZeroSchedule struct{}
   541  
   542  func (*ZeroSchedule) Next(time.Time) time.Time {
   543  	return time.Time{}
   544  }
   545  
   546  // Tests that job without time does not run
   547  func TestJobWithZeroTimeDoesNotRun(t *testing.T) {
   548  	cron := newWithSeconds()
   549  	var calls int64
   550  	cron.AddFunc("* * * * * *", func() { atomic.AddInt64(&calls, 1) })
   551  	cron.Schedule(new(ZeroSchedule), FuncJob(func() { t.Error("expected zero task will not run") }))
   552  	cron.Start()
   553  	defer cron.Stop()
   554  	<-time.After(OneSecond)
   555  	if atomic.LoadInt64(&calls) != 1 {
   556  		t.Errorf("called %d times, expected 1\n", calls)
   557  	}
   558  }
   559  
   560  func TestStopAndWait(t *testing.T) {
   561  	t.Run("nothing running, returns immediately", func(t *testing.T) {
   562  		cron := newWithSeconds()
   563  		cron.Start()
   564  		ctx := cron.Stop()
   565  		select {
   566  		case <-ctx.Done():
   567  		case <-time.After(time.Millisecond):
   568  			t.Error("context was not done immediately")
   569  		}
   570  	})
   571  
   572  	t.Run("repeated calls to Stop", func(t *testing.T) {
   573  		cron := newWithSeconds()
   574  		cron.Start()
   575  		_ = cron.Stop()
   576  		time.Sleep(time.Millisecond)
   577  		ctx := cron.Stop()
   578  		select {
   579  		case <-ctx.Done():
   580  		case <-time.After(time.Millisecond):
   581  			t.Error("context was not done immediately")
   582  		}
   583  	})
   584  
   585  	t.Run("a couple fast jobs added, still returns immediately", func(t *testing.T) {
   586  		cron := newWithSeconds()
   587  		cron.AddFunc("* * * * * *", func() {})
   588  		cron.Start()
   589  		cron.AddFunc("* * * * * *", func() {})
   590  		cron.AddFunc("* * * * * *", func() {})
   591  		cron.AddFunc("* * * * * *", func() {})
   592  		time.Sleep(time.Second)
   593  		ctx := cron.Stop()
   594  		select {
   595  		case <-ctx.Done():
   596  		case <-time.After(time.Millisecond):
   597  			t.Error("context was not done immediately")
   598  		}
   599  	})
   600  
   601  	t.Run("a couple fast jobs and a slow job added, waits for slow job", func(t *testing.T) {
   602  		cron := newWithSeconds()
   603  		cron.AddFunc("* * * * * *", func() {})
   604  		cron.Start()
   605  		cron.AddFunc("* * * * * *", func() { time.Sleep(2 * time.Second) })
   606  		cron.AddFunc("* * * * * *", func() {})
   607  		time.Sleep(time.Second)
   608  
   609  		ctx := cron.Stop()
   610  
   611  		// Verify that it is not done for at least 750ms
   612  		select {
   613  		case <-ctx.Done():
   614  			t.Error("context was done too quickly immediately")
   615  		case <-time.After(750 * time.Millisecond):
   616  			// expected, because the job sleeping for 1 second is still running
   617  		}
   618  
   619  		// Verify that it IS done in the next 500ms (giving 250ms buffer)
   620  		select {
   621  		case <-ctx.Done():
   622  			// expected
   623  		case <-time.After(1500 * time.Millisecond):
   624  			t.Error("context not done after job should have completed")
   625  		}
   626  	})
   627  
   628  	t.Run("repeated calls to stop, waiting for completion and after", func(t *testing.T) {
   629  		cron := newWithSeconds()
   630  		cron.AddFunc("* * * * * *", func() {})
   631  		cron.AddFunc("* * * * * *", func() { time.Sleep(2 * time.Second) })
   632  		cron.Start()
   633  		cron.AddFunc("* * * * * *", func() {})
   634  		time.Sleep(time.Second)
   635  		ctx := cron.Stop()
   636  		ctx2 := cron.Stop()
   637  
   638  		// Verify that it is not done for at least 1500ms
   639  		select {
   640  		case <-ctx.Done():
   641  			t.Error("context was done too quickly immediately")
   642  		case <-ctx2.Done():
   643  			t.Error("context2 was done too quickly immediately")
   644  		case <-time.After(1500 * time.Millisecond):
   645  			// expected, because the job sleeping for 2 seconds is still running
   646  		}
   647  
   648  		// Verify that it IS done in the next 1s (giving 500ms buffer)
   649  		select {
   650  		case <-ctx.Done():
   651  			// expected
   652  		case <-time.After(time.Second):
   653  			t.Error("context not done after job should have completed")
   654  		}
   655  
   656  		// Verify that ctx2 is also done.
   657  		select {
   658  		case <-ctx2.Done():
   659  			// expected
   660  		case <-time.After(time.Millisecond):
   661  			t.Error("context2 not done even though context1 is")
   662  		}
   663  
   664  		// Verify that a new context retrieved from stop is immediately done.
   665  		ctx3 := cron.Stop()
   666  		select {
   667  		case <-ctx3.Done():
   668  			// expected
   669  		case <-time.After(time.Millisecond):
   670  			t.Error("context not done even when cron Stop is completed")
   671  		}
   672  
   673  	})
   674  }
   675  
   676  func TestMultiThreadedStartAndStop(t *testing.T) {
   677  	cron := New()
   678  	go cron.Run()
   679  	time.Sleep(2 * time.Millisecond)
   680  	cron.Stop()
   681  }
   682  
   683  func wait(wg *sync.WaitGroup) chan bool {
   684  	ch := make(chan bool)
   685  	go func() {
   686  		wg.Wait()
   687  		ch <- true
   688  	}()
   689  	return ch
   690  }
   691  
   692  func stop(cron *Cron) chan bool {
   693  	ch := make(chan bool)
   694  	go func() {
   695  		cron.Stop()
   696  		ch <- true
   697  	}()
   698  	return ch
   699  }
   700  
   701  // newWithSeconds returns a Cron with the seconds field enabled.
   702  func newWithSeconds() *Cron {
   703  	return New(WithParser(secondParser), WithChain())
   704  }
   705  
   706  func panicTest1() {
   707  	//defer runtime.CatchPanic()
   708  	//time.Sleep(time.Second * 3)
   709  	fmt.Printf("SkipIfStillRunningWithLogger: %v\n", time.Now())
   710  	panic(errors.New("xx"))
   711  }
   712  
   713  func TestScheduler(t *testing.T) {
   714  	intervalSnapshot := "@every 1s"
   715  	//intervalSnapshot = "* * * * * *"
   716  	//c := New()
   717  	c := New(WithSeconds())
   718  	c.Start()
   719  	_, err := c.AddJobWithSkipIfStillRunning(intervalSnapshot, panicTest1)
   720  	if err != nil {
   721  		fmt.Println(err)
   722  		return
   723  	}
   724  
   725  	time.Sleep(100 * time.Second)
   726  
   727  }
   728  
   729  func TestSchedulerOk(t *testing.T) {
   730  	intervalSnapshot := "@every 1s"
   731  	//intervalSnapshot = "* * * * * *"
   732  	//c := New()
   733  	c := New(WithSeconds())
   734  	c.Start()
   735  	_, err := c.AddJobWithSkipIfStillRunning(intervalSnapshot, func() {
   736  		defer runtime.CatchPanic()
   737  		//time.Sleep(time.Second * 3)
   738  		fmt.Printf("SkipIfStillRunningWithLogger: %v\n", time.Now())
   739  		panic(errors.New("xx"))
   740  	})
   741  	if err != nil {
   742  		fmt.Println(err)
   743  		return
   744  	}
   745  
   746  	time.Sleep(100 * time.Second)
   747  
   748  }