visa_rs/lib.rs
1//!
2//! Safe rust bindings for VISA(Virtual Instrument Software Architecture) library
3//!
4//! Most documentation comes from [NI-VISA Product Documentation](https://www.ni.com/docs/en-US/bundle/ni-visa-20.0/page/ni-visa/help_file_title.html)
5//!
6//! # Requirements
7//! This crate needs to link to an installed visa library, for example, [NI-VISA](https://www.ni.com/en-us/support/downloads/drivers/download.ni-visa.html).
8//!
9//! You can specify path of `visa64.lib` file (or `visa32.lib` on 32-bit systems) by setting environment variable `LIB_VISA_PATH`.
10//!
11//! On Windows, the default installation path will be added if no path is specified.
12//!
13//! # Example
14//!
15//! Codes below will find the first Keysight instrument in your environment and print out its `*IDN?` response.
16//!
17//! ```
18//! fn main() -> visa_rs::Result<()>{
19//! use std::ffi::CString;
20//! use std::io::{BufRead, BufReader, Read, Write};
21//! use visa_rs::prelude::*;
22//!
23//! // open default resource manager
24//! let rm: DefaultRM = DefaultRM::new()?;
25//!
26//! // expression to match resource name
27//! let expr = CString::new("?*KEYSIGH?*INSTR").unwrap().into();
28//!
29//! // find the first resource matched
30//! let rsc = rm.find_res(&expr)?;
31//!
32//! // open a session to the resource, the session will be closed when rm is dropped
33//! let instr: Instrument = rm.open(&rsc, AccessMode::NO_LOCK, TIMEOUT_IMMEDIATE)?;
34//!
35//! // write message
36//! (&instr).write_all(b"*IDN?\n").map_err(io_to_vs_err)?;
37//!
38//! // read response
39//! let mut buf_reader = BufReader::new(&instr);
40//! let mut buf = String::new();
41//! buf_reader.read_line(&mut buf).map_err(io_to_vs_err)?;
42//!
43//! eprintln!("{}", buf);
44//! Ok(())
45//! }
46//! ```
47
48use enums::{attribute, event};
49use std::ffi::CStr;
50use std::{borrow::Cow, ffi::CString, fmt::Display, time::Duration};
51pub use visa_sys as vs;
52
53mod async_io;
54pub mod enums;
55pub mod flags;
56pub mod handler;
57mod instrument;
58pub mod prelude;
59pub mod session;
60
61pub use instrument::Instrument;
62
63use session::{AsRawSs, AsSs, FromRawSs, IntoRawSs, OwnedSs};
64
65pub const TIMEOUT_IMMEDIATE: Duration = Duration::from_millis(vs::VI_TMO_IMMEDIATE as _);
66pub const TIMEOUT_INFINITE: Duration = Duration::from_millis(vs::VI_TMO_INFINITE as _);
67macro_rules! impl_session_traits {
68 ($($id:ident),* $(,)?) => {
69 $(
70 impl IntoRawSs for $id {
71 fn into_raw_ss(self) -> session::RawSs {
72 self.0.into_raw_ss()
73 }
74 }
75
76 impl AsRawSs for $id {
77 fn as_raw_ss(&self) -> session::RawSs {
78 self.0.as_raw_ss()
79 }
80 }
81
82 impl AsSs for $id {
83 fn as_ss(&self) -> session::BorrowedSs<'_> {
84 self.0.as_ss()
85 }
86 }
87
88 impl FromRawSs for $id {
89 unsafe fn from_raw_ss(s: session::RawSs) -> Self {
90 Self(FromRawSs::from_raw_ss(s))
91 }
92 }
93 )*
94 };
95}
96
97macro_rules! impl_session_traits_for_borrowed {
98 ($($id:ident),* $(,)?) => {
99 $(
100 impl AsRawSs for $id<'_> {
101 fn as_raw_ss(&self) -> session::RawSs {
102 self.0.as_raw_ss()
103 }
104 }
105
106 impl AsSs for $id<'_> {
107 fn as_ss(&self) -> session::BorrowedSs<'_> {
108 self.0.as_ss()
109 }
110 }
111 )*
112 };
113}
114
115impl_session_traits! { DefaultRM, Instrument}
116impl_session_traits_for_borrowed! {WeakRM}
117#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
118pub struct Error(pub enums::status::ErrorCode);
119
120impl std::error::Error for Error {}
121
122impl std::fmt::Display for Error {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 self.0.fmt(f)
125 }
126}
127
128impl From<enums::status::ErrorCode> for Error {
129 fn from(s: enums::status::ErrorCode) -> Self {
130 Self(s)
131 }
132}
133
134impl From<Error> for enums::status::ErrorCode {
135 fn from(s: Error) -> Self {
136 s.0
137 }
138}
139
140impl From<Error> for vs::ViStatus {
141 fn from(s: Error) -> Self {
142 s.0.into()
143 }
144}
145
146impl TryFrom<vs::ViStatus> for Error {
147 type Error = <enums::status::ErrorCode as TryFrom<vs::ViStatus>>::Error;
148 fn try_from(value: vs::ViStatus) -> std::result::Result<Self, Self::Error> {
149 Ok(Self(value.try_into()?))
150 }
151}
152
153impl TryFrom<std::io::Error> for Error {
154 type Error = std::io::Error;
155 fn try_from(value: std::io::Error) -> std::result::Result<Self, Self::Error> {
156 if let Some(e) = value.get_ref() {
157 if let Some(e) = e.downcast_ref::<Error>() {
158 return Ok(*e);
159 }
160 }
161 Err(value)
162 }
163}
164
165/// Quickly convert [std::io::Error].
166///
167/// # Panics
168///
169/// Panic if the input Error is not converted from [visa_rs::Error](Error), use [TryInto] to perform conversion,
170/// the io error must be generated from [Instrument] IO ops
171pub fn io_to_vs_err(e: std::io::Error) -> Error {
172 e.try_into().unwrap()
173}
174
175fn vs_to_io_err(err: Error) -> std::io::Error {
176 use enums::status::ErrorCode::*;
177 use std::io::ErrorKind::*;
178 std::io::Error::new(
179 match err.0 {
180 ErrorInvObject => AddrNotAvailable,
181 ErrorNsupOper => Unsupported,
182 ErrorRsrcLocked => ConnectionRefused,
183 ErrorTmo => TimedOut,
184 ErrorRawWrProtViol | ErrorRawRdProtViol => InvalidData,
185 ErrorInpProtViol | ErrorOutpProtViol => BrokenPipe,
186 ErrorBerr => BrokenPipe,
187 ErrorInvSetup => InvalidInput,
188 ErrorNcic => PermissionDenied,
189 ErrorNlisteners => Other,
190 ErrorAsrlParity | ErrorAsrlFraming => Other,
191 ErrorAsrlOverrun => Other,
192 ErrorConnLost => BrokenPipe,
193 ErrorInvMask => InvalidInput,
194 ErrorIo => std::io::Error::last_os_error().kind(),
195 _ => unreachable!(),
196 },
197 err,
198 )
199}
200
201pub type Result<T> = std::result::Result<T, Error>;
202
203impl From<enums::attribute::AttrStatus> for Result<enums::status::CompletionCode> {
204 fn from(a: enums::attribute::AttrStatus) -> Self {
205 match a.into_inner() {
206 state if state >= SUCCESS => Ok(state.try_into().unwrap()),
207 e => Err(e.try_into().unwrap()),
208 }
209 }
210}
211
212const SUCCESS: vs::ViStatus = vs::VI_SUCCESS as _;
213
214#[doc(hidden)]
215#[macro_export]
216macro_rules! wrap_raw_error_in_unsafe {
217 ($s:expr) => {
218 match unsafe { $s } {
219 state if state >= $crate::SUCCESS && state <= i32::MAX as _ => {
220 $crate::Result::<$crate::enums::status::CompletionCode>::Ok(
221 state.try_into().expect(&format!(
222 "Converting `{state}({state:#0X})` to CompletionCode failed"
223 )),
224 )
225 }
226 e => $crate::Result::<$crate::enums::status::CompletionCode>::Err(
227 e.try_into()
228 .expect(&format!("Converting `{e}({e:#0X})` to ErrorCode failed")),
229 ),
230 }
231 };
232}
233
234/// Ability as the Default Resource Manager for VISA
235pub trait AsResourceManager: AsRawSs {
236 ///
237 /// Queries a VISA system to locate the resources associated with a specified interface.
238 ///
239 /// The viFindRsrc() operation matches the value specified in the expr parameter with the resources available for a particular interface. A regular expression is a string consisting of ordinary characters as well as special characters. You use a regular expression to specify patterns to match in a given string; in other words, it is a search criterion. The viFindRsrc() operation uses a case-insensitive compare feature when matching resource names against the regular expression specified in expr. For example, calling viFindRsrc() with "VXI?*INSTR" would return the same resources as invoking it with "vxi?*instr".
240 ///
241 /// All resource strings returned by viFindRsrc() will always be recognized by viOpen(). However, viFindRsrc() will not necessarily return all strings that you can pass to viParseRsrc() or viOpen(). This is especially true for network and TCPIP resources.
242 ///
243 /// The search criteria specified in the expr parameter has two parts: a regular expression over a resource string, and an optional logical expression over attribute values. The regular expression is matched against the resource strings of resources known to the VISA Resource Manager. If the resource string matches the regular expression, the attribute values of the resource are then matched against the expression over attribute values. If the match is successful, the resource has met the search criteria and gets added to the list of resources found.
244 ///
245 /// Special Characters and Operators| Meaning
246 /// :-----------------------------: | :----------------------------------
247 /// \? | Matches any one character.
248 /// \\ | Makes the character that follows it an ordinary character instead of special character. For example, when a question mark follows a backslash (\?), it matches the ? character instead of any one character.
249 /// \[list\] | Matches any one character from the enclosed list. You can use a hyphen to match a range of characters.
250 /// \[^list\] | Matches any character not in the enclosed list. You can use a hyphen to match a range of characters.
251 /// \* | Matches 0 or more occurrences of the preceding character or expression.
252 /// \+ | Matches 1 or more occurrences of the preceding character or expression.
253 /// Exp\|exp | Matches either the preceding or following expression. The or operator | matches the entire expression that precedes or follows it and not just the character that precedes or follows it. For example, VXI|GPIB means (VXI)|(GPIB), not VX(I|G)PIB.
254 /// (exp) | Grouping characters or expressions.
255 ///
256 /// Regular Expression | Sample Matches
257 /// :-----------------------------: | :----------------------------------
258 /// GPIB?*INSTR | GPIB0::2::INSTR, and GPIB1::1::1::INSTR.
259 /// GPIB\[0-9\]\*::?*INSTR | GPIB0::2::INSTR and GPIB1::1::1::INSTR.
260 /// GPIB\[^0\]::?*INSTR | GPIB1::1::1::INSTR but not GPIB0::2::INSTR or GPIB12::8::INSTR.
261 /// VXI?*INSTR | VXI0::1::INSTR.
262 /// ?*VXI\[0-9\]\*::?*INSTR | VXI0::1::INSTR.
263 /// ASRL\[0-9\]\*::?*INSTR | ASRL1::INSTR but not VXI0::5::INSTR.
264 /// ASRL1\+::INSTR | ASRL1::INSTR and ASRL11::INSTR but not ASRL2::INSTR.
265 /// (GPIB\|VXI)?*INSTR | GPIB1::5::INSTR and VXI0::3::INSTR but not ASRL2::INSTR.
266 /// (GPIB0\|VXI0)::1::INSTR | GPIB0::1::INSTR and VXI0::1::INSTR.
267 /// ?*INSTR | all INSTR (device) resources.
268 /// ?*VXI\[0-9\]\*::?*MEMACC | VXI0::MEMACC.
269 /// VXI0::?* | VXI0::1::INSTR, VXI0::2::INSTR, and VXI0::MEMACC.
270 /// ?* | all resources.
271 /// visa://hostname/?* | all resources on the specified remote system. The hostname can be represented as either an IP address (dot-notation) or network machine name. This remote system need not be a configured remote system.
272 /// /?* | all resources on the local machine. Configured remote systems are not queried.
273 /// visa:/ASRL?*INSTR | all ASRL resources on the local machine and returns them in URL format (for example, visa:/ASRL1::INSTR).
274 ///
275 /// see also [official doc](https://www.ni.com/docs/en-US/bundle/ni-visa-20.0/page/ni-visa/vifindrsrc.html)
276 ///
277 fn find_res_list(&self, expr: &ResID) -> Result<ResList> {
278 let mut list: vs::ViFindList = 0;
279 let mut cnt: vs::ViUInt32 = 0;
280 let mut instr_desc = new_visa_buf();
281 wrap_raw_error_in_unsafe!(vs::viFindRsrc(
282 self.as_raw_ss(),
283 expr.as_vi_const_string(),
284 &mut list,
285 &mut cnt,
286 instr_desc.as_mut_ptr() as _,
287 ))?;
288 Ok(ResList {
289 list,
290 cnt: cnt as _,
291 instr_desc,
292 })
293 }
294
295 ///
296 /// Queries a VISA system to locate the resources associated with a specified interface, return the first resource matched
297 ///
298 fn find_res(&self, expr: &ResID) -> Result<ResID> {
299 /*
300 !keysight impl visa will try to write at address vs::VI_NULL, cause exit code: 0xc0000005, STATUS_ACCESS_VIOLATION
301 let mut instr_desc = new_visa_buf();
302 let mut cnt: vs::ViUInt32 = 0;
303 wrap_raw_error_in_unsafe!(vs::viFindRsrc(
304 self.as_raw_ss(),
305 expr.as_vi_const_string(),
306 vs::VI_NULL as _,
307 &mut cnt as *mut _,
308 instr_desc.as_mut_ptr() as _,
309 ))?;
310 Ok(instr_desc.try_into().unwrap())
311 */
312
313 Ok(self.find_res_list(expr)?.instr_desc.try_into().unwrap())
314 }
315
316 /// Parse a resource string to get the interface information.
317 fn parse_res(&self, res: &ResID) -> Result<(attribute::AttrIntfType, attribute::AttrIntfNum)> {
318 let mut ty = 0;
319 let mut num = 0;
320 wrap_raw_error_in_unsafe!(vs::viParseRsrc(
321 self.as_raw_ss(),
322 res.as_vi_const_string(),
323 &mut ty as *mut _,
324 &mut num as *mut _
325 ))?;
326 unsafe {
327 Ok((
328 attribute::AttrIntfType::new_unchecked(ty),
329 attribute::AttrIntfNum::new_unchecked(num),
330 ))
331 }
332 }
333
334 /// Parse a resource string to get extended interface information.
335 ///
336 /// the returned three VisaStrings are:
337 ///
338 /// + Specifies the resource class (for example, "INSTR") of the given resource string.
339 ///
340 /// + This is the expanded version of the given resource string. The format should be similar to the VISA-defined canonical resource name.
341 ///
342 /// + Specifies the user-defined alias for the given resource string.
343 fn parse_res_ex(
344 &self,
345 res: &ResID,
346 ) -> Result<(
347 attribute::AttrIntfType,
348 attribute::AttrIntfNum,
349 VisaString,
350 VisaString,
351 VisaString,
352 )> {
353 let mut ty = 0;
354 let mut num = 0;
355 let mut str1 = new_visa_buf();
356 let mut str2 = new_visa_buf();
357 let mut str3 = new_visa_buf();
358 wrap_raw_error_in_unsafe!(vs::viParseRsrcEx(
359 self.as_raw_ss(),
360 res.as_vi_const_string(),
361 &mut ty as *mut _,
362 &mut num as *mut _,
363 str1.as_mut_ptr() as _,
364 str2.as_mut_ptr() as _,
365 str3.as_mut_ptr() as _,
366 ))?;
367 unsafe {
368 Ok((
369 attribute::AttrIntfType::new_unchecked(ty),
370 attribute::AttrIntfNum::new_unchecked(num),
371 str1.try_into().unwrap(),
372 str2.try_into().unwrap(),
373 str3.try_into().unwrap(),
374 ))
375 }
376 }
377
378 ///
379 /// Opens a session to the specified resource.
380 ///
381 /// For the parameter accessMode, either VI_EXCLUSIVE_LOCK (1) or VI_SHARED_LOCK (2).
382 ///
383 /// VI_EXCLUSIVE_LOCK (1) is used to acquire an exclusive lock immediately upon opening a session; if a lock cannot be acquired, the session is closed and an error is returned.
384 ///
385 /// VI_LOAD_CONFIG (4) is used to configure attributes to values specified by some external configuration utility. Multiple access modes can be used simultaneously by specifying a bit-wise OR of the values other than VI_NULL.
386 ///
387 /// NI-VISA currently supports VI_LOAD_CONFIG only on Serial INSTR sessions.
388 ///
389 fn open(
390 &self,
391 res_name: &ResID,
392 access_mode: flags::AccessMode,
393 open_timeout: Duration,
394 ) -> Result<Instrument> {
395 let mut instr: vs::ViSession = 0;
396 wrap_raw_error_in_unsafe!(vs::viOpen(
397 self.as_raw_ss(),
398 res_name.as_vi_const_string(),
399 access_mode.bits(),
400 open_timeout.as_millis() as _,
401 &mut instr as _,
402 ))?;
403 Ok(unsafe { Instrument::from_raw_ss(instr) })
404 }
405
406 /// Close this session and all find lists and device sessions.
407 fn close_all(&self) {
408 std::mem::drop(unsafe { DefaultRM::from_raw_ss(self.as_raw_ss()) })
409 }
410}
411
412impl<'a> AsResourceManager for WeakRM<'a> {}
413impl AsResourceManager for DefaultRM {}
414
415/// A [`ResourceManager`](AsResourceManager) which is [`Clone`] and doesn't close everything on drop
416#[derive(Debug, PartialEq, Eq, Hash, Clone)]
417pub struct WeakRM<'a>(session::BorrowedSs<'a>);
418
419impl<'a> From<&'a attribute::AttrRmSession> for WeakRM<'a> {
420 fn from(value: &'a attribute::AttrRmSession) -> Self {
421 Self(unsafe { session::BorrowedSs::borrow_raw(value.clone().into_inner()) })
422 }
423}
424
425impl From<attribute::AttrRmSession> for WeakRM<'static> {
426 fn from(value: attribute::AttrRmSession) -> Self {
427 Self(unsafe { session::BorrowedSs::borrow_raw(value.into_inner()) })
428 }
429}
430
431/// A [`ResourceManager`](AsResourceManager) which close everything on drop
432#[derive(Debug, PartialEq, Eq, Hash)]
433pub struct DefaultRM(session::OwnedSs);
434
435impl DefaultRM {
436 /// [`DefaultRM`] will close everything opened by it on drop.
437 /// By converting to a [`WeakRM`], such behavior can be avoided.
438 ///
439 /// *Note*: Sessions opened by another resource manager (get from another call to [`Self::new`]) won't be influenced.
440 pub fn leak(self) -> WeakRM<'static> {
441 unsafe { WeakRM(session::BorrowedSs::borrow_raw(self.into_raw_ss())) }
442 }
443
444 /// [`DefaultRM`] will close everything opened by it on drop.
445 /// By converting to a [`WeakRM`], such behavior can be avoided.
446 ///
447 /// *Note*: Sessions opened by another resource manager (get from another call to [`Self::new`]) won't be influenced.
448 pub fn borrow(&'_ self) -> WeakRM<'_> {
449 WeakRM(self.as_ss())
450 }
451
452 /// Returns a session to the Default Resource Manager resource.
453 ///
454 /// The first call to this function initializes the VISA system, including the Default Resource Manager resource, and also returns a session to that resource. Subsequent calls to this function return unique sessions to the same Default Resource Manager resource.
455 ///
456 /// When a Resource Manager session is dropped, not only is that session closed, but also all find lists and device sessions (which that Resource Manager session was used to create) are closed.
457 ///
458 pub fn new() -> Result<Self> {
459 let mut new: vs::ViSession = 0;
460 wrap_raw_error_in_unsafe!(vs::viOpenDefaultRM(&mut new as _))?;
461 Ok(Self(unsafe { OwnedSs::from_raw_ss(new) }))
462 }
463}
464
465/// Returned by [`DefaultRM::find_res_list`], handler to iterator over matched resources
466#[derive(Debug)]
467pub struct ResList {
468 list: vs::ViFindList,
469 cnt: i32,
470 instr_desc: VisaBuf,
471}
472
473impl Iterator for ResList {
474 type Item = Result<ResID>;
475
476 fn next(&mut self) -> Option<Self::Item> {
477 match self.find_next() {
478 Ok(o) => o.map(Ok),
479 Err(e) => Some(Err(e)),
480 }
481 }
482}
483
484impl ResList {
485 /// Returns the next resource from the list of resources found
486 pub fn find_next(&mut self) -> Result<Option<ResID>> {
487 if self.cnt < 1 {
488 return Ok(None);
489 }
490 let next: ResID = self.instr_desc.try_into().unwrap();
491 if self.cnt > 1 {
492 wrap_raw_error_in_unsafe!(vs::viFindNext(
493 self.list,
494 self.instr_desc.as_mut_ptr() as _
495 ))?;
496 }
497 self.cnt -= 1;
498 Ok(Some(next))
499 }
500}
501
502#[repr(transparent)]
503/// Simple wrapper of [std::ffi::CString]
504#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Clone)]
505pub struct VisaString(CString);
506
507impl std::ops::Deref for VisaString {
508 type Target = CString;
509
510 fn deref(&self) -> &Self::Target {
511 &self.0
512 }
513}
514
515/// resource ID
516pub type ResID = VisaString;
517
518/// Access key used in [`Instrument::lock`]
519pub type AccessKey = VisaString;
520
521impl From<CString> for VisaString {
522 fn from(c: CString) -> Self {
523 Self(c)
524 }
525}
526
527impl From<VisaString> for CString {
528 fn from(value: VisaString) -> Self {
529 value.0
530 }
531}
532
533type VisaBuf = [u8; vs::VI_FIND_BUFLEN as _];
534
535const fn new_visa_buf() -> VisaBuf {
536 [0; vs::VI_FIND_BUFLEN as _]
537}
538
539#[derive(Debug, Clone, Copy)]
540pub struct FromBytesWithNulError;
541
542impl Display for FromBytesWithNulError {
543 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544 write!(f, "Bytes include none null(\\0) character")
545 }
546}
547
548impl std::error::Error for FromBytesWithNulError {}
549
550impl TryFrom<[u8; vs::VI_FIND_BUFLEN as _]> for VisaString {
551 type Error = FromBytesWithNulError;
552 fn try_from(f: [u8; vs::VI_FIND_BUFLEN as _]) -> std::result::Result<Self, Self::Error> {
553 let mut index = f.split_inclusive(|t| *t == b'\0');
554 let cstr = index.next().ok_or(FromBytesWithNulError)?;
555 Ok(Self(
556 CStr::from_bytes_with_nul(cstr)
557 .map_err(|_| FromBytesWithNulError)?
558 .to_owned(),
559 ))
560 }
561}
562
563impl VisaString {
564 fn as_vi_const_string(&self) -> vs::ViConstString {
565 self.0.as_ptr()
566 }
567 pub fn to_string_lossy(&self) -> Cow<'_, str> {
568 self.0.to_string_lossy()
569 }
570 pub fn from_string(s: String) -> Option<Self> {
571 CString::new(s).ok().map(|x| x.into())
572 }
573}
574
575impl Display for VisaString {
576 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
577 self.to_string_lossy().fmt(f)
578 }
579}
580
581/// Job ID of an asynchronous operation.
582///
583/// Returned by [`Instrument::visa_read_async`] or [`Instrument::visa_write_async`], used to be compared with the attribute [AttrJobId](enums::attribute::AttrJobId) got from [Event](enums::event::Event) to distinguish operations.
584#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Clone, Copy)]
585pub struct JobID(pub(crate) vs::ViJobId);
586
587impl JobID {
588 ///create JobId with value null, used in [`Instrument::terminate`] to abort all calls
589 pub fn null() -> Self {
590 Self(vs::VI_NULL as _)
591 }
592}
593
594impl From<enums::attribute::AttrJobId> for JobID {
595 fn from(s: enums::attribute::AttrJobId) -> Self {
596 Self(s.into_inner())
597 }
598}
599
600impl PartialEq<enums::attribute::AttrJobId> for JobID {
601 fn eq(&self, other: &enums::attribute::AttrJobId) -> bool {
602 self.eq(&JobID::from(other.clone()))
603 }
604}
605
606#[cfg(test)]
607mod test {
608 use crate::enums::status::{CompletionCode, ErrorCode};
609 use crate::*;
610
611 #[test]
612 fn convert_from_complete_code() {
613 assert_eq!(
614 CompletionCode::try_from(0 as vs::ViStatus).unwrap(),
615 CompletionCode::Success
616 );
617 }
618 #[test]
619 fn convert_from_error_code() {
620 assert_eq!(
621 ErrorCode::try_from(0xBFFF0011u32 as vs::ViStatus).unwrap(),
622 ErrorCode::ErrorRsrcNfound
623 );
624 }
625 #[test]
626 fn convert_to_complete_code() {
627 assert_eq!(
628 0 as vs::ViStatus,
629 CompletionCode::Success.try_into().unwrap()
630 );
631 }
632 #[test]
633 fn convert_to_error_code() {
634 assert_eq!(
635 0xBFFF0011u32 as vs::ViStatus,
636 ErrorCode::ErrorRsrcNfound.try_into().unwrap()
637 );
638 }
639 #[test]
640 #[should_panic]
641 fn convert_wrong_status1() {
642 CompletionCode::try_from(ErrorCode::ErrorRsrcNfound as vs::ViStatus).unwrap();
643 }
644 #[test]
645 #[should_panic]
646 fn convert_wrong_status2() {
647 ErrorCode::try_from(CompletionCode::Success as vs::ViStatus).unwrap();
648 }
649
650 use anyhow::{bail, Result};
651 #[test]
652 fn rm_behavior() -> Result<()> {
653 let rm1 = DefaultRM::new()?;
654 let rm2 = DefaultRM::new()?;
655 let r1 = rm1.as_raw_ss();
656 assert_ne!(rm1, rm2);
657 std::mem::drop(rm1);
658 let expr = CString::new("?*").unwrap().into();
659 match unsafe { DefaultRM::from_raw_ss(r1) }.find_res(&expr) {
660 Err(crate::Error(crate::enums::status::ErrorCode::ErrorInvObject)) => {
661 Ok::<_, crate::Error>(())
662 }
663 _ => bail!("unexpected behavior using a resource manager after it is dropped"),
664 }?;
665 match rm2.find_res(&expr) {
666 Ok(_) | Err(crate::Error(crate::enums::status::ErrorCode::ErrorRsrcNfound)) => Ok(()),
667 _ => bail!("unexpected behavior using a resource manager after dropping another resource manager"),
668 }
669 }
670
671 #[test]
672 fn convert_io_error() {
673 let vs_error = Error(enums::status::ErrorCode::ErrorTmo);
674 let io_error = vs_to_io_err(vs_error);
675 assert_eq!(Error::try_from(io_error).unwrap(), vs_error);
676 let no_vs_io_error = std::io::Error::new(std::io::ErrorKind::Other, FromBytesWithNulError);
677 assert!(Error::try_from(no_vs_io_error).is_err());
678 }
679}