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  }