Skip to content

Commit a2a2599

Browse files
auricomclaude
andcommitted
test: improve patch coverage for raft shutdown and resign paths
Add unit tests for lines flagged by Codecov: - boltTxClosedFilter.Write: filter drops "tx closed", forwards others - buildRaftConfig: ElectionTimeout > 0 applied, zero uses default - FullNode.ResignLeader: nil raftNode no-op; non-leader raftNode no-op - Syncer.Stop: raftRetriever.Stop is called when raftRetriever is set - Syncer.RecoverFromRaft: GetHeader failure when local state is ahead of stale raft snapshot returns "cannot verify hash" error Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9ed4946 commit a2a2599

3 files changed

Lines changed: 132 additions & 0 deletions

File tree

block/internal/syncing/syncer_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,59 @@ func TestSyncer_RecoverFromRaft_LocalAheadOfStaleSnapshot(t *testing.T) {
532532
require.Error(t, err)
533533
require.ErrorContains(t, err, "diverged from raft")
534534
})
535+
536+
t.Run("get header fails returns error", func(t *testing.T) {
537+
// lastState is at height 2; raft snapshot at height 0.
538+
// No block is stored at height 0, so GetHeader fails.
539+
err := s.RecoverFromRaft(t.Context(), &raft.RaftBlockState{
540+
Height: 0,
541+
Hash: make([]byte, 32),
542+
Header: headerBz1,
543+
Data: dataBz1,
544+
})
545+
require.Error(t, err)
546+
require.ErrorContains(t, err, "cannot verify hash")
547+
})
548+
}
549+
550+
func TestSyncer_Stop_CallsRaftRetrieverStop(t *testing.T) {
551+
ds := dssync.MutexWrap(datastore.NewMapDatastore())
552+
st := store.New(ds)
553+
554+
cm, err := cache.NewManager(config.DefaultConfig(), st, zerolog.Nop())
555+
require.NoError(t, err)
556+
557+
raftNode := &stubRaftNode{}
558+
s := NewSyncer(
559+
st,
560+
nil,
561+
nil,
562+
cm,
563+
common.NopMetrics(),
564+
config.DefaultConfig(),
565+
genesis.Genesis{},
566+
nil,
567+
nil,
568+
zerolog.Nop(),
569+
common.DefaultBlockOptions(),
570+
make(chan error, 1),
571+
raftNode,
572+
)
573+
574+
require.NotNil(t, s.raftRetriever, "raftRetriever should be set when raftNode is provided")
575+
576+
// Manually set cancel so Stop() doesn't bail out early (simulates having been started).
577+
ctx, cancel := context.WithCancel(t.Context())
578+
s.ctx = ctx
579+
s.cancel = cancel
580+
581+
require.NoError(t, s.Stop(t.Context()))
582+
583+
// raftRetriever.Stop clears the apply callback (sets it to nil).
584+
// The stub records each SetApplyCallback call; the last one should be nil.
585+
callbacks := raftNode.recordedCallbacks()
586+
require.NotEmpty(t, callbacks, "expected at least one callback registration")
587+
assert.Nil(t, callbacks[len(callbacks)-1], "last callback should be nil after Stop")
535588
}
536589

537590
func TestSyncer_processPendingEvents(t *testing.T) {

node/full_node_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/stretchr/testify/assert"
1515
"github.com/stretchr/testify/require"
1616

17+
raftpkg "github.com/evstack/ev-node/pkg/raft"
1718
"github.com/evstack/ev-node/pkg/service"
1819
)
1920

@@ -82,3 +83,15 @@ func TestStartInstrumentationServer(t *testing.T) {
8283
assert.NoError(err, "Pprof server shutdown should not return error")
8384
}
8485
}
86+
87+
func TestFullNode_ResignLeader_NilRaftNode(t *testing.T) {
88+
n := &FullNode{} // raftNode is nil
89+
assert.NoError(t, n.ResignLeader())
90+
}
91+
92+
func TestFullNode_ResignLeader_NonLeaderRaftNode(t *testing.T) {
93+
// Empty *raftpkg.Node has nil raft field so IsLeader() returns false;
94+
// ResignLeader() is a no-op and returns nil.
95+
n := &FullNode{raftNode: &raftpkg.Node{}}
96+
assert.NoError(t, n.ResignLeader())
97+
}

pkg/raft/node_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,81 @@
11
package raft
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"testing"
8+
"time"
79

810
"github.com/hashicorp/raft"
911
"github.com/stretchr/testify/assert"
1012
"github.com/stretchr/testify/require"
1113
)
1214

15+
func TestBoltTxClosedFilter_Write(t *testing.T) {
16+
specs := map[string]struct {
17+
input string
18+
expectFwd bool
19+
}{
20+
"passes through normal log line": {
21+
input: "some normal log message\n",
22+
expectFwd: true,
23+
},
24+
"drops line containing tx closed": {
25+
input: "Rollback failed: tx closed\n",
26+
expectFwd: false,
27+
},
28+
"drops line with tx closed anywhere": {
29+
input: "error: tx closed due to commit\n",
30+
expectFwd: false,
31+
},
32+
}
33+
34+
for name, spec := range specs {
35+
t.Run(name, func(t *testing.T) {
36+
var buf bytes.Buffer
37+
f := &boltTxClosedFilter{w: &buf}
38+
n, err := f.Write([]byte(spec.input))
39+
require.NoError(t, err)
40+
assert.Equal(t, len(spec.input), n)
41+
if spec.expectFwd {
42+
assert.Equal(t, spec.input, buf.String())
43+
} else {
44+
assert.Empty(t, buf.String())
45+
}
46+
})
47+
}
48+
}
49+
50+
func TestBuildRaftConfig_ElectionTimeout(t *testing.T) {
51+
specs := map[string]struct {
52+
cfg *Config
53+
expectedElectionTimeout time.Duration
54+
}{
55+
"custom election timeout is applied": {
56+
cfg: &Config{
57+
NodeID: "node1",
58+
ElectionTimeout: 500 * time.Millisecond,
59+
},
60+
expectedElectionTimeout: 500 * time.Millisecond,
61+
},
62+
"zero election timeout uses default": {
63+
cfg: &Config{
64+
NodeID: "node1",
65+
ElectionTimeout: 0,
66+
},
67+
expectedElectionTimeout: raft.DefaultConfig().ElectionTimeout,
68+
},
69+
}
70+
71+
for name, spec := range specs {
72+
t.Run(name, func(t *testing.T) {
73+
rc := buildRaftConfig(spec.cfg)
74+
assert.Equal(t, spec.expectedElectionTimeout, rc.ElectionTimeout)
75+
})
76+
}
77+
}
78+
1379
func TestSplitPeerAddr(t *testing.T) {
1480
specs := map[string]struct {
1581
in string

0 commit comments

Comments
 (0)