Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
[workspace]
members = ["tests/rtsan_tests"]
resolver = "2"

[workspace.package]
edition = "2021"

[workspace.dependencies]
rodio = {path = "."}

[package]
name = "rodio"
version = "0.22.2"
Expand Down Expand Up @@ -110,6 +120,9 @@ hound = ["dep:hound"] # WAV
minimp3 = ["dep:minimp3_fixed"] # MP3
lewton = ["dep:lewton"] # Ogg Vorbis

# enable the realtime sanitizer on the nightly channel
rtsan = []

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
Expand All @@ -135,6 +148,7 @@ rtrb = { version = "0.3.2", optional = true }
num-rational = "0.4.2"

symphonia-adapter-libopus = { version = "0.2", optional = true }
libtest-mimic = "0.8.2"

[dev-dependencies]
quickcheck = "1"
Expand Down Expand Up @@ -272,3 +286,7 @@ required-features = ["playback", "vorbis"]
[[example]]
name = "third_party_codec"
required-features = ["playback", "symphonia", "symphonia-isomp4"]

[[test]]
harness = false
name = "rtsan"
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@
allow(unreachable_code)
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "rtsan", feature(sanitize))]
// used to overwrite the `Iterator::next` function to be sanitized by rtsan
#![cfg_attr(feature = "rtsan", feature(supertrait_item_shadowing))]

#[cfg(feature = "playback")]
pub use cpal::{
Expand Down
6 changes: 6 additions & 0 deletions src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ pub trait Source: Iterator<Item = Sample> {
/// to determine how many samples remain in the iterator.
fn current_span_len(&self) -> Option<usize>;

#[cfg(feature = "rtsan")]
#[cfg_attr(feature = "rtsan", sanitize(realtime = "nonblocking"))]
fn next(&mut self) -> Option<Sample> {
<Self as Iterator>::next(self)
}

/// Returns true if the source is exhausted (has no more samples available).
#[inline]
fn is_exhausted(&self) -> bool {
Expand Down
1 change: 1 addition & 0 deletions src/speakers/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ where
$(
cpal::SampleFormat::$sample_format => device.build_output_stream::<$generic, _, _>(
cpal_config2,
#[cfg_attr(feature = "rtsan", sanitize(realtime = "nonblocking"))]
move |data, _| {
data.iter_mut().for_each(|d| {
*d = source
Expand Down
74 changes: 74 additions & 0 deletions tests/rtsan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
extern crate libtest_mimic;

use libtest_mimic::{Arguments, Failed, Trial};
use std::{
fs::read_dir,
process::{Command, ExitCode, Stdio},
};

fn main() -> ExitCode {
let args = Arguments::from_args();

let mut tests = Vec::new();

let host_tuple = {
let output = Command::new("rustc")
.args(["--print", "host-tuple"])
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
};

// collect test cases
for file in read_dir("tests/rtsan_tests/src/bin").unwrap() {
let file = file.unwrap();

assert!(file.metadata().unwrap().is_file());

let name = file
.file_name()
.to_str()
.unwrap()
.strip_suffix(".rs")
.unwrap()
.to_owned();
let host_tuple = host_tuple.clone();

let test = Trial::test(name.clone(), move || {
let process = Command::new("cargo")
.args([
"+nightly",
"run",
"-p",
"rtsan_tests",
"--bin",
&name,
// this puts cargo in "cross compilation mode", so it doesn't try to compile the build scripts with sanitizers,
"--target",
&host_tuple,
])
.env("RUSTFLAGS", "-Zsanitizer=realtime")
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = process.wait_with_output().unwrap();
if output.status.success() {
Ok(())
} else {
Err(Failed::from(
String::from("realtime violation detected. Output: \n")
+ &String::from_utf8_lossy(&output.stderr),
))
}
});
tests.push(test);
}

libtest_mimic::run(&args, tests).exit_code()
}
7 changes: 7 additions & 0 deletions tests/rtsan_tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
edition.workspace = true
name = "rtsan_tests"
publish = false

[dependencies]
rodio = { workspace = true, features = ["rtsan"]}
16 changes: 16 additions & 0 deletions tests/rtsan_tests/src/bin/stereo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! Plays a tone alternating between right and left ears, with right being first.

use rodio::Source;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::DeviceSinkBuilder::open_default_sink()?;
let player = rodio::Player::connect_new(stream_handle.mixer());

let file = std::fs::File::open("assets/RL.ogg")?;
player.append(rodio::Decoder::try_from(file)?.amplify(0.2));

player.sleep_until_end();

Ok(())
}
Loading