use anyhow::Result;
use bytes::Bytes;
use oggmux::{BufferConfig, OggMux, VorbisBitrateMode, VorbisConfig};
use std::time::{Duration, Instant};
use tokio::time::sleep;
fn create_test_mux() -> OggMux {
OggMux::new()
.with_buffer_config(BufferConfig {
buffered_seconds: 0.5,
max_chunk_size: 4096,
})
.with_vorbis_config(VorbisConfig {
sample_rate: 44100,
bitrate: VorbisBitrateMode::CBR(320),
})
}
fn get_silence_ogg() -> Bytes {
Bytes::from_static(include_bytes!("../resources/silence_44100_320.ogg"))
}
fn contains_ogg_signatures(data: &[u8]) -> bool {
data.windows(4).any(|window| window == b"OggS")
}
#[tokio::test]
async fn test_oggmux_construction() {
let mux = OggMux::new();
let (tx, _rx) = mux.spawn();
drop(tx);
let mux = OggMux::new().with_buffer_config(BufferConfig {
buffered_seconds: 5.0,
max_chunk_size: 8192,
});
let (tx, _rx) = mux.spawn();
drop(tx);
let mux = OggMux::new().with_vorbis_config(VorbisConfig {
sample_rate: 48000,
bitrate: VorbisBitrateMode::CBR(192),
});
let (tx, _rx) = mux.spawn();
drop(tx);
let mux = OggMux::new()
.with_buffer_config(BufferConfig {
buffered_seconds: 3.0,
max_chunk_size: 4096,
})
.with_vorbis_config(VorbisConfig {
sample_rate: 22050,
bitrate: VorbisBitrateMode::CBR(64),
});
let (tx, _rx) = mux.spawn();
drop(tx);
}
#[tokio::test]
async fn test_silence_generation() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
sleep(Duration::from_millis(200)).await;
let start = Instant::now();
let mut received_packets = 0;
let mut total_bytes = 0;
while start.elapsed() < Duration::from_millis(1000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
total_bytes += packet.len();
assert!(contains_ogg_signatures(&packet));
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 3 {
break;
}
}
assert!(received_packets > 0, "No silence packets received");
assert!(total_bytes > 0, "No bytes received");
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_with_ogg_data() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
tx.send(get_silence_ogg()).await?;
let start = Instant::now();
let mut received_packets = 0;
let mut received_data = Vec::<u8>::new();
while start.elapsed() < Duration::from_millis(1000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
received_data.extend_from_slice(&packet);
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets > 5 {
break;
}
}
assert!(received_packets > 0, "No packets received");
assert!(!received_data.is_empty(), "No data received");
assert!(
contains_ogg_signatures(&received_data),
"Output does not contain Ogg pages"
);
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_multiple_pushes() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
for i in 0..3 {
tx.send(get_silence_ogg()).await?;
println!("Pushed silence chunk {}", i + 1);
sleep(Duration::from_millis(100)).await;
}
let start = Instant::now();
let mut received_packets = 0;
let mut total_bytes = 0;
while start.elapsed() < Duration::from_millis(1500) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
total_bytes += packet.len();
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets > 5 {
break;
}
}
assert!(
received_packets > 1,
"Not enough packets received: {}",
received_packets
);
assert!(
total_bytes > 0,
"Not enough bytes received: {}",
total_bytes
);
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_silence_to_audio_transition() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
sleep(Duration::from_millis(300)).await;
tx.send(get_silence_ogg()).await?;
let start = Instant::now();
let mut received_packets = 0;
while start.elapsed() < Duration::from_millis(1000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
assert!(contains_ogg_signatures(&packet));
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 5 {
break;
}
}
assert!(
received_packets > 1,
"Not enough packets received: {}",
received_packets
);
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_alternating_silence_and_audio() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
for _ in 0..3 {
sleep(Duration::from_millis(300)).await;
tx.send(get_silence_ogg()).await?;
for _ in 0..2 {
if let Some(packet) = rx.recv().await {
assert!(contains_ogg_signatures(&packet));
}
}
}
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_invalid_ogg_data() -> Result<()> {
let invalid_data = Bytes::from(b"This is not a valid Ogg stream".to_vec());
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
tx.send(invalid_data).await?;
sleep(Duration::from_millis(1000)).await;
let mut received_packets = 0;
let start = Instant::now();
while start.elapsed() < Duration::from_millis(2000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
assert!(contains_ogg_signatures(&packet));
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 3 {
break;
}
}
assert!(
received_packets > 0,
"No packets received after invalid input"
);
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_truncated_ogg_data() -> Result<()> {
let mut truncated_ogg = get_silence_ogg().to_vec();
truncated_ogg.truncate(truncated_ogg.len() / 2);
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
tx.send(Bytes::from(truncated_ogg)).await?;
sleep(Duration::from_millis(500)).await;
let mut received_packets = 0;
let start = Instant::now();
while start.elapsed() < Duration::from_millis(1000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
assert!(contains_ogg_signatures(&packet));
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 3 {
break;
}
}
assert!(
received_packets > 0,
"No packets received after truncated input"
);
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_large_buffer() -> Result<()> {
let mux = OggMux::new().with_buffer_config(BufferConfig {
buffered_seconds: 5.0,
max_chunk_size: 65536,
});
let (tx, mut rx) = mux.spawn();
for _ in 0..10 {
tx.send(get_silence_ogg()).await?;
}
let start = Instant::now();
let mut received_packets = 0;
let mut total_bytes = 0;
while start.elapsed() < Duration::from_millis(2000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
total_bytes += packet.len();
}
_ = sleep(Duration::from_millis(100)) => {}
}
if received_packets >= 10 {
break;
}
}
assert!(
received_packets > 1,
"Not enough packets received: {}",
received_packets
);
assert!(
total_bytes > 0,
"Not enough bytes received: {}",
total_bytes
);
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_many_small_chunks() -> Result<()> {
let silence_ogg = get_silence_ogg().to_vec();
let chunk_size = 64; let chunks: Vec<_> = silence_ogg
.chunks(chunk_size)
.map(|c| Bytes::from(c.to_vec()))
.collect();
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
for chunk in chunks {
tx.send(chunk).await?;
sleep(Duration::from_millis(10)).await;
}
let start = Instant::now();
let mut received_packets = 0;
let mut total_bytes = 0;
while start.elapsed() < Duration::from_millis(1500) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
total_bytes += packet.len();
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 5 {
break;
}
}
assert!(received_packets > 0, "No packets received");
assert!(total_bytes > 0, "No bytes received");
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_recovery_after_invalid_data() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
let invalid_data = Bytes::from(b"This is not a valid Ogg stream".to_vec());
tx.send(invalid_data).await?;
sleep(Duration::from_millis(1000)).await;
tx.send(get_silence_ogg()).await?;
let start = Instant::now();
let mut received_packets = 0;
let mut received_valid_data = false;
while start.elapsed() < Duration::from_millis(2000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
if contains_ogg_signatures(&packet) {
received_valid_data = true;
}
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 5 && received_valid_data {
break;
}
}
assert!(
received_valid_data,
"No valid Ogg data received after recovery"
);
assert!(received_packets > 0, "No packets received after recovery");
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_extreme_buffer_configuration() -> Result<()> {
let small_buffer_mux = OggMux::new().with_buffer_config(BufferConfig {
buffered_seconds: 0.1,
max_chunk_size: 1024,
});
let (small_tx, mut small_rx) = small_buffer_mux.spawn();
small_tx.send(get_silence_ogg()).await?;
let large_buffer_mux = OggMux::new().with_buffer_config(BufferConfig {
buffered_seconds: 30.0,
max_chunk_size: 1_048_576,
});
let (large_tx, mut large_rx) = large_buffer_mux.spawn();
large_tx.send(get_silence_ogg()).await?;
let mut small_buffer_received = false;
let mut large_buffer_received = false;
let start = Instant::now();
while start.elapsed() < Duration::from_millis(2000) {
tokio::select! {
Some(packet) = small_rx.recv() => {
assert!(contains_ogg_signatures(&packet));
small_buffer_received = true;
}
Some(packet) = large_rx.recv() => {
assert!(contains_ogg_signatures(&packet));
large_buffer_received = true;
}
_ = sleep(Duration::from_millis(50)) => {}
}
if small_buffer_received && large_buffer_received {
break;
}
}
assert!(
small_buffer_received,
"Small buffer configuration did not produce output"
);
assert!(
large_buffer_received,
"Large buffer configuration did not produce output"
);
drop(small_tx);
drop(large_tx);
Ok(())
}
#[tokio::test]
async fn test_various_cbr_configurations() -> Result<()> {
let configurations = vec![
VorbisConfig {
sample_rate: 8000,
bitrate: VorbisBitrateMode::CBR(64),
},
VorbisConfig {
sample_rate: 16000,
bitrate: VorbisBitrateMode::CBR(96),
},
VorbisConfig {
sample_rate: 48000,
bitrate: VorbisBitrateMode::CBR(256),
},
];
for config in configurations {
let mux = OggMux::new().with_vorbis_config(config);
let (tx, mut rx) = mux.spawn();
tx.send(get_silence_ogg()).await?;
let mut received_output = false;
let start = Instant::now();
while start.elapsed() < Duration::from_millis(1000) {
tokio::select! {
Some(packet) = rx.recv() => {
assert!(contains_ogg_signatures(&packet));
received_output = true;
break;
}
_ = sleep(Duration::from_millis(50)) => {}
}
}
assert!(
received_output,
"No output received for sample_rate={}, bitrate={:?}",
config.sample_rate, config.bitrate
);
drop(tx);
}
Ok(())
}
#[tokio::test]
async fn test_vbr_quality_configurations() -> Result<()> {
let configurations = vec![
VorbisConfig {
sample_rate: 44100,
bitrate: VorbisBitrateMode::VBRQuality(3),
},
VorbisConfig {
sample_rate: 48000,
bitrate: VorbisBitrateMode::VBRQuality(6),
},
];
for config in configurations {
let mux = OggMux::new().with_vorbis_config(config);
let (tx, mut rx) = mux.spawn();
tx.send(get_silence_ogg()).await?;
let mut received_output = false;
let start = Instant::now();
while start.elapsed() < Duration::from_millis(1000) {
tokio::select! {
Some(packet) = rx.recv() => {
assert!(contains_ogg_signatures(&packet));
received_output = true;
break;
}
_ = sleep(Duration::from_millis(50)) => {}
}
}
assert!(
received_output,
"No output received for sample_rate={}, bitrate={:?}",
config.sample_rate, config.bitrate
);
drop(tx);
}
Ok(())
}
#[tokio::test]
async fn test_concurrent_producers() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
let tx1 = tx.clone();
let tx2 = tx.clone();
let producer1 = tokio::spawn(async move {
for _ in 0..3 {
tx1.send(get_silence_ogg()).await.unwrap();
sleep(Duration::from_millis(300)).await;
}
});
let producer2 = tokio::spawn(async move {
sleep(Duration::from_millis(150)).await; for _ in 0..2 {
tx2.send(get_silence_ogg()).await.unwrap();
sleep(Duration::from_millis(500)).await;
}
});
let mut received_packets = 0;
let start = Instant::now();
while start.elapsed() < Duration::from_millis(2000) {
tokio::select! {
Some(packet) = rx.recv() => {
assert!(contains_ogg_signatures(&packet));
received_packets += 1;
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 10 {
break;
}
}
let _ = tokio::join!(producer1, producer2);
assert!(
received_packets > 0,
"No packets received from concurrent producers"
);
drop(tx);
Ok(())
}
#[tokio::test]
async fn test_long_running_stability() -> Result<()> {
let test_duration = Duration::from_secs(6);
let mux = OggMux::new().with_buffer_config(BufferConfig {
buffered_seconds: 0.3, max_chunk_size: 4096,
});
let (tx, mut rx) = mux.spawn();
let producer = tokio::spawn(async move {
for i in 0..5 {
tx.send(get_silence_ogg()).await.unwrap();
let wait_time = match i % 3 {
0 => 200, 1 => 500,
_ => 800,
};
sleep(Duration::from_millis(wait_time)).await;
}
sleep(Duration::from_secs(4)).await;
tx
});
sleep(Duration::from_millis(100)).await;
let start = Instant::now();
let mut received_packets = 0;
let mut last_packet_time = Instant::now();
let mut max_gap_ms = 0;
while start.elapsed() < test_duration {
tokio::select! {
Some(packet) = rx.recv() => {
assert!(contains_ogg_signatures(&packet));
let gap = last_packet_time.elapsed().as_millis();
if gap > max_gap_ms {
max_gap_ms = gap;
}
last_packet_time = Instant::now();
received_packets += 1;
println!("Received packet #{}, size: {} bytes", received_packets, packet.len());
}
_ = sleep(Duration::from_millis(50)) => {} }
}
let tx = producer.await?;
drop(tx);
println!("Total packets received: {}", received_packets);
println!("Maximum gap between packets: {}ms", max_gap_ms);
assert!(
received_packets >= 5,
"Not enough packets received in long running test"
);
assert!(
max_gap_ms < 2000, "Gap between packets too large: {}ms",
max_gap_ms
);
Ok(())
}
#[tokio::test]
async fn test_channel_close_during_processing() -> Result<()> {
let mux = create_test_mux();
let (tx, rx) = mux.spawn();
tx.send(get_silence_ogg()).await?;
drop(tx);
drop(rx);
sleep(Duration::from_millis(500)).await;
Ok(())
}
#[tokio::test]
async fn test_empty_data() -> Result<()> {
let mux = create_test_mux();
let (tx, mut rx) = mux.spawn();
tx.send(Bytes::new()).await?;
sleep(Duration::from_millis(500)).await;
let mut received_packets = 0;
let start = Instant::now();
while start.elapsed() < Duration::from_millis(1000) {
tokio::select! {
Some(packet) = rx.recv() => {
received_packets += 1;
assert!(contains_ogg_signatures(&packet));
}
_ = sleep(Duration::from_millis(50)) => {}
}
if received_packets >= 3 {
break;
}
}
assert!(received_packets > 0, "No packets received after empty data");
drop(tx);
Ok(())
}