github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/errors/errors_test.go (about)

     1  package errors
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/hashicorp/go-multierror"
     8  	"github.com/onflow/cadence/runtime"
     9  	cadenceErr "github.com/onflow/cadence/runtime/errors"
    10  	"github.com/onflow/cadence/runtime/sema"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/onflow/flow-go/model/flow"
    15  )
    16  
    17  func TestErrorHandling(t *testing.T) {
    18  	require.False(t, IsFailure(nil))
    19  
    20  	t.Run("test nonfatal error detection", func(t *testing.T) {
    21  		e1 := NewOperationNotSupportedError("some operations")
    22  		e2 := fmt.Errorf("some other errors: %w", e1)
    23  		e3 := NewInvalidProposalSignatureError(flow.ProposalKey{}, e2)
    24  		e4 := fmt.Errorf("wrapped: %w", e3)
    25  
    26  		expectedErr := WrapCodedError(
    27  			e1.Code(), // The root cause's error code
    28  			e4,        // All the error message detail.
    29  			"error caused by")
    30  
    31  		txErr, vmErr := SplitErrorTypes(e4)
    32  		require.Nil(t, vmErr)
    33  		require.Equal(t, expectedErr, txErr)
    34  
    35  		require.False(t, IsFailure(e4))
    36  		require.False(t, IsFailure(txErr))
    37  	})
    38  
    39  	t.Run("test fatal error detection", func(t *testing.T) {
    40  		e1 := NewOperationNotSupportedError("some operations")
    41  		e2 := NewEncodingFailuref(e1, "bad encoding")
    42  		e3 := NewLedgerFailure(e2)
    43  		e4 := fmt.Errorf("some other errors: %w", e3)
    44  		e5 := NewInvalidProposalSignatureError(flow.ProposalKey{}, e4)
    45  		e6 := fmt.Errorf("wrapped: %w", e5)
    46  
    47  		expectedErr := WrapCodedFailure(
    48  			e3.Code(), // The shallowest failure's error code
    49  			e6,        // All the error message detail.
    50  			"failure caused by")
    51  
    52  		txErr, vmErr := SplitErrorTypes(e6)
    53  		require.Nil(t, txErr)
    54  		require.Equal(t, expectedErr, vmErr)
    55  
    56  		require.True(t, IsFailure(e6))
    57  		require.True(t, IsFailure(vmErr))
    58  	})
    59  
    60  	t.Run("unknown error", func(t *testing.T) {
    61  		e1 := fmt.Errorf("some unknown errors")
    62  		txErr, vmErr := SplitErrorTypes(e1)
    63  		require.Nil(t, txErr)
    64  		require.NotNil(t, vmErr)
    65  
    66  		require.True(t, IsFailure(e1))
    67  	})
    68  }
    69  
    70  func TestHandleRuntimeError(t *testing.T) {
    71  	baseErr := fmt.Errorf("base error")
    72  	tests := []struct {
    73  		name        string
    74  		err         error
    75  		errorCode   ErrorCode
    76  		failureCode FailureCode
    77  	}{
    78  		{
    79  			name: "nil error",
    80  			err:  nil,
    81  		},
    82  		{
    83  			name:        "unknown error",
    84  			err:         baseErr,
    85  			failureCode: FailureCodeUnknownFailure,
    86  		},
    87  		{
    88  			name:      "runtime error",
    89  			err:       runtime.Error{Err: baseErr},
    90  			errorCode: ErrCodeCadenceRunTimeError,
    91  		},
    92  		{
    93  			name: "coded error in Unwrappable error",
    94  			err: runtime.Error{
    95  				Err: cadenceErr.ExternalError{
    96  					Recovered: NewScriptExecutionCancelledError(baseErr),
    97  				},
    98  			},
    99  			errorCode: ErrCodeScriptExecutionCancelledError,
   100  		},
   101  		{
   102  			name: "coded error in ParentError error",
   103  			err: createCheckerErr([]error{
   104  				fmt.Errorf("first error"),
   105  				NewScriptExecutionTimedOutError(),
   106  			}),
   107  			errorCode: ErrCodeScriptExecutionTimedOutError,
   108  		},
   109  		{
   110  			name: "first coded error returned",
   111  			err: createCheckerErr([]error{
   112  				fmt.Errorf("first error"),
   113  				NewScriptExecutionTimedOutError(),
   114  				NewScriptExecutionCancelledError(baseErr),
   115  			}),
   116  			errorCode: ErrCodeScriptExecutionTimedOutError,
   117  		},
   118  		{
   119  			name: "failure returned",
   120  			err: createCheckerErr([]error{
   121  				fmt.Errorf("first error"),
   122  				NewLedgerFailure(baseErr),
   123  			}),
   124  			failureCode: FailureCodeLedgerFailure,
   125  		},
   126  		{
   127  			name: "error before failure returns failure",
   128  			err: createCheckerErr([]error{
   129  				fmt.Errorf("first error"),
   130  				NewScriptExecutionTimedOutError(),
   131  				NewLedgerFailure(baseErr),
   132  			}),
   133  			failureCode: FailureCodeLedgerFailure,
   134  		},
   135  		{
   136  			name: "embedded coded errors return deepest error",
   137  			err: createCheckerErr([]error{
   138  				fmt.Errorf("first error"),
   139  				NewScriptExecutionCancelledError(
   140  					NewScriptExecutionTimedOutError(),
   141  				),
   142  			}),
   143  			errorCode: ErrCodeScriptExecutionTimedOutError,
   144  		},
   145  		{
   146  			name: "failure with embedded error returns failure",
   147  			err: createCheckerErr([]error{
   148  				fmt.Errorf("first error"),
   149  				NewLedgerFailure(
   150  					NewScriptExecutionTimedOutError(),
   151  				),
   152  			}),
   153  			failureCode: FailureCodeLedgerFailure,
   154  		},
   155  		{
   156  			name: "coded error with embedded failure returns failure",
   157  			err: createCheckerErr([]error{
   158  				fmt.Errorf("first error"),
   159  				NewScriptExecutionCancelledError(
   160  					NewLedgerFailure(baseErr),
   161  				),
   162  			}),
   163  			failureCode: FailureCodeLedgerFailure,
   164  		},
   165  		{
   166  			name: "error tree with failure returns failure",
   167  			err: createCheckerErr([]error{
   168  				fmt.Errorf("first error"),
   169  				NewScriptExecutionCancelledError(baseErr),
   170  				createCheckerErr([]error{
   171  					fmt.Errorf("first error"),
   172  					NewScriptExecutionCancelledError(
   173  						NewLedgerFailure(baseErr),
   174  					),
   175  				}),
   176  			}),
   177  			failureCode: FailureCodeLedgerFailure,
   178  		},
   179  	}
   180  
   181  	for _, tc := range tests {
   182  		t.Run(tc.name, func(t *testing.T) {
   183  			actual := HandleRuntimeError(tc.err)
   184  			if tc.err == nil {
   185  				assert.NoError(t, actual)
   186  				return
   187  			}
   188  
   189  			actualCoded, failureCoded := SplitErrorTypes(actual)
   190  
   191  			if tc.failureCode != 0 {
   192  				assert.NoError(t, actualCoded)
   193  				assert.Equalf(t, tc.failureCode, failureCoded.Code(), "error code mismatch: expected %d, got %d", tc.failureCode, failureCoded.Code())
   194  			} else {
   195  				assert.NoError(t, failureCoded)
   196  				assert.Equalf(t, tc.errorCode, actualCoded.Code(), "error code mismatch: expected %d, got %d", tc.errorCode, actualCoded.Code())
   197  			}
   198  		})
   199  	}
   200  }
   201  
   202  func TestFind(t *testing.T) {
   203  	targetCode := ErrCodeScriptExecutionCancelledError
   204  	baseErr := fmt.Errorf("base error")
   205  
   206  	tests := []struct {
   207  		name  string
   208  		err   error
   209  		found bool
   210  	}{
   211  		{
   212  			name:  "nil error",
   213  			err:   nil,
   214  			found: false,
   215  		},
   216  		{
   217  			name:  "plain error",
   218  			err:   baseErr,
   219  			found: false,
   220  		},
   221  		{
   222  			name:  "wrapped plain error",
   223  			err:   fmt.Errorf("wrapped: %w", baseErr),
   224  			found: false,
   225  		},
   226  		{
   227  			name:  "coded failure",
   228  			err:   NewLedgerFailure(baseErr),
   229  			found: false,
   230  		},
   231  		{
   232  			name:  "incorrect coded error",
   233  			err:   NewScriptExecutionTimedOutError(),
   234  			found: false,
   235  		},
   236  		{
   237  			name:  "found",
   238  			err:   NewScriptExecutionCancelledError(baseErr),
   239  			found: true,
   240  		},
   241  		{
   242  			name:  "found with embedded errors",
   243  			err:   NewScriptExecutionCancelledError(NewLedgerFailure(NewScriptExecutionTimedOutError())),
   244  			found: true,
   245  		},
   246  		{
   247  			name:  "found embedded in error",
   248  			err:   NewDerivedDataCacheImplementationFailure(NewScriptExecutionCancelledError(baseErr)),
   249  			found: true,
   250  		},
   251  		{
   252  			name:  "found embedded in failure",
   253  			err:   NewLedgerFailure(NewScriptExecutionCancelledError(baseErr)),
   254  			found: true,
   255  		},
   256  		{
   257  			name: "found embedded with multierror",
   258  			err: &multierror.Error{
   259  				Errors: []error{
   260  					baseErr,
   261  					NewScriptExecutionTimedOutError(),
   262  					NewLedgerFailure(NewScriptExecutionCancelledError(baseErr)),
   263  				},
   264  			},
   265  			found: true,
   266  		},
   267  		{
   268  			name: "found within embedded error tree",
   269  			err: createCheckerErr([]error{
   270  				fmt.Errorf("first error"),
   271  				NewLedgerFailure(baseErr),
   272  				createCheckerErr([]error{
   273  					fmt.Errorf("first error"),
   274  					NewLedgerFailure(
   275  						NewScriptExecutionCancelledError(baseErr),
   276  					),
   277  				}),
   278  			}),
   279  			found: true,
   280  		},
   281  	}
   282  
   283  	for _, tc := range tests {
   284  		t.Run(tc.name, func(t *testing.T) {
   285  			actual := Find(tc.err, targetCode)
   286  			if !tc.found {
   287  				assert.NoError(t, actual)
   288  				return
   289  			}
   290  
   291  			require.Error(t, actual, "expected error but none found")
   292  			assert.Equalf(t, targetCode, actual.Code(), "error code mismatch: expected %d, got %d", targetCode, actual.Code())
   293  		})
   294  	}
   295  }
   296  
   297  func TestFindFailure(t *testing.T) {
   298  	targetCode := FailureCodeLedgerFailure
   299  	baseErr := fmt.Errorf("base error")
   300  	tests := []struct {
   301  		name  string
   302  		err   error
   303  		found bool
   304  	}{
   305  		{
   306  			name:  "nil error",
   307  			err:   nil,
   308  			found: false,
   309  		},
   310  		{
   311  			name:  "plain error",
   312  			err:   baseErr,
   313  			found: false,
   314  		},
   315  		{
   316  			name:  "wrapped plain error",
   317  			err:   fmt.Errorf("wrapped: %w", baseErr),
   318  			found: false,
   319  		},
   320  		{
   321  			name:  "coded error",
   322  			err:   NewScriptExecutionTimedOutError(),
   323  			found: false,
   324  		},
   325  		{
   326  			name:  "incorrect coded failure",
   327  			err:   NewStateMergeFailure(baseErr),
   328  			found: false,
   329  		},
   330  		{
   331  			name:  "found",
   332  			err:   NewLedgerFailure(baseErr),
   333  			found: true,
   334  		},
   335  		{
   336  			name:  "found with embedded errors",
   337  			err:   NewLedgerFailure(NewScriptExecutionCancelledError(NewScriptExecutionTimedOutError())),
   338  			found: true,
   339  		},
   340  		{
   341  			name:  "found embedded in error",
   342  			err:   NewDerivedDataCacheImplementationFailure(NewLedgerFailure(baseErr)),
   343  			found: true,
   344  		},
   345  		{
   346  			name:  "found embedded in failure",
   347  			err:   NewStateMergeFailure(NewLedgerFailure(baseErr)),
   348  			found: true,
   349  		},
   350  		{
   351  			name: "found embedded with multierror",
   352  			err: &multierror.Error{
   353  				Errors: []error{
   354  					baseErr,
   355  					NewScriptExecutionTimedOutError(),
   356  					NewScriptExecutionCancelledError(NewLedgerFailure(baseErr)),
   357  				},
   358  			},
   359  			found: true,
   360  		},
   361  		{
   362  			name: "found within embedded error tree",
   363  			err: createCheckerErr([]error{
   364  				fmt.Errorf("first error"),
   365  				NewScriptExecutionCancelledError(baseErr),
   366  				createCheckerErr([]error{
   367  					fmt.Errorf("first error"),
   368  					NewScriptExecutionCancelledError(
   369  						NewLedgerFailure(baseErr),
   370  					),
   371  				}),
   372  			}),
   373  			found: true,
   374  		},
   375  	}
   376  
   377  	for _, tc := range tests {
   378  		t.Run(tc.name, func(t *testing.T) {
   379  			actual := FindFailure(tc.err, targetCode)
   380  			if !tc.found {
   381  				assert.NoError(t, actual)
   382  				return
   383  			}
   384  
   385  			require.Error(t, actual, "expected error but none found")
   386  			assert.Equalf(t, targetCode, actual.Code(), "error code mismatch: expected %d, got %d", targetCode, actual.Code())
   387  		})
   388  	}
   389  }
   390  
   391  func createCheckerErr(errs []error) error {
   392  	return runtime.Error{
   393  		Err: cadenceErr.ExternalError{
   394  			Recovered: sema.CheckerError{
   395  				Errors: errs,
   396  			},
   397  		},
   398  	}
   399  }