Skip to content

integration/rpctest: improve rpctest harness#2571

Open
allocz wants to merge 6 commits into
btcsuite:masterfrom
allocz:improve_rpctest_harness
Open

integration/rpctest: improve rpctest harness#2571
allocz wants to merge 6 commits into
btcsuite:masterfrom
allocz:improve_rpctest_harness

Conversation

@allocz

@allocz allocz commented Jul 7, 2026

Copy link
Copy Markdown

Change Description

The main motivation of this PR is to improve the process of writing integration
tests, currently there's no way to write complex tests in a simple way.

As discussed in #2560.

Changes

  1. Debug event stream: connect to btcd and receive debug events.
  2. Harness teardown: stops btcd returning the exit code, but allow stopping
    without cleaning up node data, enabling the test of scenarios where btcd
    exits with error or scenarios where the node should be restarted.
  3. Harness start: allow the harness to be started again after teardown is
    called.
  4. Change New() signature: pass opts as a struct instead of having to pass
    multiple zero values to get default initialization.

The debug event stream is replaced by a NOP implementation when the compile tag
debug is not defined, so that the compiler can optimize out all the debug event
broadcast calls. Also, the hidden flag --debugstream=host:port should be used
to start the event stream.

Steps to Test

make unit
make unit-cover
make unit-race

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included.
  • Bug fixes contain tests triggering the bug to prevent regressions.

Code Style and Documentation

📝 Please see our Contribution Guidelines for further guidance.

allocz added 5 commits July 8, 2026 11:14
Initialize the Harness API with a options struct instead of several
fields that may be set to the zero value of the type.
Now, Harness TearDown procedure returns an error if the btcd process
stopped with error status code. Also, SkipCleanup option was added to
TearDown, so now we can write tests that restart the node while keeping
the state.
The package debugstream provides two utility types, the Stream and the
Client.

The Stream type listens in a TCP endpoint and can be used to broadcast
events to all connected clients. The event type has a uint64 code and a
byte slice data fields. The Stream implementation is used only when btcd
is compiled with the debug tag, otherwise a nop implementation is used.

The Client connects with the Stream via TCP and starts receiving the
events, which are passed into a handler callback, allowing assertions
inside tests.

All events broadcasted in the stream are stored in memory and sent to
all clients after connection, this way if the client connects after the
broadcast of events, it still receives all of them.
btcd: integrated debugstream, now we can send debug events and improve
testing capabilities. The hidden flag --debugstream=<host:port> starts
the debug Stream when btcd is compiled with the debug tag.

integration: added debugstream integration test.

integration/rpctest: added DebugStreamHandler to harness options,
allowing integration tests to easily process debug events. The btcd
executable build to be used by the Harness is now being built with the
debug tag, but the debug stream will be started only if the
DebugStreamHandler is also passed to the Harness.
Previously the [Harness.SetUp] procedure was taking a boolean to
generate a test chain and a integer for the required number of mature
outputs, but internally, the test chain was being generated only if the
number of mature outputs was different than zero and the boolean was
true, this means that we can remove the boolean and pass zero when we
don't want to create the test chain.

After the change, all the tests still passing.
@allocz allocz force-pushed the improve_rpctest_harness branch from 14d655f to e5dfa5d Compare July 8, 2026 11:17
Fixed a goroutine leak caused by the [memWallet.Start()] procedure which
was spinning up a goroutine that was never terminated. Also, added a new
method [memWallet.Stop()], which is called by [Harness.TearDown()].

Added [Harness.DisableWalletSyncWait], to avoid blocking when
[Harness.SetUp()] is called after shutting down the node before all
blocks being processed by the wallet.
@allocz allocz mentioned this pull request Jul 8, 2026
7 tasks
func New(activeNet *chaincfg.Params, handlers *rpcclient.NotificationHandlers,
extraArgs []string, customExePath string) (*Harness, error) {

func New(opts ...HarnessOpts) (*Harness, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ...HarnessOpts parameter seems a little misleading since only the first element is ever used. Would it make sense to accept a *HarnessOpts instead, with nil representing the default configuration? That would make it clearer that only a single optional configuration is expected and avoid callers accidentally passing multiple option structs that are silently ignored.

@@ -242,13 +262,24 @@ func (n *node) cleanup() error {
// shutdown terminates the running btcd process, and cleans up all

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here in shutdown, the else if err != nil { return } path skips cleanup() entirely when stop() returns a non-exit error.
This means the pid file and temp directory are left behind if the process fails unexpectedly.
Worth documenting whether this is intentional—callers who need to restart after an unexpected failure would need to handle cleanup themselves.

Comment thread debugstream/client.go
)
for i := range maxConnAttempts {
if i > 0 {
time.Sleep((time.Millisecond*100)<<i - 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the backoff calculation here may not be doing what's intended. Since << has higher precedence than -, the expression is evaluated as ((time.Millisecond * 100) << i) - 1. That means when i == 1, the delay becomes 200ms - 1ns rather than 100ms. Would wrapping it as (time.Millisecond * 100) << (i - 1) better match the intended exponential backoff?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants