forc_test/
lib.rs

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