diff --git a/src/actions/build.rs b/src/actions/build.rs index 5c3b122..e41b30e 100644 --- a/src/actions/build.rs +++ b/src/actions/build.rs @@ -24,6 +24,7 @@ impl BuildAction<'_> { pub fn build(&self) { utils::check_target_requirements(); utils::validate_contract_name_argument(self.project, self.contracts_names()); + utils::validate_contract_names(self.project); self.build_wasm_files(); self.optimize_wasm_files(); } diff --git a/src/actions/init.rs b/src/actions/init.rs index 4a9f79c..557db15 100644 --- a/src/actions/init.rs +++ b/src/actions/init.rs @@ -20,7 +20,7 @@ use crate::{ /// InitAction configuration. #[derive(Clone)] -pub struct InitAction {} +pub struct InitAction; /// InitAction implementation. impl InitAction { @@ -74,7 +74,7 @@ impl InitAction { }, }; - cargo_generate::generate(GenerateArgs { + let project_path = cargo_generate::generate(GenerateArgs { template_path, list_favorites: false, name: Some(paths::to_snake_case(&init_command.name)), @@ -100,19 +100,14 @@ impl InitAction { Error::FailedToGenerateProjectFromTemplate(e.to_string()).print_and_die(); }); - let cargo_toml_path = match init { - true => { - let mut path = current_dir; - path.push("_Cargo.toml"); - path - } - false => { - let mut path = current_dir; - path.push(paths::to_snake_case(&init_command.name)); - path.push("_Cargo.toml"); - path - } - }; + let project_name = init_command.name.to_lowercase(); + rename_file(project_path, &project_name); + + let mut cargo_toml_path = current_dir; + if !init { + cargo_toml_path.push(project_name); + } + cargo_toml_path.push("_Cargo.toml"); Self::replace_package_placeholder( init, diff --git a/src/actions/schema.rs b/src/actions/schema.rs index 1b41a84..214f26c 100644 --- a/src/actions/schema.rs +++ b/src/actions/schema.rs @@ -23,6 +23,7 @@ impl SchemaAction<'_> { pub fn build(&self) { utils::check_target_requirements(); utils::validate_contract_name_argument(self.project, self.contracts_names()); + utils::validate_contract_names(self.project); self.generate_schema_files(); } diff --git a/src/actions/test.rs b/src/actions/test.rs index d6620aa..7872c8a 100644 --- a/src/actions/test.rs +++ b/src/actions/test.rs @@ -9,6 +9,7 @@ pub struct TestAction<'a> { backend: Option, passthrough_args: Vec, skip_build: bool, + test: Option, } /// TestAction implementation. @@ -17,6 +18,7 @@ impl<'a> TestAction<'a> { pub fn new( project: &Project, backend: Option, + test: Option, passthrough_args: Vec, skip_build: bool, ) -> TestAction { @@ -25,6 +27,7 @@ impl<'a> TestAction<'a> { passthrough_args, skip_build, project, + test, } } } @@ -45,7 +48,7 @@ impl TestAction<'_> { /// Test code against OdraVM. fn test_odra_vm(&self) { log::info("Testing against OdraVM ..."); - command::cargo_test_odra_vm(self.project.project_root(), self.get_passthrough_args()); + command::cargo_test_odra_vm(self.project.project_root(), self.args()); } /// Test specific backend. @@ -54,7 +57,7 @@ impl TestAction<'_> { command::cargo_test_backend( self.project.project_root(), self.backend_name(), - self.get_passthrough_args(), + self.args(), ); } @@ -68,6 +71,20 @@ impl TestAction<'_> { self.passthrough_args.iter().map(AsRef::as_ref).collect() } + /// Returns arguments to be passed to `cargo test` command. + /// + /// This includes the test name and passthrough arguments. + fn args(&self) -> Vec<&str> { + [ + self.test + .as_ref() + .map(|t| vec![t.as_str()]) + .unwrap_or_default(), + self.get_passthrough_args(), + ] + .concat() + } + /// Build *.wasm files before testing. fn build_wasm_files(&self) { BuildAction::new(self.project, None).build(); diff --git a/src/cli.rs b/src/cli.rs index 2a2c731..1af8051 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -118,6 +118,9 @@ pub struct TestCommand { /// Skip building wasm files. #[clap(value_parser, long, short, default_value = "false")] pub skip_build: bool, + /// Run only tests containing the given name. + #[clap(value_parser, long, short)] + pub test: Option, } #[derive(clap::Args, Debug)] @@ -170,7 +173,14 @@ pub fn make_action() { } OdraSubcommand::Test(test) => { let project = Project::detect(current_dir); - TestAction::new(&project, test.backend, test.args, test.skip_build).test(); + TestAction::new( + &project, + test.backend, + test.test, + test.args, + test.skip_build, + ) + .test(); } OdraSubcommand::Generate(generate) => { let project = Project::detect(current_dir); diff --git a/src/errors.rs b/src/errors.rs index 6246a79..a8b4897 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -58,6 +58,9 @@ pub enum Error { #[error("Contract {0} not found in Odra.toml")] ContractNotFound(String), + #[error("Contract {0} defined multiple times in Odra.toml, please make sure every contract has a unique name.")] + ContractDuplicate(String), + #[error("Odra is not a dependency of this project.")] OdraNotADependency, @@ -128,6 +131,7 @@ impl Error { Error::FailedToParseTemplatesFile(_) => 28, Error::TemplateNotFound(_) => 29, Error::IncorrectTemplateType => 30, + Error::ContractDuplicate(_) => 31, } } diff --git a/src/utils.rs b/src/utils.rs index 879d671..61561fa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -46,6 +46,22 @@ pub fn validate_contract_name_argument(project: &Project, names_string: String) }); } +/// Validate if contract names are unique. +pub fn validate_contract_names(project: &Project) { + project.odra_toml().contracts.iter().for_each(|contract| { + if project + .odra_toml() + .contracts + .iter() + .filter(|c| c.struct_name() == contract.struct_name()) + .count() + > 1 + { + Error::ContractDuplicate(contract.struct_name()).print_and_die(); + } + }); +} + fn remove_extra_spaces(input: &str) -> Result { // Ensure there are no other separators if input.chars().any(|c| c.is_whitespace() && c != ' ') {