Files
temporal/tests/cron_test.go
Rodrigo Zhou 18c8ab7f77 Replace payload.Encode with sadefs.MustEncodeValue (#10432)
## What changed?
Replace `payload.Encode` with `sadefs.MustEncodeValue` for encoding
predefined search attributes used internally.

## Why?
`sadefs.MustEncodeValue` adds metadata type to the payload.

## How did you test it?
- [x] built
- [ ] run locally and tested manually
- [x] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)

## Potential risks
2026-06-11 16:28:16 -05:00

643 lines
24 KiB
Go

package tests
import (
"errors"
"fmt"
"sort"
"testing"
"time"
"github.com/google/uuid"
commandpb "go.temporal.io/api/command/v1"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
filterpb "go.temporal.io/api/filter/v1"
taskqueuepb "go.temporal.io/api/taskqueue/v1"
workflowpb "go.temporal.io/api/workflow/v1"
"go.temporal.io/api/workflowservice/v1"
sdkclient "go.temporal.io/sdk/client"
"go.temporal.io/sdk/workflow"
"go.temporal.io/server/common"
"go.temporal.io/server/common/failure"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/payload"
"go.temporal.io/server/common/payloads"
"go.temporal.io/server/common/searchattribute/sadefs"
"go.temporal.io/server/common/testing/parallelsuite"
"go.temporal.io/server/tests/testcore"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
type CronTestSuite struct {
parallelsuite.Suite[*CronTestSuite]
}
type CronTestClientSuite struct {
parallelsuite.Suite[*CronTestClientSuite]
}
func TestCronTestSuite(t *testing.T) {
parallelsuite.Run(t, &CronTestSuite{})
}
func TestCronTestClientSuite(t *testing.T) {
parallelsuite.Run(t, &CronTestClientSuite{})
}
func (s *CronTestSuite) TestCronWorkflow_Failed_Infinite() {
env := testcore.NewEnv(s.T())
id := "functional-wf-cron-failed-infinite-test"
wt := "functional-wf-cron-failed-infinite-type"
tl := "functional-wf-cron-failed-infinite-taskqueue"
identity := "worker1"
cronSchedule := "@every 5s"
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.NewString(),
Namespace: env.Namespace().String(),
WorkflowId: id,
WorkflowType: &commonpb.WorkflowType{Name: wt},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: nil,
WorkflowRunTimeout: durationpb.New(5 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
CronSchedule: cronSchedule, // minimum interval by standard spec is 1m (* * * * *, use non-standard descriptor for short interval for test
RetryPolicy: &commonpb.RetryPolicy{
MaximumAttempts: 2,
MaximumInterval: durationpb.New(1 * time.Second),
BackoffCoefficient: 1.2,
},
}
we, err0 := env.FrontendClient().StartWorkflowExecution(s.Context(), request)
s.NoError(err0)
env.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
respondFailed := false
seeRetry := false
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if !respondFailed {
respondFailed = true
return []*commandpb.Command{
{
CommandType: enumspb.COMMAND_TYPE_FAIL_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_FailWorkflowExecutionCommandAttributes{
FailWorkflowExecutionCommandAttributes: &commandpb.FailWorkflowExecutionCommandAttributes{
Failure: failure.NewServerFailure("cron error for retry", false),
}},
}}, nil
}
startEvent := task.History.Events[0]
seeRetry = startEvent.GetWorkflowExecutionStartedEventAttributes().Initiator == enumspb.CONTINUE_AS_NEW_INITIATOR_RETRY
return []*commandpb.Command{
{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{
CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: nil,
}},
}}, nil
}
poller := &testcore.TaskPoller{
Client: env.FrontendClient(),
Namespace: env.Namespace().String(),
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Identity: identity,
WorkflowTaskHandler: wtHandler,
Logger: env.Logger,
T: s.T(),
}
env.Logger.Info("Process first cron run which fails")
_, err := poller.PollAndProcessWorkflowTask(testcore.WithDumpHistory)
s.NoError(err)
env.Logger.Info("Process first cron run which completes")
_, err = poller.PollAndProcessWorkflowTask(testcore.WithDumpHistory)
s.NoError(err)
s.True(seeRetry)
}
func (s *CronTestSuite) TestCronWorkflow() {
env := testcore.NewEnv(s.T())
id := "functional-wf-cron-test"
wt := "functional-wf-cron-type"
tl := "functional-wf-cron-taskqueue"
identity := "worker1"
cronSchedule := "@every 3s"
targetBackoffDuration := time.Second * 3
backoffDurationTolerance := time.Millisecond * 500
memo := &commonpb.Memo{
Fields: map[string]*commonpb.Payload{"memoKey": payload.EncodeString("memoVal")},
}
searchAttr := &commonpb.SearchAttributes{
IndexedFields: map[string]*commonpb.Payload{
"CustomKeywordField": sadefs.MustEncodeValue("keyword-value", enumspb.INDEXED_VALUE_TYPE_KEYWORD),
},
}
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.NewString(),
Namespace: env.Namespace().String(),
WorkflowId: id,
WorkflowType: &commonpb.WorkflowType{Name: wt},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
CronSchedule: cronSchedule, // minimum interval by standard spec is 1m (* * * * *, use non-standard descriptor for short interval for test
Memo: memo,
SearchAttributes: searchAttr,
}
// Because of rounding in GetBackoffForNextSchedule, we'll tend to stay aligned to whatever
// phase we start in relative to second boundaries, but drift slightly later within the second
// over time. If we cross a second boundary, one of our intervals will end up being 2s instead
// of 3s. To avoid this, wait until we can start early in the second.
for time.Now().Nanosecond()/int(time.Millisecond) > 150 {
time.Sleep(50 * time.Millisecond) //nolint:forbidigo
}
startWorkflowTS := time.Now().UTC()
we, err0 := env.FrontendClient().StartWorkflowExecution(s.Context(), request)
s.NoError(err0)
env.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
var executions []*commonpb.WorkflowExecution
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if task.PreviousStartedEventId == common.EmptyEventID {
startedEvent := task.History.Events[0]
if startedEvent.GetEventType() != enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED {
return []*commandpb.Command{
{
CommandType: enumspb.COMMAND_TYPE_FAIL_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_FailWorkflowExecutionCommandAttributes{FailWorkflowExecutionCommandAttributes: &commandpb.FailWorkflowExecutionCommandAttributes{
Failure: failure.NewServerFailure("incorrect first event", true),
}},
}}, nil
}
// Just check that it can be decoded
var lcr int
s.NoError(payloads.Decode(startedEvent.GetWorkflowExecutionStartedEventAttributes().GetLastCompletionResult(), &lcr))
}
executions = append(executions, task.WorkflowExecution)
if len(executions) >= 3 {
return []*commandpb.Command{
{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("cron-test-result"),
}},
}}, nil
}
return []*commandpb.Command{
{
CommandType: enumspb.COMMAND_TYPE_FAIL_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_FailWorkflowExecutionCommandAttributes{FailWorkflowExecutionCommandAttributes: &commandpb.FailWorkflowExecutionCommandAttributes{
Failure: failure.NewServerFailure("cron-test-error", false),
}},
}}, nil
}
poller := &testcore.TaskPoller{
Client: env.FrontendClient(),
Namespace: env.Namespace().String(),
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Identity: identity,
WorkflowTaskHandler: wtHandler,
Logger: env.Logger,
T: s.T(),
}
startFilter := &filterpb.StartTimeFilter{}
startFilter.EarliestTime = timestamppb.New(startWorkflowTS)
startFilter.LatestTime = timestamppb.New(time.Now().UTC())
// Sleep some time before checking the open executions.
// This will not cost extra time as the polling for first workflow task will be blocked for 3 seconds.
time.Sleep(2 * time.Second) //nolint:forbidigo
resp, err := env.FrontendClient().ListOpenWorkflowExecutions(s.Context(), &workflowservice.ListOpenWorkflowExecutionsRequest{
Namespace: env.Namespace().String(),
MaximumPageSize: 100,
StartTimeFilter: startFilter,
Filters: &workflowservice.ListOpenWorkflowExecutionsRequest_ExecutionFilter{ExecutionFilter: &filterpb.WorkflowExecutionFilter{
WorkflowId: id,
}},
})
s.NoError(err)
s.Len(resp.GetExecutions(), 1)
executionInfo := resp.GetExecutions()[0]
s.Equal(targetBackoffDuration, executionInfo.GetExecutionTime().AsTime().Sub(executionInfo.GetStartTime().AsTime()))
_, err = poller.PollAndProcessWorkflowTask()
s.NoError(err)
// Make sure the cron workflow start running at a proper time, in this case 3 seconds after the
// startWorkflowExecution request
backoffDuration := time.Now().UTC().Sub(startWorkflowTS)
s.Greater(backoffDuration, targetBackoffDuration)
s.Less(backoffDuration, targetBackoffDuration+backoffDurationTolerance)
_, err = poller.PollAndProcessWorkflowTask()
s.NoError(err)
_, err = poller.PollAndProcessWorkflowTask()
s.NoError(err)
s.Len(executions, 3)
_, terminateErr := env.FrontendClient().TerminateWorkflowExecution(s.Context(), &workflowservice.TerminateWorkflowExecutionRequest{
Namespace: env.Namespace().String(),
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
},
})
s.NoError(terminateErr)
// first two should be failures
for i := range 2 {
events := env.GetHistory(env.Namespace().String(), executions[i])
s.EqualHistoryEvents(fmt.Sprintf(`
1 WorkflowExecutionStarted {"Memo":{"Fields":{"memoKey":{"Data":"\"memoVal\""}}},"SearchAttributes":{"IndexedFields":{"CustomKeywordField":{"Data":"\"keyword-value\"","Metadata":{"type":"Keyword"}}}}}
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionFailed {"Failure":{"Message":"cron-test-error"},"NewExecutionRunId":"%s"}
`, executions[i+1].RunId), events)
}
// third should be completed
events := env.GetHistory(env.Namespace().String(), executions[2])
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted {"Memo":{"Fields":{"memoKey":{"Data":"\"memoVal\""}}},"SearchAttributes":{"IndexedFields":{"CustomKeywordField":{"Data":"\"keyword-value\"","Metadata":{"type":"Keyword"}}}}}
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionCompleted {"Result":{"Payloads":[{"Data":"\"cron-test-result\""}]}}
`, events)
startFilter.LatestTime = timestamppb.New(time.Now().UTC())
var closedExecutions []*workflowpb.WorkflowExecutionInfo
for range 10 {
resp, err := env.FrontendClient().ListClosedWorkflowExecutions(s.Context(), &workflowservice.ListClosedWorkflowExecutionsRequest{
Namespace: env.Namespace().String(),
MaximumPageSize: 100,
StartTimeFilter: startFilter,
Filters: &workflowservice.ListClosedWorkflowExecutionsRequest_ExecutionFilter{ExecutionFilter: &filterpb.WorkflowExecutionFilter{
WorkflowId: id,
}},
})
s.NoError(err)
if len(resp.GetExecutions()) == 4 {
closedExecutions = resp.GetExecutions()
break
}
time.Sleep(200 * time.Millisecond) //nolint:forbidigo
}
s.NotNil(closedExecutions)
dweResponse, err := env.FrontendClient().DescribeWorkflowExecution(s.Context(), &workflowservice.DescribeWorkflowExecutionRequest{
Namespace: env.Namespace().String(),
Execution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
})
s.NoError(err)
expectedExecutionTime := dweResponse.WorkflowExecutionInfo.GetStartTime().AsTime().Add(3 * time.Second)
s.Equal(expectedExecutionTime, dweResponse.WorkflowExecutionInfo.GetExecutionTime().AsTime())
sort.Slice(closedExecutions, func(i, j int) bool {
return closedExecutions[i].GetStartTime().AsTime().Before(closedExecutions[j].GetStartTime().AsTime())
})
lastExecution := closedExecutions[0]
for i := 1; i < 4; i++ {
executionInfo := closedExecutions[i]
expectedBackoff := executionInfo.GetExecutionTime().AsTime().Sub(lastExecution.GetExecutionTime().AsTime())
// The execution time calculated based on last execution close time.
// However, the current execution time is based on the current start time.
// This code is to remove the diff between current start time and last execution close time.
// TODO: Remove this line once we unify the time source
executionTimeDiff := executionInfo.GetStartTime().AsTime().Sub(lastExecution.GetCloseTime().AsTime())
// The backoff between any two executions should be a multiplier of the target backoff duration which is 3 in this test
s.Equal(
0,
int((expectedBackoff-executionTimeDiff).Round(time.Second).Seconds())%int(targetBackoffDuration.Seconds()),
"expected backoff %v-%v=%v should be multiplier of target backoff %v",
expectedBackoff.Seconds(),
executionTimeDiff.Seconds(),
(expectedBackoff - executionTimeDiff).Round(time.Second).Seconds(),
targetBackoffDuration.Seconds())
lastExecution = executionInfo
// TODO: Remove the describeWorkflowExecution call when firstRunID in WorkflowExecutionInfo
// is populated by Visibility api as well.
dweResponse, err := env.FrontendClient().DescribeWorkflowExecution(s.Context(), &workflowservice.DescribeWorkflowExecutionRequest{
Namespace: env.Namespace().String(),
Execution: executionInfo.GetExecution(),
})
s.NoError(err)
s.Equal(we.RunId, dweResponse.WorkflowExecutionInfo.GetFirstRunId())
}
}
func (s *CronTestClientSuite) TestCronWorkflowCompletionStates() {
// Run a cron workflow that completes in (almost) all the possible ways:
// Run 1: succeeds
// Run 2: fails
// Run 3: times out
// Run 4: succeeds
// Run 5: succeeds
// Run 6: terminated before it runs
// Continue-as-new is not tested (behavior is currently not correct)
env := testcore.NewEnv(s.T())
id := "functional-wf-cron-failed-test"
cronSchedule := "@every 3s"
targetBackoffDuration := 3 * time.Second
workflowRunTimeout := 5 * time.Second
tolerance := 500 * time.Millisecond
durationNear := func(value, target time.Duration) {
s.T().Helper()
s.Greater(value, target-tolerance)
s.Less(value, target+tolerance)
}
runIDs := make(map[string]bool)
wfCh := make(chan int)
workflowFn := func(ctx workflow.Context) (string, error) {
runIDs[workflow.GetInfo(ctx).WorkflowExecution.RunID] = true
iteration := len(runIDs)
wfCh <- iteration
var lcr string
switch iteration {
case 1:
s.False(workflow.HasLastCompletionResult(ctx))
s.NoError(workflow.GetLastError(ctx))
return "pass", nil
case 2:
s.True(workflow.HasLastCompletionResult(ctx))
s.NoError(workflow.GetLastCompletionResult(ctx, &lcr))
s.Equal("pass", lcr)
s.NoError(workflow.GetLastError(ctx))
return "", errors.New("second error") //nolint:err113
case 3:
s.True(workflow.HasLastCompletionResult(ctx))
s.NoError(workflow.GetLastCompletionResult(ctx, &lcr))
s.Equal("pass", lcr)
s.Error(workflow.GetLastError(ctx))
s.Equal("second error", workflow.GetLastError(ctx).Error())
s.NoError(workflow.Sleep(ctx, 10*time.Second)) // cause wft timeout
panic("should have been timed out on server already")
case 4:
s.True(workflow.HasLastCompletionResult(ctx))
s.NoError(workflow.GetLastCompletionResult(ctx, &lcr))
s.Equal("pass", lcr)
s.Error(workflow.GetLastError(ctx))
s.Equal("workflow timeout (type: StartToClose)", workflow.GetLastError(ctx).Error())
return "pass again", nil
case 5:
s.True(workflow.HasLastCompletionResult(ctx))
s.NoError(workflow.GetLastCompletionResult(ctx, &lcr))
s.Equal("pass again", lcr)
s.NoError(workflow.GetLastError(ctx))
return "final pass", nil
}
panic("shouldn't get here")
}
env.SdkWorker().RegisterWorkflow(workflowFn)
// Because of rounding in GetBackoffForNextSchedule, we'll tend to stay aligned to whatever
// phase we start in relative to second boundaries, but drift slightly later within the second
// over time. If we cross a second boundary, one of our intervals will end up being 2s instead
// of 3s. To avoid this, wait until we can start early in the second.
for time.Now().Nanosecond()/int(time.Millisecond) > 150 {
time.Sleep(50 * time.Millisecond) //nolint:forbidigo
}
workflowOptions := sdkclient.StartWorkflowOptions{
ID: id,
TaskQueue: env.WorkerTaskQueue(),
WorkflowRunTimeout: workflowRunTimeout,
CronSchedule: cronSchedule,
}
ts := time.Now()
startTs := ts
_, err := env.SdkClient().ExecuteWorkflow(s.Context(), workflowOptions, workflowFn)
s.NoError(err)
// check execution and history of first run
exec := s.listOpenWorkflowExecutions(env, startTs, time.Now(), id, 1)[0]
firstRunID := exec.GetExecution().RunId
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, exec.GetStatus())
historyEvents := env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(fmt.Sprintf(`
1 WorkflowExecutionStarted {"ContinuedExecutionRunId":"","CronSchedule":"@every 3s","FirstExecutionRunId":"%s", "Initiator":3}`, firstRunID), historyEvents)
attrs1 := historyEvents[0].GetWorkflowExecutionStartedEventAttributes()
// not `"FirstWorkflowTaskBackoff":{"Nanos":0,"Seconds":3}` in the history above because DurationNear is not supported by EqualHistoryEvents.
durationNear(attrs1.FirstWorkflowTaskBackoff.AsDuration(), targetBackoffDuration)
// wait for first run
s.Equal(1, <-wfCh)
durationNear(time.Since(ts), targetBackoffDuration)
ts = time.Now()
// let first run finish, then check execution and history of second run
s.Eventually(
func() bool {
exec = s.listOpenWorkflowExecutions(env, startTs, time.Now(), id, 1)[0]
return exec.GetExecution().GetRunId() != firstRunID
},
targetBackoffDuration+tolerance,
250*time.Millisecond,
)
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, exec.GetStatus())
historyEvents = env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(fmt.Sprintf(`
1 WorkflowExecutionStarted {"ContinuedExecutionRunId":"%s","CronSchedule":"@every 3s","FirstExecutionRunId":"%s", "Initiator":%d}`, firstRunID, firstRunID, enumspb.CONTINUE_AS_NEW_INITIATOR_CRON_SCHEDULE), historyEvents)
attrs2 := historyEvents[0].GetWorkflowExecutionStartedEventAttributes()
// not `"FirstWorkflowTaskBackoff":{"Nanos":0,"Seconds":3}` in the history above because DurationNear is not supported by EqualHistoryEvents.
durationNear(attrs2.FirstWorkflowTaskBackoff.AsDuration(), targetBackoffDuration)
// wait for second run
s.Equal(2, <-wfCh)
durationNear(time.Since(ts), targetBackoffDuration)
ts = time.Now()
// don't bother checking started events for subsequent runs, we covered the important parts already
// wait for third run
s.Equal(3, <-wfCh)
durationNear(time.Since(ts), targetBackoffDuration)
ts = time.Now()
// wait for fourth run (third one waits for timeout after 5s, so will run after 6s)
s.Equal(4, <-wfCh)
durationNear(time.Since(ts), 2*targetBackoffDuration)
ts = time.Now()
// wait for fifth run
s.Equal(5, <-wfCh)
durationNear(time.Since(ts), targetBackoffDuration)
// let fifth run finish and sixth get scheduled
_ = s.listClosedWorkflowExecutions(env, startTs, time.Now().Add(targetBackoffDuration), id, 5)
_ = s.listOpenWorkflowExecutions(env, startTs, time.Now().Add(targetBackoffDuration), id, 1)
// then terminate
s.NoError(env.SdkClient().TerminateWorkflow(s.Context(), id, "", "test is over"))
closedExecutions := s.listClosedWorkflowExecutions(env, startTs, time.Now(), id, 6)
exec = closedExecutions[5] // first: success
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, exec.GetStatus())
historyEvents = env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionCompleted {"Result":{"Payloads":[{"Data":"\"pass\""}]}}`, historyEvents)
exec = closedExecutions[4] // second: fail
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_FAILED, exec.GetStatus())
historyEvents = env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionFailed {"Failure":{"Message":"second error"}}`, historyEvents)
exec = closedExecutions[3] // third: timed out
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT, exec.GetStatus())
historyEvents = env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 TimerStarted
6 WorkflowExecutionTimedOut {"RetryState":5} // enumspb.RETRY_STATE_RETRY_POLICY_NOT_SET`, historyEvents)
exec = closedExecutions[2] // fourth: success
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, exec.GetStatus())
historyEvents = env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionCompleted {"Result":{"Payloads":[{"Data":"\"pass again\""}]}}`, historyEvents)
exec = closedExecutions[1] // fifth: success
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, exec.GetStatus())
historyEvents = env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionCompleted {"Result":{"Payloads":[{"Data":"\"final pass\""}]}}`, historyEvents)
exec = closedExecutions[0] // sixth: terminated
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, exec.GetStatus())
events := env.GetHistory(env.Namespace().String(), exec.GetExecution())
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowExecutionTerminated {"Reason":"test is over"}`, events)
}
func (s *CronTestClientSuite) listOpenWorkflowExecutions(env *testcore.TestEnv, start, end time.Time, id string, expectedNumber int) []*workflowpb.WorkflowExecutionInfo {
s.T().Helper()
var resp *workflowservice.ListOpenWorkflowExecutionsResponse
s.Eventuallyf(
func() bool {
var err error
resp, err = env.SdkClient().ListOpenWorkflow(
s.Context(), &workflowservice.ListOpenWorkflowExecutionsRequest{
Namespace: env.Namespace().String(),
MaximumPageSize: int32(2 * expectedNumber),
StartTimeFilter: &filterpb.StartTimeFilter{
EarliestTime: timestamppb.New(start),
LatestTime: timestamppb.New(end),
},
Filters: &workflowservice.ListOpenWorkflowExecutionsRequest_ExecutionFilter{
ExecutionFilter: &filterpb.WorkflowExecutionFilter{
WorkflowId: id,
},
},
},
)
s.NoError(err)
return len(resp.GetExecutions()) == expectedNumber
},
testcore.WaitForESToSettle,
100*time.Millisecond,
"timeout expecting %d executions, found %d",
expectedNumber,
len(resp.GetExecutions()),
)
return resp.GetExecutions()
}
func (s *CronTestClientSuite) listClosedWorkflowExecutions(env *testcore.TestEnv, start, end time.Time, id string, expectedNumber int) []*workflowpb.WorkflowExecutionInfo {
s.T().Helper()
var resp *workflowservice.ListClosedWorkflowExecutionsResponse
s.Eventuallyf(
func() bool {
var err error
resp, err = env.SdkClient().ListClosedWorkflow(
s.Context(),
&workflowservice.ListClosedWorkflowExecutionsRequest{
Namespace: env.Namespace().String(),
MaximumPageSize: int32(2 * expectedNumber),
StartTimeFilter: &filterpb.StartTimeFilter{
EarliestTime: timestamppb.New(start),
LatestTime: timestamppb.New(end),
},
Filters: &workflowservice.ListClosedWorkflowExecutionsRequest_ExecutionFilter{
ExecutionFilter: &filterpb.WorkflowExecutionFilter{
WorkflowId: id,
},
},
},
)
s.NoError(err)
return len(resp.GetExecutions()) == expectedNumber
},
testcore.WaitForESToSettle,
100*time.Millisecond,
"timeout expecting %d executions, found %d",
expectedNumber,
len(resp.GetExecutions()),
)
return resp.GetExecutions()
}