1use clap::Parser;
5use move_cli::base::{
6 self,
7 test::{self, UnitTestResult},
8};
9use move_package_alt_compilation::build_config::BuildConfig;
10use move_unit_test::{UnitTestingConfig, vm_test_setup::VMTestSetup};
11use move_vm_config::runtime::VMConfig;
12use move_vm_runtime::natives::extensions::NativeContextExtensions;
13use std::{
14 cell::RefCell,
15 collections::BTreeMap,
16 ops::{Deref, DerefMut},
17 path::Path,
18 rc::Rc,
19 sync::{Arc, LazyLock},
20};
21use sui_adapter::gas_meter::SuiGasMeter;
22use sui_move_build::decorate_warnings;
23use sui_move_natives::{
24 NativesCostTable, object_runtime::ObjectRuntime, test_scenario::InMemoryTestStore,
25 transaction_context::TransactionContext,
26};
27use sui_package_alt::{SuiFlavor, find_environment};
28use sui_protocol_config::ProtocolConfig;
29use sui_sdk::wallet_context::WalletContext;
30use sui_types::{
31 base_types::{SuiAddress, TxContext},
32 digests::TransactionDigest,
33 gas::{SuiGasStatus, SuiGasStatusAPI},
34 gas_model::{tables::GasStatus, units_types::Gas},
35 in_memory_storage::InMemoryStorage,
36 metrics::ExecutionMetrics,
37};
38
39pub static MAX_UNIT_TEST_INSTRUCTIONS: LazyLock<u64> =
41 LazyLock::new(|| ProtocolConfig::get_for_max_version_UNSAFE().max_tx_gas());
42
43const TEST_GAS_PRICE: u64 = 500;
45
46#[derive(Parser)]
47#[group(id = "sui-move-test")]
48pub struct Test {
49 #[clap(flatten)]
50 pub test: test::Test,
51}
52
53impl Test {
54 pub async fn execute(
55 self,
56 path: Option<&Path>,
57 mut build_config: BuildConfig,
58 wallet: &WalletContext,
59 flavor: SuiFlavor,
60 ) -> anyhow::Result<UnitTestResult> {
61 let compute_coverage = self.test.compute_coverage;
62 if !cfg!(feature = "tracing") && compute_coverage {
63 return Err(anyhow::anyhow!(
64 "The --coverage flag is currently supported only in builds built with the `tracing` feature enabled. \
65 Please build the Sui CLI from source with `--features tracing` to use this flag."
66 ));
67 }
68 let save_disassembly = self.test.trace.is_some();
70 if build_config.default_flavor.is_none() {
72 build_config.default_flavor = Some(move_compiler::editions::Flavor::Sui);
73 }
74
75 let rerooted_path = base::reroot_path(path)?;
77
78 let unit_test_config = self
82 .test
83 .unit_test_config(Some(*MAX_UNIT_TEST_INSTRUCTIONS));
84
85 let environment =
89 find_environment(&rerooted_path, build_config.environment, wallet, false).await?;
90 build_config.environment = Some(environment.name);
91
92 run_move_unit_tests(
93 &rerooted_path,
94 build_config,
95 Some(unit_test_config),
96 compute_coverage,
97 save_disassembly,
98 flavor,
99 )
100 .await
101 }
102}
103
104pub async fn run_move_unit_tests(
107 path: &Path,
108 build_config: BuildConfig,
109 config: Option<UnitTestingConfig>,
110 compute_coverage: bool,
111 save_disassembly: bool,
112 flavor: SuiFlavor,
113) -> anyhow::Result<UnitTestResult> {
114 let config = config.unwrap_or_else(|| {
115 UnitTestingConfig::default_with_bound(Some(*MAX_UNIT_TEST_INSTRUCTIONS))
116 });
117
118 let result = move_cli::base::test::run_move_unit_tests(
119 path,
120 build_config,
121 UnitTestingConfig {
122 report_stacktrace_on_abort: true,
123 ..config
124 },
125 flavor,
126 SuiVMTestSetup::new(),
127 compute_coverage,
128 save_disassembly,
129 &mut std::io::stdout(),
130 )
131 .await;
132
133 result.map(|(test_result, warning_diags)| {
134 if test_result == UnitTestResult::Success
135 && let Some(diags) = warning_diags
136 {
137 decorate_warnings(diags, None);
138 }
139 test_result
140 })
141}
142
143pub struct SuiVMTestSetup {
144 gas_price: u64,
145 reference_gas_price: u64,
146 protocol_config: ProtocolConfig,
147 native_function_table: move_vm_runtime::natives::functions::NativeFunctionTable,
148}
149
150impl Default for SuiVMTestSetup {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156impl SuiVMTestSetup {
157 pub fn new() -> Self {
158 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
159 let native_function_table =
160 sui_move_natives::all_natives(false, &protocol_config);
161 Self {
162 gas_price: TEST_GAS_PRICE,
163 reference_gas_price: TEST_GAS_PRICE,
164 protocol_config,
165 native_function_table,
166 }
167 }
168
169 pub fn max_gas_budget(&self) -> u64 {
170 self.protocol_config.max_tx_gas()
171 }
172}
173
174impl VMTestSetup for SuiVMTestSetup {
175 type Meter<'a> = SuiGasMeter<SuiGasStatusTestWrapper>;
176 type ExtensionsBuilder<'a> = InMemoryTestStore;
177
178 fn new_meter<'a>(&'a self, execution_bound: Option<u64>) -> Self::Meter<'a> {
179 SuiGasMeter(SuiGasStatusTestWrapper(
180 SuiGasStatus::new(
181 execution_bound.unwrap_or(*MAX_UNIT_TEST_INSTRUCTIONS),
182 self.gas_price,
183 self.reference_gas_price,
184 &self.protocol_config,
185 )
186 .unwrap(),
187 ))
188 }
189
190 fn used_gas<'a>(&'a self, execution_bound: u64, meter: Self::Meter<'a>) -> u64 {
191 let gas_status = &meter.0;
192 Gas::new(execution_bound)
193 .checked_sub(gas_status.remaining_gas())
194 .unwrap()
195 .into()
196 }
197
198 fn vm_config(&self) -> VMConfig {
199 sui_adapter::adapter::vm_config(&self.protocol_config)
200 }
201
202 fn native_function_table(&self) -> move_vm_runtime::natives::functions::NativeFunctionTable {
203 self.native_function_table.clone()
204 }
205
206 fn new_extensions_builder(&self) -> InMemoryTestStore {
207 InMemoryTestStore(RefCell::new(InMemoryStorage::default()))
208 }
209
210 fn new_native_context_extensions<'ext>(
211 &self,
212 store: &'ext InMemoryTestStore,
213 ) -> NativeContextExtensions<'ext> {
214 let mut ext = NativeContextExtensions::default();
215 let registry = prometheus::Registry::new();
217 let metrics = Arc::new(ExecutionMetrics::new(®istry));
218
219 ext.add(ObjectRuntime::new(
220 store,
221 BTreeMap::new(),
222 false,
223 Box::leak(Box::new(ProtocolConfig::get_for_max_version_UNSAFE())), metrics,
225 0, ));
227 ext.add(NativesCostTable::from_protocol_config(
228 &self.protocol_config,
229 ));
230 let tx_context = TxContext::new_from_components(
231 &SuiAddress::ZERO,
232 &TransactionDigest::default(),
233 &0,
234 0,
235 0,
236 0,
237 0,
238 None,
239 &self.protocol_config,
240 );
241 ext.add(TransactionContext::new_for_testing(Rc::new(RefCell::new(
242 tx_context,
243 ))));
244 ext.add(store);
245 ext
246 }
247}
248
249pub struct SuiGasStatusTestWrapper(SuiGasStatus);
251
252impl Deref for SuiGasStatusTestWrapper {
253 type Target = GasStatus;
254
255 fn deref(&self) -> &Self::Target {
256 self.0.move_gas_status()
257 }
258}
259
260impl DerefMut for SuiGasStatusTestWrapper {
261 fn deref_mut(&mut self) -> &mut Self::Target {
262 self.0.move_gas_status_mut()
263 }
264}