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::native_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::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::LimitsMetrics,
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 ) -> anyhow::Result<UnitTestResult> {
60 let compute_coverage = self.test.compute_coverage;
61 if !cfg!(feature = "tracing") && compute_coverage {
62 return Err(anyhow::anyhow!(
63 "The --coverage flag is currently supported only in builds built with the `tracing` feature enabled. \
64 Please build the Sui CLI from source with `--features tracing` to use this flag."
65 ));
66 }
67 let save_disassembly = self.test.trace;
69 if build_config.default_flavor.is_none() {
71 build_config.default_flavor = Some(move_compiler::editions::Flavor::Sui);
72 }
73
74 let rerooted_path = base::reroot_path(path)?;
76 let unit_test_config = self.test.unit_test_config();
77
78 let environment =
82 find_environment(&rerooted_path, build_config.environment, wallet).await?;
83 build_config.environment = Some(environment.name);
84
85 run_move_unit_tests(
86 &rerooted_path,
87 build_config,
88 Some(unit_test_config),
89 compute_coverage,
90 save_disassembly,
91 )
92 .await
93 }
94}
95
96pub async fn run_move_unit_tests(
99 path: &Path,
100 build_config: BuildConfig,
101 config: Option<UnitTestingConfig>,
102 compute_coverage: bool,
103 save_disassembly: bool,
104) -> anyhow::Result<UnitTestResult> {
105 let config = config.unwrap_or_else(|| {
106 UnitTestingConfig::default_with_bound(Some(*MAX_UNIT_TEST_INSTRUCTIONS))
107 });
108
109 let result = move_cli::base::test::run_move_unit_tests::<sui_package_alt::SuiFlavor, _, _>(
110 path,
111 build_config,
112 UnitTestingConfig {
113 report_stacktrace_on_abort: true,
114 ..config
115 },
116 SuiVMTestSetup::new(),
117 compute_coverage,
118 save_disassembly,
119 &mut std::io::stdout(),
120 )
121 .await;
122
123 result.map(|(test_result, warning_diags)| {
124 if test_result == UnitTestResult::Success
125 && let Some(diags) = warning_diags
126 {
127 decorate_warnings(diags, None);
128 }
129 test_result
130 })
131}
132
133pub struct SuiVMTestSetup {
134 gas_price: u64,
135 reference_gas_price: u64,
136 protocol_config: ProtocolConfig,
137 native_function_table: move_vm_runtime::native_functions::NativeFunctionTable,
138}
139
140impl Default for SuiVMTestSetup {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl SuiVMTestSetup {
147 pub fn new() -> Self {
148 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
149 let native_function_table =
150 sui_move_natives::all_natives(false, &protocol_config);
151 Self {
152 gas_price: TEST_GAS_PRICE,
153 reference_gas_price: TEST_GAS_PRICE,
154 protocol_config,
155 native_function_table,
156 }
157 }
158
159 pub fn max_gas_budget(&self) -> u64 {
160 self.protocol_config.max_tx_gas()
161 }
162}
163
164impl VMTestSetup for SuiVMTestSetup {
165 type Meter<'a> = SuiGasMeter<SuiGasStatusTestWrapper>;
166 type ExtensionsBuilder<'a> = InMemoryTestStore;
167
168 fn new_meter<'a>(&'a self, execution_bound: Option<u64>) -> Self::Meter<'a> {
169 SuiGasMeter(SuiGasStatusTestWrapper(
170 SuiGasStatus::new(
171 execution_bound.unwrap_or(*MAX_UNIT_TEST_INSTRUCTIONS),
172 self.gas_price,
173 self.reference_gas_price,
174 &self.protocol_config,
175 )
176 .unwrap(),
177 ))
178 }
179
180 fn used_gas<'a>(&'a self, execution_bound: u64, meter: Self::Meter<'a>) -> u64 {
181 let gas_status = &meter.0;
182 Gas::new(execution_bound)
183 .checked_sub(gas_status.remaining_gas())
184 .unwrap()
185 .into()
186 }
187
188 fn vm_config(&self) -> VMConfig {
189 sui_adapter::adapter::vm_config(&self.protocol_config)
190 }
191
192 fn native_function_table(&self) -> move_vm_runtime::native_functions::NativeFunctionTable {
193 self.native_function_table.clone()
194 }
195
196 fn new_extensions_builder(&self) -> InMemoryTestStore {
197 InMemoryTestStore(RefCell::new(InMemoryStorage::default()))
198 }
199
200 fn new_native_context_extensions<'ext>(
201 &self,
202 store: &'ext InMemoryTestStore,
203 ) -> NativeContextExtensions<'ext> {
204 let mut ext = NativeContextExtensions::default();
205 let registry = prometheus::Registry::new();
207 let metrics = Arc::new(LimitsMetrics::new(®istry));
208
209 ext.add(ObjectRuntime::new(
210 store,
211 BTreeMap::new(),
212 false,
213 Box::leak(Box::new(ProtocolConfig::get_for_max_version_UNSAFE())), metrics,
215 0, ));
217 ext.add(NativesCostTable::from_protocol_config(
218 &self.protocol_config,
219 ));
220 let tx_context = TxContext::new_from_components(
221 &SuiAddress::ZERO,
222 &TransactionDigest::default(),
223 &0,
224 0,
225 0,
226 0,
227 0,
228 None,
229 &self.protocol_config,
230 );
231 ext.add(TransactionContext::new_for_testing(Rc::new(RefCell::new(
232 tx_context,
233 ))));
234 ext.add(store);
235 ext
236 }
237}
238
239pub struct SuiGasStatusTestWrapper(SuiGasStatus);
241
242impl Deref for SuiGasStatusTestWrapper {
243 type Target = GasStatus;
244
245 fn deref(&self) -> &Self::Target {
246 self.0.move_gas_status()
247 }
248}
249
250impl DerefMut for SuiGasStatusTestWrapper {
251 fn deref_mut(&mut self) -> &mut Self::Target {
252 self.0.move_gas_status_mut()
253 }
254}