Skip to content

Commit 311a196

Browse files
authored
Add the case where changing the activities (addition/subtraction/modification in current behavior) in the switch case has no effect on replayer. (#1238)
* Added the conflicting activity registration bug * Added copyright info * Resolved git comments and split the test cases into two * Cleaned up the code * Added more comments nit
1 parent a73fe0e commit 311a196

File tree

3 files changed

+341
-0
lines changed

3 files changed

+341
-0
lines changed

test/replaytests/choice.json

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
[
2+
{
3+
"eventId": 1,
4+
"timestamp": 1679427717214248670,
5+
"eventType": "WorkflowExecutionStarted",
6+
"version": 0,
7+
"taskId": 5242986,
8+
"workflowExecutionStartedEventAttributes": {
9+
"workflowType": {
10+
"name": "choice"
11+
},
12+
"taskList": {
13+
"name": "choiceGroup"
14+
},
15+
"executionStartToCloseTimeoutSeconds": 60,
16+
"taskStartToCloseTimeoutSeconds": 60,
17+
"continuedExecutionRunId": "",
18+
"originalExecutionRunId": "e7a1f06b-39a8-4753-bb8d-e359b39a5324",
19+
"identity": "82212@agautam-NV709R969P@@4eb66ba1-536b-4258-b9a8-843256089257",
20+
"firstExecutionRunId": "e7a1f06b-39a8-4753-bb8d-e359b39a5324",
21+
"attempt": 0,
22+
"cronSchedule": "",
23+
"firstDecisionTaskBackoffSeconds": 0,
24+
"header": {}
25+
}
26+
},
27+
{
28+
"eventId": 2,
29+
"timestamp": 1679427717214310753,
30+
"eventType": "DecisionTaskScheduled",
31+
"version": 0,
32+
"taskId": 5242987,
33+
"decisionTaskScheduledEventAttributes": {
34+
"taskList": {
35+
"name": "choiceGroup"
36+
},
37+
"startToCloseTimeoutSeconds": 60,
38+
"attempt": 0
39+
}
40+
},
41+
{
42+
"eventId": 3,
43+
"timestamp": 1679427717244069128,
44+
"eventType": "DecisionTaskStarted",
45+
"version": 0,
46+
"taskId": 5242992,
47+
"decisionTaskStartedEventAttributes": {
48+
"scheduledEventId": 2,
49+
"identity": "82203@agautam-NV709R969P@choiceGroup@41d230ae-253a-4d01-9079-322ef05c09fb",
50+
"requestId": "4e47c448-bd44-467f-8938-75a05ddc5817"
51+
}
52+
},
53+
{
54+
"eventId": 4,
55+
"timestamp": 1679427717265365503,
56+
"eventType": "DecisionTaskCompleted",
57+
"version": 0,
58+
"taskId": 5242995,
59+
"decisionTaskCompletedEventAttributes": {
60+
"scheduledEventId": 2,
61+
"startedEventId": 3,
62+
"identity": "82203@agautam-NV709R969P@choiceGroup@41d230ae-253a-4d01-9079-322ef05c09fb",
63+
"binaryChecksum": "a526f53b827eadb0fbaec4ab62b6bea4"
64+
}
65+
},
66+
{
67+
"eventId": 5,
68+
"timestamp": 1679427717265639170,
69+
"eventType": "ActivityTaskScheduled",
70+
"version": 0,
71+
"taskId": 5242996,
72+
"activityTaskScheduledEventAttributes": {
73+
"activityId": "0",
74+
"activityType": {
75+
"name": "main.getOrderActivity"
76+
},
77+
"taskList": {
78+
"name": "choiceGroup"
79+
},
80+
"scheduleToCloseTimeoutSeconds": 60,
81+
"scheduleToStartTimeoutSeconds": 60,
82+
"startToCloseTimeoutSeconds": 60,
83+
"heartbeatTimeoutSeconds": 20,
84+
"decisionTaskCompletedEventId": 4,
85+
"header": {}
86+
}
87+
},
88+
{
89+
"eventId": 6,
90+
"timestamp": 1679427717265733878,
91+
"eventType": "ActivityTaskStarted",
92+
"version": 0,
93+
"taskId": 5242997,
94+
"activityTaskStartedEventAttributes": {
95+
"scheduledEventId": 5,
96+
"identity": "82203@agautam-NV709R969P@choiceGroup@41d230ae-253a-4d01-9079-322ef05c09fb",
97+
"requestId": "ae2aad96-6588-4359-807b-a39a16f0896a",
98+
"attempt": 0,
99+
"lastFailureReason": ""
100+
}
101+
},
102+
{
103+
"eventId": 7,
104+
"timestamp": 1679427717293230586,
105+
"eventType": "ActivityTaskCompleted",
106+
"version": 0,
107+
"taskId": 5243000,
108+
"activityTaskCompletedEventAttributes": {
109+
"result": "ImJhbmFuYSIK",
110+
"scheduledEventId": 5,
111+
"startedEventId": 6,
112+
"identity": "82203@agautam-NV709R969P@choiceGroup@41d230ae-253a-4d01-9079-322ef05c09fb"
113+
}
114+
},
115+
{
116+
"eventId": 8,
117+
"timestamp": 1679427717293285503,
118+
"eventType": "DecisionTaskScheduled",
119+
"version": 0,
120+
"taskId": 5243002,
121+
"decisionTaskScheduledEventAttributes": {
122+
"taskList": {
123+
"name": "agautam-NV709R969P:e38b3641-39a8-426c-a5e2-36df29585e54"
124+
},
125+
"startToCloseTimeoutSeconds": 60,
126+
"attempt": 0
127+
}
128+
},
129+
{
130+
"eventId": 9,
131+
"timestamp": 1679427717307647836,
132+
"eventType": "DecisionTaskStarted",
133+
"version": 0,
134+
"taskId": 5243006,
135+
"decisionTaskStartedEventAttributes": {
136+
"scheduledEventId": 8,
137+
"identity": "82203@agautam-NV709R969P@choiceGroup@41d230ae-253a-4d01-9079-322ef05c09fb",
138+
"requestId": "6dd1e7f1-a202-4a84-8d66-c80ff801dbf6"
139+
}
140+
},
141+
{
142+
"eventId": 10,
143+
"timestamp": 1679427717321627253,
144+
"eventType": "DecisionTaskCompleted",
145+
"version": 0,
146+
"taskId": 5243009,
147+
"decisionTaskCompletedEventAttributes": {
148+
"scheduledEventId": 8,
149+
"startedEventId": 9,
150+
"identity": "82203@agautam-NV709R969P@choiceGroup@41d230ae-253a-4d01-9079-322ef05c09fb",
151+
"binaryChecksum": "a526f53b827eadb0fbaec4ab62b6bea4"
152+
}
153+
},
154+
{
155+
"eventId": 11,
156+
"timestamp": 1679427717321780253,
157+
"eventType": "ActivityTaskScheduled",
158+
"version": 0,
159+
"taskId": 5243010,
160+
"activityTaskScheduledEventAttributes": {
161+
"activityId": "1",
162+
"activityType": {
163+
"name": "main.orderBananaActivity"
164+
},
165+
"taskList": {
166+
"name": "choiceGroup"
167+
},
168+
"input": "ImJhbmFuYSIK",
169+
"scheduleToCloseTimeoutSeconds": 60,
170+
"scheduleToStartTimeoutSeconds": 60,
171+
"startToCloseTimeoutSeconds": 60,
172+
"heartbeatTimeoutSeconds": 20,
173+
"decisionTaskCompletedEventId": 10,
174+
"header": {}
175+
}
176+
},
177+
{
178+
"eventId": 12,
179+
"timestamp": 1679427717321911295,
180+
"eventType": "WorkflowExecutionCompleted",
181+
"version": 0,
182+
"taskId": 5243011,
183+
"workflowExecutionCompletedEventAttributes": {
184+
"decisionTaskCompletedEventId": 10
185+
}
186+
}
187+
]
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (c) 2017 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package replaytests
22+
23+
import (
24+
"fmt"
25+
"time"
26+
27+
"go.uber.org/cadence/workflow"
28+
"go.uber.org/zap"
29+
)
30+
31+
/**
32+
* This sample workflow Execute one of many code paths based on the result of an activity.
33+
*/
34+
35+
const (
36+
orderChoiceApple = "apple"
37+
orderChoiceBanana = "banana"
38+
orderChoiceCherry = "cherry"
39+
)
40+
41+
// exclusiveChoiceWorkflow Workflow Decider. This workflow executes Cherry order.
42+
func exclusiveChoiceWorkflow(ctx workflow.Context) error {
43+
// Get order.
44+
ao := workflow.ActivityOptions{
45+
ScheduleToStartTimeout: time.Minute,
46+
StartToCloseTimeout: time.Minute,
47+
HeartbeatTimeout: time.Second * 20,
48+
}
49+
ctx = workflow.WithActivityOptions(ctx, ao)
50+
51+
var orderChoice string
52+
err := workflow.ExecuteActivity(ctx, getOrderActivity).Get(ctx, &orderChoice)
53+
if err != nil {
54+
return err
55+
}
56+
57+
logger := workflow.GetLogger(ctx)
58+
59+
// choose next activity based on order result
60+
switch orderChoice {
61+
case orderChoiceBanana:
62+
workflow.ExecuteActivity(ctx, orderBananaActivity, orderChoice)
63+
case orderChoiceCherry:
64+
workflow.ExecuteActivity(ctx, orderCherryActivity, orderChoice)
65+
default:
66+
logger.Error("Unexpected order", zap.String("Choice", orderChoice))
67+
}
68+
69+
logger.Info("Workflow completed.")
70+
return nil
71+
}
72+
73+
// This workflow explicitly executes Apple Activity received from the getorderActivity.
74+
func exclusiveChoiceWorkflow2(ctx workflow.Context) error {
75+
// Get order.
76+
ao := workflow.ActivityOptions{
77+
ScheduleToStartTimeout: time.Minute,
78+
StartToCloseTimeout: time.Minute,
79+
HeartbeatTimeout: time.Second * 20,
80+
}
81+
ctx = workflow.WithActivityOptions(ctx, ao)
82+
83+
var orderChoice string
84+
err := workflow.ExecuteActivity(ctx, getAppleOrderActivity).Get(ctx, &orderChoice)
85+
if err != nil {
86+
return err
87+
}
88+
89+
logger := workflow.GetLogger(ctx)
90+
91+
// choose next activity based on order result. It's apple in this case.
92+
switch orderChoice {
93+
case orderChoiceApple:
94+
workflow.ExecuteActivity(ctx, orderAppleActivity, orderChoice)
95+
default:
96+
logger.Error("Unexpected order", zap.String("Choice", orderChoice))
97+
}
98+
99+
logger.Info("Workflow completed.")
100+
return nil
101+
}
102+
103+
func getOrderActivity() (string, error) {
104+
fmt.Printf("Order is for Cherry")
105+
return "cherry", nil
106+
}
107+
108+
func getAppleOrderActivity() (string, error) {
109+
fmt.Printf("Order is for Apple")
110+
return "apple", nil
111+
}
112+
113+
func orderAppleActivity(choice string) error {
114+
fmt.Printf("Order choice: %v\n", choice)
115+
return nil
116+
}
117+
118+
func orderBananaActivity(choice string) error {
119+
fmt.Printf("Order choice: %v\n", choice)
120+
return nil
121+
}
122+
123+
func orderCherryActivity(choice string) error {
124+
fmt.Printf("Order choice: %v\n", choice)
125+
return nil
126+
}

test/replaytests/replay_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,34 @@ func TestGreetingsWorkflow2(t *testing.T) {
102102
require.Error(t, err)
103103
}
104104

105+
// Ideally replayer doesn't concern itself with the change in the activity content until it matches the expected output type.
106+
// History has recorded the output of banana activity instead. The replayer should have failed because we have not registered any
107+
// activity here in the test.
108+
// The replayer still runs whatever it found in the history and passes.
109+
func TestExclusiveChoiceWorkflowWithUnregisteredActivity(t *testing.T) {
110+
replayer := worker.NewWorkflowReplayer()
111+
112+
replayer.RegisterWorkflowWithOptions(exclusiveChoiceWorkflow, workflow.RegisterOptions{Name: "choice"})
113+
err := replayer.ReplayWorkflowHistoryFromJSONFile(zaptest.NewLogger(t), "choice.json")
114+
require.NoError(t, err)
115+
}
116+
117+
// This test registers Cherry Activity as the activity but calls Apple activity in the workflow code. Infact, Cherry and Banana
118+
// activities are not even a part of the workflow code in question.
119+
// History has recorded the output of banana activity. Here, The workflow is not waiting for the activity so it doesn't notice
120+
// that registered activity is different from executed activity.
121+
// The replayer relies on whatever is recorded in the History so as long as the main activity name in the options matched partially
122+
// it doesn't raise errors.
123+
func TestExclusiveChoiceWorkflowWithDifferentActvityCombo(t *testing.T) {
124+
replayer := worker.NewWorkflowReplayer()
125+
126+
replayer.RegisterWorkflowWithOptions(exclusiveChoiceWorkflow2, workflow.RegisterOptions{Name: "choice"})
127+
replayer.RegisterActivityWithOptions(getAppleOrderActivity, activity.RegisterOptions{Name: "main.getOrderActivity"})
128+
replayer.RegisterActivityWithOptions(orderAppleActivity, activity.RegisterOptions{Name: "testactivity"})
129+
err := replayer.ReplayWorkflowHistoryFromJSONFile(zaptest.NewLogger(t), "choice.json")
130+
require.NoError(t, err)
131+
}
132+
105133
func TestBranchWorkflow(t *testing.T) {
106134
replayer := worker.NewWorkflowReplayer()
107135

0 commit comments

Comments
 (0)