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