arcane_core/scripting/
test_runner.rs1use std::cell::RefCell;
2use std::path::Path;
3use std::rc::Rc;
4
5use anyhow::Context;
6use deno_core::JsRuntime;
7use deno_core::OpState;
8use deno_core::RuntimeOptions;
9
10use super::{ImportMap, TsModuleLoader};
11
12#[derive(Debug, Clone)]
14pub struct TestResult {
15 pub suite: String,
16 pub name: String,
17 pub passed: bool,
18 pub error: Option<String>,
19}
20
21#[derive(Debug, Clone, Default)]
23pub struct TestSummary {
24 pub total: usize,
25 pub passed: usize,
26 pub failed: usize,
27 pub results: Vec<TestResult>,
28}
29
30struct TestRunnerState {
32 summary: TestSummary,
33 results: Vec<TestResult>,
34}
35
36pub fn run_test_file(path: &Path) -> anyhow::Result<TestSummary> {
38 run_test_file_with_import_map(path, ImportMap::new())
39}
40
41pub fn run_test_file_with_import_map(path: &Path, import_map: ImportMap) -> anyhow::Result<TestSummary> {
43 let rt = tokio::runtime::Builder::new_current_thread()
44 .enable_all()
45 .build()?;
46
47 rt.block_on(run_test_file_async(path, import_map))
48}
49
50#[deno_core::op2(fast)]
51fn op_report_test(
52 state: &mut OpState,
53 #[string] suite: &str,
54 #[string] name: &str,
55 passed: bool,
56 #[string] error: &str,
57) {
58 let runner_state = state.borrow_mut::<Rc<RefCell<TestRunnerState>>>();
59 let mut s = runner_state.borrow_mut();
60
61 s.results.push(TestResult {
62 suite: suite.to_string(),
63 name: name.to_string(),
64 passed,
65 error: if error.is_empty() {
66 None
67 } else {
68 Some(error.to_string())
69 },
70 });
71
72 if passed {
73 s.summary.passed += 1;
74 } else {
75 s.summary.failed += 1;
76 }
77 s.summary.total += 1;
78}
79
80#[deno_core::op2]
81#[string]
82fn op_crypto_random_uuid_test() -> String {
83 super::runtime::generate_uuid()
84}
85
86deno_core::extension!(
87 test_runner_ext,
88 ops = [op_report_test, op_crypto_random_uuid_test],
89);
90
91async fn run_test_file_async(path: &Path, import_map: ImportMap) -> anyhow::Result<TestSummary> {
92 let state = Rc::new(RefCell::new(TestRunnerState {
93 summary: TestSummary::default(),
94 results: Vec::new(),
95 }));
96
97 let mut runtime = JsRuntime::new(RuntimeOptions {
98 module_loader: Some(Rc::new(TsModuleLoader::with_import_map(import_map))),
99 extensions: vec![test_runner_ext::init()],
100 ..Default::default()
101 });
102
103 {
105 let op_state = runtime.op_state();
106 op_state.borrow_mut().put(state.clone());
107 }
108
109 runtime.execute_script(
111 "<test_init>",
112 r#"
113 if (typeof globalThis.crypto === "undefined") {
114 globalThis.crypto = {};
115 }
116 if (typeof globalThis.crypto.randomUUID !== "function") {
117 globalThis.crypto.randomUUID = () => Deno.core.ops.op_crypto_random_uuid_test();
118 }
119 globalThis.__reportTest = (suite, name, passed, error) => {
120 Deno.core.ops.op_report_test(suite, name, passed, error ?? "");
121 };
122 "#,
123 ).map_err(|e| anyhow::anyhow!("{e}"))?;
124
125 let abs_path = std::fs::canonicalize(path)
127 .with_context(|| format!("Cannot resolve path: {}", path.display()))?;
128
129 let specifier =
130 deno_core::ModuleSpecifier::from_file_path(&abs_path).map_err(|_| {
131 anyhow::anyhow!("Cannot convert path to specifier: {}", abs_path.display())
132 })?;
133
134 let mod_id = runtime
135 .load_main_es_module(&specifier)
136 .await
137 .with_context(|| format!("Failed to load {}", path.display()))?;
138
139 let eval_result = runtime.mod_evaluate(mod_id);
140 runtime
141 .run_event_loop(Default::default())
142 .await
143 .map_err(|e| anyhow::anyhow!("{e}"))?;
144 eval_result
145 .await
146 .map_err(|e| anyhow::anyhow!("{e}"))?;
147
148 let promise = runtime.execute_script(
150 "<run_tests>",
151 "(async () => { await globalThis.__runTests(); })()",
152 ).map_err(|e| anyhow::anyhow!("{e}"))?;
153
154 let resolve = runtime.resolve(promise);
155 runtime
156 .run_event_loop(Default::default())
157 .await
158 .map_err(|e| anyhow::anyhow!("{e}"))?;
159 resolve.await.map_err(|e| anyhow::anyhow!("{e}"))?;
160
161 let s = state.borrow();
163 let mut summary = s.summary.clone();
164 summary.results = s.results.clone();
165 Ok(summary)
166}