sim-lib-stream-host 0.1.2

Host-device stream backend substrate for SIM streams.
Documentation
use std::{
    rc::Rc,
    sync::{
        Arc,
        atomic::{AtomicUsize, Ordering},
    },
};

use sim_lib_stream_core::{ClockDomain, StreamMedia};

use crate::{HostOpenStream, HostStreamDriver};

use super::support::{lan_midi_config, realtime_audio_config};

#[test]
fn realtime_local_audio_opening_rejects_wrong_media_and_clock() {
    let config = realtime_audio_config(StreamMedia::Pcm, ClockDomain::Sample);
    let opened = HostOpenStream::new_realtime_local_audio(config).unwrap();
    assert_eq!(opened.config().media(), StreamMedia::Pcm);

    let err = match HostOpenStream::new_realtime_local_audio(realtime_audio_config(
        StreamMedia::Data,
        ClockDomain::Sample,
    )) {
        Ok(_) => panic!("data stream should not open as realtime audio"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("PCM"));

    let err = match HostOpenStream::new_realtime_local_audio(realtime_audio_config(
        StreamMedia::Pcm,
        ClockDomain::Wall,
    )) {
        Ok(_) => panic!("wall-clock stream should not open as realtime audio"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("sample clock"));
}

#[test]
fn realtime_local_audio_close_shutdowns_attached_driver() {
    let shutdowns = Arc::new(AtomicUsize::new(0));
    let driver: Rc<dyn HostStreamDriver> = Rc::new(CountingDriver {
        shutdowns: Arc::clone(&shutdowns),
    });
    let opened = HostOpenStream::new_realtime_local_audio_with_driver(
        realtime_audio_config(StreamMedia::Pcm, ClockDomain::Sample),
        driver,
    )
    .unwrap();

    opened.close().unwrap();

    assert_eq!(shutdowns.load(Ordering::SeqCst), 1);
}

#[test]
fn realtime_local_audio_clone_close_close_shutdowns_driver_once() {
    let shutdowns = Arc::new(AtomicUsize::new(0));
    let driver: Rc<dyn HostStreamDriver> = Rc::new(CountingDriver {
        shutdowns: Arc::clone(&shutdowns),
    });
    let opened = HostOpenStream::new_realtime_local_audio_with_driver(
        realtime_audio_config(StreamMedia::Pcm, ClockDomain::Sample),
        driver,
    )
    .unwrap();
    let cloned = opened.clone();

    opened.close().unwrap();
    cloned.close().unwrap();

    assert_eq!(shutdowns.load(Ordering::SeqCst), 1);
}

#[test]
fn realtime_local_audio_close_then_cancel_shutdowns_driver_once() {
    let shutdowns = Arc::new(AtomicUsize::new(0));
    let driver: Rc<dyn HostStreamDriver> = Rc::new(CountingDriver {
        shutdowns: Arc::clone(&shutdowns),
    });
    let opened = HostOpenStream::new_realtime_local_audio_with_driver(
        realtime_audio_config(StreamMedia::Pcm, ClockDomain::Sample),
        driver,
    )
    .unwrap();

    opened.close().unwrap();
    opened.cancel().unwrap();

    assert_eq!(shutdowns.load(Ordering::SeqCst), 1);
}

#[test]
fn lan_midi_control_opening_rejects_wrong_media_and_clock() {
    let opened = HostOpenStream::new_lan_midi_control(lan_midi_config(
        StreamMedia::Midi,
        ClockDomain::MidiTick,
    ))
    .unwrap();
    assert_eq!(opened.config().media(), StreamMedia::Midi);

    let err = match HostOpenStream::new_lan_midi_control(lan_midi_config(
        StreamMedia::Pcm,
        ClockDomain::MidiTick,
    )) {
        Ok(_) => panic!("PCM stream should not open as LAN MIDI/control"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("MIDI media"));

    let err = match HostOpenStream::new_lan_midi_control(lan_midi_config(
        StreamMedia::Midi,
        ClockDomain::Wall,
    )) {
        Ok(_) => panic!("wall-clock stream should not open as LAN MIDI/control"),
        Err(err) => err,
    };
    assert!(err.to_string().contains("MIDI tick or control clock"));
}

struct CountingDriver {
    shutdowns: Arc<AtomicUsize>,
}

impl HostStreamDriver for CountingDriver {
    fn shutdown(&self) -> sim_kernel::Result<()> {
        self.shutdowns.fetch_add(1, Ordering::SeqCst);
        Ok(())
    }
}