tekhsi_rs 0.1.1

High-performance client for Tektronix TekHSI enabled oscilloscopes
Documentation
use proptest::prelude::*;
use tekhsi_rs::data::{AnalogSamples, Waveform};
use tekhsi_rs::{WaveformHeader, WfmType, decode_waveform_chunks};

// Helper to create a minimal valid header for a given type/width
fn make_header(wfm_type: WfmType, width: i32, samples: i32) -> WaveformHeader {
    WaveformHeader {
        wfmtype: wfm_type as i32,
        sourcewidth: width as u32,
        noofsamples: samples as u64,
        sourcename: "test".to_string(),
        verticalunits: "V".to_string(),
        horizontal_units: "s".to_string(),
        iq_window_type: "Rectangle".to_string(),
        hasdata: true,
        chunksize: 1024,
        ..Default::default()
    }
}

proptest! {
    #![proptest_config(ProptestConfig::with_cases(1000))]

    #[test]
    fn prop_decode_analog_f32_no_panic(
        // Generate a random number of samples (0 to 10k)
        sample_count in 0..10_000i32,
        // Generate random chunk configurations
        // A list of byte vectors. We want to stress the chunk boundaries.
        chunks in proptest::collection::vec(proptest::collection::vec(any::<u8>(), 0..100), 0..50)
    ) {
        // Source width 4 = f32
        let header = make_header(WfmType::WfmtypeAnalogFloat, 4, sample_count);

        // The decoder should never panic.
        // It might return Err if data is short/invalid, or Ok.
        let result = decode_waveform_chunks(&header, &chunks);

        if let Ok(Waveform::Analog(wfm)) = result {
            // If success, verify the sample count
            assert_eq!(wfm.y_axis_values.len(), sample_count as usize);
        }
    }

    #[test]
    fn prop_decode_analog_i16_no_panic(
        sample_count in 0..10_000i32,
        chunks in proptest::collection::vec(proptest::collection::vec(any::<u8>(), 0..100), 0..50)
    ) {
        let header = make_header(WfmType::WfmtypeAnalog16, 2, sample_count);
        let result = decode_waveform_chunks(&header, &chunks);

        if let Ok(Waveform::Analog(wfm)) = result {
            assert_eq!(wfm.y_axis_values.len(), sample_count as usize);
        }
    }

    #[test]
    fn prop_decode_digital_i16_no_panic(
        sample_count in 0..10_000i32,
        chunks in proptest::collection::vec(proptest::collection::vec(any::<u8>(), 0..100), 0..50)
    ) {
        let header = make_header(WfmType::WfmtypeDigital16, 2, sample_count);
        let result = decode_waveform_chunks(&header, &chunks);

        if let Ok(Waveform::Digital(wfm)) = result {
            assert_eq!(wfm.y_axis_bytes.len(), sample_count as usize);
        }
    }

    // Test the unaligned/carry logic specifically
    #[test]
    fn prop_decode_f32_split_across_chunks(
        values in proptest::collection::vec(any::<f32>(), 1..1000),
        split_idx in 1..3999usize // Where to split the byte stream
    ) {
        let sample_count = values.len() as i32;
        let header = make_header(WfmType::WfmtypeAnalogFloat, 4, sample_count);

        // Serialize f32s to bytes
        let mut all_bytes = Vec::new();
        for v in &values {
            all_bytes.extend_from_slice(&v.to_le_bytes());
        }

        // Ensure the split index is within bounds
        let split = split_idx % all_bytes.len();
        if split == 0 { return Ok(()); }

        let (first, second) = all_bytes.split_at(split);
        let chunks = vec![first.to_vec(), second.to_vec()];

        let result = decode_waveform_chunks(&header, &chunks).expect("should succeed");

        if let Waveform::Analog(wfm) = result {
            if let AnalogSamples::F32(decoded) = wfm.y_axis_values {
                assert_eq!(decoded, values);
            } else {
                panic!("Wrong sample type");
            }
        }
    }
}