forc_test/
lib.rs

1pub mod execute;
2pub mod setup;
3
4use crate::execute::TestExecutor;
5use crate::setup::{
6    ContractDeploymentSetup, ContractTestSetup, DeploymentSetup, ScriptTestSetup, TestSetup,
7};
8use forc_pkg::{self as pkg, BuildOpts};
9use fuel_abi_types::error_codes::ErrorSignal;
10use fuel_tx as tx;
11use fuel_vm::checked_transaction::builder::TransactionBuilderExt;
12use fuel_vm::{self as vm};
13use pkg::TestPassCondition;
14use pkg::{Built, BuiltPackage};
15use rand::{Rng, SeedableRng};
16use rayon::prelude::*;
17use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};
18use sway_core::BuildTarget;
19use sway_types::Span;
20use tx::consensus_parameters::ConsensusParametersV1;
21use tx::{ConsensusParameters, ContractParameters, ScriptParameters, TxParameters};
22use vm::interpreter::{InterpreterParams, MemoryInstance};
23use vm::prelude::SecretKey;
24
25/// The result of a `forc test` invocation.
26#[derive(Debug)]
27pub enum Tested {
28    Package(Box<TestedPackage>),
29    Workspace(Vec<TestedPackage>),
30}
31
32/// The result of testing a specific package.
33#[derive(Debug)]
34pub struct TestedPackage {
35    pub built: Box<pkg::BuiltPackage>,
36    /// The resulting `ProgramState` after executing the test.
37    pub tests: Vec<TestResult>,
38}
39
40#[derive(Debug)]
41pub struct TestDetails {
42    /// The file that contains the test function.
43    pub file_path: Arc<PathBuf>,
44    /// The line number for the test declaration.
45    pub line_number: usize,
46}
47
48/// The filter to be used to only run matching tests.
49#[derive(Debug, Clone)]
50pub struct TestFilter<'a> {
51    /// The phrase used for filtering, a `&str` searched/matched with test name.
52    pub filter_phrase: &'a str,
53    /// If set `true`, a complete "match" is required with test name for the test to be executed,
54    /// otherwise a test_name should "contain" the `filter_phrase`.
55    pub exact_match: bool,
56}
57
58/// The result of executing a single test within a single package.
59#[derive(Debug, Clone)]
60pub struct TestResult {
61    /// The name of the function.
62    pub name: String,
63    /// The time taken for the test to execute.
64    pub duration: std::time::Duration,
65    /// The span for the function declaring this test.
66    pub span: Span,
67    /// The file path for the function declaring this test.
68    pub file_path: Arc<PathBuf>,
69    /// The resulting state after executing the test function.
70    pub state: vm::state::ProgramState,
71    /// The required state of the VM for this test to pass.
72    pub condition: pkg::TestPassCondition,
73    /// Emitted `Receipt`s during the execution of the test.
74    pub logs: Vec<fuel_tx::Receipt>,
75    /// Gas used while executing this test.
76    pub gas_used: u64,
77}
78
79const TEST_METADATA_SEED: u64 = 0x7E57u64;
80/// A mapping from each member package of a build plan to its compiled contract dependencies.
81type ContractDependencyMap = HashMap<pkg::Pinned, Vec<Arc<pkg::BuiltPackage>>>;
82
83/// A package or a workspace that has been built, ready for test execution.
84pub enum BuiltTests {
85    Package(PackageTests),
86    Workspace(Vec<PackageTests>),
87}
88
89/// A built package ready for test execution.
90///
91/// If the built package is a contract, a second built package for the same contract without the
92/// tests are also populated.
93///
94/// For packages containing contracts or scripts, their [contract-dependencies] are needed for deployment.
95#[derive(Debug)]
96pub enum PackageTests {
97    Contract(PackageWithDeploymentToTest),
98    Script(PackageWithDeploymentToTest),
99    Predicate(Arc<pkg::BuiltPackage>),
100    Library(Arc<pkg::BuiltPackage>),
101}
102
103/// A built contract ready for test execution.
104#[derive(Debug)]
105pub struct ContractToTest {
106    /// Tests included contract.
107    pkg: Arc<pkg::BuiltPackage>,
108    /// Bytecode of the contract without tests.
109    without_tests_bytecode: pkg::BuiltPackageBytecode,
110    contract_dependencies: Vec<Arc<pkg::BuiltPackage>>,
111}
112
113/// A built script ready for test execution.
114#[derive(Debug)]
115pub struct ScriptToTest {
116    /// Tests included contract.
117    pkg: Arc<pkg::BuiltPackage>,
118    contract_dependencies: Vec<Arc<pkg::BuiltPackage>>,
119}
120
121/// A built package that requires deployment before test execution.
122#[derive(Debug)]
123pub enum PackageWithDeploymentToTest {
124    Script(ScriptToTest),
125    Contract(ContractToTest),
126}
127
128/// The set of options provided to the `test` function.
129#[derive(Default, Clone)]
130pub struct TestOpts {
131    pub pkg: pkg::PkgOpts,
132    pub print: pkg::PrintOpts,
133    pub minify: pkg::MinifyOpts,
134    /// If set, outputs a binary file representing the script bytes.
135    pub binary_outfile: Option<String>,
136    /// If set, outputs debug info to the provided file.
137    /// If the argument provided ends with .json, a JSON is emitted,
138    /// otherwise, an ELF file containing DWARF is emitted.
139    pub debug_outfile: Option<String>,
140    /// Build target to use.
141    pub build_target: BuildTarget,
142    /// Name of the build profile to use.
143    pub build_profile: String,
144    /// Use the release build profile.
145    /// The release profile can be customized in the manifest file.
146    pub release: bool,
147    /// Should warnings be treated as errors?
148    pub error_on_warnings: bool,
149    /// Output the time elapsed over each part of the compilation process.
150    pub time_phases: bool,
151    /// Profile the compilation process.
152    pub profile: bool,
153    /// Output compilation metrics into file.
154    pub metrics_outfile: Option<String>,
155    /// Set of enabled experimental flags
156    pub experimental: Vec<sway_features::Feature>,
157    /// Set of disabled experimental flags
158    pub no_experimental: Vec<sway_features::Feature>,
159}
160
161/// The set of options provided for controlling logs printed for each test.
162#[derive(Default, Clone)]
163pub struct TestPrintOpts {
164    pub pretty_print: bool,
165    pub print_logs: bool,
166}
167
168/// A `LogData` decoded into a human readable format with its type information.
169pub struct DecodedLog {
170    pub value: String,
171}
172
173impl TestedPackage {
174    pub fn tests_passed(&self) -> bool {
175        self.tests.iter().all(|test| test.passed())
176    }
177}
178
179impl PackageWithDeploymentToTest {
180    /// Returns a reference to the underlying `BuiltPackage`.
181    ///
182    /// If this is a contract built package with tests included is returned.
183    fn pkg(&self) -> &BuiltPackage {
184        match self {
185            PackageWithDeploymentToTest::Script(script) => &script.pkg,
186            PackageWithDeploymentToTest::Contract(contract) => &contract.pkg,
187        }
188    }
189
190    /// Returns an iterator over contract dependencies of the package represented by this struct.
191    fn contract_dependencies(&self) -> impl Iterator<Item = &Arc<BuiltPackage>> + '_ {
192        match self {
193            PackageWithDeploymentToTest::Script(script_to_test) => {
194                script_to_test.contract_dependencies.iter()
195            }
196            PackageWithDeploymentToTest::Contract(contract_to_test) => {
197                contract_to_test.contract_dependencies.iter()
198            }
199        }
200    }
201
202    /// Deploy the contract dependencies for packages that require deployment.
203    ///
204    /// For scripts deploys all contract dependencies.
205    /// For contract deploys all contract dependencies and the root contract itself.
206    fn deploy(&self) -> anyhow::Result<TestSetup> {
207        // Setup the interpreter for deployment.
208        let gas_price = 0;
209        let params = maxed_consensus_params();
210        let storage = vm::storage::MemoryStorage::default();
211        let interpreter_params = InterpreterParams::new(gas_price, params.clone());
212        let mut interpreter: vm::prelude::Interpreter<_, _, _, vm::interpreter::NotSupportedEcal> =
213            vm::interpreter::Interpreter::with_storage(
214                MemoryInstance::new(),
215                storage,
216                interpreter_params,
217            );
218
219        // Iterate and create deployment transactions for contract dependencies of the root
220        // contract.
221        let contract_dependency_setups = self
222            .contract_dependencies()
223            .map(|built_pkg| deployment_transaction(built_pkg, &built_pkg.bytecode, &params));
224
225        // Deploy contract dependencies of the root contract and collect their ids.
226        let contract_dependency_ids = contract_dependency_setups
227            .map(|(contract_id, tx)| {
228                // Transact the deployment transaction constructed for this contract dependency.
229                let tx = tx
230                    .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
231                    .unwrap();
232                interpreter.transact(tx).map_err(anyhow::Error::msg)?;
233                Ok(contract_id)
234            })
235            .collect::<anyhow::Result<Vec<_>>>()?;
236
237        let deployment_setup = if let PackageWithDeploymentToTest::Contract(contract_to_test) = self
238        {
239            // Root contract is the contract that we are going to be running the tests of, after this
240            // deployment.
241            let (root_contract_id, root_contract_tx) = deployment_transaction(
242                &contract_to_test.pkg,
243                &contract_to_test.without_tests_bytecode,
244                &params,
245            );
246            let root_contract_tx = root_contract_tx
247                .into_ready(gas_price, params.gas_costs(), params.fee_params(), None)
248                .unwrap();
249            // Deploy the root contract.
250            interpreter
251                .transact(root_contract_tx)
252                .map_err(anyhow::Error::msg)?;
253            let storage = interpreter.as_ref().clone();
254            DeploymentSetup::Contract(ContractTestSetup {
255                storage,
256                contract_dependency_ids,
257                root_contract_id,
258            })
259        } else {
260            let storage = interpreter.as_ref().clone();
261            DeploymentSetup::Script(ScriptTestSetup {
262                storage,
263                contract_dependency_ids,
264            })
265        };
266
267        Ok(TestSetup::WithDeployment(deployment_setup))
268    }
269}
270
271/// Returns a mapping of each member package of a build plan to its compiled contract dependencies,
272/// ordered by deployment order.
273///
274/// Each dependency package needs to be deployed before executing the test for that package.
275fn get_contract_dependency_map(
276    built: &Built,
277    build_plan: &pkg::BuildPlan,
278) -> ContractDependencyMap {
279    let built_members: HashMap<&pkg::Pinned, Arc<pkg::BuiltPackage>> =
280        built.into_members().collect();
281    // For each member node, collect their contract dependencies.
282    build_plan
283        .member_nodes()
284        .map(|member_node| {
285            let graph = build_plan.graph();
286            let pinned_member = graph[member_node].clone();
287            let contract_dependencies = build_plan
288                .contract_dependencies(member_node)
289                .map(|contract_dependency_node_ix| graph[contract_dependency_node_ix].clone())
290                .filter_map(|pinned| built_members.get(&pinned))
291                .cloned()
292                .collect::<Vec<_>>();
293            (pinned_member, contract_dependencies)
294        })
295        .collect()
296}
297
298impl BuiltTests {
299    /// Constructs a `PackageTests` from `Built`.
300    pub fn from_built(built: Built, build_plan: &pkg::BuildPlan) -> anyhow::Result<BuiltTests> {
301        let contract_dependencies = get_contract_dependency_map(&built, build_plan);
302        let built = match built {
303            Built::Package(built_pkg) => BuiltTests::Package(PackageTests::from_built_pkg(
304                built_pkg,
305                &contract_dependencies,
306            )),
307            Built::Workspace(built_workspace) => {
308                let pkg_tests = built_workspace
309                    .into_iter()
310                    .map(|built_pkg| {
311                        PackageTests::from_built_pkg(built_pkg, &contract_dependencies)
312                    })
313                    .collect();
314                BuiltTests::Workspace(pkg_tests)
315            }
316        };
317        Ok(built)
318    }
319}
320
321impl<'a> PackageTests {
322    /// Return a reference to the underlying `BuiltPackage`.
323    ///
324    /// If this `PackageTests` is `PackageTests::Contract`, built package with tests included is
325    /// returned.
326    pub(crate) fn built_pkg_with_tests(&'a self) -> &'a BuiltPackage {
327        match self {
328            PackageTests::Contract(contract) => contract.pkg(),
329            PackageTests::Script(script) => script.pkg(),
330            PackageTests::Predicate(predicate) => predicate,
331            PackageTests::Library(library) => library,
332        }
333    }
334
335    /// Construct a `PackageTests` from `BuiltPackage`.
336    fn from_built_pkg(
337        built_pkg: Arc<BuiltPackage>,
338        contract_dependencies: &ContractDependencyMap,
339    ) -> PackageTests {
340        let built_without_tests_bytecode = built_pkg.bytecode_without_tests.clone();
341        let contract_dependencies: Vec<Arc<pkg::BuiltPackage>> = contract_dependencies
342            .get(&built_pkg.descriptor.pinned)
343            .cloned()
344            .unwrap_or_default();
345        match built_without_tests_bytecode {
346            Some(contract_without_tests) => {
347                let contract_to_test = ContractToTest {
348                    pkg: built_pkg,
349                    without_tests_bytecode: contract_without_tests,
350                    contract_dependencies,
351                };
352                PackageTests::Contract(PackageWithDeploymentToTest::Contract(contract_to_test))
353            }
354            None => match built_pkg.tree_type {
355                sway_core::language::parsed::TreeType::Predicate => {
356                    PackageTests::Predicate(built_pkg)
357                }
358                sway_core::language::parsed::TreeType::Library => PackageTests::Library(built_pkg),
359                sway_core::language::parsed::TreeType::Script => {
360                    let script_to_test = ScriptToTest {
361                        pkg: built_pkg,
362                        contract_dependencies,
363                    };
364                    PackageTests::Script(PackageWithDeploymentToTest::Script(script_to_test))
365                }
366                _ => unreachable!("contracts are already handled"),
367            },
368        }
369    }
370
371    /// Run all tests after applying the provided filter and collect their results.
372    pub(crate) fn run_tests(
373        &self,
374        test_runners: &rayon::ThreadPool,
375        test_filter: Option<&TestFilter>,
376    ) -> anyhow::Result<TestedPackage> {
377        let pkg_with_tests = self.built_pkg_with_tests();
378        let tests = test_runners.install(|| {
379            pkg_with_tests
380                .bytecode
381                .entries
382                .par_iter()
383                .filter_map(|entry| {
384                    if let Some(test_entry) = entry.kind.test() {
385                        // If a test filter is specified, only the tests containing the filter phrase in
386                        // their name are going to be executed.
387                        let name = entry.finalized.fn_name.clone();
388                        if let Some(filter) = test_filter {
389                            if !filter.filter(&name) {
390                                return None;
391                            }
392                        }
393                        return Some((entry, test_entry));
394                    }
395                    None
396                })
397                .map(|(entry, test_entry)| {
398                    // Execute the test and return the result.
399                    let offset = u32::try_from(entry.finalized.imm)
400                        .expect("test instruction offset out of range");
401                    let name = entry.finalized.fn_name.clone();
402                    let test_setup = self.setup()?;
403                    TestExecutor::build(
404                        &pkg_with_tests.bytecode.bytes,
405                        offset,
406                        test_setup,
407                        test_entry,
408                        name,
409                    )?
410                    .execute()
411                })
412                .collect::<anyhow::Result<_>>()
413        })?;
414
415        Ok(TestedPackage {
416            built: Box::new(pkg_with_tests.clone()),
417            tests,
418        })
419    }
420
421    /// Setup the storage for a test and returns a contract id for testing contracts.
422    ///
423    /// For testing contracts, storage returned from this function contains the deployed contract.
424    /// For other types, default storage is returned.
425    pub fn setup(&self) -> anyhow::Result<TestSetup> {
426        match self {
427            PackageTests::Contract(contract_to_test) => {
428                let test_setup = contract_to_test.deploy()?;
429                Ok(test_setup)
430            }
431            PackageTests::Script(script_to_test) => {
432                let test_setup = script_to_test.deploy()?;
433                Ok(test_setup)
434            }
435            PackageTests::Predicate(_) | PackageTests::Library(_) => Ok(
436                TestSetup::WithoutDeployment(vm::storage::MemoryStorage::default()),
437            ),
438        }
439    }
440}
441
442impl From<TestOpts> for pkg::BuildOpts {
443    fn from(val: TestOpts) -> Self {
444        pkg::BuildOpts {
445            pkg: val.pkg,
446            print: val.print,
447            minify: val.minify,
448            binary_outfile: val.binary_outfile,
449            debug_outfile: val.debug_outfile,
450            build_target: val.build_target,
451            build_profile: val.build_profile,
452            release: val.release,
453            error_on_warnings: val.error_on_warnings,
454            time_phases: val.time_phases,
455            profile: val.profile,
456            metrics_outfile: val.metrics_outfile,
457            tests: true,
458            member_filter: Default::default(),
459            experimental: val.experimental,
460            no_experimental: val.no_experimental,
461        }
462    }
463}
464
465impl TestOpts {
466    /// Convert this set of test options into a set of build options.
467    pub fn into_build_opts(self) -> pkg::BuildOpts {
468        pkg::BuildOpts {
469            pkg: self.pkg,
470            print: self.print,
471            minify: self.minify,
472            binary_outfile: self.binary_outfile,
473            debug_outfile: self.debug_outfile,
474            build_target: self.build_target,
475            build_profile: self.build_profile,
476            release: self.release,
477            error_on_warnings: self.error_on_warnings,
478            time_phases: self.time_phases,
479            profile: self.profile,
480            metrics_outfile: self.metrics_outfile,
481            tests: true,
482            member_filter: Default::default(),
483            experimental: self.experimental,
484            no_experimental: self.no_experimental,
485        }
486    }
487}
488
489impl TestResult {
490    /// Whether or not the test passed.
491    pub fn passed(&self) -> bool {
492        match &self.condition {
493            TestPassCondition::ShouldRevert(revert_code) => match revert_code {
494                Some(revert_code) => self.state == vm::state::ProgramState::Revert(*revert_code),
495                None => matches!(self.state, vm::state::ProgramState::Revert(_)),
496            },
497            TestPassCondition::ShouldNotRevert => {
498                !matches!(self.state, vm::state::ProgramState::Revert(_))
499            }
500        }
501    }
502
503    /// Return the revert code for this [TestResult] if the test is reverted.
504    pub fn revert_code(&self) -> Option<u64> {
505        match self.state {
506            vm::state::ProgramState::Revert(revert_code) => Some(revert_code),
507            _ => None,
508        }
509    }
510
511    /// Return an [ErrorSignal] for this [TestResult] if the test is failed to pass.
512    pub fn error_signal(&self) -> anyhow::Result<ErrorSignal> {
513        let revert_code = self.revert_code().ok_or_else(|| {
514            anyhow::anyhow!("there is no revert code to convert to `ErrorSignal`")
515        })?;
516        ErrorSignal::try_from_revert_code(revert_code).map_err(|e| anyhow::anyhow!(e))
517    }
518
519    /// Return [TestDetails] from the span of the function declaring this test.
520    pub fn details(&self) -> anyhow::Result<TestDetails> {
521        let span_start = self.span.start();
522        let file_str = fs::read_to_string(&*self.file_path)?;
523        let line_number = file_str[..span_start]
524            .chars()
525            .filter(|&c| c == '\n')
526            .count();
527        Ok(TestDetails {
528            file_path: self.file_path.clone(),
529            line_number,
530        })
531    }
532}
533
534/// Used to control test runner count for forc-test. Number of runners to use can be specified using
535/// `Manual` or can be left forc-test to decide by using `Auto`.
536pub enum TestRunnerCount {
537    Manual(usize),
538    Auto,
539}
540
541#[derive(Clone, Debug, Default)]
542pub struct TestCount {
543    pub total: usize,
544    pub ignored: usize,
545}
546
547impl TestFilter<'_> {
548    fn filter(&self, fn_name: &str) -> bool {
549        if self.exact_match {
550            fn_name == self.filter_phrase
551        } else {
552            fn_name.contains(self.filter_phrase)
553        }
554    }
555}
556
557impl BuiltTests {
558    /// The total number of tests.
559    pub fn test_count(&self, test_filter: Option<&TestFilter>) -> TestCount {
560        let pkgs: Vec<&PackageTests> = match self {
561            BuiltTests::Package(pkg) => vec![pkg],
562            BuiltTests::Workspace(workspace) => workspace.iter().collect(),
563        };
564        pkgs.iter()
565            .flat_map(|pkg| {
566                pkg.built_pkg_with_tests()
567                    .bytecode
568                    .entries
569                    .iter()
570                    .filter_map(|entry| entry.kind.test().map(|test| (entry, test)))
571            })
572            .fold(TestCount::default(), |acc, (pkg_entry, _)| {
573                let num_ignored = match &test_filter {
574                    Some(filter) => {
575                        if filter.filter(&pkg_entry.finalized.fn_name) {
576                            acc.ignored
577                        } else {
578                            acc.ignored + 1
579                        }
580                    }
581                    None => acc.ignored,
582                };
583                TestCount {
584                    total: acc.total + 1,
585                    ignored: num_ignored,
586                }
587            })
588    }
589
590    /// Run all built tests, return the result.
591    pub fn run(
592        self,
593        test_runner_count: TestRunnerCount,
594        test_filter: Option<TestFilter>,
595    ) -> anyhow::Result<Tested> {
596        let test_runners = match test_runner_count {
597            TestRunnerCount::Manual(runner_count) => rayon::ThreadPoolBuilder::new()
598                .num_threads(runner_count)
599                .build(),
600            TestRunnerCount::Auto => rayon::ThreadPoolBuilder::new().build(),
601        }?;
602        run_tests(self, &test_runners, test_filter)
603    }
604}
605
606/// First builds the package or workspace, ready for execution.
607pub fn build(opts: TestOpts) -> anyhow::Result<BuiltTests> {
608    let build_opts: BuildOpts = opts.into();
609    let build_plan = pkg::BuildPlan::from_pkg_opts(&build_opts.pkg)?;
610    let built = pkg::build_with_options(&build_opts)?;
611    BuiltTests::from_built(built, &build_plan)
612}
613
614/// Returns a `ConsensusParameters` which has maximum length/size allowance for scripts, contracts,
615/// and transactions.
616pub(crate) fn maxed_consensus_params() -> ConsensusParameters {
617    let script_params = ScriptParameters::DEFAULT
618        .with_max_script_length(u64::MAX)
619        .with_max_script_data_length(u64::MAX);
620    let tx_params = TxParameters::DEFAULT.with_max_size(u64::MAX);
621    let contract_params = ContractParameters::DEFAULT
622        .with_contract_max_size(u64::MAX)
623        .with_max_storage_slots(u64::MAX);
624    ConsensusParameters::V1(ConsensusParametersV1 {
625        script_params,
626        tx_params,
627        contract_params,
628        ..Default::default()
629    })
630}
631
632/// Deploys the provided contract and returns an interpreter instance ready to be used in test
633/// executions with deployed contract.
634fn deployment_transaction(
635    built_pkg: &pkg::BuiltPackage,
636    without_tests_bytecode: &pkg::BuiltPackageBytecode,
637    params: &tx::ConsensusParameters,
638) -> ContractDeploymentSetup {
639    // Obtain the contract id for deployment.
640    let mut storage_slots = built_pkg.storage_slots.clone();
641    storage_slots.sort();
642    let bytecode = &without_tests_bytecode.bytes;
643    let contract = tx::Contract::from(bytecode.clone());
644    let root = contract.root();
645    let state_root = tx::Contract::initial_state_root(storage_slots.iter());
646    let salt = tx::Salt::zeroed();
647    let contract_id = contract.id(&salt, &root, &state_root);
648
649    // Create the deployment transaction.
650    let rng = &mut rand::rngs::StdRng::seed_from_u64(TEST_METADATA_SEED);
651
652    // Prepare the transaction metadata.
653    let secret_key = SecretKey::random(rng);
654    let utxo_id = rng.gen();
655    let amount = 1;
656    let maturity = 1u32.into();
657    // NOTE: fuel-core is using dynamic asset id and interacting with the fuel-core, using static
658    // asset id is not correct. But since forc-test maintains its own interpreter instance, correct
659    // base asset id is indeed the static `tx::AssetId::BASE`.
660    let asset_id = tx::AssetId::BASE;
661    let tx_pointer = rng.gen();
662    let block_height = (u32::MAX >> 1).into();
663
664    let tx = tx::TransactionBuilder::create(bytecode.as_slice().into(), salt, storage_slots)
665        .with_params(params.clone())
666        .add_unsigned_coin_input(secret_key, utxo_id, amount, asset_id, tx_pointer)
667        .add_output(tx::Output::contract_created(contract_id, state_root))
668        .maturity(maturity)
669        .finalize_checked(block_height);
670    (contract_id, tx)
671}
672
673/// Build the given package and run its tests after applying the filter provided.
674///
675/// Returns the result of test execution.
676fn run_tests(
677    built: BuiltTests,
678    test_runners: &rayon::ThreadPool,
679    test_filter: Option<TestFilter>,
680) -> anyhow::Result<Tested> {
681    match built {
682        BuiltTests::Package(pkg) => {
683            let tested_pkg = pkg.run_tests(test_runners, test_filter.as_ref())?;
684            Ok(Tested::Package(Box::new(tested_pkg)))
685        }
686        BuiltTests::Workspace(workspace) => {
687            let tested_pkgs = workspace
688                .into_iter()
689                .map(|pkg| pkg.run_tests(test_runners, test_filter.as_ref()))
690                .collect::<anyhow::Result<Vec<TestedPackage>>>()?;
691            Ok(Tested::Workspace(tested_pkgs))
692        }
693    }
694}
695
696#[cfg(test)]
697mod tests {
698    use std::path::PathBuf;
699
700    use crate::{build, BuiltTests, TestFilter, TestOpts, TestResult};
701
702    /// Name of the folder containing required data for tests to run, such as an example forc
703    /// project.
704    const TEST_DATA_FOLDER_NAME: &str = "test_data";
705    /// Name of the library package in the "CARGO_MANIFEST_DIR/TEST_DATA_FOLDER_NAME".
706    const TEST_LIBRARY_PACKAGE_NAME: &str = "test_library";
707    /// Name of the contract package in the "CARGO_MANIFEST_DIR/TEST_DATA_FOLDER_NAME".
708    const TEST_CONTRACT_PACKAGE_NAME: &str = "test_contract";
709    /// Name of the predicate package in the "CARGO_MANIFEST_DIR/TEST_DATA_FOLDER_NAME".
710    const TEST_PREDICATE_PACKAGE_NAME: &str = "test_predicate";
711    /// Name of the script package in the "CARGO_MANIFEST_DIR/TEST_DATA_FOLDER_NAME".
712    const TEST_SCRIPT_PACKAGE_NAME: &str = "test_script";
713
714    /// Build the tests in the test package with the given name located at
715    /// "CARGO_MANIFEST_DIR/TEST_DATA_FOLDER_NAME/TEST_LIBRARY_PACKAGE_NAME".
716    fn test_package_built_tests(package_name: &str) -> anyhow::Result<BuiltTests> {
717        let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
718        let library_package_dir = PathBuf::from(cargo_manifest_dir)
719            .join(TEST_DATA_FOLDER_NAME)
720            .join(package_name);
721        let library_package_dir_string = library_package_dir.to_string_lossy().to_string();
722        let build_options = TestOpts {
723            pkg: forc_pkg::PkgOpts {
724                path: Some(library_package_dir_string),
725                ..Default::default()
726            },
727            ..Default::default()
728        };
729        build(build_options)
730    }
731
732    fn test_package_test_results(
733        package_name: &str,
734        test_filter: Option<TestFilter>,
735    ) -> anyhow::Result<Vec<TestResult>> {
736        let built_tests = test_package_built_tests(package_name)?;
737        let test_runner_count = crate::TestRunnerCount::Auto;
738        let tested = built_tests.run(test_runner_count, test_filter)?;
739        match tested {
740            crate::Tested::Package(tested_pkg) => Ok(tested_pkg.tests),
741            crate::Tested::Workspace(_) => {
742                unreachable!("test_library is a package, not a workspace.")
743            }
744        }
745    }
746
747    #[test]
748    fn test_filter_exact_match() {
749        let filter_phrase = "test_bam";
750        let test_filter = TestFilter {
751            filter_phrase,
752            exact_match: true,
753        };
754
755        let test_library_results =
756            test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
757                .unwrap();
758        let tested_library_test_count = test_library_results.len();
759
760        let test_contract_results =
761            test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
762                .unwrap();
763        let tested_contract_test_count = test_contract_results.len();
764
765        let test_predicate_results =
766            test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
767                .unwrap();
768        let tested_predicate_test_count = test_predicate_results.len();
769
770        let test_script_results =
771            test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
772        let tested_script_test_count = test_script_results.len();
773
774        assert_eq!(tested_library_test_count, 1);
775        assert_eq!(tested_contract_test_count, 1);
776        assert_eq!(tested_predicate_test_count, 1);
777        assert_eq!(tested_script_test_count, 1);
778    }
779
780    #[test]
781    fn test_filter_exact_match_all_ignored() {
782        let filter_phrase = "test_ba";
783        let test_filter = TestFilter {
784            filter_phrase,
785            exact_match: true,
786        };
787
788        let test_library_results =
789            test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
790                .unwrap();
791        let tested_library_test_count = test_library_results.len();
792
793        let test_contract_results =
794            test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
795                .unwrap();
796        let tested_contract_test_count = test_contract_results.len();
797
798        let test_predicate_results =
799            test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
800                .unwrap();
801        let tested_predicate_test_count = test_predicate_results.len();
802
803        let test_script_results =
804            test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
805        let tested_script_test_count = test_script_results.len();
806
807        assert_eq!(tested_library_test_count, 0);
808        assert_eq!(tested_contract_test_count, 0);
809        assert_eq!(tested_predicate_test_count, 0);
810        assert_eq!(tested_script_test_count, 0);
811    }
812
813    #[test]
814    fn test_filter_match_all_ignored() {
815        let filter_phrase = "this_test_does_not_exists";
816        let test_filter = TestFilter {
817            filter_phrase,
818            exact_match: false,
819        };
820
821        let test_library_results =
822            test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
823                .unwrap();
824        let tested_library_test_count = test_library_results.len();
825
826        let test_contract_results =
827            test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
828                .unwrap();
829        let tested_contract_test_count = test_contract_results.len();
830
831        let test_predicate_results =
832            test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
833                .unwrap();
834        let tested_predicate_test_count = test_predicate_results.len();
835
836        let test_script_results =
837            test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
838        let tested_script_test_count = test_script_results.len();
839
840        assert_eq!(tested_library_test_count, 0);
841        assert_eq!(tested_contract_test_count, 0);
842        assert_eq!(tested_predicate_test_count, 0);
843        assert_eq!(tested_script_test_count, 0);
844    }
845
846    #[test]
847    fn test_filter_one_match() {
848        let filter_phrase = "test_ba";
849        let test_filter = TestFilter {
850            filter_phrase,
851            exact_match: false,
852        };
853
854        let test_library_results =
855            test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
856                .unwrap();
857        let tested_library_test_count = test_library_results.len();
858
859        let test_contract_results =
860            test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
861                .unwrap();
862        let tested_contract_test_count = test_contract_results.len();
863
864        let test_predicate_results =
865            test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
866                .unwrap();
867        let tested_predicate_test_count = test_predicate_results.len();
868
869        let test_script_results =
870            test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
871        let tested_script_test_count = test_script_results.len();
872
873        assert_eq!(tested_library_test_count, 1);
874        assert_eq!(tested_contract_test_count, 1);
875        assert_eq!(tested_predicate_test_count, 1);
876        assert_eq!(tested_script_test_count, 1);
877    }
878
879    #[test]
880    fn test_filter_all_match() {
881        let filter_phrase = "est_b";
882        let test_filter = TestFilter {
883            filter_phrase,
884            exact_match: false,
885        };
886
887        let test_library_results =
888            test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, Some(test_filter.clone()))
889                .unwrap();
890        let tested_library_test_count = test_library_results.len();
891
892        let test_contract_results =
893            test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, Some(test_filter.clone()))
894                .unwrap();
895        let tested_contract_test_count = test_contract_results.len();
896
897        let test_predicate_results =
898            test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, Some(test_filter.clone()))
899                .unwrap();
900        let tested_predicate_test_count = test_predicate_results.len();
901
902        let test_script_results =
903            test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, Some(test_filter)).unwrap();
904        let tested_script_test_count = test_script_results.len();
905
906        assert_eq!(tested_library_test_count, 2);
907        assert_eq!(tested_contract_test_count, 2);
908        assert_eq!(tested_predicate_test_count, 2);
909        assert_eq!(tested_script_test_count, 2);
910    }
911
912    #[test]
913    fn test_no_filter() {
914        let test_filter = None;
915
916        let test_library_results =
917            test_package_test_results(TEST_LIBRARY_PACKAGE_NAME, test_filter.clone()).unwrap();
918        let tested_library_test_count = test_library_results.len();
919
920        let test_contract_results =
921            test_package_test_results(TEST_CONTRACT_PACKAGE_NAME, test_filter.clone()).unwrap();
922        let tested_contract_test_count = test_contract_results.len();
923
924        let test_predicate_results =
925            test_package_test_results(TEST_PREDICATE_PACKAGE_NAME, test_filter.clone()).unwrap();
926        let tested_predicate_test_count = test_predicate_results.len();
927
928        let test_script_results =
929            test_package_test_results(TEST_SCRIPT_PACKAGE_NAME, test_filter).unwrap();
930        let tested_script_test_count = test_script_results.len();
931
932        assert_eq!(tested_library_test_count, 2);
933        assert_eq!(tested_contract_test_count, 2);
934        assert_eq!(tested_predicate_test_count, 2);
935        assert_eq!(tested_script_test_count, 2);
936    }
937}