1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
//! A single step in a sequence.
use rs_teststand_sys::{Dispatch, Value};
use crate::BreakpointScope;
use crate::Error;
use crate::dispids::step;
use crate::property::PropertyObject;
/// One step of a sequence (`Step`).
///
/// Built by [`Engine::new_step`](crate::Engine::new_step) and placed with
/// [`Sequence::insert_step`](crate::Sequence::insert_step).
///
/// This type carries the properties every step has, whatever its type. Anything
/// specific to a step type, a numeric limit test's limits, for instance, /// lives in the property tree reached through
/// [`as_property_object`](Self::as_property_object).
#[derive(Debug)]
pub struct Step {
dispatch: Box<dyn Dispatch>,
}
impl Step {
/// Wraps a dispatch handle returned by the engine.
pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
Self { dispatch }
}
/// The step's name (`Step.Name`).
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn name(&self) -> Result<String, Error> {
Ok(self.dispatch.get(step::NAME)?.into_string()?)
}
/// Sets the step's name (`Step.Name`).
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_name(&self, name: &str) -> Result<(), Error> {
self.dispatch.put(step::NAME, Value::Str(name.to_owned()))?;
Ok(())
}
/// The expression deciding whether the step runs (`Step.Precondition`).
///
/// An empty precondition means the step always runs.
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn precondition(&self) -> Result<String, Error> {
Ok(self.dispatch.get(step::PRECONDITION)?.into_string()?)
}
/// Sets the precondition expression (`Step.Precondition`).
///
/// The text is not checked here; a precondition that does not parse fails
/// when the sequence runs, not when it is set.
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_precondition(&self, expression: &str) -> Result<(), Error> {
self.dispatch
.put(step::PRECONDITION, Value::Str(expression.to_owned()))?;
Ok(())
}
/// What the engine does with the step, for one execution or for the file
/// (`Step.GetRunModeEx`).
///
/// The scope is the reason to prefer this over [`run_mode`](Self::run_mode).
/// [`BreakpointScope::Execution`] reads the mode set for that execution, so
/// a step can be skipped in one run without touching the file every other
/// run loads. [`BreakpointScope::Step`] reads the file's own mode, and so
/// does an execution that has no mode of its own.
///
/// `None` means the engine reported a mode this build does not name, which
/// is worth telling apart from a failure to read it at all.
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn run_mode_ex(&self, scope: BreakpointScope<'_>) -> Result<Option<crate::RunMode>, Error> {
let raw = self
.dispatch
.call(step::GET_RUN_MODE_EX, &[scope.argument()])?
.into_string()?;
Ok(crate::RunMode::from_value(&raw))
}
/// Sets the run mode, for one execution or for the file
/// (`Step.SetRunModeEx`).
///
/// See [`run_mode_ex`](Self::run_mode_ex) for what the scope changes.
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_run_mode_ex(
&self,
mode: crate::RunMode,
scope: BreakpointScope<'_>,
) -> Result<(), Error> {
self.dispatch.call(
step::SET_RUN_MODE_EX,
&[Value::Str(mode.as_str().to_owned()), scope.argument()],
)?;
Ok(())
}
/// What the engine does with the step when it reaches it (`Step.RunMode`).
///
/// The vendor marks this property obsolete in favor of
/// [`run_mode_ex`](Self::run_mode_ex), and it is kept because it still
/// works and is the shorter call when the file's own mode is what you want.
/// It cannot reach an execution's mode at all.
///
/// `None` means the engine reported a mode this build does not name, which
/// is worth telling apart from a failure to read it at all.
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn run_mode(&self) -> Result<Option<crate::RunMode>, Error> {
let raw = self.dispatch.get(step::RUN_MODE)?.into_string()?;
Ok(crate::RunMode::from_value(&raw))
}
/// Sets the run mode (`Step.RunMode`).
///
/// Obsolete in favor of [`set_run_mode_ex`](Self::set_run_mode_ex), which
/// can also set the mode for a single execution.
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_run_mode(&self, mode: crate::RunMode) -> Result<(), Error> {
self.dispatch
.put(step::RUN_MODE, Value::Str(mode.as_str().to_owned()))?;
Ok(())
}
/// The adapter the step calls its code module through
/// (`Step.AdapterKeyName`).
///
/// `None` means the engine reported a key this build does not name, or the
/// step calls no code module at all.
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn adapter_key_name(&self) -> Result<Option<crate::AdapterKeyName>, Error> {
let raw = self.dispatch.get(step::ADAPTER_KEY_NAME)?.into_string()?;
Ok(crate::AdapterKeyName::from_key(&raw))
}
/// The expression evaluated after the step runs (`Step.PostExpression`).
///
/// An empty expression means nothing runs afterwards.
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn post_expression(&self) -> Result<String, Error> {
Ok(self.dispatch.get(step::POST_EXPRESSION)?.into_string()?)
}
/// Sets the post expression (`Step.PostExpression`).
///
/// Like a precondition, the text is not checked here: an expression that
/// does not parse fails when the sequence runs, not when it is set.
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_post_expression(&self, expression: &str) -> Result<(), Error> {
self.dispatch
.put(step::POST_EXPRESSION, Value::Str(expression.to_owned()))?;
Ok(())
}
/// Gives the step a fresh unique identity (`Step.CreateNewUniqueStepId`).
///
/// A copy of a step carries the original's step ID, so a sequence built by
/// cloning a prototype ends up with several steps claiming the same
/// identity. Anything that refers to a step by ID, a result, a report
/// entry, a `GoTo`, then cannot tell them apart. Call this on each copy.
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn create_new_unique_step_id(&self) -> Result<(), Error> {
self.dispatch.call(step::CREATE_NEW_UNIQUE_STEP_ID, &[])?;
Ok(())
}
/// Whether this step contributes an entry to the result list
/// (`Step.ResultRecordingOption`).
///
/// Distinct from [`record_result`](Self::record_result), the plain on/off
/// switch: this one can also say "record even when the sequence says not
/// to". A step set to [`Disabled`](crate::ResultRecordingOption::Disabled)
/// leaves no entry in `ResultList`, which is the usual reason a parsed
/// report is shorter than the sequence that produced it.
///
/// # Errors
/// [`Error`] if the COM call fails or the engine reports an unnamed value.
pub fn result_recording_option(&self) -> Result<crate::ResultRecordingOption, Error> {
crate::ResultRecordingOption::from_bits(
self.dispatch.get(step::RESULT_RECORDING_OPTION)?.as_i32()?,
)
}
/// Sets whether this step records a result (`Step.ResultRecordingOption`).
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_result_recording_option(
&self,
option: crate::ResultRecordingOption,
) -> Result<(), Error> {
self.dispatch
.put(step::RESULT_RECORDING_OPTION, Value::I32(option as i32))?;
Ok(())
}
/// Whether the step's result is recorded (`Step.RecordResult`).
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn record_result(&self) -> Result<bool, Error> {
Ok(self.dispatch.get(step::RECORD_RESULT)?.as_bool()?)
}
/// Sets whether the step's result is recorded (`Step.RecordResult`).
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_record_result(&self, record: bool) -> Result<(), Error> {
self.dispatch
.put(step::RECORD_RESULT, Value::Bool(record))?;
Ok(())
}
/// The step as a property tree (`Step.AsPropertyObject`).
///
/// Type-specific settings live here, addressed by lookup path, /// `Limits.High` on a numeric limit test, for instance.
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn as_property_object(&self) -> Result<PropertyObject, Error> {
Ok(PropertyObject::new(
self.dispatch
.call(step::AS_PROPERTY_OBJECT, &[])?
.into_object()?,
))
}
/// The step's type definition (`Step.StepType`).
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn step_type(&self) -> Result<PropertyObject, Error> {
Ok(PropertyObject::new(
self.dispatch.get(step::STEP_TYPE)?.into_object()?,
))
}
/// An owned handle to the same step, for passing it back to the engine.
pub(crate) fn duplicate_dispatch(&self) -> Option<Box<dyn Dispatch>> {
self.dispatch.duplicate()
}
/// Whether this step carries a breakpoint (`Step.BreakOnStep`).
///
/// Reads the step itself. To ask about one run instead, use
/// [`break_on_step_for`](Self::break_on_step_for).
///
/// True here does not mean a run will stop. Breakpoints are only honored
/// while they are switched on, which
/// [`Engine::breakpoints_enabled`](crate::Engine::breakpoints_enabled)
/// controls for the session.
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn break_on_step(&self) -> Result<bool, Error> {
Ok(self.dispatch.get(step::BREAK_ON_STEP)?.as_bool()?)
}
/// Whether this step carries a breakpoint in the given scope
/// (`Step.GetBreakOnStepEx`).
///
/// # Errors
/// [`Error`] if the COM call fails or returns an unexpected type.
pub fn break_on_step_for(&self, scope: BreakpointScope<'_>) -> Result<bool, Error> {
Ok(self
.dispatch
.call(step::GET_BREAK_ON_STEP_EX, &[scope.argument()])?
.as_bool()?)
}
/// Sets or clears the breakpoint on this step (`Step.SetBreakOnStepEx`).
///
/// The scope decides how long it lasts.
/// [`BreakpointScope::Step`] writes it into
/// the step, so it survives the run and is saved with the sequence file.
/// [`BreakpointScope::Execution`] scopes
/// it to one run and leaves the file alone, which is what a host debugging
/// for a remote panel should use.
///
/// A stop announces itself as
/// [`UIMessageCode::BreakOnBreakpoint`](crate::UIMessageCode::BreakOnBreakpoint),
/// which arrived about 300 ms after the run started in a live measurement.
/// Continue with [`Execution::resume`](crate::Execution::resume), not
/// `Thread::resume`, which does not release a breakpoint stop.
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_break_on_step(
&self,
enabled: bool,
scope: BreakpointScope<'_>,
) -> Result<(), Error> {
self.dispatch.call(
step::SET_BREAK_ON_STEP_EX,
&[Value::Bool(enabled), scope.argument()],
)?;
Ok(())
}
/// Sets a breakpoint together with its pass count and condition
/// (`Step.SetBreakSettings`).
///
/// `is_set` places or removes the breakpoint and `enabled` decides whether
/// it is armed, so a breakpoint can stay in place while switched off.
/// `pass_count` stops on the nth arrival rather than the first.
/// `condition` is an expression the engine evaluates when it arrives; an
/// empty string means stop unconditionally.
///
/// Reading these back needs `Step.GetBreakSettings`, which returns
/// everything through `[out]` parameters and is not wrapped yet.
///
/// # Errors
/// [`Error`] if the COM call fails.
pub fn set_break_settings(
&self,
is_set: bool,
enabled: bool,
pass_count: i32,
condition: &str,
scope: BreakpointScope<'_>,
) -> Result<(), Error> {
self.dispatch.call(
step::SET_BREAK_SETTINGS,
&[
Value::Bool(is_set),
Value::Bool(enabled),
Value::I32(pass_count),
Value::Str(condition.to_owned()),
scope.argument(),
],
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use rs_teststand_sys::{ComError, Dispatch, Value};
use super::{BreakpointScope, Step};
use crate::dispids::step as dispid;
use crate::error::Error;
/// Shared with the test, because `Step` takes the dispatch by value.
type Sent = Rc<RefCell<Vec<(i32, usize)>>>;
/// Answers reads from a script and records every call.
#[derive(Debug)]
struct FakeDispatch {
reads: HashMap<i32, bool>,
sent: Sent,
}
impl Dispatch for FakeDispatch {
fn get(&self, dispid: i32) -> Result<Value, ComError> {
self.reads.get(&dispid).map_or_else(
|| Err(ComError::hresult(0, "fake: unscripted")),
|flag| Ok(Value::Bool(*flag)),
)
}
fn put(&self, _dispid: i32, _value: Value) -> Result<(), ComError> {
Err(ComError::hresult(0, "fake: put not scripted"))
}
fn call(&self, dispid: i32, args: &[Value]) -> Result<Value, ComError> {
self.sent.borrow_mut().push((dispid, args.len()));
Ok(Value::Bool(true))
}
}
fn step_recording(reads: HashMap<i32, bool>) -> (Step, Sent) {
let sent: Sent = Rc::default();
let dispatch = FakeDispatch {
reads,
sent: Rc::clone(&sent),
};
(Step::new(Box::new(dispatch)), sent)
}
#[test]
fn setting_a_run_mode_sends_the_mode_and_the_scope() -> Result<(), Error> {
// Two arguments, like the breakpoint pair, and for the same reason: an
// omitted execution is what tells the engine to edit the step itself.
let (step, sent) = step_recording(HashMap::new());
step.set_run_mode_ex(crate::RunMode::Skip, BreakpointScope::Step)?;
assert_eq!(
sent.borrow().as_slice(),
[(dispid::SET_RUN_MODE_EX, 2)],
"expected one call carrying the mode and the scope"
);
Ok(())
}
#[test]
fn reading_a_run_mode_sends_only_the_scope() {
let (step, sent) = step_recording(HashMap::new());
// The fake answers a bool, so decoding fails; the call is what matters.
let _ = step.run_mode_ex(BreakpointScope::Step);
assert_eq!(
sent.borrow().as_slice(),
[(dispid::GET_RUN_MODE_EX, 1)],
"the getter takes the scope and nothing else"
);
}
#[test]
fn break_on_step_reads_the_property() -> Result<(), Error> {
let (step, _) = step_recording(std::iter::once((dispid::BREAK_ON_STEP, true)).collect());
assert!(step.break_on_step()?);
Ok(())
}
#[test]
fn setting_a_breakpoint_sends_the_flag_and_the_scope() -> Result<(), Error> {
// Two arguments, always. The scope goes even when it is absent, because
// the engine reads an omitted execution differently from a null one.
let (step, sent) = step_recording(HashMap::new());
step.set_break_on_step(true, BreakpointScope::Step)?;
assert_eq!(
sent.borrow().as_slice(),
[(dispid::SET_BREAK_ON_STEP_EX, 2)],
"expected one call carrying the flag and the scope"
);
Ok(())
}
#[test]
fn break_settings_sends_all_five_arguments() -> Result<(), Error> {
// A short count is DISP_E_BADPARAMCOUNT on a live engine, which is the
// failure this pins.
let (step, sent) = step_recording(HashMap::new());
step.set_break_settings(true, true, 3, "Locals.Counter == 2", BreakpointScope::Step)?;
assert_eq!(
sent.borrow().as_slice(),
[(dispid::SET_BREAK_SETTINGS, 5)],
"the engine declares five input parameters"
);
Ok(())
}
}