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