Skip to content

Commit 37f5111

Browse files
committed
common: Add CLI proptests
1 parent de64847 commit 37f5111

File tree

5 files changed

+170
-14
lines changed

5 files changed

+170
-14
lines changed

common/Cargo.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,14 @@ tokio = { version = "=1.15.0", default-features = false, features = [
115115
# lower-level async TLS interface
116116
tokio-rustls = { version = "0.23", default-features = false }
117117

118+
[target.'cfg(not(target_env = "sgx"))'.dev-dependencies]
119+
# Include additional useful proptest features when testing outside of SGX
120+
proptest = "1"
121+
118122
[target.'cfg(target_env = "sgx")'.dev-dependencies]
119123
# Print backtraces in tests
120124
sgx-panic-backtrace = "0.1"
121125

122-
[target.'cfg(not(target_env = "sgx"))'.dev-dependencies]
123-
# Add some useful proptest features when testing during development
124-
proptest = "1"
125-
126126
[package.metadata.fortanix-sgx]
127127
# stack size (in bytes) for each thread, the default stack size is 0x20000.
128128
stack-size=0x200_000
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Seeds for failure cases proptest has generated in the past. It is
2+
# automatically read and these particular cases re-run before any
3+
# novel cases are generated.
4+
#
5+
# It is recommended to check this file in to source control so that
6+
# everyone who runs the test benefits from these saved cases.
7+
cc 9b6b18b685533029c7a61f31835bda632ef3ab83b477df1732d9b8a63991873e # shrinks to path_str = "", cmd = Run(RunArgs { user_pk: UserPk([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), bitcoind_rpc: BitcoindRpcInfo { username: "0", password: "A", host: "0.0.0.0", port: 0 }, owner_port: None, host_port: None, peer_port: None, network: Network(Bitcoin), shutdown_after_sync_if_no_activity: false, inactivity_timer_sec: 0, repl: true, backend_url: "", runner_url: "", mock: false })

common/src/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub mod qs;
1212
pub mod runner;
1313
pub mod vfs;
1414

15+
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
1516
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
1617
pub struct UserPk(#[serde(with = "hexstr_or_bytes")] [u8; 32]);
1718

common/src/cli.rs

Lines changed: 157 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
use std::fmt::{self, Display};
2+
#[cfg(test)]
3+
use std::net::Ipv4Addr;
24
use std::path::Path;
35
use std::process::Command;
46
use std::str::FromStr;
57

68
use anyhow::{anyhow, ensure};
79
use argh::FromArgs;
810
use lightning_invoice::Currency;
11+
#[cfg(test)]
12+
use proptest::arbitrary::{any, Arbitrary};
13+
#[cfg(test)]
14+
use proptest::strategy::{BoxedStrategy, Just, Strategy};
15+
#[cfg(test)]
16+
use proptest_derive::Arbitrary;
917

1018
use crate::api::runner::Port;
1119
use crate::api::UserPk;
@@ -75,7 +83,22 @@ impl NodeCommand {
7583
}
7684
}
7785

86+
#[cfg(test)]
87+
impl Arbitrary for NodeCommand {
88+
type Parameters = ();
89+
type Strategy = BoxedStrategy<Self>;
90+
91+
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
92+
proptest::prop_oneof! {
93+
any::<RunArgs>().prop_map(Self::Run),
94+
any::<ProvisionArgs>().prop_map(Self::Provision),
95+
}
96+
.boxed()
97+
}
98+
}
99+
78100
/// Run the Lexe node
101+
#[cfg_attr(test, derive(Arbitrary))]
79102
#[derive(Clone, Debug, PartialEq, Eq, FromArgs)]
80103
#[argh(subcommand, name = "run")]
81104
pub struct RunArgs {
@@ -164,10 +187,10 @@ impl RunArgs {
164187
pub fn to_cmd(&self, bin_path: &Path) -> Command {
165188
let mut cmd = Command::new(bin_path);
166189
cmd.arg("run")
167-
.arg("--bitcoind-rpc")
168-
.arg(&self.bitcoind_rpc.to_string())
169190
.arg("--user-pk")
170191
.arg(&self.user_pk.to_string())
192+
.arg("--bitcoind-rpc")
193+
.arg(&self.bitcoind_rpc.to_string())
171194
.arg("-i")
172195
.arg(&self.inactivity_timer_sec.to_string())
173196
.arg("--network")
@@ -180,6 +203,12 @@ impl RunArgs {
180203
if self.shutdown_after_sync_if_no_activity {
181204
cmd.arg("-s");
182205
}
206+
if self.mock {
207+
cmd.arg("--mock");
208+
}
209+
if self.repl {
210+
cmd.arg("--repl");
211+
}
183212
if let Some(owner_port) = self.owner_port {
184213
cmd.arg("--owner-port").arg(&owner_port.to_string());
185214
}
@@ -189,12 +218,12 @@ impl RunArgs {
189218
if let Some(peer_port) = self.peer_port {
190219
cmd.arg("--peer-port").arg(&peer_port.to_string());
191220
}
192-
193221
cmd
194222
}
195223
}
196224

197225
/// Provision a new Lexe node for a user
226+
#[cfg_attr(test, derive(Arbitrary))]
198227
#[derive(Clone, Debug, PartialEq, Eq, FromArgs)]
199228
#[argh(subcommand, name = "provision")]
200229
pub struct ProvisionArgs {
@@ -247,21 +276,19 @@ impl ProvisionArgs {
247276
pub fn to_cmd(&self, bin_path: &Path) -> Command {
248277
let mut cmd = Command::new(bin_path);
249278
cmd.arg("provision")
250-
.arg("--machine-id")
251-
.arg(&self.machine_id.to_string())
252279
.arg("--user-pk")
253280
.arg(&self.user_pk.to_string())
281+
.arg("--machine-id")
282+
.arg(&self.machine_id.to_string())
254283
.arg("--node-dns-name")
255284
.arg(&self.node_dns_name)
256285
.arg("--backend-url")
257286
.arg(&self.backend_url)
258287
.arg("--runner-url")
259288
.arg(&self.runner_url);
260-
261289
if let Some(port) = self.port {
262290
cmd.arg("--port").arg(&port.to_string());
263291
}
264-
265292
cmd
266293
}
267294
}
@@ -289,7 +316,6 @@ impl Default for BitcoindRpcInfo {
289316
impl BitcoindRpcInfo {
290317
fn parse_str(s: &str) -> Option<Self> {
291318
// format: <username>:<password>@<host>:<port>
292-
293319
let mut parts = s.split(':');
294320
let (username, pass_host, port) =
295321
match (parts.next(), parts.next(), parts.next(), parts.next()) {
@@ -335,6 +361,30 @@ impl Display for BitcoindRpcInfo {
335361
}
336362
}
337363

364+
#[cfg(test)]
365+
impl Arbitrary for BitcoindRpcInfo {
366+
type Parameters = ();
367+
type Strategy = BoxedStrategy<Self>;
368+
369+
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
370+
(
371+
// + denotes "at least 1"
372+
"[A-Za-z0-9]+",
373+
"[A-Za-z0-9]+",
374+
// NOTE: bitcoind-rpc parsing currently only supports ipv4
375+
any::<Ipv4Addr>().prop_map(|x| x.to_string()),
376+
any::<Port>(),
377+
)
378+
.prop_map(|(username, password, host, port)| Self {
379+
username,
380+
password,
381+
host,
382+
port,
383+
})
384+
.boxed()
385+
}
386+
}
387+
338388
/// There are slight variations is how the network is represented as strings
339389
/// across bitcoind rpc calls, lightning, etc. For consistency, we use the
340390
/// mapping defined in bitcoin::Network's FromStr impl, which is:
@@ -416,8 +466,27 @@ impl From<Network> for Currency {
416466
}
417467
}
418468

469+
#[cfg(test)]
470+
impl Arbitrary for Network {
471+
type Parameters = ();
472+
type Strategy = BoxedStrategy<Self>;
473+
474+
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
475+
proptest::prop_oneof! {
476+
// TODO: Mainnet is disabled for now
477+
// Just(Network(bitcoin::Network::Bitcoin)),
478+
Just(Network(bitcoin::Network::Testnet)),
479+
Just(Network(bitcoin::Network::Regtest)),
480+
Just(Network(bitcoin::Network::Signet)),
481+
}
482+
.boxed()
483+
}
484+
}
485+
419486
#[cfg(test)]
420487
mod test {
488+
use proptest::proptest;
489+
421490
use super::*;
422491

423492
#[test]
@@ -435,8 +504,7 @@ mod test {
435504

436505
#[test]
437506
fn test_network_roundtrip() {
438-
// Mainnet is disabled for now
439-
507+
// TODO: Mainnet is disabled for now
440508
// let mainnet1 = Network(bitcoin::Network::Bitcoin);
441509
let testnet1 = Network(bitcoin::Network::Testnet);
442510
let regtest1 = Network(bitcoin::Network::Regtest);
@@ -452,4 +520,83 @@ mod test {
452520
assert_eq!(regtest1, regtest2);
453521
assert_eq!(signet1, signet2);
454522
}
523+
524+
proptest! {
525+
#[test]
526+
fn proptest_cmd_roundtrip(
527+
path_str in ".*",
528+
cmd in any::<NodeCommand>(),
529+
) {
530+
do_cmd_roundtrip(path_str, &cmd);
531+
}
532+
}
533+
534+
fn do_cmd_roundtrip(path_str: String, cmd1: &NodeCommand) {
535+
let path = Path::new(&path_str);
536+
// Convert to std::process::Command
537+
let std_cmd = cmd1.to_cmd(&path);
538+
// Convert to an iterator over &str args
539+
let mut args_iter = std_cmd.get_args().filter_map(|s| s.to_str());
540+
// Pop the first arg which contains the subcommand name e.g. 'run'
541+
let subcommand = args_iter.next().unwrap();
542+
// Collect the remaining args into a vec
543+
let cmd_args: Vec<&str> = args_iter.collect();
544+
dbg!(&cmd_args);
545+
// Deserialize back into struct
546+
let cmd2 = NodeCommand::from_args(&[&subcommand], &cmd_args).unwrap();
547+
// Assert
548+
assert_eq!(*cmd1, cmd2);
549+
}
550+
551+
#[test]
552+
fn test_cmd_regressions() {
553+
use bitcoin::Network::Testnet;
554+
use NodeCommand::*;
555+
556+
// --mock was needed
557+
let path_str = String::from(".");
558+
let cmd = Run(RunArgs {
559+
user_pk: UserPk::from_i64(0),
560+
bitcoind_rpc: BitcoindRpcInfo {
561+
username: "0".into(),
562+
password: "a".into(),
563+
host: "0.0.0.0".into(),
564+
port: 0,
565+
},
566+
owner_port: None,
567+
host_port: None,
568+
peer_port: None,
569+
network: Network(Testnet),
570+
shutdown_after_sync_if_no_activity: false,
571+
inactivity_timer_sec: 0,
572+
repl: false,
573+
backend_url: "".into(),
574+
runner_url: "".into(),
575+
mock: true,
576+
});
577+
do_cmd_roundtrip(path_str, &cmd);
578+
579+
// --repl was needed
580+
let path_str = String::from(".");
581+
let cmd = Run(RunArgs {
582+
user_pk: UserPk::from_i64(0),
583+
bitcoind_rpc: BitcoindRpcInfo {
584+
username: "0".into(),
585+
password: "A".into(),
586+
host: "0.0.0.0".into(),
587+
port: 0,
588+
},
589+
owner_port: None,
590+
host_port: None,
591+
peer_port: None,
592+
network: Network(Testnet),
593+
shutdown_after_sync_if_no_activity: false,
594+
inactivity_timer_sec: 0,
595+
repl: true,
596+
backend_url: "".into(),
597+
runner_url: "".into(),
598+
mock: false,
599+
});
600+
do_cmd_roundtrip(path_str, &cmd);
601+
}
455602
}

common/src/enclave/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ impl fmt::Debug for Measurement {
122122
///
123123
/// [CPUSVN]: https://phlip9.com/notes/confidential%20computing/intel%20SGX/SGX%20lingo/#security-version-number-svn
124124
/// [`OWNER_EPOCH`]: https://phlip9.com/notes/confidential%20computing/intel%20SGX/SGX%20lingo/#owner-epoch
125+
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
125126
#[derive(Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
126127
pub struct MachineId(#[serde(with = "hexstr_or_bytes")] [u8; 16]);
127128

0 commit comments

Comments
 (0)