btcd/server_test.go
2026-05-14 18:05:33 -07:00

147 lines
3.8 KiB
Go

package main
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/btcsuite/btcd/chaincfg/v2"
"github.com/btcsuite/btcd/peer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
// logRotator must be non-nil or any log write (e.g. from
// OnVerAck's double-call guard) panics via logWriter.Write.
initLogRotator(filepath.Join(os.TempDir(), "btcd-server-test.log"))
os.Exit(m.Run())
}
// newTestServerPeer creates a minimal serverPeer suitable for unit
// tests that exercise the peer lifecycle logic without starting the
// full server. The returned server's peerLifecycle channel is
// buffered so the handler never blocks during tests.
func newTestServerPeer(t *testing.T) (*server, *serverPeer) {
t.Helper()
s := &server{
peerLifecycle: make(chan peerLifecycleEvent, 10),
}
sp := newServerPeer(s, false)
sp.Peer = peer.NewInboundPeer(&peer.Config{
ChainParams: &chaincfg.SimNetParams,
})
return s, sp
}
// recvLifecycleEvent reads a single event from the peerLifecycle
// channel or fails the test after a timeout.
func recvLifecycleEvent(
t *testing.T, ch <-chan peerLifecycleEvent,
) peerLifecycleEvent {
t.Helper()
select {
case ev := <-ch:
return ev
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for peerLifecycleEvent")
return peerLifecycleEvent{}
}
}
// TestOnVerAckDoubleCall verifies that calling OnVerAck twice on
// the same serverPeer does not panic. The double-call guard must
// log an error and leave verAckCh closed.
func TestOnVerAckDoubleCall(t *testing.T) {
t.Parallel()
_, sp := newTestServerPeer(t)
sp.OnVerAck(nil, nil)
select {
case <-sp.verAckCh:
default:
t.Fatal("verAckCh should be closed after first OnVerAck call")
}
require.NotPanics(t, func() {
sp.OnVerAck(nil, nil)
})
select {
case <-sp.verAckCh:
default:
t.Fatal("verAckCh should still be closed after second OnVerAck call")
}
}
// TestPeerLifecycleOrdering verifies that when verack arrives before
// disconnect, peerLifecycleHandler emits peerAdd followed by peerDone
// on the peerLifecycle channel -- never out of order.
func TestPeerLifecycleOrdering(t *testing.T) {
t.Parallel()
s, sp := newTestServerPeer(t)
// Simulate verack received before the handler starts.
close(sp.verAckCh)
go s.peerLifecycleHandler(sp)
first := recvLifecycleEvent(t, s.peerLifecycle)
require.Equal(t, peerAdd, first.action,
"first lifecycle event must be peerAdd")
require.Equal(t, sp, first.sp)
// Trigger disconnect after peerAdd is observed.
sp.Peer.Disconnect()
second := recvLifecycleEvent(t, s.peerLifecycle)
require.Equal(t, peerDone, second.action,
"second lifecycle event must be peerDone")
require.Equal(t, sp, second.sp)
}
// TestPeerLifecycleSimultaneousReady verifies that when both verAckCh
// and Peer.Done() are ready before the handler runs, the system stays
// stable: peerDone is always emitted, and if peerAdd is emitted it
// precedes peerDone. Go's select is nondeterministic so peerAdd may
// be skipped -- both outcomes are valid per documented behavior.
func TestPeerLifecycleSimultaneousReady(t *testing.T) {
t.Parallel()
const iterations = 100
var addEmitted int
for i := 0; i < iterations; i++ {
s, sp := newTestServerPeer(t)
close(sp.verAckCh)
sp.Peer.Disconnect()
go s.peerLifecycleHandler(sp)
first := recvLifecycleEvent(t, s.peerLifecycle)
if first.action == peerAdd {
addEmitted++
second := recvLifecycleEvent(t, s.peerLifecycle)
assert.Equal(t, peerDone, second.action,
"iteration %d: peerAdd must be "+
"followed by peerDone", i)
} else {
assert.Equal(t, peerDone, first.action,
"iteration %d: sole event must "+
"be peerDone", i)
}
}
t.Logf("peerAdd emitted in %d/%d iterations "+
"(both outcomes are valid per documented behavior)",
addEmitted, iterations)
}