Skip to content

Commit

Permalink
ci: added test pipeline
Browse files Browse the repository at this point in the history
Signed-off-by: Eray Ates <eates23@gmail.com>
  • Loading branch information
rytsh committed Aug 5, 2023
1 parent 4634e95 commit 5d02d8c
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 33 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
on: [push]

name: Test
jobs:
sonarcloud:
runs-on: ubuntu-latest
steps:
- name: get epository name
run: echo "REPOSITORY_NAME=${GITHUB_REPOSITORY#*/}" >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
fetch-depth: 0
- uses: actions/setup-go@v3
with:
go-version: '1.20' # The Go version to download (if necessary) and use.
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
- name: Run tests
run: |
[[ ! -f ".golangci.yml" ]] && curl -kfsSL -O https://raw.githubusercontent.com/worldline-go/guide/main/lint/.golangci.yml || true
GOPATH="$(dirname ${PWD})" golangci-lint run --out-format checkstyle ./... > golangci-lint-report.out || true
go test -coverprofile=coverage.out -json ./... > test-report.out
- name: SonarCloud Scan
uses: sonarsource/sonarcloud-github-action@master
with:
args: >
-Dsonar.organization=${{ github.repository_owner }}
-Dsonar.projectKey=${{ github.repository_owner }}_${{ env.REPOSITORY_NAME }}
-Dsonar.go.coverage.reportPaths=coverage.out
-Dsonar.go.tests.reportPaths=test-report.out
-Dsonar.go.golangci-lint.reportPaths=golangci-lint-report.out
-Dsonar.sources=.
-Dsonar.exclusions=**/vendor/**,**/*_test.go
-Dsonar.tests=.
-Dsonar.test.inclusions=**/*_test.go
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/coverage.out
/coverage.html
/coverage.html
/.golangci.yml
/golangci-lint-report.out
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# hardloop

[![License](https://img.shields.io/github/license/worldline-go/hardloop?color=red&style=flat-square)](https://raw.githubusercontent.com/worldline-go/hardloop/main/LICENSE)
[![Coverage](https://img.shields.io/sonar/coverage/worldline-go_hardloop?logo=sonarcloud&server=https%3A%2F%2Fsonarcloud.io&style=flat-square)](https://sonarcloud.io/summary/overall?id=worldline-go_hardloop)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/worldline-go/hardloop/test.yml?branch=main&logo=github&style=flat-square&label=ci)](https://github.com/worldline-go/hardloop/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/worldline-go/hardloop?style=flat-square)](https://goreportcard.com/report/github.com/worldline-go/hardloop)
[![Go PKG](https://raw.githubusercontent.com/worldline-go/guide/main/badge/custom/reference.svg)](https://pkg.go.dev/github.com/worldline-go/hardloop)
[![Web](https://img.shields.io/badge/web-document-blueviolet?style=flat-square)](https://worldline-go.github.io/hardloop/)


Hardloop is a cron time-based function runner.

Set start and end times as cron specs, and give function to run between them.
Expand All @@ -26,6 +34,9 @@ Use Timezone to set the timezone: `CRON_TZ=Europe/Istanbul 0 7 * * 1,2,3,4,5`

Default timezone is system timezone.

> Some times doens't exist in some timezones.
> For example, `CRON_TZ=Europe/Amsterdam 30 2 26 3 *` doesn't exist due to in that time 02:00 -> 03:00 DST 1 hour adding. It will be make some problems so don't use non-exist times.
```go
// Set start cron specs.
startSpecs := []string{
Expand Down Expand Up @@ -66,5 +77,6 @@ myFunctionLoop.SetLogger(myLog{})
Test code

```sh
make coverage html-wsl
make test
# make coverage html-wsl
```
23 changes: 14 additions & 9 deletions cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"github.com/robfig/cron/v3"
)

// YearLimit is the maximum number of years to search for a matching time.
var YearLimit = 5

type Schedule interface {
// Next returns the next time this schedule is activated, greater than the given time.
Next(time.Time) time.Time
Expand Down Expand Up @@ -56,7 +59,7 @@ func (s *SpecSchedule) Next(t time.Time) time.Time {
added := false

// If no time is found within five years, return zero.
yearLimit := t.Year() + 5
yearLimit := t.Year() + YearLimit

WRAP:
if t.Year() > yearLimit {
Expand Down Expand Up @@ -177,7 +180,7 @@ func (s *SpecSchedule) Prev(t time.Time) time.Time {
added := false

// If no time is found within five years, return zero.
yearLimit := t.Year() - 5
yearLimit := t.Year() - YearLimit

WRAP:
if t.Year() < yearLimit {
Expand Down Expand Up @@ -227,7 +230,7 @@ WRAP:
t = t.AddDate(0, 0, -1)

if !dayMatches(s, t) && t.Hour() == 0 {
t = t.Add(time.Hour * 23)
t = t.Add(time.Hour * 23) //nolint:gomnd // 23 is the last hour of the day
}

if t.Day()-currentDay != 1 {
Expand Down Expand Up @@ -284,7 +287,7 @@ WRAP:
return t.In(origLocation)
}

// edited function of github.com/robfig/cron/v3 to changed day of week and day of month both usage
// edited function of github.com/robfig/cron/v3 to changed day of week and day of month both usage.
func dayMatches(s *SpecSchedule, t time.Time) bool {
var (
domMatch bool = 1<<uint(t.Day())&s.Dom > 0
Expand Down Expand Up @@ -321,15 +324,17 @@ func ParseStandard(spec string) (*SpecSchedule, error) {
return nil, err
}

return &SpecSchedule{SpecSchedule: specSchedule.(*cron.SpecSchedule)}, nil
return &SpecSchedule{SpecSchedule: specSchedule.(*cron.SpecSchedule)}, nil //nolint:forcetypeassert // no need to check
}

// Parser is default parser for cron to replacing the functions.
type Parser struct {
ParseFn func(standardSpec string) (cron.Schedule, error)
}

func (p Parser) Parse(spec string) (cron.Schedule, error) {
// Parse returns a new cron schedule for the given spec.
// It use hardloop.Schedule interface instead of cron.Schedule.
func (p Parser) Parse(spec string) (cron.Schedule, error) { //nolint:ireturn // return interface to support other interfaces
parseFn := p.ParseFn
if parseFn == nil {
parseFn = cron.ParseStandard
Expand All @@ -340,17 +345,17 @@ func (p Parser) Parse(spec string) (cron.Schedule, error) {
return nil, err
}

return &SpecSchedule{SpecSchedule: specSchedule.(*cron.SpecSchedule)}, nil
return &SpecSchedule{SpecSchedule: specSchedule.(*cron.SpecSchedule)}, nil //nolint:forcetypeassert // no need to check
}

// Parse2 is a helper function for parsing the spec and returning the SpecSchedule.
func (p Parser) Parse2(spec string) (Schedule, error) {
func (p Parser) Parse2(spec string) (Schedule, error) { //nolint:ireturn // return interface to support other interfaces
v, err := p.Parse(spec)
if err != nil {
return nil, err
}

return v.(*SpecSchedule), nil
return v.(*SpecSchedule), nil //nolint:forcetypeassert // no need to check
}

var _ cron.ScheduleParser = &Parser{}
35 changes: 35 additions & 0 deletions cron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import (
)

func Test_ParseSchedule(t *testing.T) {
logAMS, err := time.LoadLocation("Europe/Amsterdam")
if err != nil {
t.Fatalf("failed to load location: %v", err)
}

type testTime struct {
timeNow time.Time
next []time.Time
Expand Down Expand Up @@ -224,6 +229,36 @@ func Test_ParseSchedule(t *testing.T) {
},
},
},
{
message: "DTS test",
schedule: "30 2 26 3 *",
tests: []testTime{
{
timeNow: time.Date(2023, time.March, 1, 0, 0, 0, 0, time.UTC),
next: []time.Time{
time.Date(2023, time.March, 26, 2, 30, 0, 0, time.UTC),
},
prev: []time.Time{
time.Date(2022, time.March, 26, 2, 30, 0, 0, time.UTC),
},
},
},
},
{
message: "Amsterdam DTS test",
schedule: "CRON_TZ=Europe/Amsterdam 30 1 26 3 *",
tests: []testTime{
{
timeNow: time.Date(2023, time.March, 1, 0, 0, 0, 0, logAMS),
next: []time.Time{
time.Date(2023, time.March, 26, 1, 30, 0, 0, logAMS),
},
prev: []time.Time{
time.Date(2022, time.March, 26, 1, 30, 0, 0, logAMS),
},
},
},
},
}
for i, tt := range tests {
t.Run(fmt.Sprintf("parse_%d", i), func(t *testing.T) {
Expand Down
51 changes: 51 additions & 0 deletions example_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package hardloop_test

import (
"fmt"
"log"
"time"

"github.com/robfig/cron/v3"
"github.com/worldline-go/hardloop"
)

func Example_parser() {
schedule := "0 7 * * *"

p := hardloop.Parser{ParseFn: cron.ParseStandard}

s, err := p.Parse2(schedule)
if err != nil {
log.Fatal(err)
}

now := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)

fmt.Println("Next 5 times:")
tmpNow := now
for i := 0; i < 5; i++ {
tmpNow = s.Next(tmpNow)
fmt.Println(tmpNow)
}

tmpNow = now
fmt.Println("Prev 5 times:")
for i := 0; i < 5; i++ {
tmpNow = s.Prev(tmpNow)
fmt.Println(tmpNow)
}

// Output:
// Next 5 times:
// 2023-01-01 07:00:00 +0000 UTC
// 2023-01-02 07:00:00 +0000 UTC
// 2023-01-03 07:00:00 +0000 UTC
// 2023-01-04 07:00:00 +0000 UTC
// 2023-01-05 07:00:00 +0000 UTC
// Prev 5 times:
// 2022-12-31 07:00:00 +0000 UTC
// 2022-12-30 07:00:00 +0000 UTC
// 2022-12-29 07:00:00 +0000 UTC
// 2022-12-28 07:00:00 +0000 UTC
// 2022-12-27 07:00:00 +0000 UTC
}
46 changes: 24 additions & 22 deletions hardloop.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import (
"time"
)

// GapDurationStart to start the function should be at least 1 second bigger than gap duration
var GapDurationStart time.Duration = 1 * time.Second

// GapDurationStop to stop the function
var GapDurationStop time.Duration = 0

var (
// GapDurationStart to start the function should be at least 1 second bigger than gap duration.
GapDurationStart time.Duration = 1 * time.Second //nolint:revive // more readable
// GapDurationStop to stop the function.
GapDurationStop time.Duration = 0 //nolint:revive // more readable

// ErrCloseLoop is returned when the loop should be closed.
ErrCloseLoop = errors.New("close loop")
errTimeNotSet = errors.New("timeless schedule")
Expand All @@ -37,11 +36,11 @@ type Loop struct {
}

// NewLoop returns a new Loop with the given start and end cron specs and function.
// - Standard crontab specs, e.g. "* * * * ?"
// - Descriptors, e.g. "@midnight", "@every 1h30m"
// - Standard crontab specs, e.g. "* * * * ?"
// - Descriptors, e.g. "@midnight", "@every 1h30m"
func NewLoop(startSpec, endSpec []string, fn func(ctx context.Context, wg *sync.WaitGroup) error) (*Loop, error) {
var startSchedules []Schedule
var stopSchedules []Schedule
startSchedules := make([]Schedule, 0, len(startSpec))
stopSchedules := make([]Schedule, 0, len(endSpec))

for _, spec := range startSpec {
startSchedule, err := ParseStandard(spec)
Expand Down Expand Up @@ -78,9 +77,9 @@ func (l *Loop) SetLogger(log Logger) {
}

// ChangeStartSchedules sets the start cron specs.
// Currently not effects immediately will add in next version..
// Not effects immediately!
func (l *Loop) ChangeStartSchedules(startSpecs []string) error {
var startSchedules []Schedule
startSchedules := make([]Schedule, 0, len(startSpecs))

for _, spec := range startSpecs {
startSchedule, err := ParseStandard(spec)
Expand All @@ -97,11 +96,11 @@ func (l *Loop) ChangeStartSchedules(startSpecs []string) error {
}

// ChangeStopSchedule sets the end cron specs.
// Currently not effects immediately will add in next version.
func (l *Loop) ChangeStopSchedules(startSpecs []string) error {
var stopSchedules []Schedule
// Not effects immediately!
func (l *Loop) ChangeStopSchedules(stopSpecs []string) error {
stopSchedules := make([]Schedule, 0, len(stopSpecs))

for _, spec := range startSpecs {
for _, spec := range stopSpecs {
stopSchedule, err := ParseStandard(spec)
if err != nil {
return err
Expand Down Expand Up @@ -257,7 +256,7 @@ func (l *Loop) Run(ctx context.Context, wg *sync.WaitGroup) {
continue
}

// set next stop time
// set next stop time and clear the previous one
stopTimerChange := time.NewTimer(*stopDuration)
chStopDuration = stopTimerChange.C

Expand All @@ -270,8 +269,8 @@ func (l *Loop) Run(ctx context.Context, wg *sync.WaitGroup) {
}
stopTimer = stopTimerChange
case <-chStopDuration:
// run function
l.stopFunction(ctxLoop, wg)
// time to stop function
l.stopFunction()
}
}
}()
Expand Down Expand Up @@ -334,13 +333,15 @@ func (l *Loop) runFunction(ctx context.Context, wg *sync.WaitGroup) {
l.stopDuration <- &stopDuration
}

func (l *Loop) stopFunction(ctx context.Context, wg *sync.WaitGroup) {
func (l *Loop) stopFunction() {
l.mx.Lock()
defer l.mx.Unlock()

// if function is not running, trigger exited to get the next start time
if !l.isFunctionRunning {
// trigger exited
l.exited <- struct{}{}

return
}

Expand All @@ -355,14 +356,15 @@ func (l *Loop) initializeTime(ctx context.Context, wg *sync.WaitGroup) {
if v != nil {
// function should run now
l.runFunction(ctx, wg)

return
}

// set next start time
l.exited <- struct{}{}
}

// getStartTime if return nil, start now
// getStartTime if return nil, start now.
func (l *Loop) getStartTime(now time.Time) (*time.Time, error) {
nextStart := FindNext(l.startSchedules, now)

Expand All @@ -373,7 +375,7 @@ func (l *Loop) getStartTime(now time.Time) (*time.Time, error) {
return &nextStart, nil
}

// getStopTime if return nil, stop now
// getStopTime if return nil, stop now.
func (l *Loop) getStopTime(now time.Time) (*time.Time, error) {
prevStop := FindPrev(l.stopSchedules, now)

Expand Down
Loading

0 comments on commit 5d02d8c

Please sign in to comment.