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