Skip to main content

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