Skip to content

Commit a117999

Browse files
committed
chore: tests
1 parent c078e2f commit a117999

14 files changed

Lines changed: 2290 additions & 12 deletions

File tree

.github/workflows/build-and-test.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ on:
1313
permissions:
1414
contents: read
1515

16+
env:
17+
DASHVERSION: "23.0.2"
18+
TEST_DATA_REPO: "dashpay/regtest-blockchain"
19+
TEST_DATA_VERSION: "v0.0.1"
20+
1621
jobs:
1722
test:
1823
name: ${{ matrix.group }}
@@ -28,5 +33,28 @@ jobs:
2833
with:
2934
shared-key: "test-${{ inputs.os }}-${{ matrix.group }}"
3035
- run: pip install pyyaml
36+
37+
# SPV integration tests: set up dashd on ubuntu-latest (x86_64), skip elsewhere
38+
- name: Cache dashd and test data
39+
if: matrix.group == 'spv' && inputs.os == 'ubuntu-latest'
40+
uses: actions/cache@v4
41+
with:
42+
path: .rust-dashcore-test
43+
key: rust-dashcore-test-${{ env.DASHVERSION }}-${{ env.TEST_DATA_VERSION }}
44+
45+
- name: Setup dashd for integration tests
46+
if: matrix.group == 'spv' && inputs.os == 'ubuntu-latest'
47+
env:
48+
CACHE_DIR: ${{ github.workspace }}/.rust-dashcore-test
49+
run: |
50+
chmod +x ./contrib/setup-dashd.sh
51+
source ./contrib/setup-dashd.sh
52+
echo "DASHD_PATH=$DASHD_PATH" >> "$GITHUB_ENV"
53+
echo "DASHD_DATADIR=$DASHD_DATADIR" >> "$GITHUB_ENV"
54+
55+
- name: Skip dashd integration tests
56+
if: matrix.group == 'spv' && inputs.os != 'ubuntu-latest'
57+
run: echo "SKIP_DASHD_TESTS=1" >> "$GITHUB_ENV"
58+
3159
- name: Run tests
3260
run: python .github/scripts/ci_config.py run-group ${{ matrix.group }} --os ${{ inputs.os }}

contrib/setup-dashd.sh

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env bash
2+
# Setup and download script for dashd and test blockchain data for integration tests.
3+
#
4+
# Usage:
5+
# ./contrib/setup-dashd.sh
6+
#
7+
# Environment variables:
8+
# DASHVERSION - Dash Core version (default: 23.0.2)
9+
# TEST_DATA_VERSION - Test data release version (default: v0.0.1)
10+
# TEST_DATA_REPO - GitHub repo for test data (default: xdustinface/regtest-blockchain)
11+
# CACHE_DIR - Cache directory (default: ~/.cache/rust-dashcore-test)
12+
13+
set -euo pipefail
14+
15+
DASHVERSION="${DASHVERSION:-23.0.2}"
16+
TEST_DATA_VERSION="${TEST_DATA_VERSION:-v0.0.1}"
17+
TEST_DATA_REPO="${TEST_DATA_REPO:-xdustinface/regtest-blockchain}"
18+
19+
CACHE_DIR="${CACHE_DIR:-$HOME/.rust-dashcore-test}"
20+
21+
# Detect platform and set asset name
22+
case "$(uname -s)" in
23+
Linux*)
24+
DASHD_ASSET="dashcore-${DASHVERSION}-x86_64-linux-gnu.tar.gz"
25+
;;
26+
Darwin*)
27+
case "$(uname -m)" in
28+
arm64) DASHD_ASSET="dashcore-${DASHVERSION}-arm64-apple-darwin.tar.gz" ;;
29+
*) DASHD_ASSET="dashcore-${DASHVERSION}-x86_64-apple-darwin.tar.gz" ;;
30+
esac
31+
;;
32+
*)
33+
echo "Unsupported platform: $(uname -s)"
34+
exit 1
35+
;;
36+
esac
37+
38+
mkdir -p "$CACHE_DIR"
39+
40+
# Download dashd if not cached
41+
DASHD_DIR="$CACHE_DIR/dashcore-${DASHVERSION}"
42+
DASHD_BIN="$DASHD_DIR/bin/dashd"
43+
if [ -x "$DASHD_BIN" ]; then
44+
echo "dashd ${DASHVERSION} already available"
45+
else
46+
echo "Downloading dashd ${DASHVERSION}..."
47+
curl -L "https://github.com/dashpay/dash/releases/download/v${DASHVERSION}/${DASHD_ASSET}" \
48+
-o "$CACHE_DIR/${DASHD_ASSET}"
49+
tar -xzf "$CACHE_DIR/${DASHD_ASSET}" -C "$CACHE_DIR"
50+
rm "$CACHE_DIR/${DASHD_ASSET}"
51+
echo "Downloaded dashd to $DASHD_DIR"
52+
fi
53+
54+
# Download test data if not cached
55+
TEST_DATA_DIR="$CACHE_DIR/regtest-blockchain-${TEST_DATA_VERSION}/regtest-1000"
56+
if [ -d "$TEST_DATA_DIR/regtest/blocks" ]; then
57+
echo "Test blockchain data ${TEST_DATA_VERSION} already available"
58+
else
59+
echo "Downloading test blockchain data ${TEST_DATA_VERSION}..."
60+
mkdir -p "$CACHE_DIR/regtest-blockchain-${TEST_DATA_VERSION}"
61+
curl -L "https://github.com/${TEST_DATA_REPO}/releases/download/${TEST_DATA_VERSION}/regtest-1000.tar.gz" \
62+
-o "$CACHE_DIR/regtest-1000.tar.gz"
63+
tar -xzf "$CACHE_DIR/regtest-1000.tar.gz" -C "$CACHE_DIR/regtest-blockchain-${TEST_DATA_VERSION}"
64+
rm "$CACHE_DIR/regtest-1000.tar.gz"
65+
echo "Downloaded test data to $TEST_DATA_DIR"
66+
fi
67+
68+
# Set environment variables
69+
export DASHD_PATH="$DASHD_DIR/bin/dashd"
70+
export DASHD_DATADIR="$TEST_DATA_DIR"
71+
72+
echo ""
73+
echo "Environment configured:"
74+
echo " DASHD_PATH=$DASHD_PATH"
75+
echo " DASHD_DATADIR=$DASHD_DATADIR"
76+
echo ""
77+
78+
# Reset strict mode (important when sourcing)
79+
set +euo pipefail

dash-spv-ffi/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ rand = "0.8"
3434
clap = { version = "4.5", features = ["derive"] }
3535

3636
[dev-dependencies]
37+
dash-spv = { path = "../dash-spv", features = ["test-utils"] }
3738
tempfile = "3.8"
3839
serial_test = "3.0"
3940
env_logger = "0.10"
41+
hex = "0.4"
4042

4143
[build-dependencies]
4244
cbindgen = "0.29"

dash-spv-ffi/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ pub unsafe extern "C" fn dash_spv_ffi_init_logging(
6868
level: level_filter,
6969
console: enable_console,
7070
file: file_config,
71+
thread_local: false,
7172
};
7273

7374
match dash_spv::init_logging(config) {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//! Integration test for FFI event callbacks.
2+
//!
3+
//! This test verifies all three callback structs work correctly in a real sync scenario:
4+
//! - FFISyncEventCallbacks
5+
//! - FFINetworkEventCallbacks
6+
//! - FFIWalletEventCallbacks
7+
8+
mod ffi_test_common;
9+
10+
use ffi_test_common::{
11+
create_full_sync_callbacks, create_network_callbacks, create_wallet_callbacks, FFINodeContext,
12+
FFITestContext,
13+
};
14+
use std::sync::atomic::Ordering;
15+
16+
#[test]
17+
#[ignore] // Run with: cargo test --test callback_integration_test -- --ignored
18+
fn test_all_callbacks_during_sync() {
19+
unsafe {
20+
dash_spv_ffi::dash_spv_ffi_init_logging(c"info".as_ptr(), true, std::ptr::null(), 0);
21+
}
22+
23+
let Some(mut node_ctx) = FFINodeContext::new() else {
24+
return;
25+
};
26+
27+
unsafe {
28+
let ctx = FFITestContext::new(node_ctx.addr);
29+
let tracker = ctx.tracker.clone();
30+
31+
ctx.add_wallet(&node_ctx.light_wallet.mnemonic);
32+
tracing::info!("Added wallet from mnemonic via FFI");
33+
34+
// Set up all three callback types
35+
let sync_callbacks = create_full_sync_callbacks(&tracker);
36+
let network_callbacks = create_network_callbacks(&tracker);
37+
let wallet_callbacks = create_wallet_callbacks(&tracker);
38+
39+
let result =
40+
dash_spv_ffi::dash_spv_ffi_client_set_sync_event_callbacks(ctx.client, sync_callbacks);
41+
assert_eq!(result, 0, "Failed to set sync callbacks");
42+
43+
let result = dash_spv_ffi::dash_spv_ffi_client_set_network_event_callbacks(
44+
ctx.client,
45+
network_callbacks,
46+
);
47+
assert_eq!(result, 0, "Failed to set network callbacks");
48+
49+
let result = dash_spv_ffi::dash_spv_ffi_client_set_wallet_event_callbacks(
50+
ctx.client,
51+
wallet_callbacks,
52+
);
53+
assert_eq!(result, 0, "Failed to set wallet callbacks");
54+
55+
let result = dash_spv_ffi::dash_spv_ffi_client_run(ctx.client);
56+
assert_eq!(result, 0, "Failed to run FFI client");
57+
tracing::info!("FFI client running with all callback types");
58+
59+
ctx.wait_for_sync(node_ctx.expected_height, 180);
60+
61+
// Log callback invocation summary
62+
let sync_start = tracker.sync_start_count.load(Ordering::SeqCst);
63+
let headers_stored = tracker.block_headers_stored_count.load(Ordering::SeqCst);
64+
let header_complete = tracker.block_header_sync_complete_count.load(Ordering::SeqCst);
65+
let filter_headers_stored = tracker.filter_headers_stored_count.load(Ordering::SeqCst);
66+
let filter_header_complete =
67+
tracker.filter_headers_sync_complete_count.load(Ordering::SeqCst);
68+
let sync_complete = tracker.sync_complete_count.load(Ordering::SeqCst);
69+
let peer_connected = tracker.peer_connected_count.load(Ordering::SeqCst);
70+
let peers_updated = tracker.peers_updated_count.load(Ordering::SeqCst);
71+
72+
tracing::info!("=== Callback Summary ===");
73+
tracing::info!(
74+
"Sync: start={}, headers_stored={}, header_complete={}, filter_headers={}, filter_complete={}, sync_complete={}",
75+
sync_start, headers_stored, header_complete, filter_headers_stored, filter_header_complete, sync_complete
76+
);
77+
tracing::info!(
78+
"Network: peer_connected={}, peers_updated={}",
79+
peer_connected, peers_updated
80+
);
81+
tracing::info!(
82+
"Wallet: tx_received={}",
83+
tracker.transaction_received_count.load(Ordering::SeqCst)
84+
);
85+
86+
// Validate sync callbacks
87+
assert!(sync_start > 0, "on_sync_start should have been called");
88+
assert!(headers_stored > 0, "on_block_headers_stored should have been called");
89+
assert_eq!(header_complete, 1, "on_block_header_sync_complete should be called once");
90+
assert!(
91+
filter_headers_stored > 0,
92+
"on_filter_headers_stored should have been called"
93+
);
94+
assert_eq!(
95+
filter_header_complete, 1,
96+
"on_filter_headers_sync_complete should be called once"
97+
);
98+
assert_eq!(sync_complete, 1, "on_sync_complete should be called once");
99+
100+
// Validate network callbacks
101+
assert!(peer_connected > 0, "on_peer_connected should have been called");
102+
assert!(peers_updated > 0, "on_peers_updated should have been called");
103+
104+
// Validate final state
105+
let final_header = tracker.last_header_tip.load(Ordering::SeqCst);
106+
assert_eq!(final_header, node_ctx.expected_height, "Final header tip mismatch");
107+
108+
{
109+
let errors = tracker.errors.lock().unwrap();
110+
if !errors.is_empty() {
111+
tracing::warn!("Errors during sync: {:?}", *errors);
112+
}
113+
}
114+
115+
node_ctx.stop();
116+
ctx.cleanup();
117+
}
118+
119+
tracing::info!("Callback integration test completed successfully");
120+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//! FFI Sync test using dashd.
2+
//!
3+
//! This test mirrors `test_wallet_sync` from dash-spv but uses FFI bindings
4+
//! with the event-based API (dash_spv_ffi_client_run + event callbacks).
5+
6+
mod ffi_test_common;
7+
8+
use ffi_test_common::{FFINodeContext, FFITestContext};
9+
use std::sync::atomic::Ordering;
10+
11+
#[test]
12+
fn test_wallet_sync_via_ffi() {
13+
unsafe {
14+
dash_spv_ffi::dash_spv_ffi_init_logging(c"info".as_ptr(), true, std::ptr::null(), 0);
15+
}
16+
17+
let Some(mut node_ctx) = FFINodeContext::new() else {
18+
return;
19+
};
20+
21+
unsafe {
22+
let ctx = FFITestContext::new(node_ctx.addr);
23+
24+
let wallet_id = ctx.add_wallet(&node_ctx.light_wallet.mnemonic);
25+
tracing::info!("Added wallet, ID: {}", hex::encode(&wallet_id));
26+
27+
ctx.run_with_sync_callbacks();
28+
tracing::info!("FFI client running");
29+
30+
ctx.wait_for_sync(node_ctx.expected_height, 180);
31+
32+
{
33+
let errors = ctx.tracker.errors.lock().unwrap();
34+
if !errors.is_empty() {
35+
tracing::error!("Sync errors: {:?}", *errors);
36+
}
37+
}
38+
39+
// Validate sync heights
40+
let final_header = ctx.tracker.last_header_tip.load(Ordering::SeqCst);
41+
let final_filter = ctx.tracker.last_filter_tip.load(Ordering::SeqCst);
42+
43+
assert_eq!(final_header, node_ctx.expected_height, "Header height mismatch");
44+
assert_eq!(final_filter, node_ctx.expected_height, "Filter header height mismatch");
45+
tracing::info!("Heights match: headers={}, filters={}", final_header, final_filter);
46+
47+
// Validate wallet balance
48+
let (confirmed, _unconfirmed) = ctx.get_wallet_balance(&wallet_id);
49+
let expected_balance = (node_ctx.light_wallet.balance * 100_000_000.0) as u64;
50+
tracing::info!(
51+
"Balance: confirmed={} satoshis, expected={} satoshis",
52+
confirmed,
53+
expected_balance
54+
);
55+
56+
if confirmed != expected_balance {
57+
tracing::warn!(
58+
"Balance mismatch: got {} satoshis, expected {} satoshis",
59+
confirmed,
60+
expected_balance
61+
);
62+
}
63+
64+
node_ctx.stop();
65+
ctx.cleanup();
66+
}
67+
68+
tracing::info!("FFI sync test completed successfully");
69+
}

0 commit comments

Comments
 (0)