Skip to main content

rs_teststand/execution/
execution.rs

1//! A running sequence.
2
3use rs_teststand_sys::{Dispatch, Value};
4
5use crate::Error;
6use crate::dispids::execution;
7
8/// A running sequence (`Execution`).
9///
10/// Created by [`Engine::new_execution`](crate::Engine::new_execution), which
11/// starts it immediately, there is no separate "run" step.
12#[derive(Debug)]
13pub struct Execution {
14    dispatch: Box<dyn Dispatch>,
15}
16
17impl Execution {
18    /// This execution as an argument for a member that scopes work to one run.
19    ///
20    /// Returns an absent argument when the dispatch cannot be duplicated, which
21    /// the engine reads as "no execution", meaning the member acts on the step
22    /// itself. Callers that must not fall back that way should check first.
23    pub(crate) fn as_argument(&self) -> Value {
24        self.dispatch
25            .duplicate()
26            .map_or(Value::Empty, Value::Object)
27    }
28    /// Wraps a dispatch handle returned by the engine.
29    pub(crate) fn new(dispatch: Box<dyn Dispatch>) -> Self {
30        Self { dispatch }
31    }
32
33    /// Lends the underlying handle for a call that takes an execution
34    /// reference.
35    pub(crate) fn duplicate_dispatch(&self) -> Option<Box<dyn Dispatch>> {
36        self.dispatch.duplicate()
37    }
38
39    /// The execution's identifier (`Execution.Id`).
40    ///
41    /// Messages posted by a sequence carry the execution that posted them, so
42    /// this is how a host attributes a message to the run that produced it.
43    ///
44    /// # Errors
45    /// [`Error`] if the COM call fails or returns an unexpected type.
46    pub fn id(&self) -> Result<i32, Error> {
47        Ok(self.dispatch.get(execution::ID)?.as_i32()?)
48    }
49
50    /// Waits for the execution to finish (`Execution.WaitForEndEx`).
51    ///
52    /// Returns `true` when it ended, `false` when the timeout came first. Pass
53    /// `-1` for no timeout.
54    ///
55    /// **Not for a host that polls for messages.** This does not pump the
56    /// message queue while it waits, so a synchronous message posted by the
57    /// sequence would never be acknowledged and both sides would stop. A
58    /// polling host should watch for
59    /// [`UIMessageCode::EndExecution`](crate::UIMessageCode::EndExecution) on
60    /// the queue instead, which is how a front end knows an execution finished.
61    ///
62    /// This is for synchronising *between* executions, waiting from a step for
63    /// another execution to finish.
64    ///
65    /// # Errors
66    /// [`Error`] if the COM call fails or returns an unexpected type.
67    pub fn wait_for_end_ex(
68        &self,
69        milliseconds: i32,
70        process_windows_messages: bool,
71    ) -> Result<bool, Error> {
72        // The two trailing parameters are optional object references and are
73        // deliberately omitted; passing a boolean into either corrupts the
74        // call, because the engine expects a variant holding an object.
75        Ok(self
76            .dispatch
77            .call(
78                execution::WAIT_FOR_END_EX,
79                &[
80                    Value::I32(milliseconds),
81                    Value::Bool(process_windows_messages),
82                ],
83            )?
84            .as_bool()?)
85    }
86
87    /// Waits for the execution to finish (`Execution.WaitForEnd`).
88    ///
89    /// Superseded by [`wait_for_end_ex`](Self::wait_for_end_ex); kept because
90    /// it is the member available on engines from TestStand 2016. The same
91    /// warning applies: it does not pump the message queue.
92    ///
93    /// # Errors
94    /// [`Error`] if the COM call fails or returns an unexpected type.
95    pub fn wait_for_end(
96        &self,
97        milliseconds: i32,
98        process_windows_messages: bool,
99    ) -> Result<bool, Error> {
100        Ok(self
101            .dispatch
102            .call(
103                execution::WAIT_FOR_END,
104                &[
105                    Value::I32(milliseconds),
106                    Value::Bool(process_windows_messages),
107                ],
108            )?
109            .as_bool()?)
110    }
111
112    /// Asks the execution to stop (`Execution.Terminate`).
113    ///
114    /// Termination is requested, not immediate: cleanup still runs.
115    ///
116    /// # Errors
117    /// [`Error`] if the COM call fails.
118    pub fn terminate(&self) -> Result<(), Error> {
119        self.dispatch.call(execution::TERMINATE, &[])?;
120        Ok(())
121    }
122
123    /// The name a front end shows for this execution (`Execution.DisplayName`).
124    ///
125    /// # Errors
126    /// [`Error`] if the COM call fails or returns an unexpected type.
127    pub fn display_name(&self) -> Result<String, Error> {
128        Ok(self.dispatch.get(execution::DISPLAY_NAME)?.into_string()?)
129    }
130
131    /// The overall result so far (`Execution.ResultStatus`).
132    ///
133    /// A string the engine owns, `"Passed"`, `"Failed"`, `"Terminated"`,
134    /// `"Error"`, `"Running"` and others. Deliberately not narrowed to an enum:
135    /// a sequence may set a status of its own, and folding an unknown one into
136    /// a fixed set would lose it.
137    ///
138    /// # Errors
139    /// [`Error`] if the COM call fails or returns an unexpected type.
140    pub fn result_status(&self) -> Result<String, Error> {
141        Ok(self.dispatch.get(execution::RESULT_STATUS)?.into_string()?)
142    }
143
144    /// The path of the sequence file being run (`Execution.SequenceFilePath`).
145    ///
146    /// # Errors
147    /// [`Error`] if the COM call fails or returns an unexpected type.
148    pub fn sequence_file_path(&self) -> Result<String, Error> {
149        Ok(self
150            .dispatch
151            .get(execution::SEQUENCE_FILE_PATH)?
152            .into_string()?)
153    }
154
155    /// How many threads the execution currently has (`Execution.NumThreads`).
156    ///
157    /// # Errors
158    /// [`Error`] if the COM call fails or returns an unexpected type.
159    pub fn num_threads(&self) -> Result<i32, Error> {
160        Ok(self.dispatch.get(execution::NUM_THREADS)?.as_i32()?)
161    }
162
163    /// Seconds spent executing (`Execution.SecondsExecuting`).
164    ///
165    /// # Errors
166    /// [`Error`] if the COM call fails or returns an unexpected type.
167    pub fn seconds_executing(&self) -> Result<f64, Error> {
168        Ok(self.dispatch.get(execution::SECONDS_EXECUTING)?.as_f64()?)
169    }
170
171    /// Seconds spent suspended (`Execution.SecondsSuspended`).
172    ///
173    /// # Errors
174    /// [`Error`] if the COM call fails or returns an unexpected type.
175    pub fn seconds_suspended(&self) -> Result<f64, Error> {
176        Ok(self.dispatch.get(execution::SECONDS_SUSPENDED)?.as_f64()?)
177    }
178
179    /// Suspends the execution (`Execution.Break`).
180    ///
181    /// Asynchronous, like every control member here: it asks, and the engine
182    /// acts when the running step allows. Do not assume the execution is
183    /// suspended by the time this returns.
184    ///
185    /// # Errors
186    /// [`Error`] if the COM call fails.
187    pub fn suspend(&self) -> Result<(), Error> {
188        self.dispatch.call(execution::BREAK, &[])?;
189        Ok(())
190    }
191
192    /// Resumes a suspended execution (`Execution.Resume`).
193    ///
194    /// # Errors
195    /// [`Error`] if the COM call fails.
196    pub fn resume(&self) -> Result<(), Error> {
197        self.dispatch.call(execution::RESUME, &[])?;
198        Ok(())
199    }
200
201    /// Stops the execution without running cleanup (`Execution.Abort`).
202    ///
203    /// The blunt counterpart to [`terminate`](Self::terminate): terminating
204    /// still runs Cleanup groups, so hardware is left in a safe state, and
205    /// aborting does not. Prefer terminating unless the point is to stop now.
206    ///
207    /// # Errors
208    /// [`Error`] if the COM call fails.
209    pub fn abort(&self) -> Result<(), Error> {
210        self.dispatch.call(execution::ABORT, &[])?;
211        Ok(())
212    }
213
214    /// Calls off a termination already under way (`Execution.CancelTermination`).
215    ///
216    /// # Deadlock
217    ///
218    /// **Call this only from inside a running step.** The reference is explicit
219    /// that calling it from an application's main thread, or from a step type's
220    /// edit substep, deadlocks, and it does: measured from a test thread, the
221    /// call never returns and the process has to be killed, which then leaves
222    /// sequence files unreleased.
223    ///
224    /// A host driving an engine from its own thread is in exactly the position
225    /// the reference warns about, so this is not a member a host calls to stop
226    /// a termination it started. It exists for code running as part of the
227    /// execution itself.
228    ///
229    /// # Errors
230    /// [`Error`] if the COM call fails.
231    pub fn cancel_termination(&self) -> Result<(), Error> {
232        self.dispatch.call(execution::CANCEL_TERMINATION, &[])?;
233        Ok(())
234    }
235
236    /// One of the execution's threads, by index (`Execution.GetThread`).
237    ///
238    /// # Errors
239    /// [`Error`] if the index is out of range or the COM call fails.
240    pub fn get_thread(&self, index: i32) -> Result<crate::execution::Thread, Error> {
241        Ok(crate::execution::Thread::new(
242            self.dispatch
243                .call(execution::GET_THREAD, &[Value::I32(index)])?
244                .into_object()?,
245        ))
246    }
247
248    /// The thread a front end is following (`Execution.ForegroundThread`).
249    ///
250    /// # Errors
251    /// [`Error`] if the COM call fails or returns an unexpected type.
252    pub fn foreground_thread(&self) -> Result<crate::execution::Thread, Error> {
253        Ok(crate::execution::Thread::new(
254            self.dispatch
255                .get(execution::FOREGROUND_THREAD)?
256                .into_object()?,
257        ))
258    }
259
260    /// The execution as a property tree (`Execution.AsPropertyObject`).
261    ///
262    /// # Errors
263    /// [`Error`] if the COM call fails or returns an unexpected type.
264    pub fn as_property_object(&self) -> Result<crate::PropertyObject, Error> {
265        Ok(crate::PropertyObject::new(
266            self.dispatch
267                .call(execution::AS_PROPERTY_OBJECT, &[])?
268                .into_object()?,
269        ))
270    }
271
272    /// The sequence file this execution is running (`Execution.GetSequenceFile`).
273    ///
274    /// Distinct from `GetModelSequenceFile`, which is the process model, a
275    /// neighbouring identifier, and mixing the two is silent.
276    ///
277    /// # Errors
278    /// [`Error`] if the COM call fails or returns an unexpected type.
279    pub fn get_sequence_file(&self) -> Result<crate::SequenceFile, Error> {
280        Ok(crate::SequenceFile::new(
281            self.dispatch
282                .call(execution::GET_SEQUENCE_FILE, &[])?
283                .into_object()?,
284        ))
285    }
286
287    /// What the run recorded (`Execution.ResultObject`).
288    ///
289    /// The root of the results tree. `ResultList` beneath it holds one entry per
290    /// step that recorded a result, which is what a headless caller reads
291    /// instead of a report file.
292    ///
293    /// # Errors
294    /// [`Error`] if the COM call fails or returns an unexpected type.
295    pub fn result_object(&self) -> Result<crate::PropertyObject, Error> {
296        Ok(crate::PropertyObject::new(
297            self.dispatch.get(execution::RESULT_OBJECT)?.into_object()?,
298        ))
299    }
300
301    /// The results this run recorded, ready to read.
302    ///
303    /// Composed from [`result_object`](Self::result_object), which is where the
304    /// `ResultList` array lives. This is the short path a headless caller wants:
305    /// run a sequence, then read what it produced without walking the tree by
306    /// hand.
307    ///
308    /// # Errors
309    /// [`Error`] if the run recorded no result list, or a COM call fails.
310    pub fn result_list(&self) -> Result<crate::ResultList, Error> {
311        crate::ResultList::from_result_object(&self.result_object()?)
312    }
313
314    /// The error the execution recorded (`Execution.ErrorObject`).
315    ///
316    /// # Errors
317    /// [`Error`] if the COM call fails or returns an unexpected type.
318    pub fn error_object(&self) -> Result<crate::PropertyObject, Error> {
319        Ok(crate::PropertyObject::new(
320            self.dispatch.get(execution::ERROR_OBJECT)?.into_object()?,
321        ))
322    }
323}