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(GasCostsValues::default(), TestGasLimit::default());
277 let storage = vm::storage::MemoryStorage::default();
278 let interpreter_params = InterpreterParams::new(gas_price, params.clone());
279 let mut interpreter: vm::prelude::Interpreter<_, _, _, vm::interpreter::NotSupportedEcal> =
280 vm::interpreter::Interpreter::with_storage(
281 MemoryInstance::new(),
282 storage,
283 interpreter_params,
284 );
285
286 let contract_dependency_setups = self
289 .contract_dependencies()
290 .map(|built_pkg| deployment_transaction(built_pkg, &built_pkg.bytecode, ¶ms));
291
292 let contract_dependency_ids = contract_dependency_setups
294 .map(|(contract_id, tx)| {
295 let tx = tx
297 .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
298 .unwrap();
299 interpreter.transact(tx).map_err(anyhow::Error::msg)?;
300 Ok(contract_id)
301 })
302 .collect::<anyhow::Result<Vec<_>>>()?;
303
304 let deployment_setup = if let PackageWithDeploymentToTest::Contract(contract_to_test) = self
305 {
306 let (root_contract_id, root_contract_tx) = deployment_transaction(
309 &contract_to_test.pkg,
310 &contract_to_test.without_tests_bytecode,
311 ¶ms,
312 );
313 let root_contract_tx = root_contract_tx
314 .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
315 .unwrap();
316 interpreter
318 .transact(root_contract_tx)
319 .map_err(anyhow::Error::msg)?;
320 let storage = interpreter.as_ref().clone();
321 DeploymentSetup::Contract(ContractTestSetup {
322 storage,
323 contract_dependency_ids,
324 root_contract_id,
325 })
326 } else {
327 let storage = interpreter.as_ref().clone();
328 DeploymentSetup::Script(ScriptTestSetup {
329 storage,
330 contract_dependency_ids,
331 })
332 };
333
334 Ok(TestSetup::WithDeployment(deployment_setup))
335 }
336}
337
338fn get_contract_dependency_map(
343 built: &Built,
344 build_plan: &pkg::BuildPlan,
345) -> ContractDependencyMap {
346 let built_members: HashMap<&pkg::Pinned, Arc<pkg::BuiltPackage>> =
347 built.into_members().collect();
348 build_plan
350 .member_nodes()
351 .map(|member_node| {
352 let graph = build_plan.graph();
353 let pinned_member = graph[member_node].clone();
354 let contract_dependencies = build_plan
355 .contract_dependencies(member_node)
356 .map(|contract_dependency_node_ix| graph[contract_dependency_node_ix].clone())
357 .filter_map(|pinned| built_members.get(&pinned))
358 .cloned()
359 .collect::<Vec<_>>();
360 (pinned_member, contract_dependencies)
361 })
362 .collect()
363}
364
365impl BuiltTests {
366 pub fn from_built(built: Built, build_plan: &pkg::BuildPlan) -> anyhow::Result<BuiltTests> {
368 let contract_dependencies = get_contract_dependency_map(&built, build_plan);
369 let built = match built {
370 Built::Package(built_pkg) => BuiltTests::Package(PackageTests::from_built_pkg(
371 built_pkg,
372 &contract_dependencies,
373 )),
374 Built::Workspace(built_workspace) => {
375 let pkg_tests = built_workspace
376 .into_iter()
377 .map(|built_pkg| {
378 PackageTests::from_built_pkg(built_pkg, &contract_dependencies)
379 })
380 .collect();
381 BuiltTests::Workspace(pkg_tests)
382 }
383 };
384 Ok(built)
385 }
386}
387
388impl<'a> PackageTests {
389 pub(crate) fn built_pkg_with_tests(&'a self) -> &'a BuiltPackage {
394 match self {
395 PackageTests::Contract(contract) => contract.pkg(),
396 PackageTests::Script(script) => script.pkg(),
397 PackageTests::Predicate(predicate) => predicate,
398 PackageTests::Library(library) => library,
399 }
400 }
401
402 fn from_built_pkg(
404 built_pkg: Arc<BuiltPackage>,
405 contract_dependencies: &ContractDependencyMap,
406 ) -> PackageTests {
407 let built_without_tests_bytecode = built_pkg.bytecode_without_tests.clone();
408 let contract_dependencies: Vec<Arc<pkg::BuiltPackage>> = contract_dependencies
409 .get(&built_pkg.descriptor.pinned)
410 .cloned()
411 .unwrap_or_default();
412 match built_without_tests_bytecode {
413 Some(contract_without_tests) => {
414 let contract_to_test = ContractToTest {
415 pkg: built_pkg,
416 without_tests_bytecode: contract_without_tests,
417 contract_dependencies,
418 };
419 PackageTests::Contract(PackageWithDeploymentToTest::Contract(contract_to_test))
420 }
421 None => match built_pkg.tree_type {
422 sway_core::language::parsed::TreeType::Predicate => {
423 PackageTests::Predicate(built_pkg)
424 }
425 sway_core::language::parsed::TreeType::Library => PackageTests::Library(built_pkg),
426 sway_core::language::parsed::TreeType::Script => {
427 let script_to_test = ScriptToTest {
428 pkg: built_pkg,
429 contract_dependencies,
430 };
431 PackageTests::Script(PackageWithDeploymentToTest::Script(script_to_test))
432 }
433 _ => unreachable!("contracts are already handled"),
434 },
435 }
436 }
437
438 pub(crate) fn run_tests(
440 &self,
441 test_runners: &rayon::ThreadPool,
442 test_filter: Option<&TestFilter>,
443 gas_costs_values: GasCostsValues,
444 gas_limit: TestGasLimit,
445 ) -> anyhow::Result<TestedPackage> {
446 let pkg_with_tests = self.built_pkg_with_tests();
447 let tests = test_runners.install(|| {
448 pkg_with_tests
449 .bytecode
450 .entries
451 .par_iter()
452 .filter_map(|entry| {
453 if let Some(test_entry) = entry.kind.test() {
454 let name = entry.finalized.fn_name.clone();
457 if let Some(filter) = test_filter {
458 if !filter.filter(&name) {
459 return None;
460 }
461 }
462 return Some((entry, test_entry));
463 }
464 None
465 })
466 .map(|(entry, test_entry)| {
467 let offset = u32::try_from(entry.finalized.imm)
469 .expect("test instruction offset out of range");
470 let name = entry.finalized.fn_name.clone();
471 let test_setup = self.setup()?;
472 TestExecutor::build(
473 &pkg_with_tests.bytecode.bytes,
474 offset,
475 test_setup,
476 test_entry,
477 name,
478 gas_costs_values.clone(),
479 gas_limit,
480 )?
481 .execute()
482 })
483 .collect::<anyhow::Result<_>>()
484 })?;
485
486 Ok(TestedPackage {
487 built: Box::new(pkg_with_tests.clone()),
488 tests,
489 })
490 }
491
492 pub fn setup(&self) -> anyhow::Result<TestSetup> {
497 match self {
498 PackageTests::Contract(contract_to_test) => {
499 let test_setup = contract_to_test.deploy()?;
500 Ok(test_setup)
501 }
502 PackageTests::Script(script_to_test) => {
503 let test_setup = script_to_test.deploy()?;
504 Ok(test_setup)
505 }
506 PackageTests::Predicate(_) | PackageTests::Library(_) => Ok(
507 TestSetup::WithoutDeployment(vm::storage::MemoryStorage::default()),
508 ),
509 }
510 }
511}
512
513impl From<TestOpts> for pkg::BuildOpts {
514 fn from(val: TestOpts) -> Self {
515 pkg::BuildOpts {
516 pkg: val.pkg,
517 print: val.print,
518 verify_ir: val.verify_ir,
519 minify: val.minify,
520 dump: DumpOpts::default(),
521 binary_outfile: val.binary_outfile,
522 debug_outfile: val.debug_outfile,
523 hex_outfile: val.hex_outfile,
524 build_target: val.build_target,
525 build_profile: val.build_profile,
526 release: val.release,
527 error_on_warnings: val.error_on_warnings,
528 time_phases: val.time_phases,
529 profile: val.profile,
530 metrics_outfile: val.metrics_outfile,
531 tests: true,
532 member_filter: Default::default(),
533 experimental: val.experimental,
534 no_experimental: val.no_experimental,
535 no_output: val.no_output,
536 }
537 }
538}
539
540impl TestOpts {
541 pub fn into_build_opts(self) -> pkg::BuildOpts {
543 pkg::BuildOpts {
544 pkg: self.pkg,
545 print: self.print,
546 verify_ir: self.verify_ir,
547 minify: self.minify,
548 dump: DumpOpts::default(),
549 binary_outfile: self.binary_outfile,
550 debug_outfile: self.debug_outfile,
551 hex_outfile: self.hex_outfile,
552 build_target: self.build_target,
553 build_profile: self.build_profile,
554 release: self.release,
555 error_on_warnings: self.error_on_warnings,
556 time_phases: self.time_phases,
557 profile: self.profile,
558 metrics_outfile: self.metrics_outfile,
559 tests: true,
560 member_filter: Default::default(),
561 experimental: self.experimental,
562 no_experimental: self.no_experimental,
563 no_output: self.no_output,
564 }
565 }
566}
567
568impl TestResult {
569 pub fn passed(&self) -> bool {
571 match &self.condition {
572 TestPassCondition::ShouldRevert(revert_code) => match revert_code {
573 Some(revert_code) => self.state == vm::state::ProgramState::Revert(*revert_code),
574 None => matches!(self.state, vm::state::ProgramState::Revert(_)),
575 },
576 TestPassCondition::ShouldNotRevert => {
577 !matches!(self.state, vm::state::ProgramState::Revert(_))
578 }
579 }
580 }
581
582 pub fn revert_info(
584 &self,
585 program_abi: Option<&fuel_abi_types::abi::program::ProgramABI>,
586 logs: &[fuel_tx::Receipt],
587 ) -> Option<RevertInfo> {
588 if let vm::state::ProgramState::Revert(revert_code) = self.state {
589 return forc_util::tx_utils::revert_info_from_receipts(
590 logs,
591 program_abi,
592 Some(revert_code),
593 )
594 .filter(|info| info.revert_code == revert_code);
595 }
596 None
597 }
598
599 pub fn details(&self) -> anyhow::Result<TestDetails> {
601 let span_start = self.span.start();
602 let file_str = fs::read_to_string(&*self.file_path)?;
603 let line_number = file_str[..span_start]
604 .chars()
605 .filter(|&c| c == '\n')
606 .count();
607 Ok(TestDetails {
608 file_path: self.file_path.clone(),
609 line_number,
610 })
611 }
612}
613
614pub enum TestRunnerCount {
617 Manual(usize),
618 Auto,
619}
620
621#[derive(Clone, Debug, Default)]
622pub struct TestCount {
623 pub total: usize,
624 pub ignored: usize,
625}
626
627impl TestFilter<'_> {
628 fn filter(&self, fn_name: &str) -> bool {
629 if self.exact_match {
630 fn_name == self.filter_phrase
631 } else {
632 fn_name.contains(self.filter_phrase)
633 }
634 }
635}
636
637impl BuiltTests {
638 pub fn test_count(&self, test_filter: Option<&TestFilter>) -> TestCount {
640 let pkgs: Vec<&PackageTests> = match self {
641 BuiltTests::Package(pkg) => vec![pkg],
642 BuiltTests::Workspace(workspace) => workspace.iter().collect(),
643 };
644 pkgs.iter()
645 .flat_map(|pkg| {
646 pkg.built_pkg_with_tests()
647 .bytecode
648 .entries
649 .iter()
650 .filter_map(|entry| entry.kind.test().map(|test| (entry, test)))
651 })
652 .fold(TestCount::default(), |acc, (pkg_entry, _)| {
653 let num_ignored = match &test_filter {
654 Some(filter) => {
655 if filter.filter(&pkg_entry.finalized.fn_name) {
656 acc.ignored
657 } else {
658 acc.ignored + 1
659 }
660 }
661 None => acc.ignored,
662 };
663 TestCount {
664 total: acc.total + 1,
665 ignored: num_ignored,
666 }
667 })
668 }
669
670 pub fn run(
672 self,
673 test_runner_count: TestRunnerCount,
674 test_filter: Option<TestFilter>,
675 gas_costs_values: GasCostsValues,
676 gas_limit: TestGasLimit,
677 ) -> anyhow::Result<Tested> {
678 let test_runners = match test_runner_count {
679 TestRunnerCount::Manual(runner_count) => rayon::ThreadPoolBuilder::new()
680 .num_threads(runner_count)
681 .build(),
682 TestRunnerCount::Auto => rayon::ThreadPoolBuilder::new().build(),
683 }?;
684 run_tests(
685 self,
686 &test_runners,
687 test_filter,
688 gas_costs_values,
689 gas_limit,
690 )
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(
705 gas_costs_values: GasCostsValues,
706 gas_limit: TestGasLimit,
707) -> ConsensusParameters {
708 let script_params = ScriptParameters::DEFAULT
709 .with_max_script_length(u64::MAX)
710 .with_max_script_data_length(u64::MAX);
711 let tx_params = match gas_limit {
712 TestGasLimit::Default => TxParameters::DEFAULT,
713 TestGasLimit::Unlimited => TxParameters::DEFAULT.with_max_gas_per_tx(u64::MAX),
714 TestGasLimit::Limited(limit) => TxParameters::DEFAULT.with_max_gas_per_tx(limit),
715 }
716 .with_max_size(u64::MAX);
717 let contract_params = ContractParameters::DEFAULT
718 .with_contract_max_size(u64::MAX)
719 .with_max_storage_slots(u64::MAX);
720 ConsensusParameters::V1(ConsensusParametersV1 {
721 script_params,
722 tx_params,
723 contract_params,
724 gas_costs: gas_costs_values.into(),
725 block_gas_limit: u64::MAX,
726 ..Default::default()
727 })
728}
729
730fn deployment_transaction(
733 built_pkg: &pkg::BuiltPackage,
734 without_tests_bytecode: &pkg::BuiltPackageBytecode,
735 params: &tx::ConsensusParameters,
736) -> ContractDeploymentSetup {
737 let mut storage_slots = built_pkg.storage_slots.clone();
739 storage_slots.sort();
740 let bytecode = &without_tests_bytecode.bytes;
741 let contract = tx::Contract::from(bytecode.clone());
742 let root = contract.root();
743 let state_root = tx::Contract::initial_state_root(storage_slots.iter());
744 let salt = tx::Salt::zeroed();
745 let contract_id = tx::Contract::id(&salt, &root, &state_root);
746
747 let rng = &mut rand::rngs::StdRng::seed_from_u64(TEST_METADATA_SEED);
749
750 let secret_key = SecretKey::random(rng);
752 let utxo_id = rng.r#gen();
753 let amount = 1;
754 let maturity = 1u32.into();
755 let asset_id = tx::AssetId::BASE;
759 let tx_pointer = rng.r#gen();
760 let block_height = (u32::MAX >> 1).into();
761
762 let tx = tx::TransactionBuilder::create(bytecode.as_slice().into(), salt, storage_slots)
763 .with_params(params.clone())
764 .add_unsigned_coin_input(secret_key, utxo_id, amount, asset_id, tx_pointer)
765 .add_output(tx::Output::contract_created(contract_id, state_root))
766 .maturity(maturity)
767 .finalize_checked(block_height);
768 (contract_id, tx)
769}
770
771fn run_tests(
775 built: BuiltTests,
776 test_runners: &rayon::ThreadPool,
777 test_filter: Option<TestFilter>,
778 gas_costs_values: GasCostsValues,
779 gas_limit: TestGasLimit,
780) -> anyhow::Result<Tested> {
781 match built {
782 BuiltTests::Package(pkg) => {
783 let tested_pkg = pkg.run_tests(
784 test_runners,
785 test_filter.as_ref(),
786 gas_costs_values.clone(),
787 gas_limit,
788 )?;
789 Ok(Tested::Package(Box::new(tested_pkg)))
790 }
791 BuiltTests::Workspace(workspace) => {
792 let tested_pkgs = workspace
793 .into_iter()
794 .map(|pkg| {
795 pkg.run_tests(
796 test_runners,
797 test_filter.as_ref(),
798 gas_costs_values.clone(),
799 gas_limit,
800 )
801 })
802 .collect::<anyhow::Result<Vec<TestedPackage>>>()?;
803 Ok(Tested::Workspace(tested_pkgs))
804 }
805 }
806}
807
808#[cfg(test)]
809mod tests {
810 use std::path::PathBuf;
811
812 use fuel_tx::GasCostsValues;
813
814 use crate::{build, BuiltTests, TestFilter, TestGasLimit, TestOpts, TestResult};
815
816 const TEST_DATA_FOLDER_NAME: &str = "test_data";
819 const TEST_LIBRARY_PACKAGE_NAME: &str = "test_library";
821 const TEST_CONTRACT_PACKAGE_NAME: &str = "test_contract";
823 const TEST_PREDICATE_PACKAGE_NAME: &str = "test_predicate";
825 const TEST_SCRIPT_PACKAGE_NAME: &str = "test_script";
827
828 fn test_package_built_tests(package_name: &str) -> anyhow::Result<BuiltTests> {
831 let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
832 let library_package_dir = PathBuf::from(cargo_manifest_dir)
833 .join(TEST_DATA_FOLDER_NAME)
834 .join(package_name);
835 let library_package_dir_string = library_package_dir.to_string_lossy().to_string();
836 let build_options = TestOpts {
837 pkg: forc_pkg::PkgOpts {
838 path: Some(library_package_dir_string),
839 ..Default::default()
840 },
841 ..Default::default()
842 };
843 build(build_options)
844 }
845
846 fn test_package_test_results(
847 package_name: &str,
848 test_filter: Option<TestFilter>,
849 ) -> anyhow::Result<Vec<TestResult>> {
850 let built_tests = test_package_built_tests(package_name)?;
851 let test_runner_count = crate::TestRunnerCount::Auto;
852 let tested = built_tests.run(
853 test_runner_count,
854 test_filter,
855 GasCostsValues::default(),
856 TestGasLimit::default(),
857 )?;
858 match tested {
859 crate::Tested::Package(tested_pkg) => Ok(tested_pkg.tests),
860 crate::Tested::Workspace(_) => {
861 unreachable!("test_library is a package, not a workspace.")
862 }
863 }
864 }
865
866 #[test]
867 fn test_filter_exact_match() {
868 let filter_phrase = "test_bam";
869 let test_filter = TestFilter {
870 filter_phrase,
871 exact_match: true,
872 };
873
874 let test_library_results =
875 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
876 .unwrap();
877 let tested_library_test_count = test_library_results.len();
878
879 let test_contract_results =
880 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
881 .unwrap();
882 let tested_contract_test_count = test_contract_results.len();
883
884 let test_predicate_results =
885 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
886 .unwrap();
887 let tested_predicate_test_count = test_predicate_results.len();
888
889 let test_script_results =
890 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
891 let tested_script_test_count = test_script_results.len();
892
893 assert_eq!(tested_library_test_count, 1);
894 assert_eq!(tested_contract_test_count, 1);
895 assert_eq!(tested_predicate_test_count, 1);
896 assert_eq!(tested_script_test_count, 1);
897 }
898
899 #[test]
900 fn test_filter_exact_match_all_ignored() {
901 let filter_phrase = "test_ba";
902 let test_filter = TestFilter {
903 filter_phrase,
904 exact_match: true,
905 };
906
907 let test_library_results =
908 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
909 .unwrap();
910 let tested_library_test_count = test_library_results.len();
911
912 let test_contract_results =
913 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
914 .unwrap();
915 let tested_contract_test_count = test_contract_results.len();
916
917 let test_predicate_results =
918 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
919 .unwrap();
920 let tested_predicate_test_count = test_predicate_results.len();
921
922 let test_script_results =
923 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
924 let tested_script_test_count = test_script_results.len();
925
926 assert_eq!(tested_library_test_count, 0);
927 assert_eq!(tested_contract_test_count, 0);
928 assert_eq!(tested_predicate_test_count, 0);
929 assert_eq!(tested_script_test_count, 0);
930 }
931
932 #[test]
933 fn test_filter_match_all_ignored() {
934 let filter_phrase = "this_test_does_not_exists";
935 let test_filter = TestFilter {
936 filter_phrase,
937 exact_match: false,
938 };
939
940 let test_library_results =
941 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
942 .unwrap();
943 let tested_library_test_count = test_library_results.len();
944
945 let test_contract_results =
946 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
947 .unwrap();
948 let tested_contract_test_count = test_contract_results.len();
949
950 let test_predicate_results =
951 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
952 .unwrap();
953 let tested_predicate_test_count = test_predicate_results.len();
954
955 let test_script_results =
956 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
957 let tested_script_test_count = test_script_results.len();
958
959 assert_eq!(tested_library_test_count, 0);
960 assert_eq!(tested_contract_test_count, 0);
961 assert_eq!(tested_predicate_test_count, 0);
962 assert_eq!(tested_script_test_count, 0);
963 }
964
965 #[test]
966 fn test_filter_one_match() {
967 let filter_phrase = "test_ba";
968 let test_filter = TestFilter {
969 filter_phrase,
970 exact_match: false,
971 };
972
973 let test_library_results =
974 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
975 .unwrap();
976 let tested_library_test_count = test_library_results.len();
977
978 let test_contract_results =
979 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
980 .unwrap();
981 let tested_contract_test_count = test_contract_results.len();
982
983 let test_predicate_results =
984 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
985 .unwrap();
986 let tested_predicate_test_count = test_predicate_results.len();
987
988 let test_script_results =
989 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
990 let tested_script_test_count = test_script_results.len();
991
992 assert_eq!(tested_library_test_count, 1);
993 assert_eq!(tested_contract_test_count, 1);
994 assert_eq!(tested_predicate_test_count, 1);
995 assert_eq!(tested_script_test_count, 1);
996 }
997
998 #[test]
999 fn test_filter_all_match() {
1000 let filter_phrase = "est_b";
1001 let test_filter = TestFilter {
1002 filter_phrase,
1003 exact_match: false,
1004 };
1005
1006 let test_library_results =
1007 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
1008 .unwrap();
1009 let tested_library_test_count = test_library_results.len();
1010
1011 let test_contract_results =
1012 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
1013 .unwrap();
1014 let tested_contract_test_count = test_contract_results.len();
1015
1016 let test_predicate_results =
1017 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
1018 .unwrap();
1019 let tested_predicate_test_count = test_predicate_results.len();
1020
1021 let test_script_results =
1022 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
1023 let tested_script_test_count = test_script_results.len();
1024
1025 assert_eq!(tested_library_test_count, 2);
1026 assert_eq!(tested_contract_test_count, 2);
1027 assert_eq!(tested_predicate_test_count, 2);
1028 assert_eq!(tested_script_test_count, 2);
1029 }
1030
1031 #[test]
1032 fn test_no_filter() {
1033 let test_filter = None;
1034
1035 let test_library_results =
1036 test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, test_filter.clone()).unwrap();
1037 let tested_library_test_count = test_library_results.len();
1038
1039 let test_contract_results =
1040 test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, test_filter.clone()).unwrap();
1041 let tested_contract_test_count = test_contract_results.len();
1042
1043 let test_predicate_results =
1044 test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, test_filter.clone()).unwrap();
1045 let tested_predicate_test_count = test_predicate_results.len();
1046
1047 let test_script_results =
1048 test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, test_filter).unwrap();
1049 let tested_script_test_count = test_script_results.len();
1050
1051 assert_eq!(tested_library_test_count, 2);
1052 assert_eq!(tested_contract_test_count, 2);
1053 assert_eq!(tested_predicate_test_count, 2);
1054 assert_eq!(tested_script_test_count, 2);
1055 }
1056}