1use std::{cell::RefCell, collections::BTreeMap, path::Path, sync::Arc};
6
7use clap::Parser;
8use iota_move_build::decorate_warnings;
9use iota_move_natives::{
10 NativesCostTable, object_runtime::ObjectRuntime, test_scenario::InMemoryTestStore,
11};
12use iota_protocol_config::ProtocolConfig;
13use iota_types::{
14 gas_model::tables::initial_cost_schedule_for_unit_tests, in_memory_storage::InMemoryStorage,
15 metrics::LimitsMetrics,
16};
17use move_cli::base::{
18 self,
19 test::{self, UnitTestResult},
20};
21use move_package::BuildConfig;
22use move_unit_test::{UnitTestingConfig, extensions::set_extension_hook};
23use move_vm_runtime::native_extensions::NativeContextExtensions;
24use once_cell::sync::Lazy;
25
26const MAX_UNIT_TEST_INSTRUCTIONS: u64 = 1_000_000;
29
30#[derive(Parser)]
31#[group(id = "iota-move-test")]
32pub struct Test {
33 #[command(flatten)]
34 pub test: test::Test,
35}
36
37impl Test {
38 pub fn execute(
39 self,
40 path: Option<&Path>,
41 build_config: BuildConfig,
42 ) -> anyhow::Result<UnitTestResult> {
43 let compute_coverage = self.test.compute_coverage;
44 if !cfg!(debug_assertions) && compute_coverage {
45 return Err(anyhow::anyhow!(
46 "The --coverage flag is currently supported only in debug builds. Please build the IOTA CLI from source in debug mode."
47 ));
48 }
49 let save_disassembly = self.test.trace_execution.is_some();
51 let rerooted_path = base::reroot_path(path)?;
54 let unit_test_config = self.test.unit_test_config();
55 run_move_unit_tests(
56 &rerooted_path,
57 build_config,
58 Some(unit_test_config),
59 compute_coverage,
60 save_disassembly,
61 )
62 }
63}
64
65thread_local! {
67 static TEST_STORE_INNER: RefCell<InMemoryStorage> = RefCell::new(InMemoryStorage::default());
68}
69
70static TEST_STORE: Lazy<InMemoryTestStore> = Lazy::new(|| InMemoryTestStore(&TEST_STORE_INNER));
71
72static SET_EXTENSION_HOOK: Lazy<()> =
73 Lazy::new(|| set_extension_hook(Box::new(new_testing_object_and_natives_cost_runtime)));
74
75pub fn run_move_unit_tests(
79 path: &Path,
80 build_config: BuildConfig,
81 config: Option<UnitTestingConfig>,
82 compute_coverage: bool,
83 save_disassembly: bool,
84) -> anyhow::Result<UnitTestResult> {
85 Lazy::force(&SET_EXTENSION_HOOK);
87
88 let config = config
89 .unwrap_or_else(|| UnitTestingConfig::default_with_bound(Some(MAX_UNIT_TEST_INSTRUCTIONS)));
90
91 let result = move_cli::base::test::run_move_unit_tests(
92 path,
93 build_config,
94 UnitTestingConfig {
95 report_stacktrace_on_abort: true,
96 ..config
97 },
98 iota_move_natives::all_natives(
99 false,
101 &ProtocolConfig::get_for_max_version_UNSAFE(),
102 ),
103 Some(initial_cost_schedule_for_unit_tests()),
104 compute_coverage,
105 save_disassembly,
106 &mut std::io::stdout(),
107 );
108 result.map(|(test_result, warning_diags)| {
109 if test_result == UnitTestResult::Success {
110 if let Some(diags) = warning_diags {
111 decorate_warnings(diags, None);
112 }
113 }
114 test_result
115 })
116}
117
118fn new_testing_object_and_natives_cost_runtime(ext: &mut NativeContextExtensions) {
119 let registry = prometheus::Registry::new();
121 let metrics = Arc::new(LimitsMetrics::new(®istry));
122 let store = Lazy::force(&TEST_STORE);
123
124 ext.add(ObjectRuntime::new(
125 store,
126 BTreeMap::new(),
127 false,
128 Box::leak(Box::new(ProtocolConfig::get_for_max_version_UNSAFE())), metrics,
130 0, ));
132 ext.add(NativesCostTable::from_protocol_config(
133 &ProtocolConfig::get_for_max_version_UNSAFE(),
134 ));
135
136 ext.add(store);
137}