Skip to content

Commit 8e92525

Browse files
authored
fix(test): address flaky TestAPIV2_ThroughRPC (#13148)
Closes: #13116
1 parent d8f7cba commit 8e92525

File tree

2 files changed

+110
-38
lines changed

2 files changed

+110
-38
lines changed

itests/api_v2_test.go

Lines changed: 104 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,32 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
3838
network.BeginMining(blockTime)
3939
subject.WaitTillChain(ctx, kit.HeightAtLeast(targetHeight))
4040

41+
// mkStableExecute creates a stable execution wrapper that ensures the chain doesn't
42+
// advance during test execution to avoid flaky tests due to chain reorgs
43+
mkStableExecute := func(getTipSet func(t *testing.T) *types.TipSet) func(fn func()) *types.TipSet {
44+
return func(fn func()) *types.TipSet {
45+
// Create a stable execute function that takes the test context
46+
return func(t *testing.T) *types.TipSet {
47+
beforeTs := getTipSet(t)
48+
for {
49+
select {
50+
case <-ctx.Done():
51+
t.Fatalf("context cancelled during stable execution: %v", ctx.Err())
52+
default:
53+
}
54+
55+
fn()
56+
afterTs := getTipSet(t)
57+
if beforeTs.Equals(afterTs) {
58+
// Chain hasn't changed during execution, safe to return
59+
return beforeTs
60+
}
61+
beforeTs = afterTs
62+
}
63+
}(t) // Pass the current test context
64+
}
65+
}
66+
4167
var (
4268
heaviest = func(t *testing.T) *types.TipSet {
4369
head, err := subject.ChainHead(ctx)
@@ -302,32 +328,49 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
302328
if test.when != nil {
303329
test.when(t)
304330
}
305-
gotResponseCode, gotResponseBody := subject.DoRawRPCRequest(t, 2, test.request)
306-
require.Equal(t, test.wantResponseStatus, gotResponseCode, string(gotResponseBody))
331+
332+
// Use stable execute to ensure the test doesn't straddle tipsets
333+
stableExecute := mkStableExecute(test.wantTipSet)
334+
if test.wantTipSet == nil {
335+
stableExecute = mkStableExecute(heaviest)
336+
}
337+
338+
var gotResponseCode int
339+
var gotResponseBody []byte
307340
var resultOrError struct {
308341
Result *types.TipSet `json:"result,omitempty"`
309342
Error *struct {
310343
Code int `json:"code,omitempty"`
311344
Message string `json:"message,omitempty"`
312345
} `json:"error,omitempty"`
313346
}
314-
require.NoError(t, json.Unmarshal(gotResponseBody, &resultOrError))
347+
348+
stableTipSet := stableExecute(func() {
349+
gotResponseCode, gotResponseBody = subject.DoRawRPCRequest(t, 2, test.request)
350+
if gotResponseCode == test.wantResponseStatus {
351+
require.NoError(t, json.Unmarshal(gotResponseBody, &resultOrError))
352+
}
353+
})
354+
require.Equal(t, test.wantResponseStatus, gotResponseCode, string(gotResponseBody))
355+
315356
if test.wantErr != "" {
316357
require.Nil(t, resultOrError.Result)
317-
require.Contains(t, resultOrError.Error.Message, test.wantErr)
358+
if resultOrError.Error != nil {
359+
require.Contains(t, resultOrError.Error.Message, test.wantErr)
360+
}
318361
} else {
319362
require.Nil(t, resultOrError.Error)
320-
require.Equal(t, test.wantTipSet(t), resultOrError.Result)
363+
if test.wantTipSet != nil {
364+
require.Equal(t, stableTipSet, resultOrError.Result)
365+
}
321366
}
322367
})
323368
}
324369
})
325370
t.Run("StateGetActor", func(t *testing.T) {
326-
v1StateGetActor := func(t *testing.T, ts func(*testing.T) *types.TipSet) func(*testing.T) *types.Actor {
327-
return func(t *testing.T) *types.Actor {
328-
wantActor, err := subject.StateGetActor(ctx, miner.ActorAddr, ts(t).Key())
329-
require.NoError(t, err)
330-
return wantActor
371+
v1StateGetActor := func(ts *types.TipSet) func() (*types.Actor, error) {
372+
return func() (*types.Actor, error) {
373+
return subject.StateGetActor(ctx, miner.ActorAddr, ts.Key())
331374
}
332375
}
333376

@@ -336,7 +379,7 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
336379
when func(t *testing.T)
337380
request string
338381
wantResponseStatus int
339-
wantActor func(t *testing.T) *types.Actor
382+
wantTipSet func(t *testing.T) *types.TipSet
340383
wantErr string
341384
}{
342385
{
@@ -349,7 +392,7 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
349392
name: "latest tag is ok",
350393
request: `{"jsonrpc":"2.0","method":"Filecoin.StateGetActor","params":["f01000",{"tag":"latest"}],"id":1}`,
351394
wantResponseStatus: http.StatusOK,
352-
wantActor: v1StateGetActor(t, heaviest),
395+
wantTipSet: heaviest,
353396
},
354397
{
355398
name: "finalized tag when f3 disabled falls back to ec",
@@ -358,7 +401,7 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
358401
},
359402
request: `{"jsonrpc":"2.0","method":"Filecoin.StateGetActor","params":["f01000",{"tag":"finalized"}],"id":1}`,
360403
wantResponseStatus: http.StatusOK,
361-
wantActor: v1StateGetActor(t, ecFinalized),
404+
wantTipSet: ecFinalized,
362405
},
363406
{
364407
name: "finalized tag is ok",
@@ -370,7 +413,7 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
370413
},
371414
request: `{"jsonrpc":"2.0","method":"Filecoin.StateGetActor","params":["f01000",{"tag":"finalized"}],"id":1}`,
372415
wantResponseStatus: http.StatusOK,
373-
wantActor: v1StateGetActor(t, tipSetAtHeight(f3FinalizedEpoch)),
416+
wantTipSet: tipSetAtHeight(f3FinalizedEpoch),
374417
},
375418
{
376419
name: "height with anchor to latest",
@@ -382,31 +425,49 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
382425
},
383426
request: `{"jsonrpc":"2.0","method":"Filecoin.StateGetActor","params":["f01000",{"height":{"at":15,"anchor":{"tag":"latest"}}}],"id":1}`,
384427
wantResponseStatus: http.StatusOK,
385-
wantActor: v1StateGetActor(t, tipSetAtHeight(15)),
428+
wantTipSet: tipSetAtHeight(15),
386429
},
387430
} {
388431
t.Run(test.name, func(t *testing.T) {
389432
if test.when != nil {
390433
test.when(t)
391434
}
392-
gotResponseCode, gotResponseBody := subject.DoRawRPCRequest(t, 2, test.request)
393-
require.Equal(t, test.wantResponseStatus, gotResponseCode, string(gotResponseBody))
394435

436+
// Use stable execute to ensure the test doesn't straddle tipsets
437+
stableExecute := mkStableExecute(test.wantTipSet)
438+
if test.wantTipSet == nil {
439+
stableExecute = mkStableExecute(heaviest)
440+
}
441+
442+
var gotResponseCode int
443+
var gotResponseBody []byte
395444
var resultOrError struct {
396445
Result *types.Actor `json:"result,omitempty"`
397446
Error *struct {
398447
Code int `json:"code,omitempty"`
399448
Message string `json:"message,omitempty"`
400449
} `json:"error,omitempty"`
401450
}
402-
require.NoError(t, json.Unmarshal(gotResponseBody, &resultOrError))
451+
452+
stableTipSet := stableExecute(func() {
453+
gotResponseCode, gotResponseBody = subject.DoRawRPCRequest(t, 2, test.request)
454+
if gotResponseCode == test.wantResponseStatus {
455+
require.NoError(t, json.Unmarshal(gotResponseBody, &resultOrError))
456+
}
457+
})
458+
require.Equal(t, test.wantResponseStatus, gotResponseCode, string(gotResponseBody))
403459

404460
if test.wantErr != "" {
405461
require.Nil(t, resultOrError.Result)
406-
require.Contains(t, resultOrError.Error.Message, test.wantErr)
462+
if resultOrError.Error != nil {
463+
require.Contains(t, resultOrError.Error.Message, test.wantErr)
464+
}
407465
} else {
408-
wantActor := test.wantActor(t)
409-
require.Equal(t, wantActor, resultOrError.Result)
466+
if test.wantTipSet != nil && stableTipSet != nil {
467+
wantActor, err := v1StateGetActor(stableTipSet)()
468+
require.NoError(t, err)
469+
require.Equal(t, wantActor, resultOrError.Result)
470+
}
410471
}
411472
})
412473
}
@@ -417,7 +478,7 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
417478
when func(t *testing.T)
418479
request string
419480
wantResponseStatus int
420-
wantID func(*testing.T) *address.Address
481+
wantTipSet func(t *testing.T) *types.TipSet
421482
wantErr string
422483
}{
423484
{
@@ -430,12 +491,7 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
430491
name: "latest tag is ok",
431492
request: `{"jsonrpc":"2.0","method":"Filecoin.StateGetID","params":["f01000",{"tag":"latest"}],"id":1}`,
432493
wantResponseStatus: http.StatusOK,
433-
wantID: func(t *testing.T) *address.Address {
434-
tsk := heaviest(t).Key()
435-
wantID, err := subject.StateLookupID(ctx, miner.ActorAddr, tsk)
436-
require.NoError(t, err)
437-
return &wantID
438-
},
494+
wantTipSet: heaviest,
439495
},
440496
{
441497
name: "finalized tag when f3 disabled falls back to ec",
@@ -444,33 +500,43 @@ func TestAPIV2_ThroughRPC(t *testing.T) {
444500
},
445501
request: `{"jsonrpc":"2.0","method":"Filecoin.StateGetID","params":["f01000",{"tag":"finalized"}],"id":1}`,
446502
wantResponseStatus: http.StatusOK,
447-
wantID: func(t *testing.T) *address.Address {
448-
tsk := tipSetAtHeight(f3FinalizedEpoch)(t).Key()
449-
wantID, err := subject.StateLookupID(ctx, miner.ActorAddr, tsk)
450-
require.NoError(t, err)
451-
return &wantID
452-
},
503+
wantTipSet: tipSetAtHeight(f3FinalizedEpoch),
453504
},
454505
} {
455506
t.Run(test.name, func(t *testing.T) {
456507
if test.when != nil {
457508
test.when(t)
458509
}
459-
gotResponseCode, gotResponseBody := subject.DoRawRPCRequest(t, 2, test.request)
460-
require.Equal(t, test.wantResponseStatus, gotResponseCode, string(gotResponseBody))
461510

511+
// Use stable execute to ensure the test doesn't straddle tipsets
512+
stableExecute := mkStableExecute(test.wantTipSet)
513+
if test.wantTipSet == nil {
514+
stableExecute = mkStableExecute(heaviest)
515+
}
516+
517+
var gotResponseCode int
518+
var gotResponseBody []byte
462519
var resultOrError struct {
463520
Result *address.Address `json:"result,omitempty"`
464521
Error *struct {
465522
Code int `json:"code,omitempty"`
466523
Message string `json:"message,omitempty"`
467524
} `json:"error,omitempty"`
468525
}
469-
require.NoError(t, json.Unmarshal(gotResponseBody, &resultOrError))
526+
527+
_ = stableExecute(func() {
528+
gotResponseCode, gotResponseBody = subject.DoRawRPCRequest(t, 2, test.request)
529+
if gotResponseCode == test.wantResponseStatus {
530+
require.NoError(t, json.Unmarshal(gotResponseBody, &resultOrError))
531+
}
532+
})
533+
require.Equal(t, test.wantResponseStatus, gotResponseCode, string(gotResponseBody))
470534

471535
if test.wantErr != "" {
472536
require.Nil(t, resultOrError.Result)
473-
require.Contains(t, resultOrError.Error.Message, test.wantErr)
537+
if resultOrError.Error != nil {
538+
require.Contains(t, resultOrError.Error.Message, test.wantErr)
539+
}
474540
}
475541
})
476542
}

itests/eth_api_f3_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,12 @@ func TestEthAPIWithF3(t *testing.T) {
836836
return func(fn func()) *types.TipSet {
837837
beforeTs := wantTipSet(t)
838838
for {
839+
select {
840+
case <-ctx.Done():
841+
t.Fatalf("context cancelled during stable execution: %v", ctx.Err())
842+
default:
843+
}
844+
839845
fn()
840846
afterTs := wantTipSet(t)
841847
if beforeTs.Equals(afterTs) {

0 commit comments

Comments
 (0)