1use crate::address::{InterfaceKind, ResourceAddress};
2use crate::error::{Error, Result};
3use crate::identity::Idn;
4use crate::kind::InstrumentKind;
5use crate::transport::{Transport, TransportIdentity};
6use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8use std::collections::VecDeque;
9use std::sync::{Arc, Mutex};
10use std::time::Duration;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(tag = "op", rename_all = "snake_case")]
15pub enum ScriptStep {
16 Write { data: String },
17 Read { data: String },
18 Clear,
19}
20
21#[derive(Debug, Clone)]
23pub struct MockTransport {
24 script: Vec<ScriptStep>,
25 steps: Arc<Mutex<Vec<ScriptStep>>>,
26 step_index: Arc<Mutex<usize>>,
27 identity: TransportIdentity,
28 fail_writes_remaining: Arc<Mutex<u32>>,
29 fail_reads_remaining: Arc<Mutex<u32>>,
30 pending_read: Arc<Mutex<VecDeque<u8>>>,
31}
32
33impl MockTransport {
34 pub fn from_script(steps: Vec<ScriptStep>) -> Self {
35 Self {
36 script: steps.clone(),
37 steps: Arc::new(Mutex::new(steps)),
38 step_index: Arc::new(Mutex::new(0)),
39 identity: TransportIdentity::default(),
40 fail_writes_remaining: Arc::new(Mutex::new(0)),
41 fail_reads_remaining: Arc::new(Mutex::new(0)),
42 pending_read: Arc::new(Mutex::new(VecDeque::new())),
43 }
44 }
45
46 pub fn reopen(&self) -> Self {
48 let mut t = Self::from_script(self.script.clone());
49 t.identity = self.identity.clone();
50 *t.fail_writes_remaining.lock().unwrap() = *self.fail_writes_remaining.lock().unwrap();
51 *t.fail_reads_remaining.lock().unwrap() = *self.fail_reads_remaining.lock().unwrap();
52 t
53 }
54
55 pub fn script(&self) -> &[ScriptStep] {
56 &self.script
57 }
58
59 pub fn with_identity(mut self, identity: TransportIdentity) -> Self {
60 self.identity = identity;
61 self
62 }
63
64 pub fn fail_writes(self, count: u32) -> Self {
66 *self.fail_writes_remaining.lock().unwrap() = count;
67 self
68 }
69
70 pub fn fail_reads(self, count: u32) -> Self {
72 *self.fail_reads_remaining.lock().unwrap() = count;
73 self
74 }
75
76 fn peek_step(&self) -> Result<ScriptStep> {
77 let idx = self.step_index.lock().unwrap();
78 let steps = self.steps.lock().unwrap();
79 if *idx >= steps.len() {
80 return Err(Error::MockExhausted);
81 }
82 Ok(steps[*idx].clone())
83 }
84
85 fn next_step(&self) -> Result<ScriptStep> {
86 let mut idx = self.step_index.lock().unwrap();
87 let steps = self.steps.lock().unwrap();
88 if *idx >= steps.len() {
89 return Err(Error::MockExhausted);
90 }
91 let step = steps[*idx].clone();
92 *idx += 1;
93 Ok(step)
94 }
95}
96
97impl Transport for MockTransport {
98 fn write(&mut self, data: &[u8]) -> Result<()> {
99 let mut fails = self.fail_writes_remaining.lock().unwrap();
100 if *fails > 0 {
101 *fails -= 1;
102 return Err(Error::Timeout);
103 }
104 drop(fails);
105
106 match self.next_step()? {
107 ScriptStep::Write { data: expected } => {
108 let actual = String::from_utf8_lossy(data).to_string();
109 if normalize_cmd(&actual) != normalize_cmd(&expected) {
110 return Err(Error::MockMismatch { expected, actual });
111 }
112 Ok(())
113 }
114 other => Err(Error::MockMismatch {
115 expected: format!("write, got {other:?}"),
116 actual: String::from_utf8_lossy(data).to_string(),
117 }),
118 }
119 }
120
121 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
122 let mut fails = self.fail_reads_remaining.lock().unwrap();
123 if *fails > 0 {
124 *fails -= 1;
125 return Err(Error::Timeout);
126 }
127 drop(fails);
128
129 if buf.is_empty() {
130 return Ok(0);
131 }
132 let mut pending = self.pending_read.lock().unwrap();
133 if pending.is_empty() {
134 if !matches!(self.peek_step()?, ScriptStep::Read { .. }) {
135 return Err(Error::Timeout);
136 }
137 if let ScriptStep::Read { data } = self.next_step()? {
138 pending.extend(data.into_bytes());
139 }
140 }
141 let n = pending.len().min(buf.len());
142 for byte in &mut buf[..n] {
143 *byte = pending.pop_front().unwrap();
144 }
145 Ok(n)
146 }
147
148 fn clear(&mut self) -> Result<()> {
149 match self.next_step()? {
150 ScriptStep::Clear => Ok(()),
151 other => Err(Error::MockMismatch {
152 expected: "clear".into(),
153 actual: format!("{other:?}"),
154 }),
155 }
156 }
157
158 fn set_read_timeout(&mut self, _timeout: Duration) -> Result<()> {
159 Ok(())
160 }
161
162 fn identity(&self) -> TransportIdentity {
163 self.identity.clone()
164 }
165}
166
167#[cfg(feature = "async")]
168impl crate::async_transport::AsyncTransport for MockTransport {
169 fn write<'a>(
170 &'a mut self,
171 data: &'a [u8],
172 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>> {
173 Box::pin(async move { Transport::write(self, data) })
174 }
175
176 fn read<'a>(
177 &'a mut self,
178 buf: &'a mut [u8],
179 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<usize>> + Send + 'a>> {
180 Box::pin(async move { Transport::read(self, buf) })
181 }
182
183 fn clear<'a>(
184 &'a mut self,
185 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>> {
186 Box::pin(async move { Transport::clear(self) })
187 }
188
189 fn set_read_timeout<'a>(
190 &'a mut self,
191 timeout: Duration,
192 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send + 'a>> {
193 Box::pin(async move { Transport::set_read_timeout(self, timeout) })
194 }
195
196 fn apply_read_timeout(&mut self, timeout: Duration) -> Result<()> {
197 Transport::set_read_timeout(self, timeout)
198 }
199
200 fn identity(&self) -> TransportIdentity {
201 Transport::identity(self)
202 }
203}
204
205fn normalize_cmd(s: &str) -> String {
206 s.trim().to_uppercase()
207}
208
209#[derive(Debug, Default)]
211pub struct ScriptedFixture {
212 idn: Idn,
213 kinds: Vec<InstrumentKind>,
214 query_handlers: IndexMap<String, String>,
215 write_handlers: Vec<String>,
216}
217
218impl ScriptedFixture {
219 pub fn builder() -> ScriptedFixtureBuilder {
220 ScriptedFixtureBuilder::default()
221 }
222
223 pub fn into_transport(self) -> MockTransport {
224 let mut steps = Vec::new();
225
226 for cmd in &self.write_handlers {
227 let data = if cmd.ends_with('\n') {
228 cmd.clone()
229 } else {
230 format!("{cmd}\n")
231 };
232 steps.push(ScriptStep::Write { data });
233 }
234
235 for (query, response) in &self.query_handlers {
236 steps.push(ScriptStep::Write {
237 data: format!("{query}\n"),
238 });
239 let resp = if response.ends_with('\n') {
240 response.clone()
241 } else {
242 format!("{response}\n")
243 };
244 steps.push(ScriptStep::Read { data: resp });
245 }
246
247 let identity = TransportIdentity {
248 manufacturer: Some(self.idn.manufacturer.clone()),
249 model: Some(self.idn.model.clone()),
250 serial: Some(self.idn.serial.clone()),
251 interface: InterfaceKind::Unknown,
252 manf_id: None,
253 model_code: None,
254 };
255
256 MockTransport::from_script(steps).with_identity(identity)
257 }
258
259 pub fn kinds(&self) -> &[InstrumentKind] {
260 &self.kinds
261 }
262
263 pub fn idn(&self) -> &Idn {
264 &self.idn
265 }
266}
267
268#[derive(Debug, Default)]
269pub struct ScriptedFixtureBuilder {
270 idn: Idn,
271 kinds: Vec<InstrumentKind>,
272 query_handlers: IndexMap<String, String>,
273 write_handlers: Vec<String>,
274}
275
276impl ScriptedFixtureBuilder {
277 pub fn idn(mut self, manufacturer: &str, model: &str, serial: &str, firmware: &str) -> Self {
278 self.idn = Idn {
279 manufacturer: manufacturer.into(),
280 model: model.into(),
281 serial: serial.into(),
282 firmware: firmware.into(),
283 };
284 self
285 }
286
287 pub fn kinds(mut self, kinds: impl IntoIterator<Item = InstrumentKind>) -> Self {
288 self.kinds = kinds.into_iter().collect();
289 self
290 }
291
292 pub fn on_query(mut self, query: &str, response: &str) -> Self {
293 self.query_handlers
294 .insert(query.to_string(), response.to_string());
295 self
296 }
297
298 pub fn on_write(mut self, command: &str) -> Self {
300 self.write_handlers.push(command.to_string());
301 self
302 }
303
304 pub fn with_idn_probe(mut self) -> Self {
306 self.query_handlers
307 .insert("*IDN?".into(), self.idn.format_response());
308 self
309 }
310
311 pub fn build(self) -> ScriptedFixture {
312 ScriptedFixture {
313 idn: self.idn,
314 kinds: self.kinds,
315 query_handlers: self.query_handlers,
316 write_handlers: self.write_handlers,
317 }
318 }
319}
320
321pub fn mock_address(name: &str) -> Result<ResourceAddress> {
323 ResourceAddress::parse(&format!("mock://{name}"))
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct Transcript {
329 pub steps: Vec<ScriptStep>,
330}
331
332impl Transcript {
333 pub fn from_steps(steps: Vec<ScriptStep>) -> Self {
334 Self { steps }
335 }
336
337 pub fn to_json(&self) -> Result<String> {
338 serde_json::to_string_pretty(self).map_err(|e| Error::Parse(e.to_string()))
339 }
340
341 pub fn from_json(json: &str) -> Result<Self> {
342 serde_json::from_str(json).map_err(|e| Error::Parse(e.to_string()))
343 }
344}