Skip to content

Conversation

creativeprojects
Copy link
Owner

No description provided.

Copy link

coderabbitai bot commented Jul 27, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes introduce a new mechanism for specifying a reference time ("fromNow") when creating and evaluating scheduled tasks. This affects task creation, scheduling triggers, and test logic. The trigger boundary fields are refactored to use pointers to time values instead of strings, and test suites are updated to support and normalise this new time reference for consistent results.

Changes

Cohort / File(s) Change Summary
schtasks/task.go Added fromNow field to Task, updated constructor to accept options, refactored trigger logic to use fromNow, and changed trigger boundary types to pointers to time.Time.
schtasks/task_options.go Introduced new file defining TaskOption interface, WithFromNowOption struct, and related constructor and method for setting fromNow.
schtasks/taskscheduler.go Updated createTaskDefinition to accept a from parameter and pass it as a task option; updated related function calls.
schtasks/taskscheduler_test.go Enhanced integration test to use a configurable fromNow reference, added time zone normalisation for task boundaries, and improved logging.
schtasks/trigger.go Changed StartBoundary and EndBoundary fields in trigger structs from string to *time.Time.
schtasks/trigger_test.go Updated test fixtures and calls to support new from parameter in task definition creation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–20 minutes

Suggested labels

enhancement

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-schtasks-flaky-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@creativeprojects creativeprojects marked this pull request as draft July 27, 2025 17:35
@creativeprojects creativeprojects changed the title Draft: enhance task scheduling with flexible time options Fix windows scheduler flaky tests Jul 27, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63dee21 and cf8ef15.

📒 Files selected for processing (6)
  • schtasks/task.go (14 hunks)
  • schtasks/task_options.go (1 hunks)
  • schtasks/taskscheduler.go (3 hunks)
  • schtasks/taskscheduler_test.go (4 hunks)
  • schtasks/trigger.go (2 hunks)
  • schtasks/trigger_test.go (2 hunks)
🧰 Additional context used
🧠 Learnings (6)
schtasks/taskscheduler.go (2)

Learnt from: creativeprojects
PR: #425
File: schedule/handler_windows.go:97-118
Timestamp: 2025-02-04T14:38:07.701Z
Learning: The shell.SplitArguments function in the resticprofile project returns only []string and does not return any error.

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

schtasks/trigger.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

schtasks/taskscheduler_test.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

schtasks/trigger_test.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

schtasks/task.go (1)

Learnt from: creativeprojects
PR: #425
File: schedule/calendar_interval.go:98-114
Timestamp: 2025-02-04T14:10:09.439Z
Learning: In schedule/calendar_interval.go, the map inside CalendarInterval is always initialized through newCalendarInterval() or clone(), so additional nil checks on the map are not needed in setCalendarIntervalValueFromType().

schtasks/task_options.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

🔇 Additional comments (18)
schtasks/trigger.go (1)

6-6: Type safety improvement with time package import.

Good addition to support the typed time boundaries instead of string representations.

schtasks/trigger_test.go (2)

40-40: Consistent integration with new reference time parameter.

All test fixtures properly initialise the new from field to time.Time{}, which maintains existing test behaviour whilst supporting the new reference time functionality.

Also applies to: 47-47, 55-55, 62-62, 69-69, 76-76, 84-84, 91-91, 99-99, 106-106, 113-113, 120-120, 128-128, 135-135, 142-142, 150-150, 157-157, 165-165, 172-172, 180-180, 187-187, 195-195


221-221: Function call updated to match new signature.

The call to createTaskDefinition correctly passes the new from parameter, maintaining consistency with the updated function signature.

schtasks/taskscheduler.go (3)

29-29: Required import for new time-based functionality.

Addition of the time package import supports the new reference time parameter.


170-175: Well-designed options pattern implementation.

The conditional creation of TaskOption slice and use of WithFromNow option when from is non-zero is elegant. This approach:

  • Avoids unnecessary allocations when no options are needed
  • Provides extensibility for future options
  • Maintains clean separation of concerns

55-55: Maintains backwards compatibility.

Passing time.Time{} to createTaskDefinition ensures existing behaviour is preserved whilst supporting the new reference time functionality.

schtasks/task_options.go (1)

1-22: Clean implementation of the options pattern.

This implementation follows Go idioms excellently:

  • Minimal, focused interface with single responsibility
  • Constructor function WithFromNow follows Go naming conventions
  • Options pattern provides extensibility for future task configuration
  • Clean integration with the Task.setFromNow method

The pattern allows for future expansion whilst maintaining a clean API.

schtasks/task.go (5)

33-33: Proper field design for reference time.

The fromNow field is correctly:

  • Unexported to maintain encapsulation
  • Excluded from XML serialisation with xml:"-"
  • Used as a reference time for scheduling calculations

36-80: Well-structured constructor with options pattern.

The NewTask constructor implementation is excellent:

  • Maintains backwards compatibility by initialising fromNow to current time
  • Properly applies options after base initialization
  • Follows the established options pattern consistently

The integration of the options pattern is clean and extensible.


114-117: Consistent state management in setFromNow.

The setFromNow method properly maintains consistency by updating both the fromNow field and the RegistrationInfo.Date, ensuring the task's metadata remains synchronised with its reference time.


121-121: Consistent use of reference time across all trigger methods.

All trigger creation methods (addTimeTrigger, addDailyTrigger, addWeeklyTrigger, addMonthlyTrigger) consistently use t.fromNow instead of time.Now(). This ensures:

  • Predictable scheduling behaviour
  • Better testability with controlled reference times
  • Consistent trigger calculations across the system

Also applies to: 139-139, 149-149, 163-163, 182-182, 191-191, 201-201, 216-216, 236-236, 246-246, 266-266, 276-276


121-121: Proper type alignment with trigger structure changes.

The StartBoundary assignments now use *time.Time pointers, correctly aligning with the type changes in trigger.go. This maintains type consistency across the scheduling system.

Also applies to: 149-149, 163-163, 182-182, 201-201, 216-216, 236-236, 266-266, 276-276

schtasks/taskscheduler_test.go (6)

128-166: Well-designed test fixture enhancements for time-based scheduling.

The addition of the fromNow field provides excellent test coverage for schedule evaluation from different reference points. The specific test cases for "every minute at 12" with reference times before (11:20) and after (13:20) noon effectively test boundary conditions. The commented-out test case is appropriately excluded as generating 60 triggers would be excessive for testing purposes.


171-252: Appropriate use of zero time values for backward compatibility.

Using time.Time{} for the majority of fixtures maintains existing test behaviour whilst enabling the new time reference functionality where specifically needed.


283-283: Correct parameter addition to support time reference functionality.

The updated function call properly passes the fromNow value from the test fixture to createTaskDefinition.


290-290: Useful diagnostic logging for trigger counts.

The logging statement provides valuable insight into the number of triggers generated, which is particularly helpful for debugging complex scheduling patterns.


309-311: Proper task normalisation for consistent comparison.

The approach of zeroing the fromNow field and converting trigger boundaries to UTC ensures reliable task comparison regardless of timezone differences. This addresses the Windows Task Scheduler's use of local time in XML definitions.


321-337: Well-implemented UTC conversion function with proper safety checks.

The taskInUTC function correctly handles timezone normalisation with appropriate nil pointer checks and clear documentation. The implementation properly addresses both TimeTrigger and CalendarTrigger types, ensuring consistent test results across different system timezones.

type TimeTrigger struct {
Enabled *bool `xml:"Enabled"` // indicates whether the trigger is enabled
StartBoundary string `xml:"StartBoundary"`
StartBoundary *time.Time `xml:"StartBoundary"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify XML serialisation behaviour for time.Time pointers.

The change from string to *time.Time for StartBoundary and EndBoundary fields improves type safety significantly. However, ensure that time.Time XML marshalling produces the expected format for the Windows Task Scheduler.

Also applies to: 24-25


🏁 Script executed:

#!/bin/bash
# Verify how time.Time is serialised to XML in Go
cd /tmp
cat > test_time_xml.go << 'EOF'
package main

import (
	"encoding/xml"
	"fmt"
	"time"
)

type TestStruct struct {
	TimeField *time.Time `xml:"TimeField"`
}

func main() {
	now := time.Now()
	test := TestStruct{TimeField: &now}
	
	data, err := xml.MarshalIndent(test, "", "  ")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	
	fmt.Printf("XML output:\n%s\n", data)
	
	// Test with nil pointer
	testNil := TestStruct{TimeField: nil}
	dataNil, err := xml.MarshalIndent(testNil, "", "  ")
	if err != nil {
		fmt.Printf("Error with nil: %v\n", err)
		return
	}
	
	fmt.Printf("\nXML output with nil:\n%s\n", dataNil)
}
EOF

go run test_time_xml.go

Length of output: 285


Ensure Windows Task Scheduler–compatible XML for time.Time pointers

Our test shows Go’s default XML marshalling of *time.Time emits RFC3339Nano (e.g. 2025-07-27T17:38:11.303820919Z) and omits nil pointers entirely:

<StartBoundary>2025-07-27T17:38:11.303820919Z</StartBoundary>

Windows Task Scheduler requires timestamps in the form “YYYY-MM-DDThh:mm:ss” (no fractional seconds or timezone). To guarantee compatibility, wrap *time.Time in a custom type that implements xml.Marshaler (and optionally xml.Unmarshaler) using t.Format("2006-01-02T15:04:05"). For example:

// In schtasks/trigger.go

// Replace the field types:
type Trigger struct {
    StartBoundary *TaskTime `xml:"StartBoundary"`
    EndBoundary   *TaskTime `xml:"EndBoundary"`
    // …
}

// TaskTime ensures formatting matches Task Scheduler’s schema.
type TaskTime time.Time

func (t *TaskTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    if t == nil {
        return nil
    }
    formatted := (*time.Time)(t).Format("2006-01-02T15:04:05")
    return e.EncodeElement(formatted, start)
}

func (t *TaskTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var v string
    if err := d.DecodeElement(&v, &start); err != nil {
        return err
    }
    tt, err := time.Parse("2006-01-02T15:04:05", v)
    if err != nil {
        return err
    }
    *t = TaskTime(tt)
    return nil
}

– Apply this pattern to both StartBoundary and EndBoundary in schtasks/trigger.go to ensure correct formatting.

🤖 Prompt for AI Agents
In schtasks/trigger.go at line 18, the StartBoundary field uses *time.Time which
marshals to XML with fractional seconds and timezone, incompatible with Windows
Task Scheduler. To fix this, define a custom TaskTime type wrapping time.Time
that implements xml.Marshaler and xml.Unmarshaler, formatting time as
"2006-01-02T15:04:05" without fractional seconds or timezone. Replace
StartBoundary and EndBoundary fields to use *TaskTime instead of *time.Time, and
implement the MarshalXML and UnmarshalXML methods on TaskTime to handle the
custom formatting.

Copy link

codecov bot commented Jul 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.72%. Comparing base (bce38f0) to head (319c000).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #533      +/-   ##
==========================================
+ Coverage   79.69%   79.72%   +0.02%     
==========================================
  Files         137      138       +1     
  Lines       13467    13483      +16     
==========================================
+ Hits        10732    10748      +16     
  Misses       2310     2310              
  Partials      425      425              
Flag Coverage Δ
unittests 79.72% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant