1pub mod ecal;
2pub mod execute;
3pub mod setup;
4
5use crate::execute::TestExecutor;
6use crate::setup::{
7 ContractDeploymentSetup, ContractTestSetup, DeploymentSetup, ScriptTestSetup, TestSetup,
8};
9use ecal::EcalSyscallHandler;
10use forc_pkg::{self as pkg, BuildOpts, DumpOpts};
11use forc_util::tx_utils::decode_fuel_vm_log_data;
12use fuel_abi_types::abi::program::ProgramABI;
13use fuel_abi_types::revert_info::RevertInfo;
14use fuel_tx::{self as tx, GasCostsValues};
15use fuel_vm::checked_transaction::builder::TransactionBuilderExt;
16use fuel_vm::{self as vm};
17use pkg::TestPassCondition;
18use pkg::{Built, BuiltPackage};
19use rand::{Rng, SeedableRng};
20use rayon::prelude::*;
21use std::str::FromStr;
22use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
23use sway_core::{BuildTarget, IrCli};
24use sway_types::Span;
25use tx::consensus_parameters::ConsensusParametersV1;
26use tx::{ConsensusParameters, ContractParameters, ScriptParameters, TxParameters};
27use vm::interpreter::{InterpreterParams, MemoryInstance};
28use vm::prelude::SecretKey;
29
30#[derive(Debug)]
32pub enum Tested {
33 Package(Box<TestedPackage>),
34 Workspace(Vec<TestedPackage>),
35}
36
37#[derive(Debug)]
39pub struct TestedPackage {
40 pub built: Box<pkg::BuiltPackage>,
41 pub tests: Vec<TestResult>,
43}
44
45#[derive(Debug)]
46pub struct TestDetails {
47 pub file_path: Arc<PathBuf>,
49 pub line_number: usize,
51}
52
53#[derive(Debug, Clone)]
55pub struct TestFilter<'a> {
56 pub filter_phrase: &'a str,
58 pub exact_match: bool,
61}
62
63#[derive(Debug, Clone)]
65pub struct TestResult {
66 pub name: String,
68 pub duration: std::time::Duration,
70 pub span: Span,
72 pub file_path: Arc<PathBuf>,
74 pub state: vm::state::ProgramState,
76 pub condition: pkg::TestPassCondition,
78 pub logs: Vec<fuel_tx::Receipt>,
80 pub gas_used: u64,
82 pub ecal: Box<EcalSyscallHandler>,
84}
85
86const TEST_METADATA_SEED: u64 = 0x7E57u64;
87type ContractDependencyMap = HashMap<pkg::Pinned, Vec<Arc<pkg::BuiltPackage>>>;
89
90pub enum BuiltTests {
92 Package(PackageTests),
93 Workspace(Vec<PackageTests>),
94}
95
96#[derive(Debug)]
103pub enum PackageTests {
104 Contract(PackageWithDeploymentToTest),
105 Script(PackageWithDeploymentToTest),
106 Predicate(Arc<pkg::BuiltPackage>),
107 Library(Arc<pkg::BuiltPackage>),
108}
109
110#[derive(Debug)]
112pub struct ContractToTest {
113 pkg: Arc<pkg::BuiltPackage>,
115 without_tests_bytecode: pkg::BuiltPackageBytecode,
117 contract_dependencies: Vec<Arc<pkg::BuiltPackage>>,
118}
119
120#[derive(Debug)]
122pub struct ScriptToTest {
123 pkg: Arc<pkg::BuiltPackage>,
125 contract_dependencies: Vec<Arc<pkg::BuiltPackage>>,
126}
127
128#[derive(Debug)]
130pub enum PackageWithDeploymentToTest {
131 Script(ScriptToTest),
132 Contract(ContractToTest),
133}
134
135#[derive(Default, Clone)]
137pub struct TestOpts {
138 pub pkg: pkg::PkgOpts,
139 pub print: pkg::PrintOpts,
140 pub verify_ir: IrCli,
141 pub minify: pkg::MinifyOpts,
142 pub binary_outfile: Option<String>,
144 pub debug_outfile: Option<String>,
148 pub hex_outfile: Option<String>,
150 pub build_target: BuildTarget,
152 pub build_profile: String,
154 pub release: bool,
157 pub error_on_warnings: bool,
159 pub time_phases: bool,
161 pub profile: bool,
163 pub metrics_outfile: Option<String>,
165 pub experimental: Vec<sway_features::Feature>,
167 pub no_experimental: Vec<sway_features::Feature>,
169 pub no_output: bool,
171}
172
173#[derive(Default, Clone)]
175pub struct TestPrintOpts {
176 pub pretty_print: bool,
177 pub print_logs: bool,
178}
179
180#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
181pub enum GasCostsSource {
182 #[default]
183 BuiltIn,
184 Mainnet,
185 Testnet,
186 File(String),
187}
188
189impl GasCostsSource {
190 pub fn provide_gas_costs(&self) -> Result<GasCostsValues, anyhow::Error> {
191 match self {
192 Self::BuiltIn => Ok(serde_json::from_str(include_str!(
196 "../gas_costs_values.json"
197 ))?),
198 Self::Mainnet => Err(anyhow::anyhow!(
201 "Fetching gas costs from mainnet is currently not implemented."
202 )),
203 Self::Testnet => Err(anyhow::anyhow!(
204 "Fetching gas costs from testnet is currently not implemented."
205 )),
206 Self::File(_file_path) => Err(anyhow::anyhow!(
207 "Loading gas costs from a JSON file is currently not implemented."
208 )),
209 }
210 }
211}
212
213impl FromStr for GasCostsSource {
214 type Err = anyhow::Error;
215
216 fn from_str(value: &str) -> Result<Self, Self::Err> {
217 match value {
218 "built-in" => Ok(Self::BuiltIn),
219 "mainnet" => Ok(Self::Mainnet),
220 "testnet" => Ok(Self::Testnet),
221 file_path => Ok(Self::File(file_path.to_string())),
222 }
223 }
224}
225
226pub struct DecodedLog {
228 pub value: String,
229}
230
231impl TestedPackage {
232 pub fn tests_passed(&self) -> bool {
233 self.tests.iter().all(|test| test.passed())
234 }
235}
236
237impl PackageWithDeploymentToTest {
238 fn pkg(&self) -> &BuiltPackage {
242 match self {
243 PackageWithDeploymentToTest::Script(script) => &script.pkg,
244 PackageWithDeploymentToTest::Contract(contract) => &contract.pkg,
245 }
246 }
247
248 fn contract_dependencies(&self) -> impl Iterator<Item = &Arc<BuiltPackage>> + '_ {
250 match self {
251 PackageWithDeploymentToTest::Script(script_to_test) => {
252 script_to_test.contract_dependencies.iter()
253 }
254 PackageWithDeploymentToTest::Contract(contract_to_test) => {
255 contract_to_test.contract_dependencies.iter()
256 }
257 }
258 }
259
260 fn deploy(&self) -> anyhow::Result<TestSetup> {
265 let gas_price = 0;
267 let params = maxed_consensus_params(GasCostsValues::default());
271 let storage = vm::storage::MemoryStorage::default();
272 let interpreter_params = InterpreterParams::new(gas_price, params.clone());
273 let mut interpreter: vm::prelude::Interpreter<_, _, _, vm::interpreter::NotSupportedEcal> =
274 vm::interpreter::Interpreter::with_storage(
275 MemoryInstance::new(),
276 storage,
277 interpreter_params,
278 );
279
280 let contract_dependency_setups = self
283 .contract_dependencies()
284 .map(|built_pkg| deployment_transaction(built_pkg, &built_pkg.bytecode, ¶ms));
285
286 let contract_dependency_ids = contract_dependency_setups
288 .map(|(contract_id, tx)| {
289 let tx = tx
291 .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
292 .unwrap();
293 interpreter.transact(tx).map_err(anyhow::Error::msg)?;
294 Ok(contract_id)
295 })
296 .collect::<anyhow::Result<Vec<_>>>()?;
297
298 let deployment_setup = if let PackageWithDeploymentToTest::Contract(contract_to_test) = self
299 {
300 let (root_contract_id, root_contract_tx) = deployment_transaction(
303 &contract_to_test.pkg,
304 &contract_to_test.without_tests_bytecode,
305 ¶ms,
306 );
307 let root_contract_tx = root_contract_tx
308 .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
309 .unwrap();
310 interpreter
312 .transact(root_contract_tx)
313 .map_err(anyhow::Error::msg)?;
314 let storage = interpreter.as_ref().clone();
315 DeploymentSetup::Contract(ContractTestSetup {
316 storage,
317 contract_dependency_ids,
318 root_contract_id,
319 })
320 } else {
321 let storage = interpreter.as_ref().clone();
322 DeploymentSetup::Script(ScriptTestSetup {
323 storage,
324 contract_dependency_ids,
325 })
326 };
327
328 Ok(TestSetup::WithDeployment(deployment_setup))
329 }
330}
331
332fn get_contract_dependency_map(
337 built: &Built,
338 build_plan: &pkg::BuildPlan,
339) -> ContractDependencyMap {
340 let built_members: HashMap<&pkg::Pinned, Arc<pkg::BuiltPackage>> =
341 built.into_members().collect();
342 build_plan
344 .member_nodes()
345 .map(|member_node| {
346 let graph = build_plan.graph();
347 let pinned_member = graph[member_node].clone();
348 let contract_dependencies = build_plan
349 .contract_dependencies(member_node)
350 .map(|contract_dependency_node_ix| graph[contract_dependency_node_ix].clone())
351 .filter_map(|pinned| built_members.get(&pinned))
352 .cloned()
353 .collect::<Vec<_>>();
354 (pinned_member, contract_dependencies)
355 })
356 .collect()
357}
358
359impl BuiltTests {
360 pub fn from_built(built: Built, build_plan: &pkg::BuildPlan) -> anyhow::Result<BuiltTests> {
362 let contract_dependencies = get_contract_dependency_map(&built, build_plan);
363 let built = match built {
364 Built::Package(built_pkg) => BuiltTests::Package(PackageTests::from_built_pkg(
365 built_pkg,
366 &contract_dependencies,
367 )),
368 Built::Workspace(built_workspace) => {
369 let pkg_tests = built_workspace
370 .into_iter()
371 .map(|built_pkg| {
372 PackageTests::from_built_pkg(built_pkg, &contract_dependencies)
373 })
374 .collect();
375 BuiltTests::Workspace(pkg_tests)
376 }
377 };
378 Ok(built)
379 }
380}
381
382impl<'a> PackageTests {
383 pub(crate) fn built_pkg_with_tests(&'a self) -> &'a BuiltPackage {
388 match self {
389 PackageTests::Contract(contract) => contract.pkg(),
390 PackageTests::Script(script) => script.pkg(),
391 PackageTests::Predicate(predicate) => predicate,
392 PackageTests::Library(library) => library,
393 }
394 }
395
396 fn from_built_pkg(
398 built_pkg: Arc<BuiltPackage>,
399 contract_dependencies: &ContractDependencyMap,
400 ) -> PackageTests {
401 let built_without_tests_bytecode = built_pkg.bytecode_without_tests.clone();
402 let contract_dependencies: Vec<Arc<pkg::BuiltPackage>> = contract_dependencies
403 .get(&built_pkg.descriptor.pinned)
404 .cloned()
405 .unwrap_or_default();
406 match built_without_tests_bytecode {
407 Some(contract_without_tests) => {
408 let contract_to_test = ContractToTest {
409 pkg: built_pkg,
410 without_tests_bytecode: contract_without_tests,
411 contract_dependencies,
412 };
413 PackageTests::Contract(PackageWithDeploymentToTest::Contract(contract_to_test))
414 }
415 None => match built_pkg.tree_type {
416 sway_core::language::parsed::TreeType::Predicate => {
417 PackageTests::Predicate(built_pkg)
418 }
419 sway_core::language::parsed::TreeType::Library => PackageTests::Library(built_pkg),
420 sway_core::language::parsed::TreeType::Script => {
421 let script_to_test = ScriptToTest {
422 pkg: built_pkg,
423 contract_dependencies,
424 };
425 PackageTests::Script(PackageWithDeploymentToTest::Script(script_to_test))
426 }
427 _ => unreachable!("contracts are already handled"),
428 },
429 }
430 }
431
432 pub(crate) fn run_tests(
434 &self,
435 test_runners: &rayon::ThreadPool,
436 test_filter: Option<&TestFilter>,
437 gas_costs_values: GasCostsValues,
438 ) -> anyhow::Result<TestedPackage> {
439 let pkg_with_tests = self.built_pkg_with_tests();
440 let tests = test_runners.install(|| {
441 pkg_with_tests
442 .bytecode
443 .entries
444 .par_iter()
445 .filter_map(|entry| {
446 if let Some(test_entry) = entry.kind.test() {
447 let name = entry.finalized.fn_name.clone();
450 if let Some(filter) = test_filter {
451 if !filter.filter(&name) {
452 return None;
453 }
454 }
455 return Some((entry, test_entry));
456 }
457 None
458 })
459 .map(|(entry, test_entry)| {
460 let offset = u32::try_from(entry.finalized.imm)
462 .expect("test instruction offset out of range");
463 let name = entry.finalized.fn_name.clone();
464 let test_setup = self.setup()?;
465 TestExecutor::build(
466 &pkg_with_tests.bytecode.bytes,
467 offset,
468 test_setup,
469 test_entry,
470 name,
471 gas_costs_values.clone(),
472 )?
473 .execute()
474 })
475 .collect::<anyhow::Result<_>>()
476 })?;
477
478 Ok(TestedPackage {
479 built: Box::new(pkg_with_tests.clone()),
480 tests,
481 })
482 }
483
484 pub fn setup(&self) -> anyhow::Result<TestSetup> {
489 match self {
490 PackageTests::Contract(contract_to_test) => {
491 let test_setup = contract_to_test.deploy()?;
492 Ok(test_setup)
493 }
494 PackageTests::Script(script_to_test) => {
495 let test_setup = script_to_test.deploy()?;
496 Ok(test_setup)
497 }
498 PackageTests::Predicate(_) | PackageTests::Library(_) => Ok(
499 TestSetup::WithoutDeployment(vm::storage::MemoryStorage::default()),
500 ),
501 }
502 }
503}
504
505impl From<TestOpts> for pkg::BuildOpts {
506 fn from(val: TestOpts) -> Self {
507 pkg::BuildOpts {
508 pkg: val.pkg,
509 print: val.print,
510 verify_ir: val.verify_ir,
511 minify: val.minify,
512 dump: DumpOpts::default(),
513 binary_outfile: val.binary_outfile,
514 debug_outfile: val.debug_outfile,
515 hex_outfile: val.hex_outfile,
516 build_target: val.build_target,
517 build_profile: val.build_profile,
518 release: val.release,
519 error_on_warnings: val.error_on_warnings,
520 time_phases: val.time_phases,
521 profile: val.profile,
522 metrics_outfile: val.metrics_outfile,
523 tests: true,
524 member_filter: Default::default(),
525 experimental: val.experimental,
526 no_experimental: val.no_experimental,
527 no_output: val.no_output,
528 }
529 }
530}
531
532impl TestOpts {
533 pub fn into_build_opts(self) -> pkg::BuildOpts {
535 pkg::BuildOpts {
536 pkg: self.pkg,
537 print: self.print,
538 verify_ir: self.verify_ir,
539 minify: self.minify,
540 dump: DumpOpts::default(),
541 binary_outfile: self.binary_outfile,
542 debug_outfile: self.debug_outfile,
543 hex_outfile: self.hex_outfile,
544 build_target: self.build_target,
545 build_profile: self.build_profile,
546 release: self.release,
547 error_on_warnings: self.error_on_warnings,
548 time_phases: self.time_phases,
549 profile: self.profile,
550 metrics_outfile: self.metrics_outfile,
551 tests: true,
552 member_filter: Default::default(),
553 experimental: self.experimental,
554 no_experimental: self.no_experimental,
555 no_output: self.no_output,
556 }
557 }
558}
559
560impl TestResult {
561 pub fn passed(&self) -> bool {
563 match &self.condition {
564 TestPassCondition::ShouldRevert(revert_code) => match revert_code {
565 Some(revert_code) => self.state == vm::state::ProgramState::Revert(*revert_code),
566 None => matches!(self.state, vm::state::ProgramState::Revert(_)),
567 },
568 TestPassCondition::ShouldNotRevert => {
569 !matches!(self.state, vm::state::ProgramState::Revert(_))
570 }
571 }
572 }
573
574 pub fn revert_code(&self) -> Option<u64> {
576 match self.state {
577 vm::state::ProgramState::Revert(revert_code) => Some(revert_code),
578 _ => None,
579 }
580 }
581
582 pub fn revert_info(
583 &self,
584 program_abi: Option<&ProgramABI>,
585 logs: &[fuel_tx::Receipt],
586 ) -> Option<RevertInfo> {
587 let decode_last_log_data = |log_id: &str, program_abi: &ProgramABI| {
588 logs.last()
589 .and_then(|log| {
590 if let fuel_tx::Receipt::LogData {
591 data: Some(data), ..
592 } = log
593 {
594 decode_fuel_vm_log_data(log_id, data, program_abi).ok()
595 } else {
596 None
597 }
598 })
599 .map(|decoded_log| decoded_log.value)
600 };
601
602 self.revert_code()
603 .map(|revert_code| RevertInfo::new(revert_code, program_abi, decode_last_log_data))
604 }
605
606 pub fn details(&self) -> anyhow::Result<TestDetails> {
608 let span_start = self.span.start();
609 let file_str = fs::read_to_string(&*self.file_path)?;
610 let line_number = file_str[..span_start]
611 .chars()
612 .filter(|&c| c == '\n')
613 .count();
614 Ok(TestDetails {
615 file_path: self.file_path.clone(),
616 line_number,
617 })
618 }
619}
620
621pub enum TestRunnerCount {
624 Manual(usize),
625 Auto,
626}
627
628#[derive(Clone, Debug, Default)]
629pub struct TestCount {
630 pub total: usize,
631 pub ignored: usize,
632}
633
634impl TestFilter<'_> {
635 fn filter(&self, fn_name: &str) -> bool {
636 if self.exact_match {
637 fn_name == self.filter_phrase
638 } else {
639 fn_name.contains(self.filter_phrase)
640 }
641 }
642}
643
644impl BuiltTests {
645 pub fn test_count(&self, test_filter: Option<&TestFilter>) -> TestCount {
647 let pkgs: Vec<&PackageTests> = match self {
648 BuiltTests::Package(pkg) => vec![pkg],
649 BuiltTests::Workspace(workspace) => workspace.iter().collect(),
650 };
651 pkgs.iter()
652 .flat_map(|pkg| {
653 pkg.built_pkg_with_tests()
654 .bytecode
655 .entries
656 .iter()
657 .filter_map(|entry| entry.kind.test().map(|test| (entry, test)))
658 })
659 .fold(TestCount::default(), |acc, (pkg_entry, _)| {
660 let num_ignored = match &test_filter {
661 Some(filter) => {
662 if filter.filter(&pkg_entry.finalized.fn_name) {
663 acc.ignored
664 } else {
665 acc.ignored + 1
666 }
667 }
668 None => acc.ignored,
669 };
670 TestCount {
671 total: acc.total + 1,
672 ignored: num_ignored,
673 }
674 })
675 }
676
677 pub fn run(
679 self,
680 test_runner_count: TestRunnerCount,
681 test_filter: Option<TestFilter>,
682 gas_costs_values: GasCostsValues,
683 ) -> anyhow::Result<Tested> {
684 let test_runners = match test_runner_count {
685 TestRunnerCount::Manual(runner_count) => rayon::ThreadPoolBuilder::new()
686 .num_threads(runner_count)
687 .build(),
688 TestRunnerCount::Auto => rayon::ThreadPoolBuilder::new().build(),
689 }?;
690 run_tests(self, &test_runners, test_filter, gas_costs_values)
691 }
692}
693
694pub fn build(opts: TestOpts) -> anyhow::Result<BuiltTests> {
696 let build_opts: BuildOpts = opts.into();
697 let build_plan = pkg::BuildPlan::from_pkg_opts(&build_opts.pkg)?;
698 let built = pkg::build_with_options(&build_opts, None)?;
699 BuiltTests::from_built(built, &build_plan)
700}
701
702pub(crate) fn maxed_consensus_params(gas_costs_values: GasCostsValues) -> ConsensusParameters {
705 let script_params = ScriptParameters::DEFAULT
706 .with_max_script_length(u64::MAX)
707 .with_max_script_data_length(u64::MAX);
708 let tx_params = TxParameters::DEFAULT.with_max_size(u64::MAX);
709 let contract_params = ContractParameters::DEFAULT
710 .with_contract_max_size(u64::MAX)
711 .with_max_storage_slots(u64::MAX);
712 ConsensusParameters::V1(ConsensusParametersV1 {
713 script_params,
714 tx_params,
715 contract_params,
716 gas_costs: gas_costs_values.into(),
717 ..Default::default()
718 })
719}
720
721fn deployment_transaction(
724 built_pkg: &pkg::BuiltPackage,
725 without_tests_bytecode: &pkg::BuiltPackageBytecode,
726 params: &tx::ConsensusParameters,
727) -> ContractDeploymentSetup {
728 let mut storage_slots = built_pkg.storage_slots.clone();
730 storage_slots.sort();
731 let bytecode = &without_tests_bytecode.bytes;
732 let contract = tx::Contract::from(bytecode.clone());
733 let root = contract.root();
734 let state_root = tx::Contract::initial_state_root(storage_slots.iter());
735 let salt = tx::Salt::zeroed();
736 let contract_id = tx::Contract::id(&salt, &root, &state_root);
737
738 let rng = &mut rand::rngs::StdRng::seed_from_u64(TEST_METADATA_SEED);
740
741 let secret_key = SecretKey::random(rng);
743 let utxo_id = rng.r#gen();
744 let amount = 1;
745 let maturity = 1u32.into();
746 let asset_id = tx::AssetId::BASE;
750 let tx_pointer = rng.r#gen();
751 let block_height = (u32::MAX >> 1).into();
752
753 let tx = tx::TransactionBuilder::create(bytecode.as_slice().into(), salt, storage_slots)
754 .with_params(params.clone())
755 .add_unsigned_coin_input(secret_key, utxo_id, amount, asset_id, tx_pointer)
756 .add_output(tx::Output::contract_created(contract_id, state_root))
757 .maturity(maturity)
758 .finalize_checked(block_height);
759 (contract_id, tx)
760}
761
762fn run_tests(
766 built: BuiltTests,
767 test_runners: &rayon::ThreadPool,
768 test_filter: Option<TestFilter>,
769 gas_costs_values: GasCostsValues,
770) -> anyhow::Result<Tested> {
771 match built {
772 BuiltTests::Package(pkg) => {
773 let tested_pkg =
774 pkg.run_tests(test_runners, test_filter.as_ref(), gas_costs_values.clone())?;
775 Ok(Tested::Package(Box::new(tested_pkg)))
776 }
777 BuiltTests::Workspace(workspace) => {
778 let tested_pkgs = workspace
779 .into_iter()
780 .map(|pkg| {
781 pkg.run_tests(test_runners, test_filter.as_ref(), gas_costs_values.clone())
782 })
783 .collect::<anyhow::Result<Vec<TestedPackage>>>()?;
784 Ok(Tested::Workspace(tested_pkgs))
785 }
786 }
787}
788
789#[cfg(test)]
790mod tests {
791 use std::path::PathBuf;
792
793 use fuel_tx::GasCostsValues;
794
795 use crate::{build, BuiltTests, TestFilter, TestOpts, TestResult};
796
797 const TEST_DATA_FOLDER_NAME: &str = "test_data";
800 const TEST_LIBRARY_PACKAGE_NAME: &str = "test_library";
802 const TEST_CONTRACT_PACKAGE_NAME: &str = "test_contract";
804 const TEST_PREDICATE_PACKAGE_NAME: &str = "test_predicate";
806 const TEST_SCRIPT_PACKAGE_NAME: &str = "test_script";
808
809 fn test_package_built_tests(package_name: &str) -> anyhow::Result<BuiltTests> {
812 let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
813 let library_package_dir = PathBuf::from(cargo_manifest_dir)
814 .join(TEST_DATA_FOLDER_NAME)
815 .join(package_name);
816 let library_package_dir_string = library_package_dir.to_string_lossy().to_string();
817 let build_options = TestOpts {
818 pkg: forc_pkg::PkgOpts {
819 path: Some(library_package_dir_string),
820 ..Default::default()
821 },
822 ..Default::default()
823 };
824 build(build_options)
825 }
826
827 fn test_package_test_results(
828 package_name: &str,
829 test_filter: Option<TestFilter>,
830 ) -> anyhow::Result<Vec<TestResult>> {
831 let built_tests = test_package_built_tests(package_name)?;
832 let test_runner_count = crate::TestRunnerCount::Auto;
833 let tested = built_tests.run(test_runner_count, test_filter, GasCostsValues::default())?;
834 match tested {
835 crate::Tested::Package(tested_pkg) => Ok(tested_pkg.tests),
836 crate::Tested::Workspace(_) => {
837 unreachable!("test_library is a package, not a workspace.")
838 }
839 }
840 }
841
842 #[test]
843 fn test_filter_exact_match() {
844 let filter_phrase = "test_bam";
845 let test_filter = TestFilter {
846 filter_phrase,
847 exact_match: true,
848 };
849
850 let test_library_results =
851 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
852 .unwrap();
853 let tested_library_test_count = test_library_results.len();
854
855 let test_contract_results =
856 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
857 .unwrap();
858 let tested_contract_test_count = test_contract_results.len();
859
860 let test_predicate_results =
861 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
862 .unwrap();
863 let tested_predicate_test_count = test_predicate_results.len();
864
865 let test_script_results =
866 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
867 let tested_script_test_count = test_script_results.len();
868
869 assert_eq!(tested_library_test_count, 1);
870 assert_eq!(tested_contract_test_count, 1);
871 assert_eq!(tested_predicate_test_count, 1);
872 assert_eq!(tested_script_test_count, 1);
873 }
874
875 #[test]
876 fn test_filter_exact_match_all_ignored() {
877 let filter_phrase = "test_ba";
878 let test_filter = TestFilter {
879 filter_phrase,
880 exact_match: true,
881 };
882
883 let test_library_results =
884 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
885 .unwrap();
886 let tested_library_test_count = test_library_results.len();
887
888 let test_contract_results =
889 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
890 .unwrap();
891 let tested_contract_test_count = test_contract_results.len();
892
893 let test_predicate_results =
894 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
895 .unwrap();
896 let tested_predicate_test_count = test_predicate_results.len();
897
898 let test_script_results =
899 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
900 let tested_script_test_count = test_script_results.len();
901
902 assert_eq!(tested_library_test_count, 0);
903 assert_eq!(tested_contract_test_count, 0);
904 assert_eq!(tested_predicate_test_count, 0);
905 assert_eq!(tested_script_test_count, 0);
906 }
907
908 #[test]
909 fn test_filter_match_all_ignored() {
910 let filter_phrase = "this_test_does_not_exists";
911 let test_filter = TestFilter {
912 filter_phrase,
913 exact_match: false,
914 };
915
916 let test_library_results =
917 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
918 .unwrap();
919 let tested_library_test_count = test_library_results.len();
920
921 let test_contract_results =
922 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
923 .unwrap();
924 let tested_contract_test_count = test_contract_results.len();
925
926 let test_predicate_results =
927 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
928 .unwrap();
929 let tested_predicate_test_count = test_predicate_results.len();
930
931 let test_script_results =
932 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
933 let tested_script_test_count = test_script_results.len();
934
935 assert_eq!(tested_library_test_count, 0);
936 assert_eq!(tested_contract_test_count, 0);
937 assert_eq!(tested_predicate_test_count, 0);
938 assert_eq!(tested_script_test_count, 0);
939 }
940
941 #[test]
942 fn test_filter_one_match() {
943 let filter_phrase = "test_ba";
944 let test_filter = TestFilter {
945 filter_phrase,
946 exact_match: false,
947 };
948
949 let test_library_results =
950 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
951 .unwrap();
952 let tested_library_test_count = test_library_results.len();
953
954 let test_contract_results =
955 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
956 .unwrap();
957 let tested_contract_test_count = test_contract_results.len();
958
959 let test_predicate_results =
960 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
961 .unwrap();
962 let tested_predicate_test_count = test_predicate_results.len();
963
964 let test_script_results =
965 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
966 let tested_script_test_count = test_script_results.len();
967
968 assert_eq!(tested_library_test_count, 1);
969 assert_eq!(tested_contract_test_count, 1);
970 assert_eq!(tested_predicate_test_count, 1);
971 assert_eq!(tested_script_test_count, 1);
972 }
973
974 #[test]
975 fn test_filter_all_match() {
976 let filter_phrase = "est_b";
977 let test_filter = TestFilter {
978 filter_phrase,
979 exact_match: false,
980 };
981
982 let test_library_results =
983 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
984 .unwrap();
985 let tested_library_test_count = test_library_results.len();
986
987 let test_contract_results =
988 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
989 .unwrap();
990 let tested_contract_test_count = test_contract_results.len();
991
992 let test_predicate_results =
993 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
994 .unwrap();
995 let tested_predicate_test_count = test_predicate_results.len();
996
997 let test_script_results =
998 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
999 let tested_script_test_count = test_script_results.len();
1000
1001 assert_eq!(tested_library_test_count, 2);
1002 assert_eq!(tested_contract_test_count, 2);
1003 assert_eq!(tested_predicate_test_count, 2);
1004 assert_eq!(tested_script_test_count, 2);
1005 }
1006
1007 #[test]
1008 fn test_no_filter() {
1009 let test_filter = None;
1010
1011 let test_library_results =
1012 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, test_filter.clone()).unwrap();
1013 let tested_library_test_count = test_library_results.len();
1014
1015 let test_contract_results =
1016 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, test_filter.clone()).unwrap();
1017 let tested_contract_test_count = test_contract_results.len();
1018
1019 let test_predicate_results =
1020 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, test_filter.clone()).unwrap();
1021 let tested_predicate_test_count = test_predicate_results.len();
1022
1023 let test_script_results =
1024 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, test_filter).unwrap();
1025 let tested_script_test_count = test_script_results.len();
1026
1027 assert_eq!(tested_library_test_count, 2);
1028 assert_eq!(tested_contract_test_count, 2);
1029 assert_eq!(tested_predicate_test_count, 2);
1030 assert_eq!(tested_script_test_count, 2);
1031 }
1032}