hm_exec/local/runner/
mod.rs1use std::collections::HashMap;
8use std::fmt;
9use std::future::Future;
10use std::pin::Pin;
11use std::sync::Arc;
12
13use anyhow::Result;
14use hm_plugin_protocol::{ExecutorInput, SnapshotRef, StepResult};
15use tokio_util::sync::CancellationToken;
16
17use crate::local::archive::ArchiveStore;
18use crate::local::events::EventBus;
19
20pub mod vm;
21
22#[derive(Clone, Debug)]
28pub struct StepContext {
29 pub event_bus: Arc<EventBus>,
30 pub archives: Arc<ArchiveStore>,
31 pub cancel: CancellationToken,
32}
33
34pub trait StepRunner: Send + Sync + fmt::Debug {
42 fn name(&self) -> &'static str;
44
45 fn execute(
54 &self,
55 ctx: &StepContext,
56 input: ExecutorInput,
57 ) -> Pin<Box<dyn Future<Output = Result<StepResult>> + Send + '_>>;
58
59 fn reap_snapshots<'a>(
68 &'a self,
69 _snapshots: Vec<SnapshotRef>,
70 ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
71 Box::pin(async {})
72 }
73}
74
75#[derive(Default)]
80pub struct RunnerRegistry {
81 runners: HashMap<String, Arc<dyn StepRunner>>,
82 default: Option<String>,
83}
84
85impl RunnerRegistry {
86 #[must_use]
88 pub fn new() -> Self {
89 Self {
90 runners: HashMap::new(),
91 default: None,
92 }
93 }
94
95 pub fn register(&mut self, runner: Arc<dyn StepRunner>, is_default: bool) {
99 let name = runner.name().to_owned();
100 if is_default {
101 self.default = Some(name.clone());
102 }
103 self.runners.insert(name, runner);
104 }
105
106 #[must_use]
109 pub fn resolve(&self, name: Option<&str>) -> Option<Arc<dyn StepRunner>> {
110 let key = name.or(self.default.as_deref())?;
111 self.runners.get(key).cloned()
112 }
113
114 #[must_use]
116 pub fn default_runner_name(&self) -> Option<&str> {
117 self.default.as_deref()
118 }
119
120 #[must_use]
122 pub fn runner_names(&self) -> Vec<&str> {
123 let mut names: Vec<&str> = self.runners.keys().map(String::as_str).collect();
124 names.sort_unstable();
125 names
126 }
127}
128
129impl fmt::Debug for RunnerRegistry {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 f.debug_struct("RunnerRegistry")
132 .field("runners", &self.runners.keys().collect::<Vec<_>>())
133 .field("default", &self.default)
134 .finish()
135 }
136}
137
138#[cfg(test)]
139#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
140mod tests {
141 use super::*;
142
143 #[derive(Debug)]
145 struct StubRunner {
146 runner_name: &'static str,
147 }
148
149 impl StubRunner {
150 fn new(name: &'static str) -> Self {
151 Self { runner_name: name }
152 }
153 }
154
155 impl StepRunner for StubRunner {
156 fn name(&self) -> &'static str {
157 self.runner_name
158 }
159
160 fn execute(
161 &self,
162 _ctx: &StepContext,
163 _input: ExecutorInput,
164 ) -> Pin<Box<dyn Future<Output = Result<StepResult>> + Send + '_>> {
165 Box::pin(async {
166 Ok(StepResult {
167 exit_code: 0,
168 committed_snapshot: None,
169 artifacts: vec![],
170 })
171 })
172 }
173 }
174
175 #[test]
176 fn resolve_by_name() {
177 let mut reg = RunnerRegistry::new();
178 reg.register(Arc::new(StubRunner::new("docker")), false);
179 reg.register(Arc::new(StubRunner::new("local")), false);
180
181 let runner = reg.resolve(Some("docker")).unwrap();
182 assert_eq!(runner.name(), "docker");
183
184 let runner = reg.resolve(Some("local")).unwrap();
185 assert_eq!(runner.name(), "local");
186
187 assert!(reg.resolve(Some("nope")).is_none());
188 }
189
190 #[test]
191 fn resolve_default() {
192 let mut reg = RunnerRegistry::new();
193 reg.register(Arc::new(StubRunner::new("docker")), true);
194 reg.register(Arc::new(StubRunner::new("local")), false);
195
196 let runner = reg.resolve(None).unwrap();
198 assert_eq!(runner.name(), "docker");
199 assert_eq!(reg.default_runner_name(), Some("docker"));
200 }
201
202 #[test]
203 fn no_default_returns_none() {
204 let mut reg = RunnerRegistry::new();
205 reg.register(Arc::new(StubRunner::new("docker")), false);
206
207 assert!(reg.resolve(None).is_none());
208 assert!(reg.default_runner_name().is_none());
209 }
210
211 #[test]
212 fn runner_names_sorted() {
213 let mut reg = RunnerRegistry::new();
214 reg.register(Arc::new(StubRunner::new("zeta")), false);
215 reg.register(Arc::new(StubRunner::new("alpha")), false);
216 reg.register(Arc::new(StubRunner::new("mid")), false);
217
218 assert_eq!(reg.runner_names(), vec!["alpha", "mid", "zeta"]);
219 }
220
221 #[test]
222 fn debug_impl() {
223 let reg = RunnerRegistry::new();
224 let _ = format!("{reg:?}");
226 }
227}