1use crate::{
22 extrinsic::{
23 bench::{Benchmark, BenchmarkParams as ExtrinsicBenchmarkParams},
24 ExtrinsicBuilder,
25 },
26 overhead::{
27 command::ChainType::{Relaychain, Teyrchain, Unknown},
28 fake_runtime_api,
29 remark_builder::BizinikiwiRemarkBuilder,
30 template::TemplateData,
31 },
32 shared::{
33 genesis_state,
34 genesis_state::{GenesisStateHandler, SpecGenesisSource},
35 HostInfoParams, WeightParams,
36 },
37};
38use clap::{error::ErrorKind, Args, CommandFactory, Parser};
39use codec::{Decode, Encode};
40use fake_runtime_api::RuntimeApi as FakeRuntimeApi;
41use genesis_state::WARN_SPEC_GENESIS_CTOR;
42use log::info;
43#[cfg(feature = "teyrchain-benchmarks")]
45use pezcumulus_client_teyrchain_inherent::MockValidationDataInherentDataProvider;
46use pezframe_support::Deserialize;
47use pezkuwi_subxt::{client::RuntimeVersion, ext::futures, Metadata};
48use pezkuwi_teyrchain_primitives::primitives::Id as ParaId;
49use pezsc_block_builder::BlockBuilderApi;
50use pezsc_chain_spec::{ChainSpec, ChainSpecExtension, GenesisBlockBuilder};
51use pezsc_cli::{CliConfiguration, Database, ImportParams, Result, SharedParams};
52use pezsc_client_api::{execution_extensions::ExecutionExtensions, UsageProvider};
53use pezsc_client_db::{BlocksPruning, DatabaseSettings};
54use pezsc_executor::WasmExecutor;
55use pezsc_runtime_utilities::fetch_latest_metadata_from_code_blob;
56use pezsc_service::{new_client, new_db_backend, BasePath, ClientConfig, TFullClient, TaskManager};
57use pezsp_api::{ApiExt, CallApiAt, Core, ProvideRuntimeApi};
58use pezsp_blockchain::HeaderBackend;
59use pezsp_core::H256;
60use pezsp_inherents::{InherentData, InherentDataProvider};
61use pezsp_runtime::{
62 generic,
63 traits::{BlakeTwo256, Block as BlockT},
64 DigestItem, OpaqueExtrinsic,
65};
66use pezsp_storage::Storage;
67use pezsp_wasm_interface::HostFunctions;
68use serde::Serialize;
69use serde_json::{json, Value};
70use std::{
71 fmt::{Debug, Display, Formatter},
72 fs,
73 path::PathBuf,
74 sync::Arc,
75};
76
77const DEFAULT_PARA_ID: u32 = 100;
78const LOG_TARGET: &'static str = "pezkuwi_sdk_frame::benchmark::overhead";
79
80#[derive(Debug, Parser)]
82pub struct OverheadCmd {
83 #[allow(missing_docs)]
84 #[clap(flatten)]
85 pub shared_params: SharedParams,
86
87 #[allow(missing_docs)]
88 #[clap(flatten)]
89 pub import_params: ImportParams,
90
91 #[allow(missing_docs)]
92 #[clap(flatten)]
93 pub params: OverheadParams,
94}
95
96#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)]
98pub struct OverheadParams {
99 #[allow(missing_docs)]
100 #[clap(flatten)]
101 pub weight: WeightParams,
102
103 #[allow(missing_docs)]
104 #[clap(flatten)]
105 pub bench: ExtrinsicBenchmarkParams,
106
107 #[allow(missing_docs)]
108 #[clap(flatten)]
109 pub hostinfo: HostInfoParams,
110
111 #[arg(long, value_name = "PATH")]
115 pub header: Option<PathBuf>,
116
117 #[arg(long)]
121 pub enable_trie_cache: bool,
122
123 #[arg(
125 long,
126 value_name = "PATH",
127 conflicts_with = "chain",
128 required_if_eq("genesis_builder", "runtime")
129 )]
130 pub runtime: Option<PathBuf>,
131
132 #[arg(long, default_value = pezsp_genesis_builder::DEV_RUNTIME_PRESET)]
137 pub genesis_builder_preset: String,
138
139 #[arg(long, value_enum, alias = "genesis-builder-policy")]
145 pub genesis_builder: Option<GenesisBuilderPolicy>,
146
147 #[arg(long)]
150 pub para_id: Option<u32>,
151}
152
153#[derive(clap::ValueEnum, Debug, Eq, PartialEq, Clone, Copy, Serialize)]
155#[clap(rename_all = "kebab-case")]
156pub enum GenesisBuilderPolicy {
157 Runtime,
160 SpecRuntime,
162 #[value(alias = "spec")]
164 SpecGenesis,
165}
166
167#[derive(Serialize, Clone, PartialEq, Copy)]
169pub(crate) enum BenchmarkType {
170 Extrinsic,
172 Block,
174}
175
176pub type TeyrchainHostFunctions = (
178 pezcumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions,
179 pezsp_io::BizinikiwiHostFunctions,
180);
181
182pub type BlockNumber = u32;
183
184pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
186
187pub type OpaqueBlock = generic::Block<Header, OpaqueExtrinsic>;
189
190type OverheadClient<Block, HF> = TFullClient<Block, FakeRuntimeApi, WasmExecutor<HF>>;
192
193fn create_inherent_data<Client: UsageProvider<Block> + HeaderBackend<Block>, Block: BlockT>(
200 client: &Arc<Client>,
201 chain_type: &ChainType,
202) -> InherentData {
203 let genesis = client.usage_info().chain.best_hash;
204 let header = client.header(genesis).unwrap().unwrap();
205
206 let mut inherent_data = InherentData::new();
207
208 #[cfg(feature = "teyrchain-benchmarks")]
211 if let Teyrchain(para_id) = chain_type {
212 let teyrchain_validation_data_provider = MockValidationDataInherentDataProvider::<()> {
213 para_id: ParaId::from(*para_id),
214 current_para_block_head: Some(header.encode().into()),
215 relay_offset: 0,
216 ..Default::default()
217 };
218 let _ = futures::executor::block_on(
219 teyrchain_validation_data_provider.provide_inherent_data(&mut inherent_data),
220 );
221 }
222 #[cfg(not(feature = "teyrchain-benchmarks"))]
223 if let Teyrchain(_) = chain_type {
224 log::warn!("Teyrchain benchmark inherents not available. Enable the 'teyrchain-benchmarks' feature.");
225 }
226
227 let para_inherent = pezkuwi_primitives::InherentData {
229 bitfields: Vec::new(),
230 backed_candidates: Vec::new(),
231 disputes: Vec::new(),
232 parent_header: header,
233 };
234
235 let timestamp =
237 pezsp_timestamp::InherentDataProvider::new(std::time::Duration::default().into());
238
239 let _ = futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data));
240 let _ =
241 inherent_data.put_data(pezkuwi_primitives::TEYRCHAINS_INHERENT_IDENTIFIER, ¶_inherent);
242
243 inherent_data
244}
245
246fn identify_chain(metadata: &Metadata, para_id: Option<u32>) -> ChainType {
251 let teyrchain_info_exists = metadata.pallet_by_name("TeyrchainInfo").is_some();
252 let teyrchain_system_exists = metadata.pallet_by_name("TeyrchainSystem").is_some();
253 let para_inherent_exists = metadata.pallet_by_name("ParaInherent").is_some();
254
255 log::debug!("{} TeyrchainSystem", if teyrchain_system_exists { "✅" } else { "❌" });
256 log::debug!("{} TeyrchainInfo", if teyrchain_info_exists { "✅" } else { "❌" });
257 log::debug!("{} ParaInherent", if para_inherent_exists { "✅" } else { "❌" });
258
259 let chain_type = if teyrchain_system_exists && teyrchain_info_exists {
260 Teyrchain(para_id.unwrap_or(DEFAULT_PARA_ID))
261 } else if para_inherent_exists {
262 Relaychain
263 } else {
264 Unknown
265 };
266
267 log::info!(target: LOG_TARGET, "Identified Chain type from metadata: {}", chain_type);
268
269 chain_type
270}
271
272#[derive(Deserialize, Serialize, Clone, ChainSpecExtension)]
273pub struct TeyrchainExtension {
274 pub para_id: Option<u32>,
276}
277
278impl OverheadCmd {
279 fn state_handler_from_cli<HF: HostFunctions>(
280 &self,
281 chain_spec_from_api: Option<Box<dyn ChainSpec>>,
282 ) -> Result<(GenesisStateHandler, Option<u32>)> {
283 let genesis_builder_to_source = || match self.params.genesis_builder {
284 Some(GenesisBuilderPolicy::Runtime) | Some(GenesisBuilderPolicy::SpecRuntime) => {
285 SpecGenesisSource::Runtime(self.params.genesis_builder_preset.clone())
286 },
287 Some(GenesisBuilderPolicy::SpecGenesis) | None => {
288 log::warn!(target: LOG_TARGET, "{WARN_SPEC_GENESIS_CTOR}");
289 SpecGenesisSource::SpecJson
290 },
291 };
292
293 if let Some(chain_spec) = chain_spec_from_api {
295 log::debug!(target: LOG_TARGET, "Initializing state handler with chain-spec from API: {:?}", chain_spec);
296
297 let source = genesis_builder_to_source();
298 return Ok((GenesisStateHandler::ChainSpec(chain_spec, source), self.params.para_id));
299 };
300
301 if let Some(chain_spec_path) = &self.shared_params.chain {
303 log::debug!(target: LOG_TARGET,
304 "Initializing state handler with chain-spec from path: {:?}",
305 chain_spec_path
306 );
307 let (chain_spec, para_id_from_chain_spec) =
308 genesis_state::chain_spec_from_path::<HF>(chain_spec_path.to_string().into())?;
309
310 let source = genesis_builder_to_source();
311
312 return Ok((
313 GenesisStateHandler::ChainSpec(chain_spec, source),
314 self.params.para_id.or(para_id_from_chain_spec),
315 ));
316 };
317
318 if let Some(runtime_path) = &self.params.runtime {
321 log::debug!(target: LOG_TARGET, "Initializing state handler with runtime from path: {:?}", runtime_path);
322
323 let runtime_blob = fs::read(runtime_path)?;
324 return Ok((
325 GenesisStateHandler::Runtime(
326 runtime_blob,
327 Some(self.params.genesis_builder_preset.clone()),
328 ),
329 self.params.para_id,
330 ));
331 };
332
333 Err("Neither a runtime nor a chain-spec were specified".to_string().into())
334 }
335
336 fn check_args(
337 &self,
338 chain_spec: &Option<Box<dyn ChainSpec>>,
339 ) -> std::result::Result<(), (ErrorKind, String)> {
340 if chain_spec.is_none()
341 && self.params.runtime.is_none()
342 && self.shared_params.chain.is_none()
343 {
344 return Err((
345 ErrorKind::MissingRequiredArgument,
346 "Provide either a runtime via `--runtime` or a chain spec via `--chain`"
347 .to_string(),
348 ));
349 }
350
351 match self.params.genesis_builder {
352 Some(GenesisBuilderPolicy::SpecGenesis | GenesisBuilderPolicy::SpecRuntime) => {
353 if chain_spec.is_none() && self.shared_params.chain.is_none() {
354 return Err((
355 ErrorKind::MissingRequiredArgument,
356 "Provide a chain spec via `--chain`.".to_string(),
357 ));
358 }
359 },
360 _ => {},
361 };
362 Ok(())
363 }
364
365 pub fn run_with_default_builder_and_spec<Block, ExtraHF>(
370 &self,
371 chain_spec: Option<Box<dyn ChainSpec>>,
372 ) -> Result<()>
373 where
374 Block: BlockT<Extrinsic = OpaqueExtrinsic, Hash = H256>,
375 ExtraHF: HostFunctions,
376 {
377 self.run_with_extrinsic_builder_and_spec::<Block, ExtraHF>(
378 Box::new(|metadata, hash, version| {
379 let genesis = pezkuwi_subxt::utils::H256::from(hash.to_fixed_bytes());
380 Box::new(BizinikiwiRemarkBuilder::new(metadata, genesis, version)) as Box<_>
381 }),
382 chain_spec,
383 )
384 }
385
386 pub fn run_with_extrinsic_builder_and_spec<Block, ExtraHF>(
392 &self,
393 ext_builder_provider: Box<
394 dyn FnOnce(Metadata, Block::Hash, RuntimeVersion) -> Box<dyn ExtrinsicBuilder>,
395 >,
396 chain_spec: Option<Box<dyn ChainSpec>>,
397 ) -> Result<()>
398 where
399 Block: BlockT<Extrinsic = OpaqueExtrinsic>,
400 ExtraHF: HostFunctions,
401 {
402 if let Err((error_kind, msg)) = self.check_args(&chain_spec) {
403 let mut cmd = OverheadCmd::command();
404 cmd.error(error_kind, msg).exit();
405 };
406
407 let (state_handler, para_id) =
408 self.state_handler_from_cli::<(TeyrchainHostFunctions, ExtraHF)>(chain_spec)?;
409
410 let executor = WasmExecutor::<(TeyrchainHostFunctions, ExtraHF)>::builder()
411 .with_allow_missing_host_functions(true)
412 .build();
413
414 let opaque_metadata =
415 fetch_latest_metadata_from_code_blob(&executor, state_handler.get_code_bytes()?)
416 .map_err(|_| {
417 <&str as Into<pezsc_cli::Error>>::into("Unable to fetch latest stable metadata")
418 })?;
419 let metadata = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice())?;
420
421 let chain_type = identify_chain(&metadata, para_id);
423
424 let genesis_patcher = match chain_type {
427 Teyrchain(para_id) => {
428 Some(Box::new(move |value| patch_genesis(value, Some(para_id))) as Box<_>)
429 },
430 _ => None,
431 };
432
433 let client = self.build_client_components::<Block, (TeyrchainHostFunctions, ExtraHF)>(
434 state_handler.build_storage::<(TeyrchainHostFunctions, ExtraHF)>(genesis_patcher)?,
435 executor,
436 &chain_type,
437 )?;
438
439 let inherent_data = create_inherent_data(&client, &chain_type);
440
441 let (ext_builder, runtime_name) = {
442 let genesis = client.usage_info().chain.best_hash;
443 let version = client.runtime_api().version(genesis).unwrap();
444 let runtime_name = version.spec_name;
445 let runtime_version = RuntimeVersion {
446 spec_version: version.spec_version,
447 transaction_version: version.transaction_version,
448 };
449
450 (ext_builder_provider(metadata, genesis, runtime_version), runtime_name)
451 };
452
453 self.run(
454 runtime_name.to_string(),
455 client,
456 inherent_data,
457 Default::default(),
458 &*ext_builder,
459 chain_type.requires_proof_recording(),
460 )
461 }
462
463 pub fn run_with_extrinsic_builder<Block, ExtraHF>(
465 &self,
466 ext_builder_provider: Box<
467 dyn FnOnce(Metadata, Block::Hash, RuntimeVersion) -> Box<dyn ExtrinsicBuilder>,
468 >,
469 ) -> Result<()>
470 where
471 Block: BlockT<Extrinsic = OpaqueExtrinsic>,
472 ExtraHF: HostFunctions,
473 {
474 self.run_with_extrinsic_builder_and_spec::<Block, ExtraHF>(ext_builder_provider, None)
475 }
476
477 fn build_client_components<Block, HF>(
478 &self,
479 genesis_storage: Storage,
480 executor: WasmExecutor<HF>,
481 chain_type: &ChainType,
482 ) -> Result<Arc<OverheadClient<Block, HF>>>
483 where
484 Block: BlockT,
485 HF: HostFunctions,
486 {
487 let extensions = ExecutionExtensions::new(None, Arc::new(executor.clone()));
488
489 let base_path = match &self.shared_params.base_path {
490 None => BasePath::new_temp_dir()?,
491 Some(path) => BasePath::from(path.clone()),
492 };
493
494 let database_source = self.database_config(
495 &base_path.path().to_path_buf(),
496 self.database_cache_size()?.unwrap_or(1024),
497 self.database()?.unwrap_or(Database::Auto),
498 )?;
499
500 let backend = new_db_backend(DatabaseSettings {
501 trie_cache_maximum_size: self.trie_cache_maximum_size()?,
502 state_pruning: None,
503 blocks_pruning: BlocksPruning::KeepAll,
504 source: database_source,
505 metrics_registry: None,
506 })?;
507
508 let genesis_block_builder = GenesisBlockBuilder::new_with_storage(
509 genesis_storage,
510 true,
511 backend.clone(),
512 executor.clone(),
513 )?;
514
515 let tokio_runtime = pezsc_cli::build_runtime()?;
516 let task_manager = TaskManager::new(tokio_runtime.handle().clone(), None)
517 .map_err(|_| "Unable to build task manager")?;
518
519 let client: Arc<OverheadClient<Block, HF>> = Arc::new(new_client(
520 backend.clone(),
521 executor,
522 genesis_block_builder,
523 Default::default(),
524 Default::default(),
525 extensions,
526 Box::new(task_manager.spawn_handle()),
527 None,
528 None,
529 ClientConfig {
530 offchain_worker_enabled: false,
531 offchain_indexing_api: false,
532 wasm_runtime_overrides: None,
533 no_genesis: false,
534 wasm_runtime_substitutes: Default::default(),
535 enable_import_proof_recording: chain_type.requires_proof_recording(),
536 },
537 )?);
538
539 Ok(client)
540 }
541
542 pub fn run<Block, C>(
547 &self,
548 chain_name: String,
549 client: Arc<C>,
550 inherent_data: pezsp_inherents::InherentData,
551 digest_items: Vec<DigestItem>,
552 ext_builder: &dyn ExtrinsicBuilder,
553 should_record_proof: bool,
554 ) -> Result<()>
555 where
556 Block: BlockT<Extrinsic = OpaqueExtrinsic>,
557 C: ProvideRuntimeApi<Block>
558 + CallApiAt<Block>
559 + UsageProvider<Block>
560 + pezsp_blockchain::HeaderBackend<Block>,
561 C::Api: ApiExt<Block> + BlockBuilderApi<Block>,
562 {
563 if ext_builder.pezpallet() != "system" || ext_builder.extrinsic() != "remark" {
564 return Err(format!("The extrinsic builder is required to build `System::Remark` extrinsics but builds `{}` extrinsics instead", ext_builder.name()).into());
565 }
566
567 let bench = Benchmark::new(
568 client,
569 self.params.bench.clone(),
570 inherent_data,
571 digest_items,
572 should_record_proof,
573 );
574
575 {
577 let (stats, proof_size) = bench.bench_block()?;
578 info!(target: LOG_TARGET, "Per-block execution overhead [ns]:\n{:?}", stats);
579 let template = TemplateData::new(
580 BenchmarkType::Block,
581 &chain_name,
582 &self.params,
583 &stats,
584 proof_size,
585 )?;
586 template.write(&self.params.weight.weight_path)?;
587 }
588 {
590 let (stats, proof_size) = bench.bench_extrinsic(ext_builder)?;
591 info!(target: LOG_TARGET, "Per-extrinsic execution overhead [ns]:\n{:?}", stats);
592 let template = TemplateData::new(
593 BenchmarkType::Extrinsic,
594 &chain_name,
595 &self.params,
596 &stats,
597 proof_size,
598 )?;
599 template.write(&self.params.weight.weight_path)?;
600 }
601
602 Ok(())
603 }
604}
605
606impl BenchmarkType {
607 pub(crate) fn short_name(&self) -> &'static str {
609 match self {
610 Self::Extrinsic => "extrinsic",
611 Self::Block => "block",
612 }
613 }
614
615 pub(crate) fn long_name(&self) -> &'static str {
617 match self {
618 Self::Extrinsic => "ExtrinsicBase",
619 Self::Block => "BlockExecution",
620 }
621 }
622}
623
624#[derive(Clone, PartialEq, Debug)]
625enum ChainType {
626 Teyrchain(u32),
627 Relaychain,
628 Unknown,
629}
630
631impl Display for ChainType {
632 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
633 match self {
634 ChainType::Teyrchain(id) => write!(f, "Teyrchain(paraid = {})", id),
635 ChainType::Relaychain => write!(f, "Relaychain"),
636 ChainType::Unknown => write!(f, "Unknown"),
637 }
638 }
639}
640
641impl ChainType {
642 fn requires_proof_recording(&self) -> bool {
643 match self {
644 Teyrchain(_) => true,
645 Relaychain => false,
646 Unknown => false,
647 }
648 }
649}
650
651fn patch_genesis(mut input_value: Value, para_id: Option<u32>) -> Value {
654 if let Some(para_id) = para_id {
658 pezsc_chain_spec::json_patch::merge(
659 &mut input_value,
660 json!({
661 "teyrchainInfo": {
662 "teyrchainId": para_id,
663 }
664 }),
665 );
666 log::debug!(target: LOG_TARGET, "Genesis Config Json");
667 log::debug!(target: LOG_TARGET, "{}", input_value);
668 }
669 input_value
670}
671
672impl CliConfiguration for OverheadCmd {
674 fn shared_params(&self) -> &SharedParams {
675 &self.shared_params
676 }
677
678 fn import_params(&self) -> Option<&ImportParams> {
679 Some(&self.import_params)
680 }
681
682 fn base_path(&self) -> Result<Option<BasePath>> {
683 Ok(Some(BasePath::new_temp_dir()?))
684 }
685
686 fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
687 if self.params.enable_trie_cache {
688 Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default())
689 } else {
690 Ok(None)
691 }
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use crate::{
698 overhead::command::{identify_chain, ChainType, TeyrchainHostFunctions, DEFAULT_PARA_ID},
699 OverheadCmd,
700 };
701 use clap::Parser;
702 use codec::Decode;
703 use pezsc_executor::WasmExecutor;
704
705 #[test]
706 fn test_chain_type_relaychain() {
707 let executor: WasmExecutor<TeyrchainHostFunctions> = WasmExecutor::builder().build();
708 let code_bytes = zagros_runtime::WASM_BINARY
709 .expect("To run this test, build the wasm binary of zagros-runtime")
710 .to_vec();
711 let opaque_metadata =
712 super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
713 let metadata = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
714 let chain_type = identify_chain(&metadata, None);
715 assert_eq!(chain_type, ChainType::Relaychain);
716 assert_eq!(chain_type.requires_proof_recording(), false);
717 }
718
719 #[test]
720 fn test_chain_type_teyrchain() {
721 let executor: WasmExecutor<TeyrchainHostFunctions> = WasmExecutor::builder().build();
722 let code_bytes = pezcumulus_test_runtime::WASM_BINARY
723 .expect("To run this test, build the wasm binary of pezcumulus-test-runtime")
724 .to_vec();
725 let opaque_metadata =
726 super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
727 let metadata = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
728 let chain_type = identify_chain(&metadata, Some(100));
729 assert_eq!(chain_type, ChainType::Teyrchain(100));
730 assert!(chain_type.requires_proof_recording());
731 assert_eq!(identify_chain(&metadata, None), ChainType::Teyrchain(DEFAULT_PARA_ID));
732 }
733
734 #[test]
735 fn test_chain_type_custom() {
736 let executor: WasmExecutor<TeyrchainHostFunctions> = WasmExecutor::builder().build();
737 let code_bytes = bizinikiwi_test_runtime::WASM_BINARY
738 .expect("To run this test, build the wasm binary of bizinikiwi-test-runtime")
739 .to_vec();
740 let opaque_metadata =
741 super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap();
742 let metadata = pezkuwi_subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap();
743 let chain_type = identify_chain(&metadata, None);
744 assert_eq!(chain_type, ChainType::Unknown);
745 assert_eq!(chain_type.requires_proof_recording(), false);
746 }
747
748 fn cli_succeed(args: &[&str]) -> Result<(), clap::Error> {
749 let cmd = OverheadCmd::try_parse_from(args)?;
750 assert!(cmd.check_args(&None).is_ok());
751 Ok(())
752 }
753
754 fn cli_fail(args: &[&str]) {
755 let cmd = OverheadCmd::try_parse_from(args);
756 if let Ok(cmd) = cmd {
757 assert!(cmd.check_args(&None).is_err());
758 }
759 }
760
761 #[test]
762 fn test_cli_conflicts() -> Result<(), clap::Error> {
763 cli_succeed(&["test", "--runtime", "path/to/runtime", "--genesis-builder", "runtime"])?;
765 cli_succeed(&["test", "--runtime", "path/to/runtime"])?;
766 cli_succeed(&[
767 "test",
768 "--runtime",
769 "path/to/runtime",
770 "--genesis-builder-preset",
771 "preset",
772 ])?;
773 cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec"]);
774 cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-genesis"]);
775 cli_fail(&["test", "--runtime", "path/to/spec", "--genesis-builder", "spec-runtime"]);
776
777 cli_succeed(&["test", "--chain", "path/to/spec"])?;
779 cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec"])?;
780 cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec-genesis"])?;
781 cli_succeed(&["test", "--chain", "path/to/spec", "--genesis-builder", "spec-runtime"])?;
782 cli_fail(&["test", "--chain", "path/to/spec", "--genesis-builder", "none"]);
783 cli_fail(&["test", "--chain", "path/to/spec", "--genesis-builder", "runtime"]);
784 cli_fail(&[
785 "test",
786 "--chain",
787 "path/to/spec",
788 "--genesis-builder",
789 "runtime",
790 "--genesis-builder-preset",
791 "preset",
792 ]);
793 Ok(())
794 }
795}