Automated Testing
Setting Up Test Environment
How to Set Up Your Local Test Environment
- Prerequisites:
# Install required tools
docker compose
go 1.21 or higher
make
- Initial setup:
# Clone and build
git clone [repository]
cd teranode
docker compose build
- Verify setup:
# Run smoke tests
make smoketest
How to Configure Test Settings
- Create local settings:
touch settings_local.conf
-
Add required settings for your specific testing activity. Common test contexts:
-
test- Standard testing environment docker- Docker-based testingdocker.ci- CI environment testing
Running Tests
How to Run Test Suites
- Run all tests in a suite:
# Format
cd /teranode/test/<suite-name>
go test -v -tags test_<suite-code>
# Examples
# Run TNA tests
cd /teranode/test/tna
go test -v -tags test_tna
# Run TNB tests
cd /teranode/test/tnb
go test -v -tags test_tnb
# Run TEC tests
cd /teranode/test/tec
go test -v -tags test_tec
- Run specific test:
# Format
go test -v -run "^<TestSuiteName>$/^<TestName>$" -tags <test-tag>
# Example: Run specific TNA test
go test -v -run "^TestTNA1TestSuite$/^TestBroadcastNewTxAllNodes$" -tags test_tna
- Run with different options:
# With timeout
go test -v -tags test_tna -timeout 10m
# With race detection
go test -v -race -tags test_tna
# With specific cache size
go test -v -tags "test_tna,testtxmetacache"
How to Debug Failed Tests
- Enable verbose logging:
# Set log level to DEBUG
export LOG_LEVEL=DEBUG
go test -v -tags test_tna
- Check container logs:
# View logs for specific node
docker logs teranode1
docker logs teranode2
docker logs teranode3
- Inspect test state:
# Check node health (verify actual ports in your settings)
curl http://localhost:18090/api/v1/bestblockheader/json
curl http://localhost:28090/api/v1/bestblockheader/json
curl http://localhost:38090/api/v1/bestblockheader/json
Adding New Tests
How to Add a New Test Case
- Create test file with proper structure:
//go:build test_tna || debug
// How to run this test manually:
// $ go test -v -run "^TestTNA5TestSuite$/^TestNewFeature$" -tags test_tna
package tna
import (
"testing"
"time"
helper "github.com/bsv-blockchain/teranode/test/utils"
"github.com/bsv-blockchain/teranode/test/utils/tconfig"
"github.com/stretchr/testify/suite"
)
type TNA5TestSuite struct {
helper.TeranodeTestSuite
}
func TestTNA5TestSuite(t *testing.T) {
suite.Run(t, &TNA5TestSuite{
TeranodeTestSuite: helper.TeranodeTestSuite{
TConfig: tconfig.LoadTConfig(
map[string]any{
tconfig.KeyTeranodeContexts: []string{
"docker.teranode1.test.tna5Test",
"docker.teranode2.test.tna5Test",
"docker.teranode3.test.tna5Test",
},
},
),
},
},
)
}
func (suite *TNA5TestSuite) TestNewFeature() {
// Access test environment
testEnv := suite.TeranodeTestEnv
ctx := testEnv.Context
t := suite.T()
logger := testEnv.Logger
// Access nodes
node1 := testEnv.Nodes[0]
blockchainClient := node1.BlockchainClient
// Your test logic here
t.Log("Starting test")
// Example: Check node health
health, err := blockchainClient.Health(ctx)
if err != nil {
t.Fatalf("Failed to get health: %v", err)
}
if !health.Ok {
t.Error("Expected health to be OK")
}
t.Log("Test completed successfully")
}
- Common test patterns:
// Create and send transaction
tx, err := helper.CreateAndSendTx(ctx, node1)
if err != nil {
t.Fatalf("Failed to create transaction: %v", err)
}
// Mine a block
blockHash, err := helper.MineBlock(ctx, node1.Settings, node1.BlockAssemblyClient, logger)
if err != nil {
t.Fatalf("Failed to mine block: %v", err)
}
// Wait for block height
err = helper.WaitForNodeBlockHeight(ctx, blockchainClient, targetHeight, 30*time.Second)
if err != nil {
t.Fatalf("Failed to reach target height: %v", err)
}
// Wait for nodes to sync
blockchainClients := []blockchain.ClientI{
testEnv.Nodes[0].BlockchainClient,
testEnv.Nodes[1].BlockchainClient,
testEnv.Nodes[2].BlockchainClient,
}
err = helper.WaitForNodesToSync(ctx, blockchainClients, 30*time.Second)
if err != nil {
t.Fatalf("Nodes failed to sync: %v", err)
}
How to Add Custom Test Configuration
- Use TConfig system to customize settings:
func TestCustomTestSuite(t *testing.T) {
suite.Run(t, &CustomTestSuite{
TeranodeTestSuite: helper.TeranodeTestSuite{
TConfig: tconfig.LoadTConfig(
map[string]any{
tconfig.KeyTeranodeContexts: []string{
"docker.teranode1.test.customTest",
},
tconfig.KeyComposeFiles: []string{
"docker-compose.e2etest.yml",
"docker-compose.custom.override.yml",
},
},
),
},
})
}
- Create custom docker compose override:
# docker-compose.custom.override.yml
services:
teranode1:
environment:
- CUSTOM_SETTING=value
Working with Multiple Nodes
How to Test Node Communication
- Setup multiple nodes:
// Ensure all nodes are running
for i, node := range testEnv.Nodes {
health, err := node.BlockchainClient.Health(ctx)
if err != nil {
t.Errorf("Node %d health check failed: %v", i, err)
}
if !health.Ok {
t.Errorf("Node %d is not healthy", i)
}
}
- Test transaction propagation:
// Send transaction to node 0
tx, err := helper.CreateAndSendTx(ctx, testEnv.Nodes[0])
if err != nil {
t.Fatalf("Failed to create transaction: %v", err)
}
// Allow time for propagation
time.Sleep(2 * time.Second)
// Verify on other nodes
block, err := testEnv.Nodes[1].BlockchainClient.GetBestBlockHeader(ctx)
if err != nil {
t.Fatalf("Failed to get block from node 1: %v", err)
}
How to Test Node Failure Scenarios
- Stop specific containers:
# Stop a specific node container
docker stop teranode2
- Restart containers:
# Restart the node
docker start teranode2
- Test resilience in code:
// Send transaction before node failure
tx1, _ := helper.CreateAndSendTx(ctx, testEnv.Nodes[0])
// Simulate node failure by stopping docker container
// (typically done manually or via docker SDK)
// Verify other nodes continue working
tx2, err := helper.CreateAndSendTx(ctx, testEnv.Nodes[1])
if err != nil {
t.Fatalf("Remaining nodes should continue working: %v", err)
}
Cleanup and Maintenance
How to Clean Test Environment
- Remove test data:
# Clean data directory
rm -rf data/test/*
- Remove containers:
# Remove all test containers
docker ps -a -q --filter label=com.docker.compose.project=e2e | xargs docker rm -f
- Full cleanup in test code:
// TearDown is handled automatically by TeranodeTestSuite
// But you can add custom cleanup in TearDownTest:
func (suite *CustomTestSuite) TearDownTest() {
// Custom cleanup here
suite.TeranodeTestSuite.TearDownTest() // Call parent teardown
}
How to Handle Common Issues
- Port conflicts:
# Check port usage
lsof -i :8087
lsof -i :8090
lsof -i :9292
- Resource cleanup:
# Force remove hanging containers
docker ps -a -q --filter label=com.docker.compose.project=e2e | xargs docker rm -f
# Remove volumes
docker volume prune -f
- Data persistence issues:
# Reset all data (use sudo if needed)
rm -rf ./data/test
# Or if permission issues:
sudo rm -rf ./data/test
Available Test Tags
Teranode uses build tags to control which tests run:
test_tna- TNA test suitetest_tnb- TNB test suitetest_tec- TEC test suitetest_tnd- TND test suitetest_tnf- TNF test suitetest_tnj- TNJ test suitetest_smoke- Smoke teststest_functional- Functional teststesttxmetacache- Use small transaction metadata cachelargetxmetacache- Use production-sized cacheaerospike- Tests requiring Aerospikedebug- Enable debug mode for tests