rs_teststand/enums.rs
1//! Type-safe Rust enums for TestStand™ options, types, and flags.
2//!
3//! All discriminants are sourced from TestStand™ type library ground truth.
4
5/// Property value types (`PropValType_*`).
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[repr(i32)]
8pub enum PropValType {
9 /// Container object (`PropValType_Container = 0`).
10 Container = 0,
11 /// String property (`PropValType_String = 1`).
12 String = 1,
13 /// Boolean property (`PropValType_Boolean = 2`).
14 Boolean = 2,
15 /// Numeric property (`PropValType_Number = 3`).
16 Number = 3,
17 /// Named type instance (`PropValType_NamedType = 4`).
18 NamedType = 4,
19 /// Property reference (`PropValType_Reference = 5`).
20 Reference = 5,
21 /// Array property (`PropValType_Array = 6`).
22 Array = 6,
23 /// Enum property (`PropValType_Enum = 7`).
24 Enum = 7,
25}
26
27bitflags::bitflags! {
28 /// A set of property value type flags (`PropValTypeFlag_*`).
29 ///
30 /// A bitmask, not an enumeration: the engine combines flags, and a caller
31 /// must be able to express "number or string".
32 ///
33 /// ```
34 /// use rs_teststand::PropertyValueTypeFlags as Flags;
35 ///
36 /// let numeric_or_text = Flags::NUMBER | Flags::STRING;
37 /// assert!(numeric_or_text.contains(Flags::STRING));
38 /// assert!(!numeric_or_text.contains(Flags::CONTAINER));
39 /// ```
40 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
41 pub struct PropertyValueTypeFlags: i32 {
42 /// No flags set.
43 const NONE = 0;
44 /// Matches any property type (`PropValTypeFlag_Any`).
45 const ANY = -1;
46 /// Boolean property (`PropValTypeFlag_Boolean`).
47 const BOOLEAN = 1;
48 /// Numeric property (`PropValTypeFlag_Number`).
49 const NUMBER = 2;
50 /// String property (`PropValTypeFlag_String`).
51 const STRING = 4;
52 /// Reference property (`PropValTypeFlag_Reference`).
53 const REFERENCE = 8;
54 /// Container property (`PropValTypeFlag_Container`).
55 const CONTAINER = 16;
56 /// Named type instance (`PropValTypeFlag_NamedType`).
57 const NAMED_TYPE = 32;
58 /// Boolean array (`PropValTypeFlag_BooleanArray`).
59 const BOOLEAN_ARRAY = 64;
60 /// Numeric array (`PropValTypeFlag_NumberArray`).
61 const NUMBER_ARRAY = 128;
62 /// String array (`PropValTypeFlag_StringArray`).
63 const STRING_ARRAY = 256;
64 /// Reference array (`PropValTypeFlag_ReferenceArray`).
65 const REFERENCE_ARRAY = 512;
66 /// Container array (`PropValTypeFlag_ContainerArray`).
67 const CONTAINER_ARRAY = 1024;
68 /// Array of a named type (`PropValTypeFlag_ArrayOfNamedType`).
69 const ARRAY_OF_NAMED_TYPE = 2048;
70 /// Empty / absent type (`PropValTypeFlag_Nothing`).
71 const NOTHING = 4096;
72 /// Object reference (`PropValTypeFlag_Object`).
73 const OBJECT = 16384;
74 /// Plain reference (`PropValTypeFlag_PlainReference`).
75 const PLAIN_REFERENCE = 32768;
76 /// Plain container (`PropValTypeFlag_PlainContainer`).
77 const PLAIN_CONTAINER = 65536;
78 /// Enum type (`PropValTypeFlag_Enum`).
79 const ENUM = 131_072;
80 }
81}
82
83/// Step execution groups in a sequence (`StepGroup_*`).
84///
85/// A sequence holds three ordered lists rather than one. Setup runs first, Main
86/// carries the test, and Cleanup runs last, including after Main fails, which
87/// is what makes it the place for anything that must happen regardless.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89#[repr(i32)]
90pub enum StepGroup {
91 /// Setup step group (`StepGroup_Setup = 0`).
92 Setup = 0,
93 /// Main step group (`StepGroup_Main = 1`).
94 Main = 1,
95 /// Cleanup step group (`StepGroup_Cleanup = 2`).
96 Cleanup = 2,
97}
98
99impl StepGroup {
100 /// Every group, in execution order.
101 pub const ALL: [Self; 3] = [Self::Setup, Self::Main, Self::Cleanup];
102
103 /// The value the COM boundary expects.
104 #[must_use]
105 pub const fn bits(self) -> i32 {
106 self as i32
107 }
108
109 /// Reads a raw value, returning it unchanged when unrecognized.
110 ///
111 /// # Errors
112 /// The raw value, when it matches no known group.
113 pub const fn from_bits(raw: i32) -> Result<Self, i32> {
114 Ok(match raw {
115 0 => Self::Setup,
116 1 => Self::Main,
117 2 => Self::Cleanup,
118 other => return Err(other),
119 })
120 }
121}
122
123/// Execution running states (`ExecRunState_*`).
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125#[repr(i32)]
126pub enum ExecRunState {
127 /// Execution is currently running (`ExecRunState_Running = 1`).
128 Running = 1,
129 /// Execution is paused (`ExecRunState_Paused = 2`).
130 Paused = 2,
131 /// Execution is stopped (`ExecRunState_Stopped = 3`).
132 Stopped = 3,
133}
134
135/// Execution termination states (`ExecTermState_*`).
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137#[repr(i32)]
138pub enum ExecTermState {
139 /// Normal execution state (`ExecTermState_Normal = 1`).
140 Normal = 1,
141 /// Execution is terminating (`ExecTermState_Terminating = 2`).
142 Terminating = 2,
143 /// Interactive termination (`ExecTermState_TerminatingInteractive = 3`).
144 TerminatingInteractive = 3,
145 /// Execution is aborting (`ExecTermState_Aborting = 4`).
146 Aborting = 4,
147 /// Killing execution threads (`ExecTermState_KillingThreads = 5`).
148 KillingThreads = 5,
149}
150
151/// Search directory categories (`SearchDirectoryType_*`).
152// Every variant ends in `Dir`, which trips `clippy::enum_variant_names`. The
153// names are deliberate: they mirror the type library one-for-one
154// (`SearchDirectoryType_TestStandDir` -> `TestStandDir`) per the twin-API rule,
155// so renaming them to satisfy the lint would make the mapping unpredictable.
156#[allow(
157 clippy::enum_variant_names,
158 reason = "variant names mirror the type library"
159)]
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
161#[repr(i32)]
162pub enum SearchDirectoryType {
163 /// TestStand root directory (`SearchDirectoryType_TestStandDir = 1`).
164 TestStandDir = 1,
165 /// TestStand bin directory (`SearchDirectoryType_TestStandBinDir = 2`).
166 TestStandBinDir = 2,
167 /// Adapter support directory (`SearchDirectoryType_AdapterSupportDir = 3`).
168 AdapterSupportDir = 3,
169 /// Application directory (`SearchDirectoryType_ApplicationDir = 4`).
170 ApplicationDir = 4,
171 /// Initial working directory (`SearchDirectoryType_InitialWorkingDir = 5`).
172 InitialWorkingDir = 5,
173 /// Windows system directory (`SearchDirectoryType_WindowsSystemDir = 6`).
174 WindowsSystemDir = 6,
175 /// Windows directory (`SearchDirectoryType_WindowsDir = 7`).
176 WindowsDir = 7,
177 /// PATH environment variable directory (`SearchDirectoryType_PathEnvironmentVarDir = 8`).
178 PathEnvironmentVarDir = 8,
179 /// Current sequence file directory (`SearchDirectoryType_CurrentSequenceFileDir = 9`).
180 CurrentSequenceFileDir = 9,
181 /// User/Public components directory (`SearchDirectoryType_UserComponentsDir = 11`).
182 UserComponentsDir = 11,
183 /// NI components directory (`SearchDirectoryType_NIComponentsDir = 12`).
184 NIComponentsDir = 12,
185 /// Current workspace directory (`SearchDirectoryType_CurrentWorkspaceDir = 13`).
186 CurrentWorkspaceDir = 13,
187 /// Containing project directory (`SearchDirectoryType_ContainingProjectDir = 14`).
188 ContainingProjectDir = 14,
189 /// Explicit user directory (`SearchDirectoryType_ExplicitDir = 15`).
190 ExplicitDir = 15,
191 /// TestStand public directory (`SearchDirectoryType_TestStandPublicDir = 16`).
192 TestStandPublicDir = 16,
193}
194
195/// Options for opening workspace files (`OpenWorkspaceFileOptions_*`).
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
197#[repr(i32)]
198pub enum OpenWorkspaceFileOptions {
199 /// No options (`OpenWorkspaceFile_NoOptions = 0`).
200 NoOptions = 0,
201 /// Ignore missing files (`OpenWorkspaceFile_IgnoreMissingFiles = 1`).
202 IgnoreMissingFiles = 1,
203 /// Search current directory (`OpenWorkspaceFile_SearchCurrentDirectory = 2`).
204 SearchCurrentDirectory = 2,
205 /// Use search directories (`OpenWorkspaceFile_UseSearchDirectories = 4`).
206 UseSearchDirectories = 4,
207}
208
209/// Options for saving workspace files (`SaveWorkspaceFileOptions_*`).
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
211#[repr(i32)]
212pub enum SaveWorkspaceFileOptions {
213 /// No options (`SaveWorkspaceFile_NoOptions = 0`).
214 NoOptions = 0,
215 /// Prompt user (`SaveWorkspaceFile_PromptUser = 1`).
216 PromptUser = 1,
217 /// Skip workspace file (`SaveWorkspaceFile_SkipWorkspaceFile = 2`).
218 SkipWorkspaceFile = 2,
219 /// Skip read-only files (`SaveWorkspaceFile_SkipReadOnlyFiles = 4`).
220 SkipReadOnlyFiles = 4,
221}
222
223impl TryFrom<i32> for SearchDirectoryType {
224 type Error = i32;
225
226 fn try_from(value: i32) -> Result<Self, Self::Error> {
227 match value {
228 1 => Ok(Self::TestStandDir),
229 2 => Ok(Self::TestStandBinDir),
230 3 => Ok(Self::AdapterSupportDir),
231 4 => Ok(Self::ApplicationDir),
232 5 => Ok(Self::InitialWorkingDir),
233 6 => Ok(Self::WindowsSystemDir),
234 7 => Ok(Self::WindowsDir),
235 8 => Ok(Self::PathEnvironmentVarDir),
236 9 => Ok(Self::CurrentSequenceFileDir),
237 11 => Ok(Self::UserComponentsDir),
238 12 => Ok(Self::NIComponentsDir),
239 13 => Ok(Self::CurrentWorkspaceDir),
240 14 => Ok(Self::ContainingProjectDir),
241 15 => Ok(Self::ExplicitDir),
242 16 => Ok(Self::TestStandPublicDir),
243 other => Err(other),
244 }
245 }
246}
247
248impl std::fmt::Display for SearchDirectoryType {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 write!(f, "{self:?}")
251 }
252}
253
254impl std::fmt::Display for PropValType {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 write!(f, "{self:?}")
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn discriminants_match_tlb_ground_truth() {
266 assert_eq!(PropValType::Container as i32, 0);
267 assert_eq!(PropValType::String as i32, 1);
268 assert_eq!(PropValType::Boolean as i32, 2);
269 assert_eq!(PropValType::Number as i32, 3);
270 assert_eq!(PropValType::Enum as i32, 7);
271
272 assert_eq!(PropertyValueTypeFlags::ANY.bits(), -1);
273 assert_eq!(PropertyValueTypeFlags::CONTAINER.bits(), 16);
274 assert_eq!(PropertyValueTypeFlags::ENUM.bits(), 131_072);
275 }
276
277 #[test]
278 fn value_type_flags_combine_and_round_trip() {
279 // The engine treats these as a mask: a combination is a legal value and
280 // must survive a round trip through the COM boundary unchanged.
281 let mask = PropertyValueTypeFlags::NUMBER | PropertyValueTypeFlags::STRING;
282 assert_eq!(mask.bits(), 6);
283 assert!(mask.contains(PropertyValueTypeFlags::NUMBER));
284 assert!(mask.contains(PropertyValueTypeFlags::STRING));
285 assert!(!mask.contains(PropertyValueTypeFlags::CONTAINER));
286 assert_eq!(PropertyValueTypeFlags::from_bits_retain(mask.bits()), mask);
287
288 let mut acc = PropertyValueTypeFlags::NONE;
289 assert!(acc.is_empty());
290 acc |= PropertyValueTypeFlags::CONTAINER;
291 assert_eq!(acc, PropertyValueTypeFlags::CONTAINER);
292 assert_eq!(
293 acc & PropertyValueTypeFlags::CONTAINER,
294 PropertyValueTypeFlags::CONTAINER
295 );
296 }
297
298 #[test]
299 fn remaining_discriminants_match_tlb_ground_truth() {
300 assert_eq!(StepGroup::Setup as i32, 0);
301 assert_eq!(StepGroup::Main as i32, 1);
302 assert_eq!(StepGroup::Cleanup as i32, 2);
303
304 assert_eq!(ExecRunState::Running as i32, 1);
305 assert_eq!(ExecTermState::Normal as i32, 1);
306
307 assert_eq!(SearchDirectoryType::ExplicitDir as i32, 15);
308 assert_eq!(
309 SearchDirectoryType::try_from(15),
310 Ok(SearchDirectoryType::ExplicitDir)
311 );
312
313 assert_eq!(OpenWorkspaceFileOptions::NoOptions as i32, 0);
314 assert_eq!(OpenWorkspaceFileOptions::UseSearchDirectories as i32, 4);
315
316 assert_eq!(SaveWorkspaceFileOptions::NoOptions as i32, 0);
317 assert_eq!(SaveWorkspaceFileOptions::SkipReadOnlyFiles as i32, 4);
318 }
319}