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