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