Skip to content

Commit 5e3cb06

Browse files
Add integration test framework
1 parent c20eb5a commit 5e3cb06

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed

tests/integration.rs

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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+
use serde_json::{json, Value};
15+
use std::convert::From;
16+
use std::process::Command;
17+
18+
/// Testing errors for integration tests
19+
#[derive(Debug)]
20+
enum IntTestError {
21+
// IO error
22+
IO(std::io::Error),
23+
// Command execution error
24+
CmdExec(String),
25+
// Json Data error
26+
JsonData(String),
27+
}
28+
29+
impl From<std::io::Error> for IntTestError {
30+
fn from(e: std::io::Error) -> Self {
31+
IntTestError::IO(e)
32+
}
33+
}
34+
35+
// Helper function
36+
// Runs a system command with given args
37+
fn run_cmd_with_args(cmd: &str, args: &[&str]) -> Result<serde_json::Value, IntTestError> {
38+
let output = Command::new(cmd).args(args).output().unwrap();
39+
let mut value = output.stdout;
40+
let error = output.stderr;
41+
if value.len() == 0 {
42+
return Err(IntTestError::CmdExec(String::from_utf8(error).unwrap()));
43+
}
44+
value.pop(); // remove `\n` at end
45+
let output_string = std::str::from_utf8(&value).unwrap();
46+
let json_value: serde_json::Value = match serde_json::from_str(output_string) {
47+
Ok(value) => value,
48+
Err(_) => json!(output_string), // bitcoin-cli will sometime return raw string
49+
};
50+
Ok(json_value)
51+
}
52+
53+
// Helper Function
54+
// Transforms a json value to string
55+
fn value_to_string(value: &Value) -> Result<String, IntTestError> {
56+
match value {
57+
Value::Bool(bool) => match bool {
58+
true => Ok("true".to_string()),
59+
false => Ok("false".to_string()),
60+
},
61+
Value::Number(n) => Ok(n.to_string()),
62+
Value::String(s) => Ok(s.to_string()),
63+
_ => Err(IntTestError::JsonData(
64+
"Value parsing not implemented for this type".to_string(),
65+
)),
66+
}
67+
}
68+
69+
// Helper Function
70+
// Extracts value from a given json object and key
71+
fn get_value(json: &Value, key: &str) -> Result<String, IntTestError> {
72+
let map = json
73+
.as_object()
74+
.ok_or(IntTestError::JsonData("Json is not an object".to_string()))?;
75+
let value = map
76+
.get(key)
77+
.ok_or(IntTestError::JsonData("Invalid key".to_string()))?
78+
.to_owned();
79+
let string_value = value_to_string(&value)?;
80+
Ok(string_value)
81+
}
82+
83+
/// The Bitcoin Cli process structure
84+
/// Use this to spawn and manage the bitcoin regtest backend
85+
struct BitcoinCli {
86+
/// bitcoin-cli execution target
87+
target: String,
88+
/// bitcoin-cli test wallet name
89+
wallet: String,
90+
}
91+
92+
impl BitcoinCli {
93+
/// Create a new [`BitcoinCli`] struct
94+
fn new(target: Option<&str>, wallet: Option<&str>) -> Self {
95+
let target = target.unwrap_or("bitcoin-cli").to_owned();
96+
let wallet = wallet.unwrap_or("test").to_owned();
97+
Command::new(&target)
98+
.args(&["createwallet", &wallet])
99+
.output()
100+
.unwrap();
101+
Command::new(&target)
102+
.args(&["loadwallet", &wallet])
103+
.output()
104+
.unwrap();
105+
106+
Self { target, wallet }
107+
}
108+
109+
/// Execute a bitcoin-cli command with given args
110+
fn exec(&mut self, args: &[&str]) -> Result<Value, IntTestError> {
111+
let cli_wallet = format!("-rpcwallet={}", &self.wallet);
112+
let mut cli_args = [cli_wallet.as_str()].to_vec();
113+
for arg in args {
114+
cli_args.push(arg);
115+
}
116+
run_cmd_with_args(&self.target, &cli_args)
117+
}
118+
}
119+
120+
/// The bdk-cli command struct
121+
/// Use it to perform all bdk-cli operations
122+
struct BdkCli {
123+
target: String,
124+
network: String,
125+
verbosity: bool,
126+
recv_desc: Option<String>,
127+
chang_desc: Option<String>,
128+
}
129+
130+
impl BdkCli {
131+
/// Construct a new [`BdkCli`] struct
132+
fn new(network: &str, verbosity: bool, features: &[&str]) -> Result<Self, IntTestError> {
133+
// Build bdk-cli with given features
134+
let mut feat = "--features=".to_string();
135+
for item in features {
136+
feat.push_str(item);
137+
feat.push_str(",");
138+
}
139+
feat.pop(); // remove the last comma
140+
let _build = Command::new("cargo").args(&["build", &feat]).output()?;
141+
142+
let mut bdk_cli = Self {
143+
target: "./target/debug/bdk-cli".to_string(),
144+
network: network.to_string(),
145+
verbosity,
146+
recv_desc: None,
147+
chang_desc: None,
148+
};
149+
150+
let bdk_master_key = bdk_cli.key_exec(&["generate"])?;
151+
let bdk_xprv = get_value(&bdk_master_key, "xprv")?;
152+
153+
let bdk_recv_desc =
154+
bdk_cli.key_exec(&["derive", "--path", "m/84h/1h/0h/0", "--xprv", &bdk_xprv])?;
155+
let bdk_recv_desc = get_value(&bdk_recv_desc, "xprv")?;
156+
let bdk_recv_desc = format!("wpkh({})", bdk_recv_desc);
157+
158+
let bdk_chng_desc =
159+
bdk_cli.key_exec(&["derive", "--path", "m/84h/1h/0h/1", "--xprv", &bdk_xprv])?;
160+
let bdk_chng_desc = get_value(&bdk_chng_desc, "xprv")?;
161+
let bdk_chng_desc = format!("wpkh({})", bdk_chng_desc);
162+
163+
bdk_cli.recv_desc = Some(bdk_recv_desc);
164+
bdk_cli.chang_desc = Some(bdk_chng_desc);
165+
166+
Ok(bdk_cli)
167+
}
168+
169+
/// Execute bdk-cli wallet commands with given args
170+
fn wallet_exec(&mut self, args: &[&str]) -> Result<Value, IntTestError> {
171+
let mut wallet_args = ["--network", &self.network, "wallet"].to_vec();
172+
if self.verbosity {
173+
wallet_args.push("-v");
174+
}
175+
176+
wallet_args.push("-d");
177+
wallet_args.push(self.recv_desc.as_ref().unwrap());
178+
wallet_args.push("-c");
179+
wallet_args.push(&self.chang_desc.as_ref().unwrap());
180+
181+
for arg in args {
182+
wallet_args.push(arg);
183+
}
184+
run_cmd_with_args(&self.target, &wallet_args)
185+
}
186+
187+
/// Execute bdk-cli key commands with given args
188+
fn key_exec(&mut self, args: &[&str]) -> Result<Value, IntTestError> {
189+
let mut key_args = ["key"].to_vec();
190+
for arg in args {
191+
key_args.push(arg);
192+
}
193+
run_cmd_with_args(&self.target, &key_args)
194+
}
195+
}
196+
197+
#[test]
198+
fn test_basic() {
199+
// Test basic building, fmt and unit tests
200+
let features = [
201+
"default",
202+
"electrum",
203+
"esplora-ureq",
204+
"esplora-reqwest",
205+
"compiler",
206+
"compact_filters",
207+
"reserves",
208+
"reserves,electrum",
209+
"reserves,esplora-ureq",
210+
"reserves,compact_filters",
211+
"rpc",
212+
];
213+
214+
// Test for build and fmt
215+
for feature in features {
216+
Command::new("cargo")
217+
.args(["build", "--features", feature, "--locked"])
218+
.output()
219+
.unwrap();
220+
Command::new("cargo")
221+
.args(["fmt", "--features", feature, "--locked"])
222+
.output()
223+
.unwrap();
224+
println!("Building with {} feature completed", feature);
225+
}
226+
}
227+
228+
#[test]
229+
fn test_wallet_ops() {
230+
// Create a bitcoin-cli instance
231+
let mut bitcoin_cli = BitcoinCli::new(None, None);
232+
233+
// Get a address from core
234+
let core_addr_json = bitcoin_cli.exec(&["getnewaddress"]).unwrap();
235+
let core_addr = value_to_string(&core_addr_json).unwrap();
236+
237+
// Generate few blocks to sync the chain
238+
bitcoin_cli
239+
.exec(&["generatetoaddress", "101", &core_addr])
240+
.unwrap();
241+
242+
// Create bdk-cli instance
243+
let mut bdk_cli = BdkCli::new("regtest", false, &["rpc"]).unwrap();
244+
245+
// Get a bdk address
246+
let bdk_addr_json = bdk_cli.wallet_exec(&["get_new_address"]).unwrap();
247+
let bdk_addr = get_value(&bdk_addr_json, "address").unwrap();
248+
249+
// Send coins from core to bdk
250+
bitcoin_cli
251+
.exec(&["sendtoaddress", &bdk_addr, "10"])
252+
.unwrap();
253+
bitcoin_cli
254+
.exec(&["generatetoaddress", "1", &core_addr])
255+
.unwrap();
256+
257+
// Sync the bdk wallet
258+
bdk_cli.wallet_exec(&["sync"]).unwrap();
259+
260+
// Get the balance
261+
let balance_json = bdk_cli.wallet_exec(&["get_balance"]).unwrap();
262+
let balance = get_value(&balance_json, "satoshi").unwrap();
263+
assert_eq!(balance, "1000000000");
264+
}

0 commit comments

Comments
 (0)