Skip to content

Commit 05b271d

Browse files
committed
Test changes in process failure handling
1 parent df8ae97 commit 05b271d

File tree

1 file changed

+138
-20
lines changed

1 file changed

+138
-20
lines changed

engine/execution/computation/computer/computer_test.go

Lines changed: 138 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package computer_test
22

33
import (
4+
"bytes"
45
"context"
6+
"encoding/json"
57
"fmt"
68
"math/rand"
79
"strings"
@@ -1421,26 +1423,36 @@ func Test_ScheduledCallback(t *testing.T) {
14211423

14221424
t.Run("process callback transaction execution error", func(t *testing.T) {
14231425
processCallbackError := fvmErrors.NewInvalidAddressErrorf(flow.EmptyAddress, "process callback execution failed")
1424-
testScheduledCallbackWithError(t, chain, []cadence.Event{}, 0, processCallbackError)
1426+
testScheduledCallbackWithError(t, chain, []cadence.Event{}, 0, processCallbackError, nil)
14251427
})
14261428

14271429
t.Run("process callback transaction output error", func(t *testing.T) {
14281430
processCallbackError := fvmErrors.NewInvalidAddressErrorf(flow.EmptyAddress, "process callback output error")
1429-
testScheduledCallbackWithError(t, chain, []cadence.Event{}, 0, processCallbackError)
1431+
testScheduledCallbackWithError(t, chain, []cadence.Event{}, 2, nil, processCallbackError)
14301432
})
14311433
}
14321434

14331435
func testScheduledCallback(t *testing.T, chain flow.Chain, callbackEvents []cadence.Event, expectedTransactionCount int) {
1434-
testScheduledCallbackWithError(t, chain, callbackEvents, expectedTransactionCount, nil)
1436+
testScheduledCallbackWithError(t, chain, callbackEvents, expectedTransactionCount, nil, nil)
14351437
}
14361438

1437-
func testScheduledCallbackWithError(t *testing.T, chain flow.Chain, callbackEvents []cadence.Event, expectedTransactionCount int, processCallbackError fvmErrors.CodedError) {
1439+
func testScheduledCallbackWithError(
1440+
t *testing.T,
1441+
chain flow.Chain,
1442+
callbackEvents []cadence.Event,
1443+
expectedTransactionCount int,
1444+
processExecuteError fvmErrors.CodedError,
1445+
processOutputError fvmErrors.CodedError,
1446+
) {
14381447
rag := &RandomAddressGenerator{}
14391448
executorID := unittest.IdentifierFixture()
14401449

1450+
testLogger := NewTestLogger()
1451+
14411452
execCtx := fvm.NewContext(
14421453
fvm.WithScheduleCallbacksEnabled(true), // Enable callbacks
14431454
fvm.WithChain(chain),
1455+
fvm.WithLogger(testLogger.Logger),
14441456
)
14451457

14461458
// track which transactions were executed and their details
@@ -1473,9 +1485,10 @@ func testScheduledCallbackWithError(t *testing.T, chain flow.Chain, callbackEven
14731485
vm := &callbackTestVM{
14741486
testVM: testVM{
14751487
t: t,
1476-
eventsPerTransaction: 0, // we'll handle events manually
1477-
err: processCallbackError, // inject error if provided
1488+
eventsPerTransaction: 0, // we'll handle events manually
1489+
err: processExecuteError, // inject error if provided
14781490
},
1491+
processOutputErr: processOutputError,
14791492
executedTransactions: executedTransactions,
14801493
executedMutex: &executedTransactionsMutex,
14811494
eventPayloads: eventPayloads,
@@ -1506,15 +1519,34 @@ func testScheduledCallbackWithError(t *testing.T, chain flow.Chain, callbackEven
15061519
Return(nil).
15071520
Times(1)
15081521

1509-
// expect the specified number of transactions
1510-
exemetrics.On("ExecutionTransactionExecuted",
1511-
mock.Anything,
1512-
mock.MatchedBy(func(arg module.TransactionExecutionResultStats) bool {
1513-
return !arg.Failed && arg.SystemTransaction
1514-
}),
1515-
mock.Anything).
1516-
Return(nil).
1517-
Times(expectedTransactionCount)
1522+
if processOutputError != nil {
1523+
// expect 1 failed transaction (process callback) + 1 successful transaction (system chunk)
1524+
exemetrics.On("ExecutionTransactionExecuted",
1525+
mock.Anything,
1526+
mock.MatchedBy(func(arg module.TransactionExecutionResultStats) bool {
1527+
return arg.Failed && arg.SystemTransaction
1528+
}),
1529+
mock.Anything).
1530+
Return(nil).
1531+
Times(1)
1532+
exemetrics.On("ExecutionTransactionExecuted",
1533+
mock.Anything,
1534+
mock.MatchedBy(func(arg module.TransactionExecutionResultStats) bool {
1535+
return !arg.Failed && arg.SystemTransaction
1536+
}),
1537+
mock.Anything).
1538+
Return(nil).
1539+
Times(expectedTransactionCount - 1)
1540+
} else {
1541+
exemetrics.On("ExecutionTransactionExecuted",
1542+
mock.Anything,
1543+
mock.MatchedBy(func(arg module.TransactionExecutionResultStats) bool {
1544+
return !arg.Failed && arg.SystemTransaction
1545+
}),
1546+
mock.Anything).
1547+
Return(nil).
1548+
Times(expectedTransactionCount)
1549+
}
15181550

15191551
exemetrics.On(
15201552
"ExecutionChunkDataPackGenerated",
@@ -1555,7 +1587,7 @@ func testScheduledCallbackWithError(t *testing.T, chain flow.Chain, callbackEven
15551587
execCtx,
15561588
exemetrics,
15571589
trace.NewNoopTracer(),
1558-
zerolog.Nop(),
1590+
testLogger.Logger,
15591591
committer,
15601592
me,
15611593
prov,
@@ -1576,12 +1608,32 @@ func testScheduledCallbackWithError(t *testing.T, chain flow.Chain, callbackEven
15761608
derived.NewEmptyDerivedBlockData(0))
15771609

15781610
// If we expect an error, verify it and return early
1579-
if processCallbackError != nil {
1611+
if processExecuteError != nil {
15801612
require.Error(t, err)
15811613
require.Contains(t, err.Error(), "system process transaction")
15821614
return
15831615
}
15841616

1617+
if processOutputError != nil {
1618+
require.NoError(t, err)
1619+
require.Truef(
1620+
t,
1621+
testLogger.HasLogWithField("system process transaction output error", "critical_error", true),
1622+
"expected critical error log not found",
1623+
)
1624+
1625+
// verify the process callback transaction failed as expected
1626+
require.Len(t, result.AllTransactionResults(), expectedTransactionCount)
1627+
processCallbackResult := result.AllTransactionResults()[0]
1628+
require.NotEmpty(t, processCallbackResult.ErrorMessage, "process callback transaction should have failed")
1629+
require.Contains(t, processCallbackResult.ErrorMessage, "process callback output error")
1630+
1631+
// verify system chunk transaction succeeded
1632+
systemChunkResult := result.AllTransactionResults()[1]
1633+
require.Empty(t, systemChunkResult.ErrorMessage, "system chunk transaction should not have failed")
1634+
return
1635+
}
1636+
15851637
require.NoError(t, err)
15861638

15871639
// verify execution results
@@ -1639,7 +1691,8 @@ func testScheduledCallbackWithError(t *testing.T, chain flow.Chain, callbackEven
16391691

16401692
// callbackTestVM is a custom VM for testing callback execution
16411693
type callbackTestVM struct {
1642-
testVM // Embed testVM
1694+
testVM
1695+
processOutputErr fvmErrors.CodedError
16431696
executedTransactions map[string]string
16441697
executedMutex *sync.Mutex
16451698
eventPayloads [][]byte
@@ -1688,17 +1741,18 @@ func (c *callbackTestExecutor) Execute() error {
16881741
// from the output of the procedure executor
16891742
func (c *callbackTestExecutor) Output() fvm.ProcedureOutput {
16901743
// Return error if one was injected for process callback transaction
1691-
if c.vm.err != nil {
1744+
if c.vm.processOutputErr != nil {
16921745
txProc, ok := c.proc.(*fvm.TransactionProcedure)
16931746
if ok {
16941747
script := string(txProc.Transaction.Script)
16951748
if strings.Contains(script, "scheduler.process") {
16961749
return fvm.ProcedureOutput{
1697-
Err: c.vm.err,
1750+
Err: c.vm.processOutputErr,
16981751
}
16991752
}
17001753
}
17011754
}
1755+
17021756
c.vm.executedMutex.Lock()
17031757
defer c.vm.executedMutex.Unlock()
17041758

@@ -2065,3 +2119,67 @@ func (p *programLoader) Compute(
20652119
) {
20662120
return p.load()
20672121
}
2122+
2123+
// TestLogger captures log output for testing and provides methods to verify logged messages.
2124+
type TestLogger struct {
2125+
buffer bytes.Buffer
2126+
Logger zerolog.Logger
2127+
}
2128+
2129+
func NewTestLogger() *TestLogger {
2130+
tl := &TestLogger{}
2131+
tl.Logger = zerolog.New(&tl.buffer).Level(zerolog.DebugLevel)
2132+
return tl
2133+
}
2134+
2135+
type LogEntry struct {
2136+
Level string
2137+
Message string
2138+
Fields map[string]interface{}
2139+
}
2140+
2141+
func (tl *TestLogger) Logs() []LogEntry {
2142+
var entries []LogEntry
2143+
lines := strings.Split(tl.buffer.String(), "\n")
2144+
for _, line := range lines {
2145+
if line == "" {
2146+
continue
2147+
}
2148+
var rawEntry map[string]interface{}
2149+
if err := json.Unmarshal([]byte(line), &rawEntry); err != nil {
2150+
continue
2151+
}
2152+
entry := LogEntry{
2153+
Fields: make(map[string]interface{}),
2154+
}
2155+
for k, v := range rawEntry {
2156+
switch k {
2157+
case "level":
2158+
entry.Level = fmt.Sprintf("%v", v)
2159+
case "message":
2160+
entry.Message = fmt.Sprintf("%v", v)
2161+
default:
2162+
entry.Fields[k] = v
2163+
}
2164+
}
2165+
entries = append(entries, entry)
2166+
}
2167+
return entries
2168+
}
2169+
2170+
func (tl *TestLogger) HasLog(message string) bool {
2171+
return strings.Contains(tl.buffer.String(), message)
2172+
}
2173+
2174+
func (tl *TestLogger) HasLogWithField(message string, fieldName string, fieldValue interface{}) bool {
2175+
for _, entry := range tl.Logs() {
2176+
if strings.Contains(entry.Message, message) {
2177+
if val, ok := entry.Fields[fieldName]; ok {
2178+
if val == fieldValue {
2179+
return true
2180+
}
2181+
}
2182+
}
2183+
}
2184+
return false
2185+
}

0 commit comments

Comments
 (0)