11use std:: fmt:: { self , Display } ;
2+ #[ cfg( test) ]
3+ use std:: net:: Ipv4Addr ;
24use std:: path:: Path ;
35use std:: process:: Command ;
46use std:: str:: FromStr ;
57
68use anyhow:: { anyhow, ensure} ;
79use argh:: FromArgs ;
810use 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
1018use crate :: api:: runner:: Port ;
1119use 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" ) ]
81104pub 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" ) ]
200229pub 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 {
289316impl 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) ]
420487mod 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}
0 commit comments