go.uber.org/cadence@v1.2.9/internal/error_test.go (about) 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 internal 22 23 import ( 24 "errors" 25 "fmt" 26 "testing" 27 28 "github.com/stretchr/testify/require" 29 "go.uber.org/zap/zaptest" 30 31 "go.uber.org/cadence/.gen/go/shared" 32 "go.uber.org/cadence/internal/common" 33 ) 34 35 const ( 36 // assume this is some error reason defined by activity implementation. 37 customErrReasonA = "CustomReasonA" 38 ) 39 40 type testStruct struct { 41 Name string 42 Age int 43 } 44 45 type testStruct2 struct { 46 Name string 47 Age int 48 Favorites *[]string 49 } 50 51 type testErrorStruct struct { 52 message string 53 } 54 55 var ( 56 testErrorDetails1 = "my details" 57 testErrorDetails2 = 123 58 testErrorDetails3 = testStruct{"a string", 321} 59 testErrorDetails4 = testStruct2{"a string", 321, &[]string{"eat", "code"}} 60 ) 61 62 // Creates a new workflow environment with the correct logger configured. 63 func newTestActivityEnv(t *testing.T) *TestActivityEnvironment { 64 s := &WorkflowTestSuite{} 65 s.SetLogger(zaptest.NewLogger(t)) 66 // same tally note 67 env := s.NewTestActivityEnvironment() 68 return env 69 } 70 71 func (tes *testErrorStruct) Error() string { 72 return tes.message 73 } 74 75 func Test_GenericError(t *testing.T) { 76 // test activity error 77 t.Run("activities", func(t *testing.T) { 78 errorActivityFn := func() error { 79 return errors.New("error:foo") 80 } 81 env := newTestActivityEnv(t) 82 env.RegisterActivity(errorActivityFn) 83 _, err := env.ExecuteActivity(errorActivityFn) 84 require.Error(t, err) 85 require.Equal(t, &GenericError{"error:foo"}, err) 86 }) 87 // test workflow error 88 t.Run("workflows", func(t *testing.T) { 89 errorWorkflowFn := func(ctx Context) error { 90 return errors.New("error:foo") 91 } 92 env := newTestWorkflowEnv(t) 93 env.RegisterWorkflow(errorWorkflowFn) 94 env.ExecuteWorkflow(errorWorkflowFn) 95 err := env.GetWorkflowError() 96 require.Error(t, err) 97 require.Equal(t, &GenericError{"error:foo"}, err) 98 }) 99 } 100 101 func Test_ActivityNotRegistered(t *testing.T) { 102 registeredActivityFn, unregisteredActivitFn := "RegisteredActivity", "UnregisteredActivityFn" 103 env := newTestActivityEnv(t) 104 env.RegisterActivityWithOptions(func() error { return nil }, RegisterActivityOptions{Name: registeredActivityFn}) 105 _, err := env.ExecuteActivity(unregisteredActivitFn) 106 require.Error(t, err) 107 require.Contains(t, err.Error(), fmt.Sprintf("unable to find activityType=%v", unregisteredActivitFn)) 108 require.Contains(t, err.Error(), registeredActivityFn) 109 } 110 111 func Test_TimeoutError(t *testing.T) { 112 timeoutErr := NewTimeoutError(shared.TimeoutTypeScheduleToStart) 113 require.False(t, timeoutErr.HasDetails()) 114 var data string 115 require.Equal(t, ErrNoData, timeoutErr.Details(&data)) 116 117 heartbeatErr := NewHeartbeatTimeoutError(testErrorDetails1) 118 require.True(t, heartbeatErr.HasDetails()) 119 require.NoError(t, heartbeatErr.Details(&data)) 120 require.Equal(t, testErrorDetails1, data) 121 } 122 123 func Test_TimeoutError_WithDetails(t *testing.T) { 124 testTimeoutErrorDetails(t, shared.TimeoutTypeHeartbeat) 125 testTimeoutErrorDetails(t, shared.TimeoutTypeScheduleToClose) 126 testTimeoutErrorDetails(t, shared.TimeoutTypeStartToClose) 127 } 128 129 func testTimeoutErrorDetails(t *testing.T, timeoutType shared.TimeoutType) { 130 context := &workflowEnvironmentImpl{ 131 decisionsHelper: newDecisionsHelper(), 132 dataConverter: getDefaultDataConverter(), 133 } 134 h := newDecisionsHelper() 135 var actualErr error 136 activityID := "activityID" 137 context.decisionsHelper.scheduledEventIDToActivityID[5] = activityID 138 di := h.newActivityDecisionStateMachine( 139 &shared.ScheduleActivityTaskDecisionAttributes{ActivityId: common.StringPtr(activityID)}) 140 di.state = decisionStateInitiated 141 di.setData(&scheduledActivity{ 142 callback: func(r []byte, e error) { 143 actualErr = e 144 }, 145 }) 146 context.decisionsHelper.addDecision(di) 147 encodedDetails1, _ := context.dataConverter.ToData(testErrorDetails1) 148 event := createTestEventActivityTaskTimedOut(7, &shared.ActivityTaskTimedOutEventAttributes{ 149 Details: encodedDetails1, 150 ScheduledEventId: common.Int64Ptr(5), 151 StartedEventId: common.Int64Ptr(6), 152 TimeoutType: &timeoutType, 153 }) 154 weh := &workflowExecutionEventHandlerImpl{context, nil} 155 weh.handleActivityTaskTimedOut(event) 156 err, ok := actualErr.(*TimeoutError) 157 require.True(t, ok) 158 require.True(t, err.HasDetails()) 159 data := "" 160 require.NoError(t, err.Details(&data)) 161 require.Equal(t, testErrorDetails1, data) 162 } 163 164 func Test_CustomError(t *testing.T) { 165 // test ErrorDetailValues as Details 166 var a1 string 167 var a2 int 168 var a3 testStruct 169 err0 := NewCustomError(customErrReasonA, testErrorDetails1) 170 require.True(t, err0.HasDetails()) 171 err0.Details(&a1) 172 require.Equal(t, testErrorDetails1, a1) 173 a1 = "" 174 err0 = NewCustomError(customErrReasonA, testErrorDetails1, testErrorDetails2, testErrorDetails3) 175 require.True(t, err0.HasDetails()) 176 err0.Details(&a1, &a2, &a3) 177 require.Equal(t, testErrorDetails1, a1) 178 require.Equal(t, testErrorDetails2, a2) 179 require.Equal(t, testErrorDetails3, a3) 180 181 // test EncodedValues as Details 182 errorActivityFn := func() error { 183 return err0 184 } 185 env := newTestActivityEnv(t) 186 env.RegisterActivity(errorActivityFn) 187 _, err := env.ExecuteActivity(errorActivityFn) 188 require.Error(t, err) 189 err1, ok := err.(*CustomError) 190 require.True(t, ok) 191 require.True(t, err1.HasDetails()) 192 var b1 string 193 var b2 int 194 var b3 testStruct 195 err1.Details(&b1, &b2, &b3) 196 require.Equal(t, testErrorDetails1, b1) 197 require.Equal(t, testErrorDetails2, b2) 198 require.Equal(t, testErrorDetails3, b3) 199 200 // test reason and no detail 201 require.Panics(t, func() { NewCustomError("cadenceInternal:testReason") }) 202 newReason := "another reason" 203 err2 := NewCustomError(newReason) 204 require.True(t, !err2.HasDetails()) 205 require.Equal(t, ErrNoData, err2.Details()) 206 require.Equal(t, newReason, err2.Reason()) 207 err3 := NewCustomError(newReason, nil) 208 // TODO: probably we want to handle this case when details are nil, HasDetails return false 209 require.True(t, err3.HasDetails()) 210 211 // test workflow error 212 errorWorkflowFn := func(ctx Context) error { 213 return err0 214 } 215 wfEnv := newTestWorkflowEnv(t) 216 wfEnv.RegisterWorkflow(errorWorkflowFn) 217 wfEnv.ExecuteWorkflow(errorWorkflowFn) 218 err = wfEnv.GetWorkflowError() 219 require.Error(t, err) 220 err4, ok := err.(*CustomError) 221 require.True(t, ok) 222 require.True(t, err4.HasDetails()) 223 err4.Details(&b1, &b2, &b3) 224 require.Equal(t, testErrorDetails1, b1) 225 require.Equal(t, testErrorDetails2, b2) 226 require.Equal(t, testErrorDetails3, b3) 227 } 228 229 func Test_CustomError_Pointer(t *testing.T) { 230 a1 := testStruct2{} 231 err1 := NewCustomError(customErrReasonA, testErrorDetails4) 232 require.True(t, err1.HasDetails()) 233 err := err1.Details(&a1) 234 require.NoError(t, err) 235 require.Equal(t, testErrorDetails4, a1) 236 237 a2 := &testStruct2{} 238 err2 := NewCustomError(customErrReasonA, &testErrorDetails4) // // pointer in details 239 require.True(t, err2.HasDetails()) 240 err = err2.Details(&a2) 241 require.NoError(t, err) 242 require.Equal(t, &testErrorDetails4, a2) 243 244 // test EncodedValues as Details 245 errorActivityFn := func() error { 246 return err1 247 } 248 env := newTestActivityEnv(t) 249 env.RegisterActivity(errorActivityFn) 250 _, err = env.ExecuteActivity(errorActivityFn) 251 require.Error(t, err) 252 err3, ok := err.(*CustomError) 253 require.True(t, ok) 254 require.True(t, err3.HasDetails()) 255 b1 := testStruct2{} 256 require.NoError(t, err3.Details(&b1)) 257 require.Equal(t, testErrorDetails4, b1) 258 259 errorActivityFn2 := func() error { 260 return err2 // pointer in details 261 } 262 env.RegisterActivity(errorActivityFn2) 263 _, err = env.ExecuteActivity(errorActivityFn2) 264 require.Error(t, err) 265 err4, ok := err.(*CustomError) 266 require.True(t, ok) 267 require.True(t, err4.HasDetails()) 268 b2 := &testStruct2{} 269 require.NoError(t, err4.Details(&b2)) 270 require.Equal(t, &testErrorDetails4, b2) 271 272 // test workflow error 273 errorWorkflowFn := func(ctx Context) error { 274 return err1 275 } 276 wfEnv := newTestWorkflowEnv(t) 277 wfEnv.RegisterWorkflow(errorWorkflowFn) 278 wfEnv.ExecuteWorkflow(errorWorkflowFn) 279 err = wfEnv.GetWorkflowError() 280 require.Error(t, err) 281 err5, ok := err.(*CustomError) 282 require.True(t, ok) 283 require.True(t, err5.HasDetails()) 284 err5.Details(&b1) 285 require.NoError(t, err5.Details(&b1)) 286 require.Equal(t, testErrorDetails4, b1) 287 288 errorWorkflowFn2 := func(ctx Context) error { 289 return err2 // pointer in details 290 } 291 wfEnv = newTestWorkflowEnv(t) 292 wfEnv.RegisterWorkflow(errorWorkflowFn2) 293 wfEnv.ExecuteWorkflow(errorWorkflowFn2) 294 err = wfEnv.GetWorkflowError() 295 require.Error(t, err) 296 err6, ok := err.(*CustomError) 297 require.True(t, ok) 298 require.True(t, err6.HasDetails()) 299 err6.Details(&b2) 300 require.NoError(t, err6.Details(&b2)) 301 require.Equal(t, &testErrorDetails4, b2) 302 } 303 304 func Test_CustomError_WrongDecodedType(t *testing.T) { 305 err := NewCustomError("reason", testErrorDetails1, testErrorDetails2) 306 var d1 string 307 var d2 string // will cause error since it should be of type int 308 err1 := err.Details(&d1, &d2) 309 require.Error(t, err1) 310 311 err = NewCustomError("reason", testErrorDetails3) 312 var d3 testStruct2 // will cause error since it should be of type testStruct 313 err2 := err.Details(&d3) 314 require.Error(t, err2) 315 } 316 317 func Test_CanceledError(t *testing.T) { 318 // test ErrorDetailValues as Details 319 var a1 string 320 var a2 int 321 var a3 testStruct 322 err0 := NewCanceledError(testErrorDetails1) 323 require.True(t, err0.HasDetails()) 324 err0.Details(&a1) 325 require.Equal(t, testErrorDetails1, a1) 326 a1 = "" 327 err0 = NewCanceledError(testErrorDetails1, testErrorDetails2, testErrorDetails3) 328 require.True(t, err0.HasDetails()) 329 err0.Details(&a1, &a2, &a3) 330 require.Equal(t, testErrorDetails1, a1) 331 require.Equal(t, testErrorDetails2, a2) 332 require.Equal(t, testErrorDetails3, a3) 333 334 // test EncodedValues as Details 335 errorActivityFn := func() error { 336 return err0 337 } 338 env := newTestActivityEnv(t) 339 env.RegisterActivity(errorActivityFn) 340 _, err := env.ExecuteActivity(errorActivityFn) 341 require.Error(t, err) 342 err1, ok := err.(*CanceledError) 343 require.True(t, ok) 344 require.True(t, err1.HasDetails()) 345 var b1 string 346 var b2 int 347 var b3 testStruct 348 err1.Details(&b1, &b2, &b3) 349 require.Equal(t, testErrorDetails1, b1) 350 require.Equal(t, testErrorDetails2, b2) 351 require.Equal(t, testErrorDetails3, b3) 352 353 err2 := NewCanceledError() 354 require.False(t, err2.HasDetails()) 355 356 // test workflow error 357 errorWorkflowFn := func(ctx Context) error { 358 return err0 359 } 360 wfEnv := newTestWorkflowEnv(t) 361 wfEnv.RegisterWorkflow(errorWorkflowFn) 362 wfEnv.ExecuteWorkflow(errorWorkflowFn) 363 err = wfEnv.GetWorkflowError() 364 require.Error(t, err) 365 err3, ok := err.(*CanceledError) 366 require.True(t, ok) 367 require.True(t, err3.HasDetails()) 368 err3.Details(&b1, &b2, &b3) 369 require.Equal(t, testErrorDetails1, b1) 370 require.Equal(t, testErrorDetails2, b2) 371 require.Equal(t, testErrorDetails3, b3) 372 } 373 374 func Test_IsCanceledError(t *testing.T) { 375 376 tests := []struct { 377 name string 378 err error 379 expected bool 380 }{ 381 { 382 name: "empty detail", 383 err: NewCanceledError(), 384 expected: true, 385 }, 386 { 387 name: "with detail", 388 err: NewCanceledError("details"), 389 expected: true, 390 }, 391 { 392 name: "not canceled error", 393 err: errors.New("details"), 394 expected: false, 395 }, 396 } 397 398 for _, test := range tests { 399 require.Equal(t, test.expected, IsCanceledError(test.err)) 400 } 401 } 402 403 func TestErrorDetailsValues(t *testing.T) { 404 e := ErrorDetailsValues{} 405 require.Equal(t, ErrNoData, e.Get()) 406 407 e = ErrorDetailsValues{testErrorDetails1, testErrorDetails2, testErrorDetails3} 408 var a1 string 409 var a2 int 410 var a3 testStruct 411 require.True(t, e.HasValues()) 412 e.Get(&a1) 413 require.Equal(t, testErrorDetails1, a1) 414 e.Get(&a1, &a2, &a3) 415 require.Equal(t, testErrorDetails1, a1) 416 require.Equal(t, testErrorDetails2, a2) 417 require.Equal(t, testErrorDetails3, a3) 418 419 require.Equal(t, ErrTooManyArg, e.Get(&a1, &a2, &a3, &a3)) 420 } 421 422 func TestErrorDetailsValues_WrongDecodedType(t *testing.T) { 423 e := ErrorDetailsValues{testErrorDetails1} 424 var d1 int // will cause error since it should be of type string 425 err := e.Get(&d1) 426 require.Error(t, err) 427 } 428 429 func TestErrorDetailsValues_AssignableType(t *testing.T) { 430 e := ErrorDetailsValues{&testErrorStruct{message: "my message"}} 431 var errorOut error 432 err := e.Get(&errorOut) 433 require.NoError(t, err) 434 require.Equal(t, "my message", errorOut.Error()) 435 } 436 437 func Test_SignalExternalWorkflowExecutionFailedError(t *testing.T) { 438 context := &workflowEnvironmentImpl{ 439 decisionsHelper: newDecisionsHelper(), 440 dataConverter: getDefaultDataConverter(), 441 } 442 h := newDecisionsHelper() 443 var actualErr error 444 var initiatedEventID int64 = 101 445 signalID := "signalID" 446 context.decisionsHelper.scheduledEventIDToSignalID[initiatedEventID] = signalID 447 di := h.newSignalExternalWorkflowStateMachine( 448 &shared.SignalExternalWorkflowExecutionDecisionAttributes{}, 449 signalID, 450 ) 451 di.state = decisionStateInitiated 452 di.setData(&scheduledSignal{ 453 callback: func(r []byte, e error) { 454 actualErr = e 455 }, 456 }) 457 context.decisionsHelper.addDecision(di) 458 weh := &workflowExecutionEventHandlerImpl{context, nil} 459 event := createTestEventSignalExternalWorkflowExecutionFailed(1, &shared.SignalExternalWorkflowExecutionFailedEventAttributes{ 460 InitiatedEventId: common.Int64Ptr(initiatedEventID), 461 Cause: shared.SignalExternalWorkflowExecutionFailedCauseUnknownExternalWorkflowExecution.Ptr(), 462 }) 463 require.NoError(t, weh.handleSignalExternalWorkflowExecutionFailed(event)) 464 _, ok := actualErr.(*UnknownExternalWorkflowExecutionError) 465 require.True(t, ok) 466 } 467 468 func Test_ContinueAsNewError(t *testing.T) { 469 var a1 = 1234 470 var a2 = "some random input" 471 472 continueAsNewWfName := "continueAsNewWorkflowFn" 473 continueAsNewWorkflowFn := func(ctx Context, testInt int, testString string) error { 474 return NewContinueAsNewError(ctx, continueAsNewWfName, a1, a2) 475 } 476 477 header := &shared.Header{ 478 Fields: map[string][]byte{"test": []byte("test-data")}, 479 } 480 481 s := &WorkflowTestSuite{ 482 header: header, 483 ctxProps: []ContextPropagator{NewStringMapPropagator([]string{"test"})}, 484 } 485 s.SetLogger(zaptest.NewLogger(t)) 486 wfEnv := s.NewTestWorkflowEnvironment() 487 wfEnv.Test(t) 488 wfEnv.RegisterWorkflowWithOptions(continueAsNewWorkflowFn, RegisterWorkflowOptions{ 489 Name: continueAsNewWfName, 490 }) 491 wfEnv.ExecuteWorkflow(continueAsNewWorkflowFn, 101, "another random string") 492 err := wfEnv.GetWorkflowError() 493 494 require.Error(t, err) 495 continueAsNewErr, ok := err.(*ContinueAsNewError) 496 require.True(t, ok) 497 require.Equal(t, continueAsNewWfName, continueAsNewErr.WorkflowType().Name) 498 499 args := continueAsNewErr.Args() 500 intArg, ok := args[0].(int) 501 require.True(t, ok) 502 require.Equal(t, a1, intArg) 503 stringArg, ok := args[1].(string) 504 require.True(t, ok) 505 require.Equal(t, a2, stringArg) 506 require.Equal(t, header, continueAsNewErr.params.header) 507 }