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 }