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}