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}