Skip to content

Commit 462346d

Browse files
test(anvil): improve flaky test (#317)
We have new flaky tested related to anvil detected [here](https://github.com/smartcontractkit/chainlink-deployments-framework/actions/runs/17208033799/job/48812870948#step:2:581) I have made few changes to improve or reduce the flakiness. - Use cleanup to ensure container is terminated even when error. - health check on anvil node when spinning docker container up
1 parent 50e9141 commit 462346d

File tree

2 files changed

+67
-3
lines changed

2 files changed

+67
-3
lines changed

chain/evm/provider/ctf_anvil_provider.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,9 @@ import (
207207
"errors"
208208
"fmt"
209209
"math/big"
210+
"net/http"
210211
"strconv"
212+
"strings"
211213
"sync"
212214
"testing"
213215
"time"
@@ -398,7 +400,7 @@ func (p *CTFAnvilChainProvider) Initialize(ctx context.Context) (chain.BlockChai
398400
},
399401
}, p.config.ClientOpts...)
400402
if err != nil {
401-
return nil, err
403+
return nil, fmt.Errorf("failed to create multiclient for Anvil at %s: %w", httpURL, err)
402404
}
403405

404406
// Get the Chain ID as big.Int for transactor generation
@@ -591,7 +593,18 @@ func (p *CTFAnvilChainProvider) startContainer(ctx context.Context, chainID stri
591593
testcontainers.CleanupContainer(p.config.T, output.Container)
592594
}
593595

594-
return output.Nodes[0].ExternalHTTPUrl, nil
596+
// Validate that the ExternalHTTPUrl is not empty
597+
externalURL := output.Nodes[0].ExternalHTTPUrl
598+
if externalURL == "" {
599+
return "", errors.New("container started but ExternalHTTPUrl is empty")
600+
}
601+
602+
// Perform health check to ensure Anvil is ready
603+
if healthErr := p.waitForAnvilReady(ctx, externalURL); healthErr != nil {
604+
return "", fmt.Errorf("anvil container started but health check failed: %w", healthErr)
605+
}
606+
607+
return externalURL, nil
595608
},
596609
retry.Context(ctx),
597610
retry.Attempts(attempts),
@@ -659,3 +672,44 @@ func (p *CTFAnvilChainProvider) getUserTransactors(chainID string) ([]*bind.Tran
659672

660673
return transactors, nil
661674
}
675+
676+
// waitForAnvilReady performs a health check on the Anvil node to ensure it's ready to accept requests.
677+
// It sends a simple JSON-RPC request to check if the node is responding correctly.
678+
func (p *CTFAnvilChainProvider) waitForAnvilReady(ctx context.Context, httpURL string) error {
679+
const (
680+
maxAttempts = 30
681+
retryDelay = 1 * time.Second
682+
)
683+
684+
// Simple JSON-RPC request to check if Anvil is ready
685+
jsonRPCRequest := `{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}`
686+
687+
return retry.Do(func() error {
688+
reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
689+
defer cancel()
690+
691+
req, err := http.NewRequestWithContext(reqCtx, http.MethodPost, httpURL, strings.NewReader(jsonRPCRequest))
692+
if err != nil {
693+
return fmt.Errorf("failed to create request: %w", err)
694+
}
695+
req.Header.Set("Content-Type", "application/json")
696+
697+
client := &http.Client{Timeout: 5 * time.Second}
698+
resp, err := client.Do(req)
699+
if err != nil {
700+
return fmt.Errorf("failed to make request: %w", err)
701+
}
702+
defer resp.Body.Close()
703+
704+
if resp.StatusCode != http.StatusOK {
705+
return fmt.Errorf("received non-200 status code: %d", resp.StatusCode)
706+
}
707+
708+
return nil
709+
},
710+
retry.Context(ctx),
711+
retry.Attempts(maxAttempts),
712+
retry.Delay(retryDelay),
713+
retry.DelayType(retry.FixedDelay),
714+
)
715+
}

chain/evm/provider/ctf_anvil_provider_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,9 @@ func TestCTFAnvilChainProvider_Cleanup(t *testing.T) {
512512

513513
// Allocate a free port for this test
514514
port := freeport.GetOne(t)
515-
defer freeport.Return([]int{port})
515+
t.Cleanup(func() {
516+
freeport.Return([]int{port})
517+
})
516518

517519
var once sync.Once
518520
config := CTFAnvilChainProviderConfig{
@@ -525,6 +527,14 @@ func TestCTFAnvilChainProvider_Cleanup(t *testing.T) {
525527
selector := uint64(13264668187771770619) // Chain ID 31337
526528
provider := NewCTFAnvilChainProvider(selector, config)
527529

530+
t.Cleanup(func() {
531+
// Ensure cleanup is called at the end of the test to avoid leaking containers
532+
// even if the test fails
533+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
534+
defer cancel()
535+
_ = provider.Cleanup(ctx)
536+
})
537+
528538
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Minute)
529539
defer cancel()
530540

0 commit comments

Comments
 (0)