Files
tasks/chasm/lib/scheduler/generator_tasks_test.go
zeekay 5a601d081b rebrand: go.temporal.io/server -> github.com/hanzoai/tasks
Full module path rebrand from Temporal to Hanzo Tasks:
- go.mod module: github.com/hanzoai/tasks
- All internal Go imports updated (1906 files)
- cmd/server renamed to cmd/tasksd
- Binary name: tasksd (was temporal-server)
- Docker images: hanzoai/tasks (was temporalio/server)
- README rewritten with Hanzo Tasks branding
- LLM.md created with integration notes
- Makefile, goreleaser, CI workflows updated
- go.temporal.io/sdk and go.temporal.io/api imports preserved (separate repos)

Fork of Temporal.io (MIT license).
2026-03-19 10:36:40 -07:00

156 lines
5.5 KiB
Go

package scheduler_test
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/hanzoai/tasks/chasm"
"github.com/hanzoai/tasks/chasm/lib/scheduler"
"github.com/hanzoai/tasks/chasm/lib/scheduler/gen/schedulerpb/v1"
"github.com/hanzoai/tasks/common/metrics"
queueerrors "github.com/hanzoai/tasks/service/history/queues/errors"
"github.com/hanzoai/tasks/service/history/tasks"
legacyscheduler "github.com/hanzoai/tasks/service/worker/scheduler"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/types/known/timestamppb"
)
func newGeneratorExecutor(env *testEnv) *scheduler.GeneratorTaskExecutor {
return scheduler.NewGeneratorTaskExecutor(scheduler.GeneratorTaskExecutorOptions{
Config: defaultConfig(),
MetricsHandler: metrics.NoopMetricsHandler,
BaseLogger: env.Logger,
SpecProcessor: env.SpecProcessor,
SpecBuilder: legacyscheduler.NewSpecBuilder(),
})
}
func TestGeneratorTask_Execute_ProcessTimeRangeFails(t *testing.T) {
// Create a custom mock spec processor that fails on ProcessTimeRange.
ctrl := gomock.NewController(t)
specProcessor := scheduler.NewMockSpecProcessor(ctrl)
now := time.Now()
// First call during newTestEnv's CloseTransaction should succeed.
specProcessor.EXPECT().ProcessTimeRange(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(&scheduler.ProcessedTimeRange{
NextWakeupTime: now.Add(defaultInterval),
LastActionTime: now,
}, nil).Times(1)
// Second call during test should fail.
specProcessor.EXPECT().ProcessTimeRange(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
).Return(nil, errors.New("processTimeRange bug"))
specProcessor.EXPECT().NextTime(gomock.Any(), gomock.Any()).Return(legacyscheduler.GetNextTimeResult{
Next: now.Add(defaultInterval),
Nominal: now.Add(defaultInterval),
}, nil).AnyTimes()
env := newTestEnv(t, withSpecProcessor(specProcessor))
executor := newGeneratorExecutor(env)
ctx := env.MutableContext()
generator := env.Scheduler.Generator.Get(ctx)
// If ProcessTimeRange fails, we should fail the task as an internal error.
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
var target *queueerrors.UnprocessableTaskError
require.ErrorAs(t, err, &target)
require.Equal(t, "failed to process a time range: processTimeRange bug", target.Message)
}
func TestGeneratorTask_ExecuteBufferTask_Basic(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
ctx := env.MutableContext()
sched := env.Scheduler
generator := sched.Generator.Get(ctx)
// Move high water mark back in time (Generator always compares high water mark
// against system time) to generate buffered actions.
highWatermark := ctx.Now(generator).UTC().Add(-defaultInterval * 5)
generator.LastProcessedTime = timestamppb.New(highWatermark)
// Execute the generate task.
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
// We expect 5 buffered starts.
invoker := sched.Invoker.Get(ctx)
require.Len(t, invoker.BufferedStarts, 5)
// Validate RequestId -> WorkflowId mapping.
for _, start := range invoker.BufferedStarts {
require.Equal(t, start.WorkflowId, invoker.RunningWorkflowID(start.RequestId))
}
// Generator's high water mark should have advanced.
newHighWatermark := generator.LastProcessedTime.AsTime()
require.True(t, newHighWatermark.After(highWatermark))
// Ensure we scheduled a physical side-effect task on the tree at immediate time.
// The InvokerExecuteTask is a side-effect task that starts workflows.
// The InvokerProcessBufferTask (pure) executes inline during CloseTransaction.
require.NoError(t, env.CloseTransaction())
require.True(t, env.HasTask(&tasks.ChasmTask{}, chasm.TaskScheduledTimeImmediate))
}
func TestGeneratorTask_UpdateFutureActionTimes_UnlimitedActions(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
ctx := env.MutableContext()
generator := env.Scheduler.Generator.Get(ctx)
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
require.NotEmpty(t, generator.FutureActionTimes)
require.Len(t, generator.FutureActionTimes, 10)
}
func TestGeneratorTask_UpdateFutureActionTimes_LimitedActions(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
ctx := env.MutableContext()
sched := env.Scheduler
generator := sched.Generator.Get(ctx)
sched.Schedule.State.LimitedActions = true
sched.Schedule.State.RemainingActions = 2
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
require.Len(t, generator.FutureActionTimes, 2)
}
func TestGeneratorTask_UpdateFutureActionTimes_SkipsBeforeUpdateTime(t *testing.T) {
env := newTestEnv(t)
executor := newGeneratorExecutor(env)
ctx := env.MutableContext()
sched := env.Scheduler
generator := sched.Generator.Get(ctx)
// UpdateTime acts as a floor - action times at or before it are skipped.
baseTime := ctx.Now(generator).UTC()
updateTime := baseTime.Add(defaultInterval / 2)
sched.Info.UpdateTime = timestamppb.New(updateTime)
err := executor.Execute(ctx, generator, chasm.TaskAttributes{}, &schedulerpb.GeneratorTask{})
require.NoError(t, err)
require.NotEmpty(t, generator.FutureActionTimes)
for _, futureTime := range generator.FutureActionTimes {
require.True(t, futureTime.AsTime().After(updateTime))
}
}