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, extensions::set_extension_hook};
11use move_vm_runtime::native_extensions::NativeContextExtensions;
12use once_cell::sync::Lazy;
13use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc, sync::Arc};
14use sui_move_build::decorate_warnings;
15use sui_move_natives::{
16 NativesCostTable, object_runtime::ObjectRuntime, test_scenario::InMemoryTestStore,
17 transaction_context::TransactionContext,
18};
19use sui_package_alt::find_environment;
20use sui_protocol_config::ProtocolConfig;
21use sui_sdk::wallet_context::WalletContext;
22use sui_types::{
23 base_types::{SuiAddress, TxContext},
24 digests::TransactionDigest,
25 gas_model::tables::initial_cost_schedule_for_unit_tests,
26 in_memory_storage::InMemoryStorage,
27 metrics::LimitsMetrics,
28};
29
30const MAX_UNIT_TEST_INSTRUCTIONS: u64 = 1_000_000;
32
33#[derive(Parser)]
34#[group(id = "sui-move-test")]
35pub struct Test {
36 #[clap(flatten)]
37 pub test: test::Test,
38}
39
40impl Test {
41 pub async fn execute(
42 self,
43 path: Option<&Path>,
44 mut build_config: BuildConfig,
45 wallet: &WalletContext,
46 ) -> anyhow::Result<UnitTestResult> {
47 let compute_coverage = self.test.compute_coverage;
48 if !cfg!(feature = "tracing") && compute_coverage {
49 return Err(anyhow::anyhow!(
50 "The --coverage flag is currently supported only in builds built with the `tracing` feature enabled. \
51 Please build the Sui CLI from source with `--features tracing` to use this flag."
52 ));
53 }
54 let save_disassembly = self.test.trace;
56 if build_config.default_flavor.is_none() {
58 build_config.default_flavor = Some(move_compiler::editions::Flavor::Sui);
59 }
60
61 let rerooted_path = base::reroot_path(path)?;
63 let unit_test_config = self.test.unit_test_config();
64
65 let environment =
69 find_environment(&rerooted_path, build_config.environment, wallet).await?;
70 build_config.environment = Some(environment.name);
71
72 run_move_unit_tests(
73 &rerooted_path,
74 build_config,
75 Some(unit_test_config),
76 compute_coverage,
77 save_disassembly,
78 )
79 .await
80 }
81}
82
83thread_local! {
85 static TEST_STORE_INNER: RefCell<InMemoryStorage> = RefCell::new(InMemoryStorage::default());
86}
87
88static TEST_STORE: Lazy<InMemoryTestStore> = Lazy::new(|| InMemoryTestStore(&TEST_STORE_INNER));
89
90static SET_EXTENSION_HOOK: Lazy<()> =
91 Lazy::new(|| set_extension_hook(Box::new(new_testing_object_and_natives_cost_runtime)));
92
93pub async fn run_move_unit_tests(
96 path: &Path,
97 build_config: BuildConfig,
98 config: Option<UnitTestingConfig>,
99 compute_coverage: bool,
100 save_disassembly: bool,
101) -> anyhow::Result<UnitTestResult> {
102 Lazy::force(&SET_EXTENSION_HOOK);
104
105 let config = config
106 .unwrap_or_else(|| UnitTestingConfig::default_with_bound(Some(MAX_UNIT_TEST_INSTRUCTIONS)));
107
108 let result = move_cli::base::test::run_move_unit_tests::<sui_package_alt::SuiFlavor, _>(
109 path,
110 build_config,
111 UnitTestingConfig {
112 report_stacktrace_on_abort: true,
113 ..config
114 },
115 sui_move_natives::all_natives(
116 false,
117 &ProtocolConfig::get_for_max_version_UNSAFE(),
118 ),
119 Some(initial_cost_schedule_for_unit_tests()),
120 compute_coverage,
121 save_disassembly,
122 &mut std::io::stdout(),
123 )
124 .await;
125
126 result.map(|(test_result, warning_diags)| {
127 if test_result == UnitTestResult::Success
128 && let Some(diags) = warning_diags
129 {
130 decorate_warnings(diags, None);
131 }
132 test_result
133 })
134}
135
136fn new_testing_object_and_natives_cost_runtime(ext: &mut NativeContextExtensions) {
137 let registry = prometheus::Registry::new();
139 let metrics = Arc::new(LimitsMetrics::new(®istry));
140 let store = Lazy::force(&TEST_STORE);
141 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
142
143 ext.add(ObjectRuntime::new(
144 store,
145 BTreeMap::new(),
146 false,
147 Box::leak(Box::new(ProtocolConfig::get_for_max_version_UNSAFE())), metrics,
149 0, ));
151 ext.add(NativesCostTable::from_protocol_config(&protocol_config));
152 let tx_context = TxContext::new_from_components(
153 &SuiAddress::ZERO,
154 &TransactionDigest::default(),
155 &0,
156 0,
157 0,
158 0,
159 0,
160 None,
161 &protocol_config,
162 );
163 ext.add(TransactionContext::new_for_testing(Rc::new(RefCell::new(
164 tx_context,
165 ))));
166 ext.add(store);
167}