diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bd7198e7ab28310de1f0e4b93b55ea29446b4a29..760ef9749c7b4fe37e96c30a2cec87b43aa5dc9f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,6 +99,7 @@ test-new-project-template: - cargo run --all-features -- contract build --manifest-path new_project/Cargo.toml - cargo run --all-features -- contract check --manifest-path new_project/Cargo.toml + - cargo run --all-features -- contract test --manifest-path new_project/Cargo.toml - cd new_project - cargo check --verbose diff --git a/README.md b/README.md index c57dc5a3109aa476ea5a84428f67ccb2a95ad3a1..8c8ee05dbea531e690f02a1a96f3e9dae065cd7e 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,9 @@ toolchain of a directory by executing `rustup override set nightly` in it. Checks that the code builds as WebAssembly. This command does not output any `.contract` artifact to the `target/` directory. +##### `cargo contract test` + +Runs test suites defined for a smart contract off-chain. ## License diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 82e3ac01e97c407c9dc32d3eaa95414a715b3b70..b507a9c5ce70a430bec0fcca886e141c06d4e433 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -21,7 +21,11 @@ mod deploy; mod instantiate; pub mod metadata; pub mod new; +pub mod test; -pub(crate) use self::build::{BuildCommand, CheckCommand}; +pub(crate) use self::{ + build::{BuildCommand, CheckCommand}, + test::TestCommand, +}; #[cfg(feature = "extrinsics")] pub(crate) use self::{deploy::execute_deploy, instantiate::execute_instantiate}; diff --git a/src/cmd/test.rs b/src/cmd/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..98b9dae139323e2ddd158987e0e30666a75f2072 --- /dev/null +++ b/src/cmd/test.rs @@ -0,0 +1,94 @@ +// Copyright 2018-2021 Parity Technologies (UK) Ltd. +// This file is part of cargo-contract. +// +// cargo-contract 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. +// +// cargo-contract 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 cargo-contract. If not, see . + +use crate::{maybe_println, util, workspace::ManifestPath, Verbosity, VerbosityFlags}; +use anyhow::Result; +use colored::Colorize; +use std::{convert::TryFrom, path::PathBuf}; +use structopt::StructOpt; + +/// Executes smart-contract tests off-chain by delegating to `cargo test`. +#[derive(Debug, StructOpt)] +#[structopt(name = "test")] +pub struct TestCommand { + /// Path to the `Cargo.toml` of the contract to test. + #[structopt(long, parse(from_os_str))] + manifest_path: Option, + #[structopt(flatten)] + verbosity: VerbosityFlags, +} + +impl TestCommand { + pub fn exec(&self) -> Result { + let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + let verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; + + execute(&manifest_path, verbosity) + } +} + +/// Result of the test runs. +pub struct TestResult { + /// The `cargo +nightly test` child process standard output stream buffer. + pub stdout: Vec, + /// The verbosity flags. + pub verbosity: Verbosity, +} + +impl TestResult { + pub fn display(&self) -> Result { + Ok(String::from_utf8(self.stdout.clone())?) + } +} + +/// Executes `cargo +nightly test`. +pub(crate) fn execute(manifest_path: &ManifestPath, verbosity: Verbosity) -> Result { + util::assert_channel()?; + + maybe_println!( + verbosity, + " {} {}", + format!("[{}/{}]", 1, 1).bold(), + "Running tests".bright_green().bold() + ); + + let stdout = util::invoke_cargo("test", &[""], manifest_path.directory(), verbosity)?; + + Ok(TestResult { stdout, verbosity }) +} + +#[cfg(feature = "test-ci-only")] +#[cfg(test)] +mod tests_ci_only { + use crate::{util::tests::with_new_contract_project, Verbosity}; + use regex::Regex; + + #[test] + fn passing_tests_yield_stdout() { + with_new_contract_project(|manifest_path| { + let ok_output_pattern = + Regex::new(r"test result: ok. \d+ passed; 0 failed; \d+ ignored") + .expect("regex pattern compilation failed"); + + let res = + super::execute(&manifest_path, Verbosity::Default).expect("test execution failed"); + + assert!(ok_output_pattern.is_match(&String::from_utf8_lossy(&res.stdout))); + + Ok(()) + }) + } +} diff --git a/src/main.rs b/src/main.rs index 4e4a565db8d552b591eda77479c929b375ddaaf3..a74056b927bf0a637fc95bd52cc8712b6bb9d66a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ mod workspace; use self::workspace::ManifestPath; -use crate::cmd::{metadata::MetadataResult, BuildCommand, CheckCommand}; +use crate::cmd::{metadata::MetadataResult, BuildCommand, CheckCommand, TestCommand}; #[cfg(feature = "extrinsics")] use sp_core::{crypto::Pair, sr25519, H256}; @@ -383,7 +383,7 @@ enum Command { Check(CheckCommand), /// Test the smart contract off-chain #[structopt(name = "test")] - Test {}, + Test(TestCommand), /// Upload the smart contract code to the chain #[cfg(feature = "extrinsics")] #[structopt(name = "deploy")] @@ -472,7 +472,14 @@ fn exec(cmd: Command) -> Result> { Ok(None) } } - Command::Test {} => Err(anyhow::anyhow!("Command unimplemented")), + Command::Test(test) => { + let res = test.exec()?; + if res.verbosity.is_verbose() { + Ok(Some(res.display()?)) + } else { + Ok(None) + } + } #[cfg(feature = "extrinsics")] Command::Deploy { extrinsic_opts,