Skip to main content

harn_cli/test_runner/
session.rs

1use std::collections::BTreeSet;
2use std::path::PathBuf;
3use std::sync::Mutex;
4
5use serde::Serialize;
6
7use super::reporting::SuiteModulePreparation;
8
9/// Reusable runtime state for repeated user-test runs.
10///
11/// Logical workers share one bounded cache of immutable prepared bytecode while
12/// every test case still receives a fresh VM and fresh runtime state. Sharing
13/// lets the suite prepare its import graph once before per-test clocks start,
14/// instead of making one arbitrary test on every worker pay the cold compile.
15pub struct TestRunSession {
16    prepared_module_cache: harn_vm::PreparedModuleCache,
17    workers: Mutex<usize>,
18    clock: std::sync::Arc<dyn harn_vm::clock::Clock>,
19    stdio_available: bool,
20    callable_preparations: Mutex<(usize, usize)>,
21}
22
23impl Default for TestRunSession {
24    fn default() -> Self {
25        Self {
26            prepared_module_cache: harn_vm::PreparedModuleCache::default(),
27            workers: Mutex::new(0),
28            clock: harn_vm::clock::RealClock::arc(),
29            stdio_available: true,
30            callable_preparations: Mutex::new((0, 0)),
31        }
32    }
33}
34
35/// Aggregate prepared-module cache counters for a [`TestRunSession`].
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
37#[non_exhaustive]
38pub struct TestRunSessionStats {
39    pub workers: usize,
40    pub hits: u64,
41    pub misses: u64,
42    pub insertions: u64,
43    pub evictions: u64,
44    pub entries: usize,
45    pub test_files_compiled: usize,
46    pub test_entries_compiled: usize,
47}
48
49impl TestRunSession {
50    /// Build a session for an embedder whose control protocol owns stdio.
51    pub fn without_stdio() -> Self {
52        Self {
53            stdio_available: false,
54            ..Self::default()
55        }
56    }
57
58    pub fn stats(&self) -> TestRunSessionStats {
59        let stats = self.prepared_module_cache.stats();
60        let callable = *self.callable_preparations.lock().unwrap();
61        TestRunSessionStats {
62            workers: *self.workers.lock().unwrap(),
63            hits: stats.hits,
64            misses: stats.misses,
65            insertions: stats.insertions,
66            evictions: stats.evictions,
67            entries: stats.entries,
68            test_files_compiled: callable.0,
69            test_entries_compiled: callable.1,
70        }
71    }
72
73    pub(super) fn prepared_module_cache(
74        &self,
75        worker_index: usize,
76    ) -> harn_vm::PreparedModuleCache {
77        let mut workers = self.workers.lock().unwrap();
78        *workers = (*workers).max(worker_index.saturating_add(1));
79        self.prepared_module_cache.clone()
80    }
81
82    pub(super) fn prepare_import_graphs(
83        &self,
84        roots: impl IntoIterator<Item = (PathBuf, bool)>,
85    ) -> SuiteModulePreparation {
86        let mut user_files = BTreeSet::new();
87        let mut trusted_files = BTreeSet::new();
88        for (path, trusted_host_dispatch) in roots {
89            if trusted_host_dispatch {
90                trusted_files.insert(path);
91            } else {
92                user_files.insert(path);
93            }
94        }
95        let user = self.prepare_import_graph(&user_files.into_iter().collect::<Vec<_>>(), false);
96        let trusted =
97            self.prepare_import_graph(&trusted_files.into_iter().collect::<Vec<_>>(), true);
98        SuiteModulePreparation {
99            duration_ms: user.duration_ms.saturating_add(trusted.duration_ms),
100            modules: user.modules.saturating_add(trusted.modules),
101        }
102    }
103
104    fn prepare_import_graph(
105        &self,
106        roots: &[std::path::PathBuf],
107        trusted_host_dispatch: bool,
108    ) -> SuiteModulePreparation {
109        let started_ms = self.clock.monotonic_ms();
110        let modules = if trusted_host_dispatch {
111            self.prepared_module_cache
112                .prepare_trusted_host_dispatch_import_graph(roots)
113        } else {
114            self.prepared_module_cache.prepare_import_graph(roots)
115        };
116        let duration_ms = self.clock.monotonic_ms().saturating_sub(started_ms).max(0) as u64;
117        SuiteModulePreparation {
118            duration_ms,
119            modules,
120        }
121    }
122
123    pub(super) fn record_callable_preparation(&self, files: usize, entries: usize) {
124        let mut totals = self.callable_preparations.lock().unwrap();
125        totals.0 = totals.0.saturating_add(files);
126        totals.1 = totals.1.saturating_add(entries);
127    }
128
129    pub(super) fn stdio_available(&self) -> bool {
130        self.stdio_available
131    }
132}