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;
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 minify: pkg::MinifyOpts,
147 pub binary_outfile: Option<String>,
149 pub debug_outfile: Option<String>,
153 pub hex_outfile: Option<String>,
155 pub build_target: BuildTarget,
157 pub build_profile: String,
159 pub release: bool,
162 pub error_on_warnings: bool,
164 pub time_phases: bool,
166 pub profile: bool,
168 pub metrics_outfile: Option<String>,
170 pub experimental: Vec<sway_features::Feature>,
172 pub no_experimental: Vec<sway_features::Feature>,
174 pub no_output: bool,
176}
177
178#[derive(Default, Clone)]
180pub struct TestPrintOpts {
181 pub pretty_print: bool,
182 pub print_logs: bool,
183}
184
185#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
186pub enum GasCostsSource {
187 #[default]
188 BuiltIn,
189 Mainnet,
190 Testnet,
191 File(String),
192}
193
194impl GasCostsSource {
195 pub fn provide_gas_costs(&self) -> Result<GasCostsValues, anyhow::Error> {
196 match self {
197 Self::BuiltIn => Ok(serde_json::from_str(include_str!(
201 "../gas_costs_values.json"
202 ))?),
203 Self::Mainnet => Err(anyhow::anyhow!(
206 "Fetching gas costs from mainnet is currently not implemented."
207 )),
208 Self::Testnet => Err(anyhow::anyhow!(
209 "Fetching gas costs from testnet is currently not implemented."
210 )),
211 Self::File(_file_path) => Err(anyhow::anyhow!(
212 "Loading gas costs from a JSON file is currently not implemented."
213 )),
214 }
215 }
216}
217
218impl FromStr for GasCostsSource {
219 type Err = anyhow::Error;
220
221 fn from_str(value: &str) -> Result<Self, Self::Err> {
222 match value {
223 "built-in" => Ok(Self::BuiltIn),
224 "mainnet" => Ok(Self::Mainnet),
225 "testnet" => Ok(Self::Testnet),
226 file_path => Ok(Self::File(file_path.to_string())),
227 }
228 }
229}
230
231pub struct DecodedLog {
233 pub value: String,
234}
235
236impl TestedPackage {
237 pub fn tests_passed(&self) -> bool {
238 self.tests.iter().all(|test| test.passed())
239 }
240}
241
242impl PackageWithDeploymentToTest {
243 fn pkg(&self) -> &BuiltPackage {
247 match self {
248 PackageWithDeploymentToTest::Script(script) => &script.pkg,
249 PackageWithDeploymentToTest::Contract(contract) => &contract.pkg,
250 }
251 }
252
253 fn contract_dependencies(&self) -> impl Iterator<Item = &Arc<BuiltPackage>> + '_ {
255 match self {
256 PackageWithDeploymentToTest::Script(script_to_test) => {
257 script_to_test.contract_dependencies.iter()
258 }
259 PackageWithDeploymentToTest::Contract(contract_to_test) => {
260 contract_to_test.contract_dependencies.iter()
261 }
262 }
263 }
264
265 fn deploy(&self) -> anyhow::Result<TestSetup> {
270 let gas_price = 0;
272 let params = maxed_consensus_params(
276 GasCostsSource::BuiltIn.provide_gas_costs()?,
277 TestGasLimit::default(),
278 );
279 let storage = vm::storage::MemoryStorage::default();
280 let interpreter_params = InterpreterParams::new(gas_price, params.clone());
281 let mut interpreter: vm::prelude::Interpreter<_, _, _, vm::interpreter::NotSupportedEcal> =
282 vm::interpreter::Interpreter::with_storage(
283 MemoryInstance::new(),
284 storage,
285 interpreter_params,
286 );
287
288 let contract_dependency_setups = self
291 .contract_dependencies()
292 .map(|built_pkg| deployment_transaction(built_pkg, &built_pkg.bytecode, ¶ms));
293
294 let contract_dependency_ids = contract_dependency_setups
296 .map(|(contract_id, tx)| {
297 let tx = tx
299 .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
300 .unwrap();
301 interpreter.transact(tx).map_err(anyhow::Error::msg)?;
302 Ok(contract_id)
303 })
304 .collect::<anyhow::Result<Vec<_>>>()?;
305
306 let deployment_setup = if let PackageWithDeploymentToTest::Contract(contract_to_test) = self
307 {
308 let (root_contract_id, root_contract_tx) = deployment_transaction(
311 &contract_to_test.pkg,
312 &contract_to_test.without_tests_bytecode,
313 ¶ms,
314 );
315 let root_contract_tx = root_contract_tx
316 .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
317 .unwrap();
318 interpreter
320 .transact(root_contract_tx)
321 .map_err(anyhow::Error::msg)?;
322 let storage = interpreter.as_ref().clone();
323 DeploymentSetup::Contract(ContractTestSetup {
324 storage,
325 contract_dependency_ids,
326 root_contract_id,
327 })
328 } else {
329 let storage = interpreter.as_ref().clone();
330 DeploymentSetup::Script(ScriptTestSetup {
331 storage,
332 contract_dependency_ids,
333 })
334 };
335
336 Ok(TestSetup::WithDeployment(deployment_setup))
337 }
338}
339
340fn get_contract_dependency_map(
345 built: &Built,
346 build_plan: &pkg::BuildPlan,
347) -> ContractDependencyMap {
348 let built_members: HashMap<&pkg::Pinned, Arc<pkg::BuiltPackage>> =
349 built.into_members().collect();
350 build_plan
352 .member_nodes()
353 .map(|member_node| {
354 let graph = build_plan.graph();
355 let pinned_member = graph[member_node].clone();
356 let contract_dependencies = build_plan
357 .contract_dependencies(member_node)
358 .map(|contract_dependency_node_ix| graph[contract_dependency_node_ix].clone())
359 .filter_map(|pinned| built_members.get(&pinned))
360 .cloned()
361 .collect::<Vec<_>>();
362 (pinned_member, contract_dependencies)
363 })
364 .collect()
365}
366
367impl BuiltTests {
368 pub fn from_built(built: Built, build_plan: &pkg::BuildPlan) -> anyhow::Result<BuiltTests> {
370 let contract_dependencies = get_contract_dependency_map(&built, build_plan);
371 let built = match built {
372 Built::Package(built_pkg) => BuiltTests::Package(PackageTests::from_built_pkg(
373 built_pkg,
374 &contract_dependencies,
375 )),
376 Built::Workspace(built_workspace) => {
377 let pkg_tests = built_workspace
378 .into_iter()
379 .map(|built_pkg| {
380 PackageTests::from_built_pkg(built_pkg, &contract_dependencies)
381 })
382 .collect();
383 BuiltTests::Workspace(pkg_tests)
384 }
385 };
386 Ok(built)
387 }
388}
389
390impl<'a> PackageTests {
391 pub(crate) fn built_pkg_with_tests(&'a self) -> &'a BuiltPackage {
396 match self {
397 PackageTests::Contract(contract) => contract.pkg(),
398 PackageTests::Script(script) => script.pkg(),
399 PackageTests::Predicate(predicate) => predicate,
400 PackageTests::Library(library) => library,
401 }
402 }
403
404 fn from_built_pkg(
406 built_pkg: Arc<BuiltPackage>,
407 contract_dependencies: &ContractDependencyMap,
408 ) -> PackageTests {
409 let built_without_tests_bytecode = built_pkg.bytecode_without_tests.clone();
410 let contract_dependencies: Vec<Arc<pkg::BuiltPackage>> = contract_dependencies
411 .get(&built_pkg.descriptor.pinned)
412 .cloned()
413 .unwrap_or_default();
414 match built_without_tests_bytecode {
415 Some(contract_without_tests) => {
416 let contract_to_test = ContractToTest {
417 pkg: built_pkg,
418 without_tests_bytecode: contract_without_tests,
419 contract_dependencies,
420 };
421 PackageTests::Contract(PackageWithDeploymentToTest::Contract(contract_to_test))
422 }
423 None => match built_pkg.tree_type {
424 sway_core::language::parsed::TreeType::Predicate => {
425 PackageTests::Predicate(built_pkg)
426 }
427 sway_core::language::parsed::TreeType::Library => PackageTests::Library(built_pkg),
428 sway_core::language::parsed::TreeType::Script => {
429 let script_to_test = ScriptToTest {
430 pkg: built_pkg,
431 contract_dependencies,
432 };
433 PackageTests::Script(PackageWithDeploymentToTest::Script(script_to_test))
434 }
435 _ => unreachable!("contracts are already handled"),
436 },
437 }
438 }
439
440 pub(crate) fn run_tests(
442 &self,
443 test_runners: &rayon::ThreadPool,
444 test_filter: Option<&TestFilter>,
445 gas_costs_values: GasCostsValues,
446 gas_limit: TestGasLimit,
447 ) -> anyhow::Result<TestedPackage> {
448 let pkg_with_tests = self.built_pkg_with_tests();
449 let tests = test_runners.install(|| {
450 pkg_with_tests
451 .bytecode
452 .entries
453 .par_iter()
454 .filter_map(|entry| {
455 if let Some(test_entry) = entry.kind.test() {
456 let name = entry.finalized.fn_name.clone();
459 if let Some(filter) = test_filter {
460 if !filter.filter(&name) {
461 return None;
462 }
463 }
464 return Some((entry, test_entry));
465 }
466 None
467 })
468 .map(|(entry, test_entry)| {
469 let offset = u32::try_from(entry.finalized.imm)
471 .expect("test instruction offset out of range");
472 let name = entry.finalized.fn_name.clone();
473 let test_setup = self.setup()?;
474 TestExecutor::build(
475 &pkg_with_tests.bytecode.bytes,
476 offset,
477 test_setup,
478 test_entry,
479 name,
480 gas_costs_values.clone(),
481 gas_limit,
482 )?
483 .execute()
484 })
485 .collect::<anyhow::Result<_>>()
486 })?;
487
488 Ok(TestedPackage {
489 built: Box::new(pkg_with_tests.clone()),
490 tests,
491 })
492 }
493
494 pub fn setup(&self) -> anyhow::Result<TestSetup> {
499 match self {
500 PackageTests::Contract(contract_to_test) => {
501 let test_setup = contract_to_test.deploy()?;
502 Ok(test_setup)
503 }
504 PackageTests::Script(script_to_test) => {
505 let test_setup = script_to_test.deploy()?;
506 Ok(test_setup)
507 }
508 PackageTests::Predicate(_) | PackageTests::Library(_) => Ok(
509 TestSetup::WithoutDeployment(vm::storage::MemoryStorage::default()),
510 ),
511 }
512 }
513}
514
515impl From<TestOpts> for pkg::BuildOpts {
516 fn from(val: TestOpts) -> Self {
517 pkg::BuildOpts {
518 pkg: val.pkg,
519 print: val.print,
520 minify: val.minify,
521 dump: DumpOpts::default(),
522 binary_outfile: val.binary_outfile,
523 debug_outfile: val.debug_outfile,
524 hex_outfile: val.hex_outfile,
525 build_target: val.build_target,
526 build_profile: val.build_profile,
527 release: val.release,
528 error_on_warnings: val.error_on_warnings,
529 time_phases: val.time_phases,
530 profile: val.profile,
531 metrics_outfile: val.metrics_outfile,
532 tests: true,
533 member_filter: Default::default(),
534 experimental: val.experimental,
535 no_experimental: val.no_experimental,
536 no_output: val.no_output,
537 }
538 }
539}
540
541impl TestOpts {
542 pub fn into_build_opts(self) -> pkg::BuildOpts {
544 pkg::BuildOpts {
545 pkg: self.pkg,
546 print: self.print,
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 crate::{
813 build, BuiltTests, GasCostsSource, TestFilter, TestGasLimit, TestOpts, TestResult,
814 };
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 GasCostsSource::BuiltIn.provide_gas_costs()?,
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}