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