Skip to main content

rustradio/
lib.rs

1// Enable `std::simd` if feature simd is enabled.
2#![cfg_attr(feature = "simd", feature(portable_simd))]
3// Enable RISC-V arch detection, if on a RISC-V arch.
4#![cfg_attr(
5    all(
6        feature = "simd",
7        any(target_arch = "riscv32", target_arch = "riscv64")
8    ),
9    feature(stdarch_riscv_feature_detection)
10)]
11#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
12
13/*! This create provides a framework for running SDR (software defined
14radio) applications.
15
16It's heavily inspired by [GNURadio][gnuradio], except of course
17written in Rust.
18
19In addition to the example applications in this crate, there's also a
20[sparslog][sparslog] project using this framework, that decodes IKEA
21Sparsnäs electricity meter RF signals.
22
23# Architecture overview
24
25A RustRadio application consists of blocks that are connected by
26unidirectional streams. Each block has zero or more input streams, and
27zero or more output streams.
28
29The signal flows through the blocks from "sources" (blocks without any
30input streams) to "sinks" (blocks without any output streams.
31
32These blocks and streams are called a "graph", like the mathematical
33concept of graphs that have nodes and edges.
34
35A block does something to its input(s), and passes the result to its
36output(s).
37
38A typical graph will be something like:
39
40```text
41  [ Raw radio source ]
4243      [ Filtering ]
4445      [ Resampling ]
4647     [ Demodulation ]
4849     [ Symbol Sync ]
5051[ Packet assembly and save ]
52```
53
54Or concretely, for [sparslog][sparslog]:
55
56```text
57     [ RtlSdrSource ]
5859  [ RtlSdrDecode to convert from ]
60  [ own format to complex I/Q    ]
6162     [ FftFilter ]
6364      [ RationalResampler ]
6566      [ QuadratureDemod ]
6768  [ AddConst for frequency offset ]
6970   [ ZeroCrossing symbol sync ]
7172     [ Custom Sparsnäs decoder ]
73     [ block in the binary,    ]
74     [ not in the framework    ]
75```
76
77# Examples
78
79Here's a simple example that creates a couple of blocks, connects them
80with streams, and runs the graph.
81
82```
83use rustradio::graph::{Graph, GraphRunner};
84use rustradio::blocks::{AddConst, VectorSource, DebugSink};
85use rustradio::Complex;
86let (src, prev) = VectorSource::new(
87    vec![
88        Complex::new(10.0, 0.0),
89        Complex::new(-20.0, 0.0),
90        Complex::new(100.0, -100.0),
91    ],
92);
93let (add, prev) = AddConst::new(prev, Complex::new(1.1, 2.0));
94let sink = DebugSink::new(prev);
95let mut g = Graph::new();
96g.add(Box::new(src));
97g.add(Box::new(add));
98g.add(Box::new(sink));
99g.run()?;
100# Ok::<(), anyhow::Error>(())
101```
102
103## Features
104
105* `async`: Add support for `AsyncGraph`.
106* `audio`: Add support for `AudioSink` (adds dependency).
107* `fast-math`: Add a dependency in order to speed up some math.
108* `fftw`: GPL code only: Add support to use `libfftw` instead of `rustfft`.
109* `pipewire`: Add support for pipewire blocks (adds dependency).
110* `rtlsdr`: Enable `RtlSdrSource` block, and adds the `rtlsdr` crate as a
111* `simd` (only with `nightly` Rust): Enable some code using `std::simd`.
112  dependency at build time, and thus `librtlsdr.so` as a dependency at runtime.
113* `soapysdr`: Add dependency on `soapysdr`, for its various SDR support.
114* `tokio-unstable`: For async graphs, allow use of tokio unstable API,
115* `volk`: Add dependency on the `volk` library, to speed up some inner loops.
116* `wasm`: Enable very experimental WASM support.
117
118`tokio-unstable` allows tasks to be named, which helps when running
119`tokio-console`. But it does require the user to run `cargo build` with the env
120`RUSTFLAGS="--cfg tokio_unstable"` set too.
121
122## Links
123
124* Main repo: <https://github.com/ThomasHabets/rustradio>
125* crates.io: <https://crates.io/crates/rustradio>
126* This documentation: <https://docs.rs/rustradio/latest/rustradio/>
127
128[sparslog]: https://github.com/ThomasHabets/sparslog
129[gnuradio]: https://www.gnuradio.org/
130 */
131// Enable some normally pedantic clippies.
132#![deny(clippy::needless_for_each)]
133#![deny(clippy::explicit_iter_loop)]
134#![deny(clippy::items_after_statements)]
135#![deny(clippy::format_push_string)]
136#![deny(clippy::return_self_not_must_use)]
137// Macro.
138pub use rustradio_macros;
139
140// Blocks.
141pub mod add;
142pub mod add_const;
143pub mod au;
144pub mod binary_slicer;
145pub mod burst_tagger;
146pub mod canary;
147pub mod cma;
148pub mod complex_to_mag2;
149pub mod constant_source;
150pub mod convert;
151pub mod correlate_access_code;
152pub mod debug_sink;
153pub mod delay;
154pub mod descrambler;
155pub mod fft;
156pub mod fft_filter;
157pub mod fft_stream;
158pub mod file_sink;
159pub mod file_source;
160pub mod fir;
161pub mod hasher;
162pub mod hdlc_deframer;
163pub mod hdlc_framer;
164pub mod hilbert;
165pub mod iir_filter;
166pub mod il2p_deframer;
167pub mod iq_balance;
168pub mod kiss;
169pub mod morse_encode;
170pub mod multiply_const;
171pub mod nrzi;
172pub mod null_sink;
173pub mod pdu_to_stream;
174pub mod pdu_writer;
175pub mod quadrature_demod;
176pub mod rational_resampler;
177pub mod reader_source;
178pub mod rtlsdr_decode;
179pub mod sigmf;
180pub mod signal_source;
181pub mod single_pole_iir_filter;
182pub mod skip;
183pub mod stream_to_pdu;
184pub mod strobe;
185pub mod symbol_sync;
186pub mod tcp_source;
187pub mod tee;
188pub mod to_text;
189pub mod vco;
190pub mod vec_to_stream;
191pub mod vector_sink;
192pub mod vector_source;
193pub mod wpcr;
194pub mod writer_sink;
195pub mod xor;
196pub mod xor_const;
197pub mod zero_crossing;
198
199#[cfg(feature = "audio")]
200pub mod audio_sink;
201
202#[cfg(feature = "pipewire")]
203pub mod pipewire_sink;
204
205#[cfg(feature = "pipewire")]
206pub mod pipewire_source;
207
208#[cfg(feature = "rtlsdr")]
209pub mod rtlsdr_source;
210
211#[cfg(feature = "soapysdr")]
212pub mod soapysdr_sink;
213
214#[cfg(feature = "soapysdr")]
215pub mod soapysdr_source;
216
217pub mod block;
218pub mod blocks;
219
220#[cfg(not(feature = "wasm"))]
221pub mod nowasm;
222
223#[cfg(feature = "wasm")]
224pub mod wasm;
225
226pub mod sys {
227    #[cfg(not(feature = "wasm"))]
228    pub use super::nowasm::export::*;
229    #[cfg(feature = "wasm")]
230    pub use super::wasm::export::*;
231}
232
233pub mod graph;
234#[cfg(not(feature = "wasm"))]
235pub mod mtgraph;
236pub mod stream;
237pub mod window;
238
239#[cfg(feature = "async")]
240pub mod agraph;
241
242/// Float type used. Usually f32, but not guaranteed.
243pub type Float = f32;
244
245/// Complex (I/Q) data.
246pub type Complex = num_complex::Complex<Float>;
247
248pub(crate) static NEXT_STREAM_ID: std::sync::atomic::AtomicUsize =
249    std::sync::atomic::AtomicUsize::new(1);
250
251/// RustRadio error.
252#[derive(thiserror::Error, Debug)]
253#[non_exhaustive]
254pub enum Error {
255    /// File error annotated with a specific path.
256    #[error("IO Error on {path:?}: {source:?}")]
257    FileIo {
258        #[source]
259        source: std::io::Error,
260        path: std::path::PathBuf,
261    },
262
263    /// An error happened with a device such as SDR or audio device.
264    #[error("DeviceError: {msg:?}: {source:?}")]
265    DeviceError {
266        #[source]
267        source: Box<dyn std::error::Error + Send + Sync>,
268        msg: Option<String>,
269    },
270
271    /// An IO error without a known file associated.
272    #[error("IO Error: {0}")]
273    Io(#[from] std::io::Error),
274
275    /// An error with only a plain text message.
276    #[error("An error occurred: {0}")]
277    Plain(String),
278
279    /// A wrapper around another error.
280    #[error("{msg:?}: {source:?}")]
281    Other {
282        #[source]
283        source: Box<dyn std::error::Error + Send + Sync>,
284        msg: Option<String>,
285    },
286}
287
288impl Error {
289    /// Create error from message.
290    #[must_use]
291    pub fn msg<S: Into<String>>(msg: S) -> Self {
292        Self::Plain(msg.into())
293    }
294
295    /// Wrap an IO error also including the path.
296    #[must_use]
297    pub fn file_io<P: Into<std::path::PathBuf>>(source: std::io::Error, path: P) -> Self {
298        Self::FileIo {
299            path: path.into(),
300            source,
301        }
302    }
303
304    /// Wrap another error into an `Error::Other`.
305    ///
306    /// The underlying error is provided, as well as optional extra context.
307    #[must_use]
308    pub fn wrap<S: Into<String>>(
309        source: impl std::error::Error + Send + Sync + 'static,
310        msg: S,
311    ) -> Self {
312        let msg = msg.into();
313        Self::Other {
314            source: Box::new(source),
315            msg: if msg.is_empty() { None } else { Some(msg) },
316        }
317    }
318
319    /// Wrap an error blaming some hardware or simulated hardware.
320    ///
321    /// The underlying error is provided, as well as optional extra context.
322    #[must_use]
323    pub fn device<S: Into<String>>(
324        source: impl std::error::Error + Send + Sync + 'static,
325        msg: S,
326    ) -> Self {
327        let msg = msg.into();
328        Self::DeviceError {
329            source: Box::new(source),
330            msg: if msg.is_empty() { None } else { Some(msg) },
331        }
332    }
333}
334
335/// Create default `From<T>` for `Error`, with optional extra context.
336///
337/// ## Example
338///
339/// ```text
340/// use rustradio::error_from;
341/// error_from!(
342///     "audio",
343///     cpal::PlayStreamError,
344///     cpal::BuildStreamError,
345///     cpal::DevicesError,
346///     cpal::DeviceNameError,
347///     cpal::SupportedStreamConfigsError,
348///     cpal::DefaultStreamConfigError,
349/// );
350/// ```
351#[macro_export]
352macro_rules! error_from {
353    ($ctx:literal, $($err_ty:ty),* $(,)?) => {
354        $(
355            impl From<$err_ty> for Error {
356                fn from(e: $err_ty) -> Self {
357                    let s = if $ctx.is_empty() {
358                        format!("{}", std::any::type_name::<$err_ty>())
359                    } else {
360                        format!("{} in {}", std::any::type_name::<$err_ty>(), $ctx)
361                    };
362                    Error::wrap(e, s)
363                }
364            }
365        )*
366    };
367}
368
369/// Helper macro for creating a series of one-in, one-out blocks and adding them
370/// to a graph.
371///
372/// ## Example
373///
374/// ```
375/// use rustradio::graph::{Graph, GraphRunner};
376/// use rustradio::blocks::*;
377/// use rustradio::blockchain;
378///
379/// let mut g = Graph::new();
380/// let prev = blockchain![
381///     g,      // Graph object.
382///     prev,   // Variable to use for the streams from block to block.
383///     SignalSourceFloat::new(44100.0, 1000.0, 1.0),
384///     MultiplyConst::new(prev, 2.0),
385///     MultiplyConst::new(prev, 4.0),
386/// ];
387/// ```
388#[macro_export]
389macro_rules! blockchain {
390    ($g:expr, $prev:ident, $($cons:expr),* $(,)?) => {{
391        $(
392            let (block, $prev) = $cons;
393            $g.add(Box::new(block));
394            )*
395            $prev
396    }};
397}
398
399error_from!(
400    "", // Can't attribute to a specific set of blocks.
401    std::sync::mpsc::RecvError,
402    std::sync::mpsc::TryRecvError,
403    std::string::FromUtf8Error,
404    std::array::TryFromSliceError,
405    std::num::TryFromIntError,
406);
407
408/// Result convenience type.
409pub type Result<T> = std::result::Result<T, Error>;
410
411/// Repeat between zero and infinite times.
412#[derive(Debug)]
413pub struct Repeat {
414    repeater: Repeater,
415    count: u64,
416}
417
418impl Repeat {
419    /// Repeat finite number of times. 0 Means not even once. 1 is default.
420    #[must_use]
421    pub fn finite(n: u64) -> Self {
422        Self {
423            repeater: Repeater::Finite(n),
424            count: 0,
425        }
426    }
427
428    /// Repeat infinite number of times.
429    #[must_use]
430    pub fn infinite() -> Self {
431        Self {
432            repeater: Repeater::Infinite,
433            count: 0,
434        }
435    }
436
437    /// Register a repeat being done, and return true if we should continue.
438    #[must_use]
439    pub fn again(&mut self) -> bool {
440        self.count += 1;
441        match self.repeater {
442            Repeater::Finite(0) => {
443                log::error!(
444                    "Repeat::again() called when repeat is 0, count {}",
445                    self.count
446                );
447                false
448            }
449            Repeater::Finite(n) => self.count < n,
450            Repeater::Infinite => true,
451        }
452    }
453
454    /// Return true if repeating is done.
455    #[must_use]
456    pub fn done(&self) -> bool {
457        match self.repeater {
458            Repeater::Finite(n) => self.count >= n,
459            Repeater::Infinite => false,
460        }
461    }
462
463    /// Return how many repeats have fully completed.
464    #[must_use]
465    pub fn count(&self) -> u64 {
466        self.count
467    }
468}
469
470#[derive(Debug)]
471enum Repeater {
472    Finite(u64),
473    Infinite,
474}
475
476/// A CPU feature, such as `AVX` (x86) or `Vector` (RISC-V).
477pub struct Feature {
478    name: String,
479    build: bool,
480    detected: bool,
481}
482
483impl Feature {
484    #[must_use]
485    fn new<S: Into<String>>(name: S, build: bool, detected: bool) -> Self {
486        Self {
487            name: name.into(),
488            build,
489            detected,
490        }
491    }
492}
493
494/// Turn list of features into a nicely formatted table.
495#[must_use]
496pub fn environment_str(features: &[Feature]) -> String {
497    use std::fmt::Write;
498    let mut s = "Feature   Build Detected\n".to_string();
499    for feature in features {
500        let _ = writeln!(
501            s,
502            "{:10} {:-5}    {:-5}",
503            feature.name, feature.build, feature.detected
504        );
505    }
506    s
507}
508
509/// Check that the code wasn't built with features not detected at runtime.
510///
511/// This is a bigger problem than merely a runtime error would imply. If built
512/// for features that are not here, that can only mean UB, before `main()` even
513/// runs. So maybe this checking function doesn't actually add any value.
514///
515/// It does return the state of the features, though.
516///
517/// # Errors
518///
519/// If there are enabled processor features that are not supported. Though
520/// again, this is UB.
521pub fn check_environment() -> Result<Vec<Feature>> {
522    #[allow(unused_mut)]
523    let mut assumptions: Vec<Feature> = Vec::new();
524    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
525    {
526        assumptions.push(Feature::new(
527            "FMA",
528            cfg!(target_feature = "fma"),
529            is_x86_feature_detected!("fma"),
530        ));
531        assumptions.push(Feature::new(
532            "SSE",
533            cfg!(target_feature = "sse"),
534            is_x86_feature_detected!("sse"),
535        ));
536        assumptions.push(Feature::new(
537            "SSE3",
538            cfg!(target_feature = "sse3"),
539            is_x86_feature_detected!("sse3"),
540        ));
541        assumptions.push(Feature::new(
542            "AVX",
543            cfg!(target_feature = "avx"),
544            is_x86_feature_detected!("avx"),
545        ));
546        assumptions.push(Feature::new(
547            "AVX2",
548            cfg!(target_feature = "avx2"),
549            is_x86_feature_detected!("avx2"),
550        ));
551    }
552
553    // TODO: ideally we don't duplicate this test here, but reuse it from the
554    // top of the file.
555    //
556    // We check for feature `simd` here as a substitute for checking we're on
557    // nightly, where the feature stuff is allowed.
558    #[cfg(all(
559        feature = "simd",
560        any(target_arch = "riscv32", target_arch = "riscv64")
561    ))]
562    {
563        assumptions.push(Feature::new(
564            "Vector",
565            cfg!(target_feature = "v"),
566            std::arch::is_riscv_feature_detected!("v"),
567        ));
568    }
569
570    let errs: Vec<_> = assumptions
571        .iter()
572        .filter_map(|f| {
573            if f.build && !f.detected {
574                Some(format!(
575                    "Feature {} assumed by build flags but not detected",
576                    f.name
577                ))
578            } else {
579                None
580            }
581        })
582        .collect();
583    if errs.is_empty() {
584        Ok(assumptions)
585    } else {
586        Err(Error::msg(format!("{errs:?}")))
587    }
588}
589
590/// Parse verbosity like "error", "warn", …
591///
592/// For use with clap. E.g.:
593///
594/// ```rust
595/// use rustradio::parse_verbosity;
596/// #[derive(clap::Parser)]
597/// struct Opt {
598///     #[arg(long, value_parser=parse_verbosity)]
599///     verbose: usize,
600/// }
601/// ```
602///
603/// # Errors
604///
605/// If the log level is not one of the known strings.
606pub fn parse_verbosity(in_s: &str) -> std::result::Result<usize, String> {
607    use std::str::FromStr;
608    log::Level::from_str(in_s)
609        .map_err(|e| format!("{e}. Valid values are: error, warn, info, debug, trace"))
610        .map(|v| v as usize - 1)
611}
612
613/// Parse frequencies like "100k", "2M", etc.
614///
615/// For use with clap. E.g.:
616///
617/// ```rust
618/// use rustradio::parse_frequency;
619/// #[derive(clap::Parser)]
620/// struct Opt {
621///     /// Frequency.
622///     #[arg(long, value_parser=parse_frequency)]
623///     freq: f64,
624///     /// Sample rate.
625///     #[arg(long, value_parser=parse_frequency, default_value_t = 300000.0)]
626///     sample_rate: f64,
627/// }
628/// ```
629///
630/// Supported features:
631/// * k/m/g suffix (case insensitive).
632/// * underscores are stripped.
633///
634/// # Errors
635///
636/// If frequency string is not of a valid form.
637pub fn parse_frequency(in_s: &str) -> std::result::Result<f64, String> {
638    let s_binding;
639    let s = if in_s.contains('_') {
640        // Only create a copy if input actually contains underscores.
641        s_binding = in_s.replace('_', "");
642        s_binding.as_str()
643    } else {
644        in_s
645    };
646    let (nums, mul) = {
647        let last = match s.chars().last() {
648            None => return Err("empty string is not a frequency".into()),
649            Some(ch) => ch.to_lowercase().next().ok_or("Empty string")?,
650        };
651        if s.len() > 1 {
652            let rest = &s[..(s.len() - 1)];
653            match last {
654                'k' => (rest, 1_000.0),
655                'm' => (rest, 1_000_000.0),
656                'g' => (rest, 1_000_000_000.0),
657                _ => (s, 1.0),
658            }
659        } else {
660            (s, 1.0)
661        }
662    };
663    Ok(nums.parse::<f64>().map_err(|e| {
664        format!("Invalid number {in_s}: {e}. Has to be a float with optional k/m/g suffix")
665    })? * mul)
666}
667
668/// A trait all sample types must implement.
669pub trait Sample: Copy + Default + Send + Sync + 'static {
670    /// The type of the sample.
671    type Type;
672
673    /// The serialized size of one sample.
674    #[must_use]
675    fn size() -> usize;
676
677    /// Parse one sample.
678    ///
679    /// # Errors
680    ///
681    /// For the currently implemented Sample types, it can only fail if the
682    /// passed byte slice is of the wrong size.
683    fn parse(data: &[u8]) -> Result<Self::Type>;
684
685    /// Serialize one sample.
686    #[must_use]
687    fn serialize(&self) -> Vec<u8>;
688}
689
690impl Sample for Complex {
691    type Type = Complex;
692    fn size() -> usize {
693        std::mem::size_of::<Self>()
694    }
695    fn parse(data: &[u8]) -> Result<Self::Type> {
696        if data.len() != Self::size() {
697            return Err(Error::msg(format!(
698                "Tried to parse Complex from {} bytes",
699                data.len()
700            )));
701        }
702        let i = Float::from_le_bytes(data[0..Self::size() / 2].try_into()?);
703        let q = Float::from_le_bytes(data[Self::size() / 2..].try_into()?);
704        Ok(Complex::new(i, q))
705    }
706    fn serialize(&self) -> Vec<u8> {
707        let mut ret = Vec::new();
708        ret.extend(Float::to_le_bytes(self.re));
709        ret.extend(Float::to_le_bytes(self.im));
710        ret
711    }
712}
713
714impl Sample for Float {
715    type Type = Float;
716    fn size() -> usize {
717        std::mem::size_of::<Self>()
718    }
719    fn parse(data: &[u8]) -> Result<Self::Type> {
720        if data.len() != Self::size() {
721            return Err(Error::msg(format!(
722                "Tried to parse Float from {} bytes",
723                data.len()
724            )));
725        }
726        Ok(Float::from_le_bytes(data[0..Self::size()].try_into()?))
727    }
728    fn serialize(&self) -> Vec<u8> {
729        Float::to_le_bytes(*self).to_vec()
730    }
731}
732
733impl Sample for u8 {
734    type Type = u8;
735    fn size() -> usize {
736        std::mem::size_of::<Self>()
737    }
738    fn parse(data: &[u8]) -> Result<Self::Type> {
739        if data.len() != Self::size() {
740            return Err(Error::msg(format!(
741                "Tried to parse u8 from {} bytes",
742                data.len()
743            )));
744        }
745        Ok(data[0])
746    }
747    fn serialize(&self) -> Vec<u8> {
748        vec![*self]
749    }
750}
751
752impl Sample for u32 {
753    type Type = u32;
754    fn size() -> usize {
755        4
756    }
757    fn parse(data: &[u8]) -> Result<Self::Type> {
758        if data.len() != Self::size() {
759            return Err(Error::msg(format!(
760                "Tried to parse u32 from {} bytes",
761                data.len()
762            )));
763        }
764        Ok(u32::from_le_bytes(data[0..Self::size()].try_into()?))
765    }
766    fn serialize(&self) -> Vec<u8> {
767        u32::to_le_bytes(*self).to_vec()
768    }
769}
770
771impl Sample for i32 {
772    type Type = i32;
773    fn size() -> usize {
774        std::mem::size_of::<Self>()
775    }
776    fn parse(data: &[u8]) -> Result<Self::Type> {
777        if data.len() != Self::size() {
778            return Err(Error::msg(format!(
779                "Tried to parse i32 from {} bytes",
780                data.len()
781            )));
782        }
783        Ok(i32::from_le_bytes(data[0..Self::size()].try_into()?))
784    }
785    fn serialize(&self) -> Vec<u8> {
786        i32::to_le_bytes(*self).to_vec()
787    }
788}
789
790/// Trivial trait for types that have `.len()`.
791#[allow(clippy::len_without_is_empty)]
792pub trait Len {
793    /// Get the length.
794    #[must_use]
795    fn len(&self) -> usize;
796}
797impl<T> Len for Vec<T> {
798    fn len(&self) -> usize {
799        self.len()
800    }
801}
802
803#[cfg(test)]
804#[cfg_attr(coverage_nightly, coverage(off))]
805pub mod tests {
806    //! Test helper functions.
807    use super::*;
808
809    /// For testing, assert that two slices are almost equal.
810    ///
811    /// Floating point numbers are almost never exactly equal.
812    pub fn assert_almost_equal_complex(left: &[Complex], right: &[Complex]) {
813        assert_eq!(
814            left.len(),
815            right.len(),
816            "\nleft: {left:?}\nright: {right:?}",
817        );
818        for i in 0..left.len() {
819            let dist = (left[i] - right[i]).norm_sqr().sqrt();
820            if dist > 0.001 {
821                assert_eq!(
822                    left[i], right[i],
823                    "\nElement {i}:\nleft: {left:?}\nright: {right:?}",
824                );
825            }
826        }
827    }
828
829    /// For testing, assert that two slices are almost equal.
830    ///
831    /// Floating point numbers are almost never exactly equal.
832    pub fn assert_almost_equal_float(left: &[Float], right: &[Float]) {
833        assert_eq!(
834            left.len(),
835            right.len(),
836            "\nleft: {left:?}\nright: {right:?}",
837        );
838        for i in 0..left.len() {
839            let dist = (left[i] - right[i]).sqrt();
840            if dist > 0.001 {
841                assert_eq!(left[i], right[i], "\nleft: {left:?}\nright: {right:?}");
842            }
843        }
844    }
845
846    #[test]
847    fn check_env() -> Result<()> {
848        assert!(!environment_str(&check_environment()?).is_empty());
849        Ok(())
850    }
851
852    #[test]
853    fn error_wrap() {
854        use std::error::Error as SysError;
855        let e = Error::msg("foo");
856        assert!(matches![e, Error::Plain(_)]);
857        let _e2: &dyn std::error::Error = &e;
858        let e_str = e.to_string();
859        assert_eq!(e_str, "An error occurred: foo");
860        let e3 = Error::wrap(e, "foo");
861        assert!(matches![e3, Error::Other { source: _, msg: _ }]);
862        let e4 = e3.source().unwrap();
863        assert_eq!(e_str, e4.to_string());
864        let e5 = e4.downcast_ref::<Error>().unwrap();
865        assert!(matches![e5, Error::Plain(_)]);
866    }
867
868    #[test]
869    fn frequency() {
870        for (i, want) in &[
871            ("", None),
872            (".", None),
873            ("k", None),
874            ("r", None),
875            (".k", None),
876            ("0", Some(0.0f64)),
877            ("0.", Some(0.0f64)),
878            ("0.0", Some(0.0f64)),
879            (".3", Some(0.3f64)),
880            (".3k", Some(300.0f64)),
881            ("3.k", Some(3_000.0f64)),
882            ("100", Some(100.0)),
883            ("123k", Some(123_000.0)),
884            ("123kk", None),
885            ("123.78922K", Some(123_789.22)),
886            ("321m", Some(321_000_000.0)),
887            ("2.45g", Some(2_450_000_000.0)),
888            ("100r", None),
889            ("r100", None),
890            ("10k0", None),
891            ("100_000", Some(100_000.0)),
892            ("_1_2_3._4_", Some(123.4)),
893        ] {
894            let got = parse_frequency(i);
895            match (got, want) {
896                (Err(_), None) => {}
897                (Ok(got), None) => panic!("For {i} got {got}, want error"),
898                (Err(e), Some(want)) => panic!("For {i} got error {e:?}, want {want}"),
899                (Ok(got), Some(want)) if got == *want => {}
900                (Ok(got), Some(want)) => panic!("For {i} got {got} want {want}"),
901            }
902        }
903    }
904
905    #[test]
906    fn repeat_test_infinite() {
907        let mut r = Repeat::infinite();
908        for n in 0..100 {
909            assert_eq!(n, r.count());
910            assert!(!r.done());
911            assert!(r.again());
912        }
913    }
914
915    #[test]
916    fn repeat_test_none() {
917        let mut r = Repeat::finite(0);
918        assert_eq!(0, r.count());
919        assert!(r.done());
920        assert!(!r.again());
921        assert_eq!(1, r.count());
922        assert!(r.done());
923        assert!(!r.again());
924        assert_eq!(2, r.count());
925    }
926
927    #[test]
928    fn repeat_test_one() {
929        let mut r = Repeat::finite(1);
930        assert_eq!(0, r.count());
931        assert!(!r.done());
932
933        assert!(!r.again());
934        assert_eq!(1, r.count());
935        assert!(r.done());
936
937        assert!(!r.again());
938        assert_eq!(2, r.count());
939        assert!(r.done());
940    }
941
942    #[test]
943    fn repeat_test_three() {
944        let mut r = Repeat::finite(3);
945        assert_eq!(0, r.count());
946        assert!(!r.done());
947
948        assert!(r.again());
949        assert_eq!(1, r.count());
950        assert!(!r.done());
951
952        assert!(r.again());
953        assert_eq!(2, r.count());
954        assert!(!r.done());
955
956        assert!(!r.again());
957        assert_eq!(3, r.count());
958        assert!(r.done());
959    }
960}