diff --git a/Cargo.lock b/Cargo.lock index 4fb67215546..94bc21f2d72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3875,6 +3875,7 @@ name = "gcli" version = "1.0.3" dependencies = [ "anyhow", + "async-trait", "base64 0.21.5", "clap 4.4.10", "color-eyre", diff --git a/gcli/Cargo.toml b/gcli/Cargo.toml index 13cd71cc71e..7dd19f3665c 100644 --- a/gcli/Cargo.toml +++ b/gcli/Cargo.toml @@ -11,13 +11,10 @@ license.workspace = true homepage.workspace = true repository.workspace = true -[[bin]] -path = "bin/gcli.rs" -name = "gcli" - [dependencies] gsdk.workspace = true anyhow.workspace = true +async-trait.workspace = true base64.workspace = true color-eyre.workspace = true dirs.workspace = true diff --git a/gcli/examples/mycli.rs b/gcli/examples/mycli.rs new file mode 100644 index 00000000000..9b049342ae2 --- /dev/null +++ b/gcli/examples/mycli.rs @@ -0,0 +1,54 @@ +// This file is part of Gear. +// +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use gcli::{async_trait, App, Command, Parser}; + +/// My customized sub commands. +#[derive(Debug, Parser)] +pub enum SubCommand { + /// GCli preset commands. + #[clap(flatten)] + GCliCommands(Command), + /// My customized ping command. + Ping, +} + +/// My customized gcli. +#[derive(Debug, Parser)] +pub struct MyGCli { + #[clap(subcommand)] + command: SubCommand, +} + +#[async_trait] +impl App for MyGCli { + async fn exec(&self) -> anyhow::Result<()> { + match &self.command { + SubCommand::GCliCommands(command) => command.exec(self).await, + SubCommand::Ping => { + println!("pong"); + Ok(()) + } + } + } +} + +#[tokio::main] +async fn main() -> color_eyre::Result<()> { + MyGCli::parse().run().await +} diff --git a/gcli/src/app.rs b/gcli/src/app.rs new file mode 100644 index 00000000000..0946db8cff2 --- /dev/null +++ b/gcli/src/app.rs @@ -0,0 +1,135 @@ +// This file is part of Gear. +// +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//! Command line application abstraction + +use crate::keystore; +use clap::Parser; +use color_eyre::{eyre::eyre, Result}; +use env_logger::{Builder, Env}; +use gsdk::{signer::Signer, Api}; + +/// Command line gear program application abstraction. +/// +/// ```ignore +/// use gcli::{async_trait, App, Command, Parser}; +/// +/// /// My customized sub commands. +/// #[derive(Debug, Parser)] +/// pub enum SubCommand { +/// /// GCli preset commands. +/// #[clap(flatten)] +/// GCliCommands(Command), +/// /// My customized ping command. +/// Ping, +/// } +/// +/// /// My customized gcli. +/// #[derive(Debug, Parser)] +/// pub struct MyGCli { +/// #[clap(subcommand)] +/// command: SubCommand, +/// } +/// +/// #[async_trait] +/// impl App for MyGCli { +/// async fn exec(&self) -> anyhow::Result<()> { +/// match &self.command { +/// SubCommand::GCliCommands(command) => command.exec(self).await, +/// SubCommand::Ping => { +/// println!("pong"); +/// Ok(()) +/// } +/// } +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() -> color_eyre::Result<()> { +/// MyGCli::parse().run().await +/// } +/// ``` +#[async_trait::async_trait] +pub trait App: Parser + Sync { + /// Timeout of rpc requests. + fn timeout(&self) -> u64 { + 60000 + } + + /// The verbosity logging level. + fn verbose(&self) -> u16 { + 0 + } + + /// The endpoint of the gear node. + fn endpoint(&self) -> Option { + None + } + + /// Password of the signer account. + fn passwd(&self) -> Option { + None + } + + /// Exec program from the parsed arguments. + async fn exec(&self) -> anyhow::Result<()>; + + /// Get signer. + async fn signer(&self) -> anyhow::Result { + let endpoint = self.endpoint().clone(); + let timeout = self.timeout(); + let passwd = self.passwd(); + + let api = Api::new_with_timeout(endpoint.as_deref(), Some(timeout)).await?; + let pair = if let Ok(s) = keystore::cache(passwd.as_deref()) { + s + } else { + keystore::keyring(passwd.as_deref())? + }; + + Ok((api, pair).into()) + } + + /// Run application. + /// + /// This is a wrapper of [`Self::exec`] with preset retry + /// and verbose level. + async fn run(&self) -> Result<()> { + color_eyre::install()?; + sp_core::crypto::set_default_ss58_version(crate::VARA_SS58_PREFIX.into()); + + let name = Self::command().get_name().to_string(); + let filter = match self.verbose() { + 0 => format!("{name}=info,gsdk=info"), + 1 => format!("{name}=debug,gsdk=debug"), + 2 => "debug".into(), + _ => "trace".into(), + }; + + let mut builder = Builder::from_env(Env::default().default_filter_or(filter)); + builder + .format_target(false) + .format_module_path(false) + .format_timestamp(None); + builder.try_init()?; + + self.exec() + .await + .map_err(|e| eyre!("Failed to run app, {e}")) + } +} diff --git a/gcli/bin/gcli.rs b/gcli/src/bin/gcli.rs similarity index 73% rename from gcli/bin/gcli.rs rename to gcli/src/bin/gcli.rs index 178aa739dea..1ed0e8eefb0 100644 --- a/gcli/bin/gcli.rs +++ b/gcli/src/bin/gcli.rs @@ -16,18 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! gear command entry -use color_eyre::eyre::Result; +use gcli::{cmd::Opt, App, Parser}; #[tokio::main] -async fn main() -> Result<()> { - color_eyre::install()?; - - sp_core::crypto::set_default_ss58_version(gcli::VARA_SS58_PREFIX.into()); - - if let Err(e) = gcli::cmd::Opt::run().await { - log::error!("{}", e); - } - - Ok(()) +async fn main() -> color_eyre::Result<()> { + Opt::parse().run().await } diff --git a/gcli/src/cmd/key.rs b/gcli/src/cmd/key.rs index 1a5f85d0ac1..68c1bf3bc00 100644 --- a/gcli/src/cmd/key.rs +++ b/gcli/src/cmd/key.rs @@ -96,6 +96,9 @@ pub struct Key { /// Key actions #[command(subcommand)] action: Action, + /// Passphrase override + #[arg(short, long)] + passwd: Option, } macro_rules! match_scheme { @@ -121,15 +124,15 @@ impl Key { /// # NOTE /// /// Reserved the `passwd` for getting suri from cache. - pub fn exec(&self, passwd: Option<&str>) -> Result<()> { + pub fn exec(&self) -> Result<()> { match &self.action { - Action::Generate => self.generate(passwd)?, + Action::Generate => self.generate()?, #[cfg(feature = "node-key")] Action::GenerateNodeKey => Self::generate_node_key(), - Action::Inspect { suri } => self.inspect(suri, passwd)?, + Action::Inspect { suri } => self.inspect(suri)?, #[cfg(feature = "node-key")] Action::InspectNodeKey { secret } => Self::inspect_node_key(secret)?, - Action::Sign { suri, message } => self.sign(suri, message, passwd)?, + Action::Sign { suri, message } => self.sign(suri, message)?, Action::Verify { signature, message, @@ -140,7 +143,8 @@ impl Key { Ok(()) } - fn generate(&self, passwd: Option<&str>) -> Result<()> { + fn generate(&self) -> Result<()> { + let passwd = self.passwd.as_deref(); match_scheme!(self.scheme, generate_with_phrase(passwd), res, { let (pair, phrase, seed) = res; let signer = pair.signer(); @@ -181,9 +185,10 @@ impl Key { println!(" SS58 Address: {}", signer.public().into_account()); } - fn inspect(&self, suri: &str, passwd: Option<&str>) -> Result<()> { + fn inspect(&self, suri: &str) -> Result<()> { let key = KeyT::from_string(suri); let key_ref = &key; + let passwd = self.passwd.as_deref(); match_scheme!(self.scheme, pair(key_ref, passwd), pair, { Self::info(&format!("Secret Key URI `{suri}`"), pair.0.signer(), pair.1) }); @@ -209,9 +214,10 @@ impl Key { Ok(()) } - fn sign(&self, suri: &str, message: &str, passwd: Option<&str>) -> Result<()> { + fn sign(&self, suri: &str, message: &str) -> Result<()> { let key = KeyT::from_string(suri); let key_ref = &key; + let passwd = self.passwd.as_deref(); match_scheme!(self.scheme, pair(key_ref, passwd), pair, { let signer = pair.0.signer(); diff --git a/gcli/src/cmd/mod.rs b/gcli/src/cmd/mod.rs index 6f22dbb1588..29ea7ef4c41 100644 --- a/gcli/src/cmd/mod.rs +++ b/gcli/src/cmd/mod.rs @@ -17,11 +17,8 @@ // along with this program. If not, see . //! commands -use crate::{keystore, result::Result}; +use crate::App; use clap::Parser; -use env_logger::{Builder, Env}; -use gsdk::Api; -use log::LevelFilter; pub mod claim; pub mod create; @@ -36,7 +33,7 @@ pub mod transfer; pub mod update; pub mod upload; -/// Commands of cli `gear` +/// All SubCommands of gear command line interface. #[derive(Debug, Parser)] pub enum Command { Claim(claim::Claim), @@ -54,26 +51,42 @@ pub enum Command { Update(update::Update), } -/// gear command-line tools -/// ___ ___ _ ___ -/// / __| / __| | | |_ _| -/// | (_ | | (__ | |__ | | -/// \___| \___| |____| |___| -/// _|"""""|_|"""""|_|"""""|_|"""""| -/// "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' +impl Command { + /// Execute the command. + pub async fn exec(&self, app: &impl App) -> anyhow::Result<()> { + match self { + Command::Key(key) => key.exec()?, + Command::Login(login) => login.exec()?, + Command::New(new) => new.exec().await?, + Command::Program(program) => program.exec(app).await?, + Command::Update(update) => update.exec().await?, + Command::Claim(claim) => claim.exec(app.signer().await?).await?, + Command::Create(create) => create.exec(app.signer().await?).await?, + Command::Info(info) => info.exec(app.signer().await?).await?, + Command::Send(send) => send.exec(app.signer().await?).await?, + Command::Upload(upload) => upload.exec(app.signer().await?).await?, + Command::Transfer(transfer) => transfer.exec(app.signer().await?).await?, + Command::Reply(reply) => reply.exec(app.signer().await?).await?, + } + + Ok(()) + } +} + +/// Gear command-line interface. #[derive(Debug, Parser)] -#[clap(author, version, verbatim_doc_comment)] +#[clap(author, version)] #[command(name = "gcli")] pub struct Opt { /// Commands. #[command(subcommand)] pub command: Command, - /// How many times we'll retry when RPC requests failed. - #[arg(short, long, default_value = "5")] - pub retry: u16, + /// Timeout for rpc requests. + #[arg(short, long, default_value = "60000")] + pub timeout: u64, /// Enable verbose logs. - #[arg(short, long)] - pub verbose: bool, + #[clap(short, long, action = clap::ArgAction::Count)] + pub verbose: u16, /// Gear node rpc endpoint. #[arg(short, long)] pub endpoint: Option, @@ -82,87 +95,33 @@ pub struct Opt { pub passwd: Option, } -impl Opt { - /// setup logs - fn setup_logs(&self) -> Result<()> { - let mut builder = if self.verbose { - Builder::from_env(Env::default().default_filter_or("gcli=debug")) - } else { - match &self.command { - Command::Claim(_) - | Command::Create(_) - | Command::Reply(_) - | Command::Send(_) - | Command::Upload(_) - | Command::Transfer(_) => { - let mut builder = Builder::from_env(Env::default().default_filter_or("info")); - builder - .format_target(false) - .format_module_path(false) - .format_timestamp(None) - .filter_level(LevelFilter::Info); - - builder - } - _ => Builder::from_default_env(), - } - }; +#[async_trait::async_trait] +impl App for Opt { + fn timeout(&self) -> u64 { + self.timeout + } - builder.try_init()?; - Ok(()) + fn verbose(&self) -> u16 { + self.verbose } - /// run program - pub async fn run() -> Result<()> { - let opt = Opt::parse(); + fn endpoint(&self) -> Option { + self.endpoint.clone() + } - opt.setup_logs()?; - opt.exec().await?; - Ok(()) + fn passwd(&self) -> Option { + self.passwd.clone() } - /// Create api client from endpoint - async fn api(&self) -> Result { - Api::new(self.endpoint.as_deref()).await.map_err(Into::into) + async fn exec(&self) -> anyhow::Result<()> { + self.command.exec(self).await } +} - /// Execute command sync +impl Opt { + /// Run command sync. pub fn exec_sync(&self) -> color_eyre::Result<()> { let rt = tokio::runtime::Runtime::new().unwrap(); - - rt.block_on(self.exec()).map_err(Into::into) - } - - /// Execute command. - pub async fn exec(&self) -> Result<()> { - match &self.command { - Command::Key(key) => key.exec(self.passwd.as_deref())?, - Command::Login(login) => login.exec()?, - Command::New(new) => new.exec().await?, - Command::Program(program) => program.exec(self.api().await?).await?, - Command::Update(update) => update.exec().await?, - sub => { - let api = self.api().await?; - let pair = if let Ok(s) = keystore::cache(self.passwd.as_deref()) { - s - } else { - keystore::keyring(self.passwd.as_deref())? - }; - let signer = (api, pair).into(); - - match sub { - Command::Claim(claim) => claim.exec(signer).await?, - Command::Create(create) => create.exec(signer).await?, - Command::Info(info) => info.exec(signer).await?, - Command::Send(send) => send.exec(signer).await?, - Command::Upload(upload) => upload.exec(signer).await?, - Command::Transfer(transfer) => transfer.exec(signer).await?, - Command::Reply(reply) => reply.exec(signer).await?, - _ => unreachable!("Already matched"), - } - } - } - - Ok(()) + rt.block_on(self.run()).map_err(Into::into) } } diff --git a/gcli/src/cmd/program.rs b/gcli/src/cmd/program.rs index 80db325eb2b..e6c7bdaba78 100644 --- a/gcli/src/cmd/program.rs +++ b/gcli/src/cmd/program.rs @@ -17,13 +17,13 @@ // along with this program. If not, see . //! Command `program`. -use crate::{meta::Meta, result::Result}; +use crate::{meta::Meta, result::Result, App}; use clap::Parser; use gsdk::{ext::sp_core::H256, Api}; use std::{fs, path::PathBuf}; /// Read program state, etc. -#[derive(Clone, Debug, Parser)] +#[derive(Debug, Parser)] pub enum Program { /// Display metadata of the program. /// @@ -61,7 +61,7 @@ pub enum Program { impl Program { /// Run command program. - pub async fn exec(&self, api: Api) -> Result<()> { + pub async fn exec(&self, app: &impl App) -> Result<()> { match self { Program::State { pid, @@ -70,6 +70,7 @@ impl Program { args, at, } => { + let api = app.signer().await?.api().clone(); if let (Some(wasm), Some(method)) = (wasm, method) { // read state from wasm. Self::wasm_state(api, *pid, wasm.to_vec(), method, args.clone(), *at).await?; diff --git a/gcli/src/cmd/upload.rs b/gcli/src/cmd/upload.rs index 34635284f92..e55389efb23 100644 --- a/gcli/src/cmd/upload.rs +++ b/gcli/src/cmd/upload.rs @@ -18,6 +18,7 @@ //! command `upload_program` use crate::{result::Result, utils::Hex}; +use anyhow::anyhow; use clap::Parser; use gsdk::signer::Signer; use std::{fs, path::PathBuf}; @@ -47,7 +48,8 @@ pub struct Upload { impl Upload { /// Exec command submit pub async fn exec(&self, signer: Signer) -> Result<()> { - let code = fs::read(&self.code)?; + let code = + fs::read(&self.code).map_err(|e| anyhow!("program {:?} not found, {e}", &self.code))?; if self.code_only { signer.calls.upload_code(code).await?; return Ok(()); diff --git a/gcli/src/lib.rs b/gcli/src/lib.rs index 569987e692c..364fe1e00a8 100644 --- a/gcli/src/lib.rs +++ b/gcli/src/lib.rs @@ -122,6 +122,7 @@ //! //! GPL v3.0 +mod app; pub mod cmd; pub mod keystore; pub mod meta; @@ -129,5 +130,10 @@ pub mod result; pub mod template; pub mod utils; +pub use self::{app::App, cmd::Command}; +pub use async_trait::async_trait; +pub use clap::Parser; +pub use gsdk::signer::Signer; + /// SS58 prefix for vara network. pub const VARA_SS58_PREFIX: u8 = 137; diff --git a/gcli/tests/cmd/claim.rs b/gcli/tests/cmd/claim.rs index 7d9e57b924d..de82638fc2c 100644 --- a/gcli/tests/cmd/claim.rs +++ b/gcli/tests/cmd/claim.rs @@ -18,7 +18,7 @@ //! Integration tests for command `send` -use crate::common::{self, logs, traits::NodeExec, Args, Result, ALICE_SS58_ADDRESS as ADDRESS}; +use crate::common::{self, logs, node::NodeExec, Args, Result, ALICE_SS58_ADDRESS as ADDRESS}; use gsdk::Api; const REWARD_PER_BLOCK: u128 = 75_000; // 3_000 gas * 25 value per gas @@ -68,9 +68,7 @@ async fn test_command_claim_works() -> Result<()> { let burned_after = signer.api().get_balance(&signer.address()).await? - initial_stash; let after = signer.api().get_balance(ADDRESS).await?; - assert_eq!(initial_balance - before - burned_before, REWARD_PER_BLOCK,); - + assert_eq!(initial_balance - before - burned_before, REWARD_PER_BLOCK); assert_eq!(initial_balance - burned_after, after); - Ok(()) } diff --git a/gcli/tests/cmd/info.rs b/gcli/tests/cmd/info.rs index 90360519a59..83b08aa8996 100644 --- a/gcli/tests/cmd/info.rs +++ b/gcli/tests/cmd/info.rs @@ -19,7 +19,7 @@ //! Integration tests for command `deploy` use crate::common::{ self, logs, - traits::{Convert, NodeExec}, + node::{Convert, NodeExec}, Args, Result, }; diff --git a/gcli/tests/cmd/key.rs b/gcli/tests/cmd/key.rs index afad9b0e186..977d1a19a3a 100644 --- a/gcli/tests/cmd/key.rs +++ b/gcli/tests/cmd/key.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . //! Integration tests for command `key` -use crate::common::{self, traits::Convert, Result}; +use crate::common::{self, node::Convert, Result}; const SIGNATURE_PATT: &str = "Signature:"; const SEED_PATT: &str = "Seed:"; @@ -27,6 +27,10 @@ const PUBLIC_PATT: &str = "Public key:"; fn parse_from<'s>(log: &'s str, patt: &'s str) -> &'s str { let arr = log.split(patt).collect::>(); + if arr.len() != 2 { + panic!("Failed to parse {patt}, log: {log}"); + } + arr[1].split_whitespace().collect::>()[0] } diff --git a/gcli/tests/cmd/program.rs b/gcli/tests/cmd/program.rs index d1e1594afa1..12c32f83ca7 100644 --- a/gcli/tests/cmd/program.rs +++ b/gcli/tests/cmd/program.rs @@ -19,7 +19,7 @@ //! Integration tests for command `program` use crate::common::{ self, env, logs, - traits::{Convert, NodeExec}, + node::{Convert, NodeExec}, Args, Result, }; use demo_new_meta::{MessageInitIn, Wallet}; @@ -100,9 +100,8 @@ fn test_command_program_metadata_works() -> Result<()> { let node = common::dev()?; let meta = env::wasm_bin("demo_new_meta.meta.txt"); let args = Args::new("program").action("meta").meta(meta); - let result = node.run(args)?; + let stdout = node.stdout(args)?; - let stdout = result.stdout.convert(); assert_eq!( stdout.trim(), DEMO_NEW_META_METADATA.trim(), @@ -121,9 +120,7 @@ fn test_command_program_metadata_derive_works() -> Result<()> { .flag("--derive") .derive("Person"); - let result = node.run(args)?; - let stdout = result.stdout.convert(); - + let stdout = node.stdout(args)?; let expected = "Person { surname: String, name: String }"; assert_eq!( stdout.trim(), @@ -158,9 +155,8 @@ fn test_command_program_metawasm_works() -> Result<()> { let node = common::dev()?; let meta = env::wasm_bin("demo_meta_state_v1.meta.wasm"); let args = Args::new("program").action("meta").meta(meta); - let result = node.run(args)?; + let stdout = node.stdout(args)?; - let stdout = result.stdout.convert(); assert_eq!( stdout.trim(), META_WASM_V1_OUTPUT.trim(), @@ -179,8 +175,7 @@ fn test_command_program_metawasm_derive_works() -> Result<()> { .flag("--derive") .derive("Person"); - let result = node.run(args)?; - let stdout = result.stdout.convert(); + let stdout = node.stdout(args)?; let expected = "Person { surname: String, name: String }"; assert_eq!( diff --git a/gcli/tests/cmd/send.rs b/gcli/tests/cmd/send.rs index 83d8fb2020c..f7e3ac2c2da 100644 --- a/gcli/tests/cmd/send.rs +++ b/gcli/tests/cmd/send.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . //! Integration tests for command `send` -use crate::common::{self, traits::NodeExec, Args, Result}; +use crate::common::{self, node::NodeExec, Args, Result}; use gsdk::Api; use scale_info::scale::Encode; diff --git a/gcli/tests/cmd/upload.rs b/gcli/tests/cmd/upload.rs index 3a28c7d20b8..39aa6cdc6ea 100644 --- a/gcli/tests/cmd/upload.rs +++ b/gcli/tests/cmd/upload.rs @@ -19,7 +19,7 @@ //! Integration tests for command `upload` use crate::common::{ self, env, logs, - traits::{Convert, NodeExec}, + node::{Convert, NodeExec}, Args, Result, }; use gear_core::ids::CodeId; @@ -32,9 +32,10 @@ async fn test_command_upload_works() { node.wait_for_log_record(logs::gear_node::IMPORTING_BLOCKS) .expect("node timeout"); - let signer = Api::new(Some(&node.ws())) + let ws = node.ws(); + let signer = Api::new(Some(&ws)) .await - .expect("build api failed") + .unwrap_or_else(|_| panic!("failed to connect to {ws}")) .signer("//Alice", None) .expect("get signer failed"); @@ -53,8 +54,8 @@ async fn test_command_upload_works() { .stderr .convert() .contains(logs::gear_program::EX_UPLOAD_PROGRAM), - "code should be uploaded, but got: {:?}", - output.stderr + "code should be uploaded, but got: {}", + output.stderr.convert(), ); assert!( signer.api().code_storage(code_id).await.is_ok(), diff --git a/gcli/tests/common/mod.rs b/gcli/tests/common/mod.rs index 78494bb3d1b..aa245fe6bbd 100644 --- a/gcli/tests/common/mod.rs +++ b/gcli/tests/common/mod.rs @@ -19,14 +19,16 @@ //! Common utils for integration tests pub use self::{ args::Args, + node::{Convert, NodeExec}, result::{Error, Result}, - traits::{Convert, NodeExec}, }; +use anyhow::anyhow; use gear_core::ids::{CodeId, ProgramId}; use gsdk::{ ext::{sp_core::crypto::Ss58Codec, sp_runtime::AccountId32}, testing::Node, }; +pub use scale_info::scale::Encode; use std::{ iter::IntoIterator, process::{Command, Output}, @@ -35,17 +37,10 @@ use std::{ mod args; pub mod env; pub mod logs; +pub mod node; mod result; -pub mod traits; -mod prelude { - pub use scale_info::scale::Encode; - - pub const ALICE_SS58_ADDRESS: &str = "kGkLEU3e3XXkJp2WK4eNpVmSab5xUNL9QtmLPh8QfCL2EgotW"; -} - -#[cfg(not(feature = "vara-testing"))] -pub use prelude::*; +pub const ALICE_SS58_ADDRESS: &str = "kGkLEU3e3XXkJp2WK4eNpVmSab5xUNL9QtmLPh8QfCL2EgotW"; impl NodeExec for Node { fn ws(&self) -> String { @@ -73,8 +68,9 @@ pub fn gcli(args: impl IntoIterator) -> Result { /// Run the dev node pub fn dev() -> Result { let args = vec!["--tmp", "--dev"]; - - Node::try_from_path(env::bin("gear"), args).map_err(Into::into) + let node = env::bin("gear"); + Node::try_from_path(&node, args) + .map_err(|e| anyhow!("gear-node {} not found, {e}", node).into()) } /// Init env logger diff --git a/gcli/tests/common/traits.rs b/gcli/tests/common/node.rs similarity index 81% rename from gcli/tests/common/traits.rs rename to gcli/tests/common/node.rs index 797dc11569c..9ff5469b429 100644 --- a/gcli/tests/common/traits.rs +++ b/gcli/tests/common/node.rs @@ -16,9 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Shared traits. +//! Shared node. use crate::common::{Args, Output, Result}; +use anyhow::anyhow; /// Convert self into `String`. pub trait Convert { @@ -48,6 +49,18 @@ pub trait NodeExec { /// ``` fn run(&self, args: Args) -> Result; + /// Execute command gcli with Node instance + /// and return stdout. + fn stdout(&self, args: Args) -> Result { + let output = self.run(args)?; + + if output.stdout.is_empty() { + return Err(anyhow!("stdout is empty, stderr: {}", output.stderr.convert()).into()); + } + + Ok(output.stdout.convert()) + } + /// Formats websocket address to string. /// /// This interface is used for constructing the `endpoint` diff --git a/gsdk/src/client.rs b/gsdk/src/client.rs index 0a416da914b..71c2cd95128 100644 --- a/gsdk/src/client.rs +++ b/gsdk/src/client.rs @@ -71,6 +71,7 @@ impl RpcClient { timeout.unwrap_or(DEFAULT_TIMEOUT), ); + log::info!("Connecting to {url} ..."); if url.starts_with("ws") { Ok(Self::Ws( WsClientBuilder::default() diff --git a/gsdk/src/testing/node.rs b/gsdk/src/testing/node.rs index 6dcade77aaf..6c1cff3975b 100644 --- a/gsdk/src/testing/node.rs +++ b/gsdk/src/testing/node.rs @@ -18,6 +18,7 @@ use crate::testing::{port, Error, Result}; use std::{ + env, ffi::OsStr, io::{BufRead, BufReader}, net::{Ipv4Addr, SocketAddrV4}, @@ -43,10 +44,13 @@ impl Node { let port_string = port.to_string(); let mut args = args; - args.push("--ws-port"); - args.push(&port_string); + args.extend_from_slice(&["--ws-port", &port_string]); let process = Command::new(path) + .env( + "RUST_LOG", + env::var("RUST_LOG").unwrap_or_else(|_| "".into()), + ) .args(args) .stderr(Stdio::piped()) .stdout(Stdio::piped())