Skip to content

Commit 2302ff5

Browse files
Add integration tests
Add the full integration test framework using the `regtest-*` features. Tests can be run by both `regtest-bitcoin` and `regtest-electrum`.
1 parent 96fa854 commit 2302ff5

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed

tests/integration.rs

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
4+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
6+
// You may not use this file except in accordance with one or both of these
7+
// licenses.
8+
9+
//! bdk-cli Integration Test Framework
10+
//!
11+
//! This modules performs the necessary integration test for bdk-cli
12+
//! The tests can be run using `cargo test`
13+
14+
#[cfg(feature = "regtest-node")]
15+
mod test {
16+
use electrsd::bitcoind::tempfile::TempDir;
17+
use serde_json::{json, Value};
18+
use std::convert::From;
19+
use std::path::PathBuf;
20+
use std::process::Command;
21+
22+
/// Testing errors for integration tests
23+
#[derive(Debug)]
24+
enum IntTestError {
25+
// IO error
26+
IO(std::io::Error),
27+
// Command execution error
28+
CmdExec(String),
29+
// Json Data error
30+
JsonData(String),
31+
}
32+
33+
impl From<std::io::Error> for IntTestError {
34+
fn from(e: std::io::Error) -> Self {
35+
IntTestError::IO(e)
36+
}
37+
}
38+
39+
// Helper function
40+
// Runs a system command with given args
41+
fn run_cmd_with_args(cmd: &str, args: &[&str]) -> Result<serde_json::Value, IntTestError> {
42+
let output = Command::new(cmd).args(args).output().unwrap();
43+
let mut value = output.stdout;
44+
let error = output.stderr;
45+
if value.len() == 0 {
46+
return Err(IntTestError::CmdExec(String::from_utf8(error).unwrap()));
47+
}
48+
value.pop(); // remove `\n` at end
49+
let output_string = std::str::from_utf8(&value).unwrap();
50+
let json_value: serde_json::Value = match serde_json::from_str(output_string) {
51+
Ok(value) => value,
52+
Err(_) => json!(output_string), // bitcoin-cli will sometime return raw string
53+
};
54+
Ok(json_value)
55+
}
56+
57+
// Helper Function
58+
// Transforms a json value to string
59+
fn value_to_string(value: &Value) -> Result<String, IntTestError> {
60+
match value {
61+
Value::Bool(bool) => match bool {
62+
true => Ok("true".to_string()),
63+
false => Ok("false".to_string()),
64+
},
65+
Value::Number(n) => Ok(n.to_string()),
66+
Value::String(s) => Ok(s.to_string()),
67+
_ => Err(IntTestError::JsonData(
68+
"Value parsing not implemented for this type".to_string(),
69+
)),
70+
}
71+
}
72+
73+
// Helper Function
74+
// Extracts value from a given json object and key
75+
fn get_value(json: &Value, key: &str) -> Result<String, IntTestError> {
76+
let map = json
77+
.as_object()
78+
.ok_or(IntTestError::JsonData("Json is not an object".to_string()))?;
79+
let value = map
80+
.get(key)
81+
.ok_or(IntTestError::JsonData("Invalid key".to_string()))?
82+
.to_owned();
83+
let string_value = value_to_string(&value)?;
84+
Ok(string_value)
85+
}
86+
87+
/// The bdk-cli command struct
88+
/// Use it to perform all bdk-cli operations
89+
#[derive(Debug)]
90+
struct BdkCli {
91+
target: String,
92+
network: String,
93+
verbosity: bool,
94+
recv_desc: Option<String>,
95+
chang_desc: Option<String>,
96+
node_datadir: Option<PathBuf>,
97+
}
98+
99+
impl BdkCli {
100+
/// Construct a new [`BdkCli`] struct
101+
fn new(
102+
network: &str,
103+
node_datadir: Option<PathBuf>,
104+
verbosity: bool,
105+
features: &[&str],
106+
) -> Result<Self, IntTestError> {
107+
// Build bdk-cli with given features
108+
let mut feat = "--features=".to_string();
109+
for item in features {
110+
feat.push_str(item);
111+
feat.push_str(",");
112+
}
113+
feat.pop(); // remove the last comma
114+
let _build = Command::new("cargo").args(&["build", &feat]).output()?;
115+
116+
let mut bdk_cli = Self {
117+
target: "./target/debug/bdk-cli".to_string(),
118+
network: network.to_string(),
119+
verbosity,
120+
recv_desc: None,
121+
chang_desc: None,
122+
node_datadir,
123+
};
124+
125+
println!("BDK-CLI Config : {:#?}", bdk_cli);
126+
let bdk_master_key = bdk_cli.key_exec(&["generate"])?;
127+
let bdk_xprv = get_value(&bdk_master_key, "xprv")?;
128+
129+
let bdk_recv_desc =
130+
bdk_cli.key_exec(&["derive", "--path", "m/84h/1h/0h/0", "--xprv", &bdk_xprv])?;
131+
let bdk_recv_desc = get_value(&bdk_recv_desc, "xprv")?;
132+
let bdk_recv_desc = format!("wpkh({})", bdk_recv_desc);
133+
134+
let bdk_chng_desc =
135+
bdk_cli.key_exec(&["derive", "--path", "m/84h/1h/0h/1", "--xprv", &bdk_xprv])?;
136+
let bdk_chng_desc = get_value(&bdk_chng_desc, "xprv")?;
137+
let bdk_chng_desc = format!("wpkh({})", bdk_chng_desc);
138+
139+
bdk_cli.recv_desc = Some(bdk_recv_desc);
140+
bdk_cli.chang_desc = Some(bdk_chng_desc);
141+
142+
Ok(bdk_cli)
143+
}
144+
145+
/// Execute bdk-cli wallet commands with given args
146+
fn wallet_exec(&self, args: &[&str]) -> Result<Value, IntTestError> {
147+
// Check if data directory is specified
148+
let mut wallet_args = if let Some(datadir) = &self.node_datadir {
149+
let datadir = datadir.as_os_str().to_str().unwrap();
150+
["--network", &self.network, "--datadir", datadir, "wallet"].to_vec()
151+
} else {
152+
["--network", &self.network, "wallet"].to_vec()
153+
};
154+
155+
if self.verbosity {
156+
wallet_args.push("-v");
157+
}
158+
159+
wallet_args.push("-d");
160+
wallet_args.push(self.recv_desc.as_ref().unwrap());
161+
wallet_args.push("-c");
162+
wallet_args.push(&self.chang_desc.as_ref().unwrap());
163+
164+
for arg in args {
165+
wallet_args.push(arg);
166+
}
167+
run_cmd_with_args(&self.target, &wallet_args)
168+
}
169+
170+
/// Execute bdk-cli key commands with given args
171+
fn key_exec(&self, args: &[&str]) -> Result<Value, IntTestError> {
172+
let mut key_args = ["key"].to_vec();
173+
for arg in args {
174+
key_args.push(arg);
175+
}
176+
run_cmd_with_args(&self.target, &key_args)
177+
}
178+
179+
/// Execute bdk-cli node command
180+
fn node_exec(&self, args: &[&str]) -> Result<Value, IntTestError> {
181+
// Check if data directory is specified
182+
let mut node_args = if let Some(datadir) = &self.node_datadir {
183+
let datadir = datadir.as_os_str().to_str().unwrap();
184+
["--network", &self.network, "--datadir", datadir, "node"].to_vec()
185+
} else {
186+
["--network", &self.network, "node"].to_vec()
187+
};
188+
189+
for arg in args {
190+
node_args.push(arg);
191+
}
192+
run_cmd_with_args(&self.target, &node_args)
193+
}
194+
}
195+
196+
// Run A Basic wallet operation test, with given feature
197+
#[cfg(test)]
198+
fn basic_wallet_ops(feature: &str) {
199+
// Create a temporary directory for testing env
200+
let mut test_dir = std::env::current_dir().unwrap();
201+
test_dir.push("bdk-testing");
202+
203+
let test_temp_dir = TempDir::new().unwrap();
204+
let test_dir = test_temp_dir.into_path().to_path_buf();
205+
206+
// Create bdk-cli instance
207+
let bdk_cli = BdkCli::new("regtest", Some(test_dir), false, &[feature]).unwrap();
208+
209+
// Generate 101 blocks
210+
bdk_cli.node_exec(&["generate", "101"]).unwrap();
211+
212+
// Get a bdk address
213+
let bdk_addr_json = bdk_cli.wallet_exec(&["get_new_address"]).unwrap();
214+
let bdk_addr = get_value(&bdk_addr_json, "address").unwrap();
215+
216+
// Send coins from core to bdk
217+
bdk_cli
218+
.node_exec(&["sendtoaddress", &bdk_addr, "1000000000"])
219+
.unwrap();
220+
221+
bdk_cli.node_exec(&["generate", "1"]).unwrap();
222+
223+
// Sync the bdk wallet
224+
bdk_cli.wallet_exec(&["sync"]).unwrap();
225+
226+
// Get the balance
227+
let balance_json = bdk_cli.wallet_exec(&["get_balance"]).unwrap();
228+
let balance = get_value(&balance_json, "satoshi").unwrap();
229+
assert_eq!(balance, "1000000000");
230+
}
231+
232+
#[test]
233+
#[cfg(feature = "regtest-bitcoin")]
234+
fn test_basic_wallet_op_bitcoind() {
235+
basic_wallet_ops("regtest-bitcoin")
236+
}
237+
238+
#[test]
239+
#[cfg(feature = "regtest-electrum")]
240+
fn test_basic_wallet_op_electrum() {
241+
basic_wallet_ops("regtest-electrum")
242+
}
243+
}

0 commit comments

Comments
 (0)