rs_teststand/execution/termination_option.rs
1//! How a thread answers a request to terminate its execution.
2
3/// What a thread does when its execution is asked to terminate
4/// (`ThreadTerminationOptions`).
5///
6/// An execution cannot finish terminating while any of its threads refuses to
7/// stop, so this decides whether `Execution::terminate` completes promptly or
8/// waits.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[non_exhaustive]
11pub enum ThreadTerminationOption {
12 /// Stop with the execution. The default, and what an unattended host wants
13 /// almost everywhere.
14 Normal,
15 /// Refuse to stop unless the execution is told to override refusals.
16 ///
17 /// When every remaining thread chooses this, the engine posts
18 /// `UIMsg_NonTerminatableThreadsArePreventingTermination` and waits. On a
19 /// station with somebody watching, that is a question. On a headless host
20 /// it is a termination that never finishes unless the host handles that
21 /// message and overrides, so treat this as something to notice rather than
22 /// something to set casually.
23 Prompt,
24 /// Never stop with the execution; the thread runs to its own end first.
25 ///
26 /// For work that must not be cut in half, such as leaving hardware in a
27 /// safe state. The execution cannot end until the thread does, so anything
28 /// choosing this needs a bounded amount of work left to do.
29 Never,
30}
31
32impl ThreadTerminationOption {
33 /// Maps the engine's number onto an option.
34 ///
35 /// # Errors
36 /// The raw value, when it is one this build does not name.
37 pub const fn from_bits(bits: i32) -> Result<Self, i32> {
38 match bits {
39 0 => Ok(Self::Normal),
40 1 => Ok(Self::Prompt),
41 2 => Ok(Self::Never),
42 unknown => Err(unknown),
43 }
44 }
45
46 /// The engine's number for this option.
47 #[must_use]
48 pub const fn bits(self) -> i32 {
49 match self {
50 Self::Normal => 0,
51 Self::Prompt => 1,
52 Self::Never => 2,
53 }
54 }
55
56 /// Whether a thread with this option stops when its execution terminates.
57 ///
58 /// False for both [`Prompt`](Self::Prompt) and [`Never`](Self::Never), so a
59 /// host can tell in one call whether a thread is going to hold up a
60 /// termination.
61 #[must_use]
62 pub const fn stops_with_execution(self) -> bool {
63 matches!(self, Self::Normal)
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::ThreadTerminationOption;
70
71 #[test]
72 fn every_documented_value_round_trips() {
73 for raw in 0..=2 {
74 assert_eq!(
75 ThreadTerminationOption::from_bits(raw).map(ThreadTerminationOption::bits),
76 Ok(raw),
77 "{raw} is documented but did not round-trip"
78 );
79 }
80 }
81
82 #[test]
83 fn an_unknown_value_is_returned_rather_than_guessed() {
84 assert_eq!(ThreadTerminationOption::from_bits(7), Err(7));
85 }
86
87 #[test]
88 fn only_normal_stops_with_its_execution() {
89 // The distinction a host branches on when a terminate does not finish.
90 assert!(ThreadTerminationOption::Normal.stops_with_execution());
91 assert!(!ThreadTerminationOption::Prompt.stops_with_execution());
92 assert!(!ThreadTerminationOption::Never.stops_with_execution());
93 }
94}