Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions cron/constantdelay.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,23 @@ You can check the original license at:
https://github.com/robfig/cron/blob/master/LICENSE
*/

//nolint
package cron

import "time"

// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
// It does not support jobs more frequent than once a second.
type ConstantDelaySchedule struct {
Delay time.Duration
}

// Every returns a crontab Schedule that activates once every duration.
// Delays of less than a second are not supported (will round up to 1 second).
// Any fields less than a Second are truncated.
func Every(duration time.Duration) ConstantDelaySchedule {
if duration < time.Second {
duration = time.Second
}
return ConstantDelaySchedule{
Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
Delay: duration,
}
}

// Next returns the next time this should be run.
// This rounds so that the next activation time will be on the second.
func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
return t.Add(schedule.Delay)
}
18 changes: 4 additions & 14 deletions cron/constantdelay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ You can check the original license at:
https://github.com/robfig/cron/blob/master/LICENSE
*/

//nolint
package cron

import (
Expand All @@ -29,9 +28,12 @@ func TestConstantDelayNext(t *testing.T) {
expected string
}{
// Simple cases
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00:00.00000005 2012"},
{"Mon Jul 9 14:59 2012", 15 * time.Minute, "Mon Jul 9 15:14 2012"},
{"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 15:14:59 2012"},
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:00.015 2012"},
{"Mon Jul 9 14:45:00.015 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:00.030 2012"},
{"Mon Jul 9 14:45:00.000000050 2012", 15 * time.Nanosecond, "Mon Jul 9 14:45:00.000000065 2012"},

// Wrap around hours
{"Mon Jul 9 15:45 2012", 35 * time.Minute, "Mon Jul 9 16:20 2012"},
Expand All @@ -47,18 +49,6 @@ func TestConstantDelayNext(t *testing.T) {

// Wrap around minute, hour, day, month, and year
{"Mon Dec 31 23:59:45 2012", 15 * time.Second, "Tue Jan 1 00:00:00 2013"},

// Round to nearest second on the delay
{"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},

// Round up to 1 second if the duration is less.
{"Mon Jul 9 14:45:00 2012", 15 * time.Millisecond, "Mon Jul 9 14:45:01 2012"},

// Round to nearest second when calculating the next time.
{"Mon Jul 9 14:45:00.005 2012", 15 * time.Minute, "Mon Jul 9 15:00 2012"},

// Round to nearest second for both.
{"Mon Jul 9 14:45:00.005 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 15:00 2012"},
}

for _, c := range tests {
Expand Down
55 changes: 55 additions & 0 deletions cron/cron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,61 @@ func TestMockClock(t *testing.T) {
assert.Equal(t, int64(10), counter.Load())
}

func TestMillisecond(t *testing.T) {
clk := clocktesting.NewFakeClock(time.Now())
cron := New(WithClock(clk))
counter1ms := atomic.Int64{}
counter15ms := atomic.Int64{}
counter100ms := atomic.Int64{}

cron.AddFunc("@every 1ms", func() {
counter1ms.Add(1)
})
cron.AddFunc("@every 15ms", func() {
counter15ms.Add(1)
})
cron.AddFunc("@every 100ms", func() {
counter100ms.Add(1)
})

cron.Start()
defer cron.Stop()
for range 1000 {
assert.Eventually(t, clk.HasWaiters, OneSecond, 1*time.Millisecond)
clk.Step(1 * time.Millisecond)
}
ctx := cron.Stop()
<-ctx.Done()

assert.Equal(t, int64(1000), counter1ms.Load())
assert.Equal(t, int64(66), counter15ms.Load())
assert.Equal(t, int64(10), counter100ms.Load())
}

func TestNanoseconds(t *testing.T) {
clk := clocktesting.NewFakeClock(time.Now())
cron := New(WithClock(clk))

counter100ns := atomic.Int64{}
cron.AddFunc("@every 100ns", func() {
counter100ns.Add(1)
})

cron.Start()
defer cron.Stop()

for range 500 {
assert.Eventually(t, clk.HasWaiters, OneSecond, 1*time.Millisecond)
clk.Step(5 * time.Nanosecond)
}
ctx := cron.Stop()
<-ctx.Done()

// 500 * 5 ns = 2500 ns
// 2500 every 100ns = 25
assert.Equal(t, int64(25), counter100ns.Load())
}

func TestMultiThreadedStartAndStop(*testing.T) {
cron := New()
go cron.Run()
Expand Down
3 changes: 2 additions & 1 deletion cron/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ You can check the original license at:
https://github.com/robfig/cron/blob/master/LICENSE
*/

//nolint
package cron

import (
Expand Down Expand Up @@ -167,6 +166,8 @@ func TestParseSchedule(t *testing.T) {
{standardParser, "CRON_TZ=UTC 5 * * * *", every5min(time.UTC)},
{secondParser, "CRON_TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)},
{secondParser, "@every 5m", ConstantDelaySchedule{5 * time.Minute}},
{secondParser, "@every 5ms", ConstantDelaySchedule{5 * time.Millisecond}},
{secondParser, "@every 5ns", ConstantDelaySchedule{5 * time.Nanosecond}},
{secondParser, "@midnight", midnight(time.Local)},
{secondParser, "TZ=UTC @midnight", midnight(time.UTC)},
{secondParser, "TZ=Asia/Tokyo @midnight", midnight(tokyo)},
Expand Down
4 changes: 4 additions & 0 deletions crypto/spiffe/trustanchors/file/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ func TestFile_Watch(t *testing.T) {
go func() {
errCh <- f.Run(ctx)
}()
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running

watchDone := make(chan struct{})
go func() {
Expand Down Expand Up @@ -413,6 +414,7 @@ func TestFile_Watch(t *testing.T) {
go func() {
errCh <- f.Run(ctx1)
}()
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running

watchDone := make(chan struct{})
ctx2, cancel2 := context.WithCancel(t.Context())
Expand Down Expand Up @@ -460,6 +462,7 @@ func TestFile_Watch(t *testing.T) {
go func() {
errCh <- f.Run(ctx)
}()
time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running

select {
case <-f.readyCh:
Expand Down Expand Up @@ -544,6 +547,7 @@ func TestFile_CurrentTrustAnchors(t *testing.T) {
errCh <- f.Run(ctx)
}()

time.Sleep(time.Millisecond * 10) // adding a small delay to ensure f.Run has finished and running
//nolint:gocritic
roots := append(pki1.RootCertPEM, pki2.RootCertPEM...)
require.NoError(t, os.WriteFile(tmp, roots, 0o600))
Expand Down
4 changes: 3 additions & 1 deletion time/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ func ParseDuration(from string) (int, int, int, time.Duration, int, error) {
// - ISO8601 duration format
// - time.Duration string format
// - RFC3339 datetime format
// - RFC3339nano datetime format
// For duration formats, an offset is added.
func ParseTime(from string, offset *time.Time) (time.Time, error) {
var start time.Time
Expand All @@ -210,8 +211,9 @@ func ParseTime(from string, offset *time.Time) (time.Time, error) {
if dur, err = time.ParseDuration(from); err == nil {
return start.Add(dur), nil
}
if t, err := time.Parse(time.RFC3339, from); err == nil {
if t, err := time.Parse(time.RFC3339Nano, from); err == nil {
return t, nil
}

return time.Time{}, errors.New("unsupported time/duration format: " + from)
}
7 changes: 7 additions & 0 deletions time/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@ func TestParseTime(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, time.Duration(0), expected.Sub(tm))
})
t.Run("parse RFC3339nano datetime", func(t *testing.T) {
dummy := time.Now().Add(1000 * time.Nanosecond)
expected := time.Now().Add(100 * time.Nanosecond)
tm, err := ParseTime(expected.Format(time.RFC3339Nano), &dummy)
require.NoError(t, err)
assert.Equal(t, time.Duration(0), expected.Sub(tm))
})
t.Run("parse empty string", func(t *testing.T) {
_, err := ParseTime("", nil)
require.ErrorContains(t, err, "unsupported time/duration format")
Expand Down