Compare commits

...

4 Commits

9 changed files with 102 additions and 11 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
target target
Cargo.lock Cargo.lock
test_assets/*
!test_assets/.gitkeep

View File

@ -31,6 +31,7 @@
pkgs.mpv pkgs.mpv
pkgs.grcov pkgs.grcov
pkgs.cargo-nextest pkgs.cargo-nextest
pkgs.ffmpeg
]; ];
RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library"; RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library";
}); });

21
setup_test_assets.sh Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
REQUIRED_COMMANDS=(
"git"
"ffmpeg"
)
for cmd in "${REQUIRED_COMMANDS[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
echo "Command '$cmd' not found. Please install it and try again."
exit 1
fi
done
ROOT_DIR=$(git rev-parse --show-toplevel)
# Generate 30 seconds of 480p video with black background
ffmpeg -f lavfi -i color=c=black:s=640x480:d=30 -c:v libx264 -t 30 -pix_fmt yuv420p "$ROOT_DIR/test_assets/black-background-30s-480p.mp4"

0
test_assets/.gitkeep Normal file
View File

View File

@ -115,7 +115,7 @@ async fn graceful_shutdown(
#[test(tokio::test)] #[test(tokio::test)]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_highlevel_event_pause() -> Result<(), MpvError> { async fn test_highlevel_event_pause() -> Result<(), MpvError> {
let (proc, mpv) = spawn_headless_mpv().await?; let (proc, mpv) = spawn_mpv(true).await?;
mpv.observe_property(MPV_CHANNEL_ID, "pause").await?; mpv.observe_property(MPV_CHANNEL_ID, "pause").await?;
@ -144,7 +144,7 @@ async fn test_highlevel_event_pause() -> Result<(), MpvError> {
#[test(tokio::test)] #[test(tokio::test)]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_highlevel_event_volume() -> Result<(), MpvError> { async fn test_highlevel_event_volume() -> Result<(), MpvError> {
let (proc, mpv) = spawn_headless_mpv().await?; let (proc, mpv) = spawn_mpv(true).await?;
mpv.observe_property(1337, "volume").await?; mpv.observe_property(1337, "volume").await?;
let events = mpv.get_event_stream().await; let events = mpv.get_event_stream().await;
@ -174,7 +174,7 @@ async fn test_highlevel_event_volume() -> Result<(), MpvError> {
#[test(tokio::test)] #[test(tokio::test)]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_highlevel_event_mute() -> Result<(), MpvError> { async fn test_highlevel_event_mute() -> Result<(), MpvError> {
let (proc, mpv) = spawn_headless_mpv().await?; let (proc, mpv) = spawn_mpv(true).await?;
mpv.observe_property(1337, "mute").await?; mpv.observe_property(1337, "mute").await?;
let events = mpv.get_event_stream().await; let events = mpv.get_event_stream().await;
@ -202,7 +202,7 @@ async fn test_highlevel_event_mute() -> Result<(), MpvError> {
#[test(tokio::test)] #[test(tokio::test)]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_highlevel_event_duration() -> Result<(), MpvError> { async fn test_highlevel_event_duration() -> Result<(), MpvError> {
let (proc, mpv) = spawn_headless_mpv().await?; let (proc, mpv) = spawn_mpv(true).await?;
mpv.observe_property(1337, "duration").await?; mpv.observe_property(1337, "duration").await?;

View File

@ -0,0 +1,35 @@
use super::util::{get_test_asset, spawn_mpv};
use mpvipc_async::{
MpvError, MpvExt, PlaylistAddOptions, PlaylistAddTypeOptions, SeekOptions, Switch,
};
#[tokio::test]
#[cfg(target_family = "unix")]
async fn test_seek() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_mpv(false).await.unwrap();
mpv.playlist_add(
&get_test_asset("black-background-30s-480p.mp4"),
PlaylistAddTypeOptions::File,
PlaylistAddOptions::Append,
)
.await?;
mpv.set_playback(Switch::On).await?;
mpv.set_playback(Switch::Off).await?;
// TODO: wait for property "seekable" to be true
mpv.seek(10.0, SeekOptions::Relative).await?;
let time_pos: f64 = mpv.get_property("time-pos").await?.unwrap();
assert_eq!(time_pos, 10.0);
mpv.seek(5.0, SeekOptions::Relative).await?;
let time_pos: f64 = mpv.get_property("time-pos").await?.unwrap();
assert_eq!(time_pos, 15.0);
mpv.kill().await.unwrap();
proc.kill().await.unwrap();
Ok(())
}

View File

@ -5,7 +5,7 @@ use super::*;
#[tokio::test] #[tokio::test]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_get_mpv_version() -> Result<(), MpvError> { async fn test_get_mpv_version() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap(); let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
let version: String = mpv.get_property("mpv-version").await?.unwrap(); let version: String = mpv.get_property("mpv-version").await?.unwrap();
assert!(version.starts_with("mpv")); assert!(version.starts_with("mpv"));
@ -18,7 +18,7 @@ async fn test_get_mpv_version() -> Result<(), MpvError> {
#[tokio::test] #[tokio::test]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_set_property() -> Result<(), MpvError> { async fn test_set_property() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap(); let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
mpv.set_property("pause", true).await.unwrap(); mpv.set_property("pause", true).await.unwrap();
let paused: bool = mpv.get_property("pause").await?.unwrap(); let paused: bool = mpv.get_property("pause").await?.unwrap();
assert!(paused); assert!(paused);
@ -32,7 +32,7 @@ async fn test_set_property() -> Result<(), MpvError> {
#[tokio::test] #[tokio::test]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_get_unavailable_property() -> Result<(), MpvError> { async fn test_get_unavailable_property() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap(); let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
let time_pos = mpv.get_property::<f64>("time-pos").await; let time_pos = mpv.get_property::<f64>("time-pos").await;
assert_eq!(time_pos, Ok(None)); assert_eq!(time_pos, Ok(None));
@ -45,7 +45,7 @@ async fn test_get_unavailable_property() -> Result<(), MpvError> {
#[tokio::test] #[tokio::test]
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
async fn test_get_nonexistent_property() -> Result<(), MpvError> { async fn test_get_nonexistent_property() -> Result<(), MpvError> {
let (mut proc, mpv) = spawn_headless_mpv().await.unwrap(); let (mut proc, mpv) = spawn_mpv(true).await.unwrap();
let nonexistent = mpv.get_property::<f64>("nonexistent").await; let nonexistent = mpv.get_property::<f64>("nonexistent").await;
match nonexistent { match nonexistent {

View File

@ -1,4 +1,5 @@
mod event_property_parser; mod event_property_parser;
mod highlevel_api;
mod misc; mod misc;
mod util; mod util;

View File

@ -6,8 +6,35 @@ use tokio::{
time::{sleep, timeout}, time::{sleep, timeout},
}; };
pub fn assert_test_assets_exist() {
let test_data_dir = Path::new("test_assets");
if !test_data_dir.exists()
|| !test_data_dir.is_dir()
// `.gitkeep` should always be present, so there should be at least 2 entries
|| test_data_dir.read_dir().unwrap().count() <= 1
{
panic!(
"Test assets directory not found at {:?}, please run `./setup_test_assets.sh`",
test_data_dir
);
}
}
#[inline]
pub fn get_test_assets_dir() -> &'static Path {
Path::new("test_assets")
}
pub fn get_test_asset(file_name: &str) -> String {
assert_test_assets_exist();
let test_assets_dir = get_test_assets_dir();
let file_path = test_assets_dir.join(file_name);
file_path.to_str().unwrap().to_string()
}
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub async fn spawn_headless_mpv() -> Result<(Child, Mpv), MpvError> { pub async fn spawn_mpv(headless: bool) -> Result<(Child, Mpv), MpvError> {
let socket_path_str = format!("/tmp/mpv-ipc-{}", uuid::Uuid::new_v4()); let socket_path_str = format!("/tmp/mpv-ipc-{}", uuid::Uuid::new_v4());
let socket_path = Path::new(&socket_path_str); let socket_path = Path::new(&socket_path_str);
@ -15,8 +42,11 @@ pub async fn spawn_headless_mpv() -> Result<(Child, Mpv), MpvError> {
let process_handle = Command::new("mpv") let process_handle = Command::new("mpv")
.arg("--no-config") .arg("--no-config")
.arg("--idle") .arg("--idle")
.arg("--no-video") .args(if headless {
.arg("--no-audio") vec!["--no-video", "--no-audio"]
} else {
vec![]
})
.arg(format!( .arg(format!(
"--input-ipc-server={}", "--input-ipc-server={}",
&socket_path.to_str().unwrap() &socket_path.to_str().unwrap()