Merge a8b1b0e618 into a325cc745a
This commit is contained in:
commit
c04bb01fe9
|
|
@ -41,6 +41,8 @@ import (
|
|||
//+kubebuilder:printcolumn:JSONPath=".status.runningEphemeralRunners",name=Running Runners,type=integer
|
||||
//+kubebuilder:printcolumn:JSONPath=".status.finishedEphemeralRunners",name=Finished Runners,type=integer
|
||||
//+kubebuilder:printcolumn:JSONPath=".status.deletingEphemeralRunners",name=Deleting Runners,type=integer
|
||||
//+kubebuilder:printcolumn:JSONPath=".status.desiredMinRunners",name=Desired Minimum Runners,type=integer
|
||||
//+kubebuilder:printcolumn:JSONPath=".status.scheduledOverridesSummary",name=Schedule,type=string
|
||||
|
||||
// AutoscalingRunnerSet is the Schema for the autoscalingrunnersets API
|
||||
type AutoscalingRunnerSet struct {
|
||||
|
|
@ -84,6 +86,13 @@ type AutoscalingRunnerSetSpec struct {
|
|||
// +optional
|
||||
// +kubebuilder:validation:Minimum:=0
|
||||
MinRunners *int `json:"minRunners,omitempty"`
|
||||
|
||||
// +optional
|
||||
// ScheduledOverrides is the list of ScheduledOverride.
|
||||
// It can be used to override a few fields of AutoscalingRunnerSetSpec on schedule.
|
||||
// The earlier a scheduled override is, the higher it is prioritized.
|
||||
// +optional
|
||||
ScheduledOverrides []ScheduledOverride `json:"scheduledOverrides,omitempty"`
|
||||
}
|
||||
|
||||
type GitHubServerTLSConfig struct {
|
||||
|
|
@ -232,6 +241,40 @@ type ProxyServerConfig struct {
|
|||
CredentialSecretRef string `json:"credentialSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// ScheduledOverride can be used to override a few fields of AutoscalingRunnerSetSpec on schedule.
|
||||
// A schedule can optionally be recurring, so that the corresponding override happens every day, week, month, or year.
|
||||
type ScheduledOverride struct {
|
||||
// StartTime is the time at which the first override starts.
|
||||
StartTime metav1.Time `json:"startTime"`
|
||||
|
||||
// EndTime is the time at which the first override ends.
|
||||
EndTime metav1.Time `json:"endTime"`
|
||||
|
||||
// MinRunners is the number of runners while overriding.
|
||||
// If omitted, it doesn't override minRunners.
|
||||
// +optional
|
||||
// +nullable
|
||||
// +kubebuilder:validation:Minimum=0
|
||||
MinRunners *int `json:"minRunners,omitempty"`
|
||||
|
||||
// +optional
|
||||
RecurrenceRule RecurrenceRule `json:"recurrenceRule,omitempty"`
|
||||
}
|
||||
|
||||
type RecurrenceRule struct {
|
||||
// Frequency is the name of a predefined interval of each recurrence.
|
||||
// The valid values are "Daily", "Weekly", "Monthly", and "Yearly".
|
||||
// If empty, the corresponding override happens only once.
|
||||
// +optional
|
||||
// +kubebuilder:validation:Enum=Daily;Weekly;Monthly;Yearly
|
||||
Frequency string `json:"frequency,omitempty"`
|
||||
|
||||
// UntilTime is the time of the final recurrence.
|
||||
// If empty, the schedule recurs forever.
|
||||
// +optional
|
||||
UntilTime metav1.Time `json:"untilTime,omitempty"`
|
||||
}
|
||||
|
||||
// AutoscalingRunnerSetStatus defines the observed state of AutoscalingRunnerSet
|
||||
type AutoscalingRunnerSetStatus struct {
|
||||
// +optional
|
||||
|
|
@ -248,6 +291,12 @@ type AutoscalingRunnerSetStatus struct {
|
|||
RunningEphemeralRunners int `json:"runningEphemeralRunners"`
|
||||
// +optional
|
||||
FailedEphemeralRunners int `json:"failedEphemeralRunners"`
|
||||
|
||||
// +optional
|
||||
// +kubebuilder:validation:Minimum:=0
|
||||
DesiredMinRunners int `json:"desiredMinRunners"`
|
||||
// +optional
|
||||
ScheduledOverridesSummary *string `json:"scheduledOverridesSummary,omitempty"`
|
||||
}
|
||||
|
||||
func (ars *AutoscalingRunnerSet) ListenerSpecHash() string {
|
||||
|
|
|
|||
|
|
@ -106,6 +106,11 @@ spec:
|
|||
minRunners: {{ .Values.minRunners | int }}
|
||||
{{- end }}
|
||||
|
||||
{{- if and .Values.scheduledOverrides (kindIs "slice" .Values.scheduledOverrides) }}
|
||||
scheduledOverrides:
|
||||
{{- .Values.scheduledOverrides | toYaml | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.listenerTemplate}}
|
||||
listenerTemplate:
|
||||
{{- toYaml . | nindent 4}}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ githubConfigSecret:
|
|||
## calculated as a sum of minRunners and the number of jobs assigned to the scale set.
|
||||
# minRunners: 0
|
||||
|
||||
## scheduledOverrides is a list of scheduled overrides to the minReplicas.
|
||||
## See docs for more info: https://github.com/actions/actions-runner-controller/blob/master/docs/automatically-scaling-runners.md#scheduled-overrides
|
||||
## Please note that the field minReplicas was renamed to minRunners
|
||||
# scheduledOverrides: []
|
||||
|
||||
# runnerGroup: "default"
|
||||
|
||||
## name of the runner scale set to create. Defaults to the helm release name
|
||||
|
|
@ -205,7 +210,6 @@ template:
|
|||
- name: runner
|
||||
image: ghcr.io/actions/actions-runner:latest
|
||||
command: ["/home/runner/run.sh"]
|
||||
|
||||
## Optional controller service account that needs to have required Role and RoleBinding
|
||||
## to operate this gha-runner-scale-set installation.
|
||||
## The helm chart will try to find the controller deployment and its service account at installation time.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||
"github.com/actions/actions-runner-controller/build"
|
||||
|
|
@ -244,10 +245,17 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
|||
log.Info("AutoscalingListener does not exist.")
|
||||
}
|
||||
|
||||
desiredMinRunnersChanged := false
|
||||
// If the listener's minRunners value is different from the autoscalingRunnerSet desired minRunners, we recreate the listener
|
||||
if listenerFound && listener.Spec.MinRunners != autoscalingRunnerSet.Status.DesiredMinRunners {
|
||||
log.Info("RunnerScaleSetListener minRunners is different from the autoscalingRunnerSet desired minRunners", "current", listener.Spec.MinRunners, "desired", autoscalingRunnerSet.Status.DesiredMinRunners)
|
||||
desiredMinRunnersChanged = true
|
||||
}
|
||||
|
||||
// Our listener pod is out of date, so we need to delete it to get a new recreate.
|
||||
listenerValuesHashChanged := listener.Annotations[annotationKeyValuesHash] != autoscalingRunnerSet.Annotations[annotationKeyValuesHash]
|
||||
listenerSpecHashChanged := listener.Annotations[annotationKeyRunnerSpecHash] != autoscalingRunnerSet.ListenerSpecHash()
|
||||
if listenerFound && (listenerValuesHashChanged || listenerSpecHashChanged) {
|
||||
if listenerFound && (listenerValuesHashChanged || listenerSpecHashChanged || desiredMinRunnersChanged) {
|
||||
log.Info("RunnerScaleSetListener is out of date. Deleting it so that it is recreated", "name", listener.Name)
|
||||
if err := r.Delete(ctx, listener); err != nil {
|
||||
if kerrors.IsNotFound(err) {
|
||||
|
|
@ -300,20 +308,61 @@ func (r *AutoscalingRunnerSetReconciler) Reconcile(ctx context.Context, req ctrl
|
|||
return r.createAutoScalingListenerForRunnerSet(ctx, autoscalingRunnerSet, latestRunnerSet, log)
|
||||
}
|
||||
|
||||
minRunners, active, upcoming, err := r.getMinRunners(log, time.Now(), *autoscalingRunnerSet)
|
||||
if err != nil {
|
||||
log.Error(err, "Could not compute desired min runners")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
var overridesSummary string
|
||||
|
||||
currentReplicasOutOfDate := latestRunnerSet.Status.CurrentReplicas != autoscalingRunnerSet.Status.CurrentRunners
|
||||
minReplicasOutOfDate := autoscalingRunnerSet.Status.DesiredMinRunners != minRunners
|
||||
|
||||
// Update the status of autoscaling runner set.
|
||||
if latestRunnerSet.Status.CurrentReplicas != autoscalingRunnerSet.Status.CurrentRunners {
|
||||
if minReplicasOutOfDate || currentReplicasOutOfDate {
|
||||
if err := patchSubResource(ctx, r.Status(), autoscalingRunnerSet, func(obj *v1alpha1.AutoscalingRunnerSet) {
|
||||
if currentReplicasOutOfDate {
|
||||
obj.Status.CurrentRunners = latestRunnerSet.Status.CurrentReplicas
|
||||
obj.Status.PendingEphemeralRunners = latestRunnerSet.Status.PendingEphemeralRunners
|
||||
obj.Status.RunningEphemeralRunners = latestRunnerSet.Status.RunningEphemeralRunners
|
||||
obj.Status.FailedEphemeralRunners = latestRunnerSet.Status.FailedEphemeralRunners
|
||||
}); err != nil {
|
||||
log.Error(err, "Failed to update autoscaling runner set status with current runner count")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if minReplicasOutOfDate {
|
||||
obj.Status.DesiredMinRunners = minRunners
|
||||
|
||||
if (active != nil && upcoming == nil) || (active != nil && upcoming != nil && active.Period.EndTime.Before(upcoming.Period.StartTime)) {
|
||||
var after int
|
||||
if obj.Status.DesiredMinRunners >= 0 {
|
||||
after = obj.Status.DesiredMinRunners
|
||||
}
|
||||
|
||||
overridesSummary = fmt.Sprintf("min=%d status=active endTime=%s", after, active.Period.EndTime)
|
||||
}
|
||||
|
||||
if active == nil && upcoming != nil || (active != nil && upcoming != nil && active.Period.EndTime.After(upcoming.Period.StartTime)) {
|
||||
if upcoming.ScheduledOverride.MinRunners != nil {
|
||||
overridesSummary = fmt.Sprintf("min=%d status=upcoming startTime=%s", *upcoming.ScheduledOverride.MinRunners, upcoming.Period.StartTime)
|
||||
}
|
||||
}
|
||||
|
||||
if overridesSummary != "" {
|
||||
obj.Status.ScheduledOverridesSummary = &overridesSummary
|
||||
} else {
|
||||
obj.Status.ScheduledOverridesSummary = nil
|
||||
}
|
||||
}
|
||||
}); err != nil {
|
||||
log.Error(err, "Failed to update autoscaling runner set status")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
return ctrl.Result{RequeueAfter: DefaultScaleSetHealthyRequeueAfter}, nil
|
||||
}
|
||||
|
||||
// Prevents overprovisioning of runners.
|
||||
|
|
@ -766,6 +815,84 @@ func (r *AutoscalingRunnerSetReconciler) SetupWithManager(mgr ctrl.Manager) erro
|
|||
Complete(r)
|
||||
}
|
||||
|
||||
type Override struct {
|
||||
ScheduledOverride v1alpha1.ScheduledOverride
|
||||
Period Period
|
||||
}
|
||||
|
||||
func (r *AutoscalingRunnerSetReconciler) matchScheduledOverrides(log logr.Logger, now time.Time, autoscalingRunnerSet v1alpha1.AutoscalingRunnerSet) (*int, *Override, *Override, error) {
|
||||
var minRunners *int
|
||||
var active, upcoming *Override
|
||||
|
||||
for _, o := range autoscalingRunnerSet.Spec.ScheduledOverrides {
|
||||
log.V(1).Info(
|
||||
"Checking scheduled override",
|
||||
"now", now,
|
||||
"startTime", o.StartTime,
|
||||
"endTime", o.EndTime,
|
||||
"frequency", o.RecurrenceRule.Frequency,
|
||||
"untilTime", o.RecurrenceRule.UntilTime,
|
||||
)
|
||||
|
||||
a, u, err := MatchSchedule(
|
||||
now, o.StartTime.Time, o.EndTime.Time,
|
||||
RecurrenceRule{
|
||||
Frequency: o.RecurrenceRule.Frequency,
|
||||
UntilTime: o.RecurrenceRule.UntilTime.Time,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return minRunners, nil, nil, err
|
||||
}
|
||||
|
||||
// Use the first when there are two or more active scheduled overrides,
|
||||
// as the spec defines that the earlier scheduled override is prioritized higher than later ones.
|
||||
if a != nil && active == nil {
|
||||
active = &Override{Period: *a, ScheduledOverride: o}
|
||||
|
||||
if o.MinRunners != nil {
|
||||
minRunners = o.MinRunners
|
||||
|
||||
log.V(1).Info(
|
||||
"Found active scheduled override",
|
||||
"activeStartTime", a.StartTime,
|
||||
"activeEndTime", a.EndTime,
|
||||
"activeMinRunners", minRunners,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if u != nil && (upcoming == nil || u.StartTime.Before(upcoming.Period.StartTime)) {
|
||||
upcoming = &Override{Period: *u, ScheduledOverride: o}
|
||||
|
||||
log.V(1).Info(
|
||||
"Found upcoming scheduled override",
|
||||
"upcomingStartTime", u.StartTime,
|
||||
"upcomingEndTime", u.EndTime,
|
||||
"upcomingMinRunners", o.MinRunners,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return minRunners, active, upcoming, nil
|
||||
}
|
||||
|
||||
func (r *AutoscalingRunnerSetReconciler) getMinRunners(log logr.Logger, now time.Time, autoscalingRunnerSet v1alpha1.AutoscalingRunnerSet) (int, *Override, *Override, error) {
|
||||
var minRunners int
|
||||
if autoscalingRunnerSet.Spec.MinRunners != nil && *autoscalingRunnerSet.Spec.MinRunners >= 0 {
|
||||
minRunners = *autoscalingRunnerSet.Spec.MinRunners
|
||||
}
|
||||
|
||||
m, active, upcoming, err := r.matchScheduledOverrides(log, now, autoscalingRunnerSet)
|
||||
if err != nil {
|
||||
return 0, nil, nil, err
|
||||
} else if m != nil {
|
||||
minRunners = *m
|
||||
}
|
||||
|
||||
return minRunners, active, upcoming, nil
|
||||
}
|
||||
|
||||
type autoscalingRunnerSetFinalizerDependencyCleaner struct {
|
||||
// configuration fields
|
||||
client client.Client
|
||||
|
|
|
|||
|
|
@ -585,6 +585,101 @@ var _ = Describe("Test AutoScalingRunnerSet controller", Ordered, func() {
|
|||
})
|
||||
})
|
||||
|
||||
Context("When a scheduled override is active in an AutoscalingRunnerSet", func() {
|
||||
It("It should override min runners in the AutoscalingListener", func() {
|
||||
// Wait till the listener is created
|
||||
listener := new(v1alpha1.AutoscalingListener)
|
||||
Eventually(
|
||||
func() error {
|
||||
return k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
||||
},
|
||||
autoscalingRunnerSetTestTimeout,
|
||||
autoscalingRunnerSetTestInterval,
|
||||
).Should(Succeed(), "Listener should be created")
|
||||
|
||||
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
||||
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
||||
Expect(len(runnerSetList.Items)).To(Equal(1), "There should be 1 EphemeralRunnerSet")
|
||||
runnerSet := runnerSetList.Items[0]
|
||||
|
||||
minOverride := 0
|
||||
|
||||
// Patch the AutoScalingRunnerSet with a scheduled override
|
||||
patched := autoscalingRunnerSet.DeepCopy()
|
||||
patched.Spec.ScheduledOverrides = []v1alpha1.ScheduledOverride{
|
||||
{
|
||||
StartTime: metav1.Now(),
|
||||
EndTime: metav1.NewTime(metav1.Now().Add(1 * time.Hour)),
|
||||
MinRunners: &minOverride,
|
||||
},
|
||||
}
|
||||
err = k8sClient.Patch(ctx, patched, client.MergeFrom(autoscalingRunnerSet))
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to patch AutoScalingRunnerSet")
|
||||
autoscalingRunnerSet = patched.DeepCopy()
|
||||
|
||||
// We should not re-create a new EphemeralRunnerSet
|
||||
Consistently(
|
||||
func() (string, error) {
|
||||
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
||||
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(runnerSetList.Items) != 1 {
|
||||
return "", fmt.Errorf("We should have only 1 EphemeralRunnerSet, but got %v", len(runnerSetList.Items))
|
||||
}
|
||||
|
||||
return string(runnerSetList.Items[0].UID), nil
|
||||
},
|
||||
autoscalingRunnerSetTestTimeout,
|
||||
autoscalingRunnerSetTestInterval).Should(BeEquivalentTo(string(runnerSet.UID)), "New EphemeralRunnerSet should not be created")
|
||||
|
||||
// We should only re-create a new listener
|
||||
Eventually(
|
||||
func() (string, error) {
|
||||
listener := new(v1alpha1.AutoscalingListener)
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: scaleSetListenerName(autoscalingRunnerSet), Namespace: autoscalingRunnerSet.Namespace}, listener)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(listener.UID), nil
|
||||
},
|
||||
autoscalingRunnerSetTestTimeout,
|
||||
autoscalingRunnerSetTestInterval).ShouldNot(BeEquivalentTo(string(listener.UID)), "New Listener should be created")
|
||||
|
||||
// Check if the listener has the min runners override
|
||||
Expect(listener.Spec.MinRunners).To(Equal(minOverride), "Listener should have the min runners override")
|
||||
|
||||
desiredScheduledOverridesSummary := fmt.Sprintf("min=%d status=active endTime=%s", *autoscalingRunnerSet.Spec.ScheduledOverrides[0].MinRunners, autoscalingRunnerSet.Spec.ScheduledOverrides[0].EndTime.Time)
|
||||
|
||||
desiredStatus := v1alpha1.AutoscalingRunnerSetStatus{
|
||||
CurrentRunners: 0,
|
||||
State: "",
|
||||
PendingEphemeralRunners: 0,
|
||||
RunningEphemeralRunners: 0,
|
||||
FailedEphemeralRunners: 0,
|
||||
ScheduledOverridesSummary: &desiredScheduledOverridesSummary,
|
||||
DesiredMinRunners: 0,
|
||||
}
|
||||
|
||||
Eventually(
|
||||
func() (v1alpha1.AutoscalingRunnerSetStatus, error) {
|
||||
updated := new(v1alpha1.AutoscalingRunnerSet)
|
||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: autoscalingRunnerSet.Name, Namespace: autoscalingRunnerSet.Namespace}, updated)
|
||||
if err != nil {
|
||||
return v1alpha1.AutoscalingRunnerSetStatus{}, fmt.Errorf("failed to get AutoScalingRunnerSet: %w", err)
|
||||
}
|
||||
return updated.Status, nil
|
||||
},
|
||||
autoscalingRunnerSetTestTimeout,
|
||||
autoscalingRunnerSetTestInterval,
|
||||
).Should(BeEquivalentTo(desiredStatus), "AutoScalingRunnerSet status should be updated")
|
||||
})
|
||||
})
|
||||
|
||||
It("Should update Status on EphemeralRunnerSet status Update", func() {
|
||||
ars := new(v1alpha1.AutoscalingRunnerSet)
|
||||
Eventually(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/actions/actions-runner-controller/logging"
|
||||
)
|
||||
|
||||
|
|
@ -68,6 +70,9 @@ const DefaultScaleSetListenerLogLevel = string(logging.LogLevelDebug)
|
|||
// DefaultScaleSetListenerLogFormat is the default log format applied
|
||||
const DefaultScaleSetListenerLogFormat = string(logging.LogFormatText)
|
||||
|
||||
// DefaultScaleSetHealthyRequeueAfter is the default requeue time for healthy scale sets
|
||||
const DefaultScaleSetHealthyRequeueAfter = time.Duration(1 * time.Minute)
|
||||
|
||||
// ownerKey is field selector matching the owner name of a particular resource
|
||||
const resourceOwnerKey = ".metadata.controller"
|
||||
|
||||
|
|
|
|||
|
|
@ -79,14 +79,11 @@ func (b *ResourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
|
|||
return nil, err
|
||||
}
|
||||
|
||||
effectiveMinRunners := 0
|
||||
effectiveMinRunners := autoscalingRunnerSet.Status.DesiredMinRunners
|
||||
effectiveMaxRunners := math.MaxInt32
|
||||
if autoscalingRunnerSet.Spec.MaxRunners != nil {
|
||||
effectiveMaxRunners = *autoscalingRunnerSet.Spec.MaxRunners
|
||||
}
|
||||
if autoscalingRunnerSet.Spec.MinRunners != nil {
|
||||
effectiveMinRunners = *autoscalingRunnerSet.Spec.MinRunners
|
||||
}
|
||||
|
||||
labels := b.mergeLabels(autoscalingRunnerSet.Labels, map[string]string{
|
||||
LabelKeyGitHubScaleSetNamespace: autoscalingRunnerSet.Namespace,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/teambition/rrule-go"
|
||||
)
|
||||
|
||||
type RecurrenceRule struct {
|
||||
Frequency string
|
||||
UntilTime time.Time
|
||||
}
|
||||
|
||||
type Period struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
}
|
||||
|
||||
func (r *Period) String() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return r.StartTime.Format(time.RFC3339) + "-" + r.EndTime.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func MatchSchedule(now time.Time, startTime, endTime time.Time, recurrenceRule RecurrenceRule) (*Period, *Period, error) {
|
||||
return calculateActiveAndUpcomingRecurringPeriods(
|
||||
now,
|
||||
startTime,
|
||||
endTime,
|
||||
recurrenceRule.Frequency,
|
||||
recurrenceRule.UntilTime,
|
||||
)
|
||||
}
|
||||
|
||||
func calculateActiveAndUpcomingRecurringPeriods(now, startTime, endTime time.Time, frequency string, untilTime time.Time) (*Period, *Period, error) {
|
||||
var freqValue rrule.Frequency
|
||||
|
||||
var freqDurationDay int
|
||||
var freqDurationMonth int
|
||||
var freqDurationYear int
|
||||
|
||||
switch frequency {
|
||||
case "Daily":
|
||||
freqValue = rrule.DAILY
|
||||
freqDurationDay = 1
|
||||
case "Weekly":
|
||||
freqValue = rrule.WEEKLY
|
||||
freqDurationDay = 7
|
||||
case "Monthly":
|
||||
freqValue = rrule.MONTHLY
|
||||
freqDurationMonth = 1
|
||||
case "Yearly":
|
||||
freqValue = rrule.YEARLY
|
||||
freqDurationYear = 1
|
||||
case "":
|
||||
if now.Before(startTime) {
|
||||
return nil, &Period{StartTime: startTime, EndTime: endTime}, nil
|
||||
}
|
||||
|
||||
if now.Before(endTime) {
|
||||
return &Period{StartTime: startTime, EndTime: endTime}, nil, nil
|
||||
}
|
||||
|
||||
return nil, nil, nil
|
||||
default:
|
||||
return nil, nil, fmt.Errorf(`invalid freq %q: It must be one of "Daily", "Weekly", "Monthly", and "Yearly"`, frequency)
|
||||
}
|
||||
|
||||
freqDurationLater := time.Date(
|
||||
now.Year()+freqDurationYear,
|
||||
time.Month(int(now.Month())+freqDurationMonth),
|
||||
now.Day()+freqDurationDay,
|
||||
now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), now.Location(),
|
||||
)
|
||||
|
||||
freqDuration := freqDurationLater.Sub(now)
|
||||
|
||||
overrideDuration := endTime.Sub(startTime)
|
||||
if overrideDuration > freqDuration {
|
||||
return nil, nil, fmt.Errorf("override's duration %s must be equal to or shorter than the duration implied by freq %q (%s)", overrideDuration, frequency, freqDuration)
|
||||
}
|
||||
|
||||
rrule, err := rrule.NewRRule(rrule.ROption{
|
||||
Freq: freqValue,
|
||||
Dtstart: startTime,
|
||||
Until: untilTime,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
overrideDurationBefore := now.Add(-overrideDuration + 1)
|
||||
activeOverrideStarts := rrule.Between(overrideDurationBefore, now, true)
|
||||
|
||||
var active *Period
|
||||
|
||||
if len(activeOverrideStarts) > 1 {
|
||||
return nil, nil, fmt.Errorf("[bug] unexpted number of active overrides found: %v", activeOverrideStarts)
|
||||
} else if len(activeOverrideStarts) == 1 {
|
||||
active = &Period{
|
||||
StartTime: activeOverrideStarts[0],
|
||||
EndTime: activeOverrideStarts[0].Add(overrideDuration),
|
||||
}
|
||||
}
|
||||
|
||||
oneSecondLater := now.Add(1)
|
||||
upcomingOverrideStarts := rrule.Between(oneSecondLater, freqDurationLater, true)
|
||||
|
||||
var next *Period
|
||||
|
||||
if len(upcomingOverrideStarts) > 0 {
|
||||
next = &Period{
|
||||
StartTime: upcomingOverrideStarts[0],
|
||||
EndTime: upcomingOverrideStarts[0].Add(overrideDuration),
|
||||
}
|
||||
}
|
||||
|
||||
return active, next, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,617 @@
|
|||
package actionsgithubcom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCalculateActiveAndUpcomingRecurringPeriods(t *testing.T) {
|
||||
type recurrence struct {
|
||||
Start string
|
||||
End string
|
||||
Freq string
|
||||
Until string
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
now string
|
||||
|
||||
recurrence recurrence
|
||||
|
||||
wantActive string
|
||||
wantUpcoming string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
_, err := time.Parse(time.RFC3339, "2021-05-08T00:00:00Z")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
now, err := time.Parse(time.RFC3339, tc.now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
active, upcoming, err := parseAndMatchRecurringPeriod(now, tc.recurrence.Start, tc.recurrence.End, tc.recurrence.Freq, tc.recurrence.Until)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if active.String() != tc.wantActive {
|
||||
t.Errorf("unexpected active: want %q, got %q", tc.wantActive, active)
|
||||
}
|
||||
|
||||
if upcoming.String() != tc.wantUpcoming {
|
||||
t.Errorf("unexpected upcoming: want %q, got %q", tc.wantUpcoming, upcoming)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("onetime override about to start", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-04-30T23:59:59+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("onetime override started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("onetime override about to end", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-02T23:59:59+09:00",
|
||||
|
||||
wantActive: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("onetime override ended", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-03T00:00:00+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override about to start", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-04-30T23:59:59+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "2021-05-08T00:00:00+09:00-2021-05-10T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override about to end", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-02T23:59:59+09:00",
|
||||
|
||||
wantActive: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "2021-05-08T00:00:00+09:00-2021-05-10T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override ended", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-03T00:00:00+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2021-05-08T00:00:00+09:00-2021-05-10T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override reccurrence about to start", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-07T23:59:59+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2021-05-08T00:00:00+09:00-2021-05-10T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override reccurrence started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-08T00:00:00+09:00",
|
||||
|
||||
wantActive: "2021-05-08T00:00:00+09:00-2021-05-10T00:00:00+09:00",
|
||||
wantUpcoming: "2021-05-15T00:00:00+09:00-2021-05-17T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override reccurrence about to end", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-09T23:59:59+09:00",
|
||||
|
||||
wantActive: "2021-05-08T00:00:00+09:00-2021-05-10T00:00:00+09:00",
|
||||
wantUpcoming: "2021-05-15T00:00:00+09:00-2021-05-17T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override reccurrence ended", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-10T00:00:00+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2021-05-15T00:00:00+09:00-2021-05-17T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override's last reccurrence about to start", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-04-29T23:59:59+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2022-04-30T00:00:00+09:00-2022-05-02T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override reccurrence started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-04-30T00:00:00+09:00",
|
||||
|
||||
wantActive: "2022-04-30T00:00:00+09:00-2022-05-02T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override reccurrence about to end", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-05-01T23:59:59+09:00",
|
||||
|
||||
wantActive: "2022-04-30T00:00:00+09:00-2022-05-02T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override reccurrence ended", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-05-02T00:00:00+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("weekly override repeated forever started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Weekly",
|
||||
},
|
||||
|
||||
now: "2021-05-08T00:00:00+09:00",
|
||||
|
||||
wantActive: "2021-05-08T00:00:00+09:00-2021-05-10T00:00:00+09:00",
|
||||
wantUpcoming: "2021-05-15T00:00:00+09:00-2021-05-17T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("monthly override started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Monthly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "2021-06-01T00:00:00+09:00-2021-06-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("monthly override recurrence started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Monthly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-06-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2021-06-01T00:00:00+09:00-2021-06-03T00:00:00+09:00",
|
||||
wantUpcoming: "2021-07-01T00:00:00+09:00-2021-07-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("monthly override's last reccurence about to start", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Monthly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-04-30T23:59:59+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2022-05-01T00:00:00+09:00-2022-05-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("monthly override's last reccurence started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Monthly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-05-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2022-05-01T00:00:00+09:00-2022-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("monthly override's last reccurence started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Monthly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-05-01T00:00:01+09:00",
|
||||
|
||||
wantActive: "2022-05-01T00:00:00+09:00-2022-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("monthly override's last reccurence ending", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Monthly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-05-02T23:59:59+09:00",
|
||||
|
||||
wantActive: "2022-05-01T00:00:00+09:00-2022-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("monthly override's last reccurence ended", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Monthly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-05-03T00:00:00+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("yearly override started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Yearly",
|
||||
Until: "2022-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2021-05-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2021-05-01T00:00:00+09:00-2021-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "2022-05-01T00:00:00+09:00-2022-05-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("yearly override reccurrence started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Yearly",
|
||||
Until: "2023-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2022-05-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2022-05-01T00:00:00+09:00-2022-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "2023-05-01T00:00:00+09:00-2023-05-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("yearly override's last recurrence about to start", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Yearly",
|
||||
Until: "2023-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2023-04-30T23:59:59+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "2023-05-01T00:00:00+09:00-2023-05-03T00:00:00+09:00",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("yearly override's last recurrence started", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Yearly",
|
||||
Until: "2023-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2023-05-01T00:00:00+09:00",
|
||||
|
||||
wantActive: "2023-05-01T00:00:00+09:00-2023-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("yearly override's last recurrence ending", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Yearly",
|
||||
Until: "2023-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2023-05-02T23:23:59+09:00",
|
||||
|
||||
wantActive: "2023-05-01T00:00:00+09:00-2023-05-03T00:00:00+09:00",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("yearly override's last recurrence ended", func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
check(t, testcase{
|
||||
recurrence: recurrence{
|
||||
Start: "2021-05-01T00:00:00+09:00",
|
||||
End: "2021-05-03T00:00:00+09:00",
|
||||
Freq: "Yearly",
|
||||
Until: "2023-05-01T00:00:00+09:00",
|
||||
},
|
||||
|
||||
now: "2023-05-03T00:00:00+09:00",
|
||||
|
||||
wantActive: "",
|
||||
wantUpcoming: "",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func parseAndMatchRecurringPeriod(now time.Time, start, end, frequency, until string) (*Period, *Period, error) {
|
||||
startTime, err := time.Parse(time.RFC3339, start)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
endTime, err := time.Parse(time.RFC3339, end)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var untilTime time.Time
|
||||
|
||||
if until != "" {
|
||||
ut, err := time.Parse(time.RFC3339, until)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
untilTime = ut
|
||||
}
|
||||
|
||||
return MatchSchedule(now, startTime, endTime, RecurrenceRule{Frequency: frequency, UntilTime: untilTime})
|
||||
}
|
||||
|
||||
func FuzzMatchSchedule(f *testing.F) {
|
||||
start := time.Now()
|
||||
end := time.Now()
|
||||
now := time.Now()
|
||||
f.Fuzz(func(t *testing.T, freq string) {
|
||||
// Verify that it never panics
|
||||
_, _, _ = MatchSchedule(now, start, end, RecurrenceRule{Frequency: freq})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue