test(oak-codec, oak-core): boundary and branch coverage
Fixture-backed unit and contract tests for FFmpeg helpers and state machines, OCIO color factories, wgpu backend fallbacks, and the safe parts of the platform import module (review report section 10.2).
This commit is contained in:
@@ -460,13 +460,30 @@ pub fn create_from_id(id: &str) -> Option<Arc<dyn Decoder>> {
|
||||
/// not injected, in which case the built-in list below is used.
|
||||
static TEST_DECODERS: OnceLock<Mutex<Vec<Arc<dyn Decoder>>>> = OnceLock::new();
|
||||
|
||||
/// Serializes every test that reads the built-in decoder registry. Tests
|
||||
/// inject through [`set_test_decoders`] under [`crate::lock_tests`] (the
|
||||
/// shared test lock), so the registry assertions below take that same lock
|
||||
/// to never race with an injected list.
|
||||
/// Serializes every test that reads the built-in decoder registry and
|
||||
/// clears the test injection on drop: a panicking assertion must not leak
|
||||
/// fake decoders into the process-wide registry used by later tests. The
|
||||
/// held [`crate::TestLock`] is released only after the clear.
|
||||
#[cfg(test)]
|
||||
fn registry_guard() -> crate::TestLock {
|
||||
crate::lock_tests()
|
||||
struct RegistryGuard {
|
||||
/// Held for the test's duration; never read, only dropped.
|
||||
_lock: crate::TestLock,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Drop for RegistryGuard {
|
||||
fn drop(&mut self) {
|
||||
set_test_decoders(Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
/// Take the shared test lock and clear any injection left behind by an
|
||||
/// earlier panicking test, then return the guard.
|
||||
#[cfg(test)]
|
||||
fn registry_guard() -> RegistryGuard {
|
||||
let lock = crate::lock_tests();
|
||||
set_test_decoders(Vec::new());
|
||||
RegistryGuard { _lock: lock }
|
||||
}
|
||||
|
||||
/// Replace the decoder registry with `list`; pass an empty list to restore
|
||||
@@ -785,6 +802,149 @@ mod tests_unimplemented {
|
||||
assert_eq!(off.numerator(), 0);
|
||||
assert_eq!(off.denominator(), 1);
|
||||
}
|
||||
|
||||
/// A decoder that relies on every trait default (no overrides), so the
|
||||
/// conservative default bodies are covered.
|
||||
struct DefaultsOnly;
|
||||
|
||||
impl Decoder for DefaultsOnly {
|
||||
fn id(&self) -> String {
|
||||
"defaults".to_string()
|
||||
}
|
||||
fn probe(&self, _f: &str, _c: Option<&CancelAtom>) -> Option<FootageDescription> {
|
||||
None
|
||||
}
|
||||
fn open(&self, _s: &CodecStream) -> crate::error::Result<()> {
|
||||
Err(crate::error::Error::Invalid)
|
||||
}
|
||||
fn close(&self) -> crate::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn stream(&self) -> CodecStream {
|
||||
CodecStream::new()
|
||||
}
|
||||
fn retrieve_video_frame(
|
||||
&self,
|
||||
_p: &RetrieveVideoParams,
|
||||
) -> crate::error::Result<Arc<Frame>> {
|
||||
Err(crate::error::Error::Invalid)
|
||||
}
|
||||
fn retrieve_video(
|
||||
&self,
|
||||
_p: &RetrieveVideoParams,
|
||||
) -> crate::error::Result<OakRenderTexture> {
|
||||
Err(crate::error::Error::Invalid)
|
||||
}
|
||||
fn retrieve_audio(
|
||||
&self,
|
||||
_d: &mut [f32],
|
||||
_r: &TimeRange,
|
||||
_s: i32,
|
||||
_l: u64,
|
||||
) -> crate::error::Result<RetrieveAudioStatus> {
|
||||
Ok(RetrieveAudioStatus::Unsupported)
|
||||
}
|
||||
fn conform_audio(
|
||||
&self,
|
||||
_o: &[String],
|
||||
_s: i32,
|
||||
_l: u64,
|
||||
_sf: i32,
|
||||
_c: Option<&CancelAtom>,
|
||||
) -> crate::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn test_params() -> RetrieveVideoParams {
|
||||
RetrieveVideoParams {
|
||||
stream: CodecStream::new(),
|
||||
time: Rational::new(0, 1),
|
||||
length: TimeRange::default(),
|
||||
force_range: K_COLOR_RANGE_DEFAULT,
|
||||
is_image_sequence: false,
|
||||
image_sequence_digits: 0,
|
||||
image_sequence_number: 0,
|
||||
mode: RenderMode::Offline,
|
||||
alpha_is_premultiplied: false,
|
||||
target_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_trait_defaults_are_conservative() {
|
||||
let d = DefaultsOnly;
|
||||
assert!(!d.supports_video());
|
||||
assert!(!d.supports_audio());
|
||||
assert!(!d.hardware_decoding());
|
||||
assert_eq!(d.get_audio_start_offset(), Rational::new(0, 1));
|
||||
assert!(d.retrieve_video_frame_gpu(&test_params()).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oiio_placeholder_reports_unimplemented() {
|
||||
let _g = registry_guard();
|
||||
let d = builtin("oiio");
|
||||
assert!(d.probe("x.exr", None).is_none());
|
||||
assert!(d
|
||||
.open(&CodecStream::with_block("x.exr".to_string(), 0, None))
|
||||
.is_err());
|
||||
assert!(d.close().is_err());
|
||||
assert_eq!(d.stream().filename(), "");
|
||||
let p = test_params();
|
||||
assert!(d.retrieve_video_frame(&p).is_err());
|
||||
assert!(d.retrieve_video(&p).is_err());
|
||||
let mut dest = [0f32; 4];
|
||||
assert!(d
|
||||
.retrieve_audio(
|
||||
&mut dest,
|
||||
&TimeRange::new(Rational::new(0, 1), Rational::new(1, 1)),
|
||||
48000,
|
||||
0x3
|
||||
)
|
||||
.is_err());
|
||||
assert!(d
|
||||
.conform_audio(&["a.pcm".to_string()], 48000, 0x3, 10, None)
|
||||
.is_err());
|
||||
// The placeholder keeps the conservative defaults too.
|
||||
assert!(!d.hardware_decoding());
|
||||
assert_eq!(d.get_audio_start_offset(), Rational::new(0, 1));
|
||||
assert!(d.retrieve_video_frame_gpu(&p).unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoder_registry_injection_wins_and_restores() {
|
||||
let _g = registry_guard();
|
||||
set_test_decoders(vec![Arc::new(DefaultsOnly)]);
|
||||
assert_eq!(
|
||||
create_from_id("defaults").map(|d| d.id()).as_deref(),
|
||||
Some("defaults")
|
||||
);
|
||||
assert!(create_from_id("ffmpeg").is_none());
|
||||
set_test_decoders(Vec::new());
|
||||
assert!(create_from_id("ffmpeg").is_some());
|
||||
}
|
||||
|
||||
/// The injection restore is panic-safe: the guard clears the registry
|
||||
/// on unwind, so a failing assertion cannot leave the fake decoder
|
||||
/// installed for later tests in the same process.
|
||||
#[test]
|
||||
fn decoder_registry_clears_after_a_panicking_injection() {
|
||||
let panicked = std::panic::catch_unwind(|| {
|
||||
let _g = registry_guard();
|
||||
set_test_decoders(vec![Arc::new(DefaultsOnly)]);
|
||||
assert!(
|
||||
create_from_id("defaults").is_some(),
|
||||
"the fake decoder is installed"
|
||||
);
|
||||
panic!("simulated assertion failure after injection");
|
||||
});
|
||||
assert!(panicked.is_err(), "the closure must panic");
|
||||
let _g = registry_guard();
|
||||
assert!(
|
||||
create_from_id("ffmpeg").is_some(),
|
||||
"no fake decoder may leak into the next test"
|
||||
);
|
||||
assert!(create_from_id("defaults").is_none());
|
||||
}
|
||||
}
|
||||
+ /// Held for the test's duration; never read, only dropped.
|
||||
+ set_test_decoders(Vec::new());
|
||||
|
||||
@@ -121,6 +121,31 @@ pub fn set_test_encoders(list: Vec<Arc<dyn Encoder>>) {
|
||||
*store.lock().unwrap() = list;
|
||||
}
|
||||
|
||||
/// Serializes every test that reads the built-in encoder registry and
|
||||
/// clears the test injection on drop: a panicking assertion must not leak
|
||||
/// fake encoders into the process-wide registry used by later tests. The
|
||||
/// held [`crate::TestLock`] is released only after the clear.
|
||||
#[cfg(test)]
|
||||
struct RegistryGuard {
|
||||
/// Held for the test's duration; never read, only dropped.
|
||||
_lock: crate::TestLock,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Drop for RegistryGuard {
|
||||
fn drop(&mut self) {
|
||||
set_test_encoders(Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
/// Take the shared test lock and return the guard.
|
||||
#[cfg(test)]
|
||||
fn registry_guard() -> RegistryGuard {
|
||||
RegistryGuard {
|
||||
_lock: crate::lock_tests(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `Encoder::create_from_params` — instantiate an encoder for `params`.
|
||||
///
|
||||
/// # CPP-PARITY
|
||||
@@ -251,6 +276,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn create_from_params_maps_formats() {
|
||||
let _guard = registry_guard();
|
||||
let mut p = EncodingParams::default();
|
||||
|
||||
// FFmpeg-backed containers.
|
||||
@@ -346,7 +372,63 @@ mod tests {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoder_trait_defaults_are_conservative() {
|
||||
let e = UnimplementedDummy;
|
||||
assert!(!e.supports_video());
|
||||
assert!(!e.supports_audio());
|
||||
assert!(!e.supports_subtitles());
|
||||
assert!(!e.supports_image_sequences());
|
||||
assert!(!e.is_configurable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoder_registry_injection_wins_and_restores() {
|
||||
let _guard = registry_guard();
|
||||
// Unknown format: the built-in mapping returns None.
|
||||
let p = EncodingParams {
|
||||
format: 999,
|
||||
..EncodingParams::default()
|
||||
};
|
||||
assert!(create_from_params(&p).is_none());
|
||||
|
||||
set_test_encoders(vec![Arc::new(UnimplementedDummy)]);
|
||||
let injected = create_from_params(&p).expect("injected encoder wins");
|
||||
assert_eq!(injected.id(), "dummy");
|
||||
|
||||
set_test_encoders(Vec::new());
|
||||
assert!(create_from_params(&p).is_none());
|
||||
}
|
||||
|
||||
/// The injection restore is panic-safe: the guard clears the registry
|
||||
/// on unwind, so a failing assertion cannot leave the fake encoder
|
||||
/// installed for later tests in the same process.
|
||||
#[test]
|
||||
fn encoder_registry_clears_after_a_panicking_injection() {
|
||||
let panicked = std::panic::catch_unwind(|| {
|
||||
let _guard = registry_guard();
|
||||
set_test_encoders(vec![Arc::new(UnimplementedDummy)]);
|
||||
let p = EncodingParams {
|
||||
format: 999,
|
||||
..EncodingParams::default()
|
||||
};
|
||||
assert_eq!(
|
||||
create_from_params(&p).map(|e| e.id()).as_deref(),
|
||||
Some("dummy"),
|
||||
"the fake encoder is installed"
|
||||
);
|
||||
panic!("simulated assertion failure after injection");
|
||||
});
|
||||
assert!(panicked.is_err(), "the closure must panic");
|
||||
let _guard = registry_guard();
|
||||
let p = EncodingParams {
|
||||
format: 999,
|
||||
..EncodingParams::default()
|
||||
};
|
||||
assert!(
|
||||
create_from_params(&p).is_none(),
|
||||
"no fake encoder may leak into the next test"
|
||||
);
|
||||
}
|
||||
}
|
||||
+ /// Held for the test's duration; never read, only dropped.
|
||||
+ set_test_encoders(Vec::new());
|
||||
+ _lock: crate::lock_tests(),
|
||||
|
||||
@@ -1118,4 +1118,65 @@ mod tests {
|
||||
assert_eq!(offset_of!(EncodingParams, color_range), 1548);
|
||||
}
|
||||
}
|
||||
assert_eq!(offset_of!(EncodingParams, color_range), 1548);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_coverage {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn load_rejects_malformed_and_foreign_documents() {
|
||||
let mut p = EncodingParams::default();
|
||||
for doc in [
|
||||
"",
|
||||
"<export",
|
||||
"<export x=1></export>",
|
||||
"<export x=\"1></export>",
|
||||
"<export></other>",
|
||||
"<export><video/>text</export>",
|
||||
"<not-export/>",
|
||||
] {
|
||||
assert!(p.load(doc).is_err(), "must reject: {doc}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_maps_video_audio_and_falls_back_on_unknown_codes() {
|
||||
let mut p = EncodingParams::default();
|
||||
p.load(
|
||||
r#"<export><filename>out.mov</filename><format>2</format>
|
||||
<video enabled="1"><codec>4</codec><width>1920</width><height>1080</height>
|
||||
<format>4</format><timebase>30000/1001</timebase>
|
||||
<pixelaspect>1/1</pixelaspect><vscale>2</vscale></video>
|
||||
<audio enabled="0"><format>5</format><bitrate>192000</bitrate></audio>
|
||||
</export>"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(p.extension(), "mp4", "format 2 is MPEG-4 video");
|
||||
assert_eq!(p.video_width, 1920);
|
||||
assert_eq!(p.video_height, 1080);
|
||||
assert_eq!(p.video_pixel_format, PixelFormat::F32);
|
||||
assert_eq!(p.video_time_base_num, 30000);
|
||||
assert_eq!(p.video_time_base_den, 1001);
|
||||
assert_eq!(p.video_scaling_method, VideoScalingMethod::Crop);
|
||||
assert_eq!(p.audio_sample_format, SampleFormat::F64Planar);
|
||||
assert_eq!(p.audio_bit_rate, 192000);
|
||||
|
||||
// Unknown numeric codes fall back to Invalid / Stretch.
|
||||
let mut p = EncodingParams::default();
|
||||
p.load(
|
||||
r#"<export><video><format>99</format><vscale>99</vscale></video>
|
||||
<audio><format>99</format></audio></export>"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(p.video_pixel_format, PixelFormat::Invalid);
|
||||
assert_eq!(p.video_scaling_method, VideoScalingMethod::Stretch);
|
||||
assert_eq!(p.audio_sample_format, SampleFormat::Invalid);
|
||||
|
||||
// -1 is the explicit Invalid code; vscale 0 is Fit.
|
||||
let mut p = EncodingParams::default();
|
||||
p.load(r#"<export><video><format>-1</format><vscale>0</vscale></video></export>"#)
|
||||
.unwrap();
|
||||
assert_eq!(p.video_pixel_format, PixelFormat::Invalid);
|
||||
assert_eq!(p.video_scaling_method, VideoScalingMethod::Fit);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -214,4 +214,12 @@ mod tests {
|
||||
mgr.clear();
|
||||
assert_eq!(mgr.pool.lock().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instance_is_a_process_singleton() {
|
||||
let a = FrameManager::instance();
|
||||
let b = FrameManager::instance();
|
||||
assert!(std::ptr::eq(a, b));
|
||||
a.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +72,37 @@ pub const CONFIG_KEY_HARDWARE_DECODING: &str = "HardwareDecoding";
|
||||
static UNAVAILABLE_DEVICES: [std::sync::atomic::AtomicBool; 64] =
|
||||
[const { std::sync::atomic::AtomicBool::new(false) }; 64];
|
||||
|
||||
/// Test-only counter of `open_hw_accel` device-context creation attempts
|
||||
/// (incremented at the top of the function, before any FFmpeg call). Lets
|
||||
/// tests prove the negative cache short-circuits before FFmpeg is
|
||||
/// involved.
|
||||
#[cfg(test)]
|
||||
static CREATE_ATTEMPTS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
thread_local! {
|
||||
/// Test-only counter of `open_hw_accel` device-context creation
|
||||
/// attempts on the calling thread (incremented at the top of the
|
||||
/// function, before any FFmpeg call). Lets tests prove the negative
|
||||
/// cache short-circuits before FFmpeg is involved.
|
||||
///
|
||||
/// Thread-local on purpose: the real-media tests decode on other
|
||||
/// threads *without* the shared test lock, so a process-wide counter
|
||||
/// would let their attempts perturb the negative-cache assertion on
|
||||
/// this thread.
|
||||
static CREATE_ATTEMPTS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
/// The calling thread's creation-attempt count (tests only).
|
||||
#[cfg(test)]
|
||||
fn creation_attempts() -> u64 {
|
||||
CREATE_ATTEMPTS.with(std::cell::Cell::get)
|
||||
}
|
||||
|
||||
/// Count one creation attempt on the calling thread (tests only).
|
||||
#[cfg(test)]
|
||||
fn note_creation_attempt() {
|
||||
CREATE_ATTEMPTS.with(|count| count.set(count.get() + 1));
|
||||
}
|
||||
|
||||
/// Reset the calling thread's creation-attempt count (tests only).
|
||||
#[cfg(test)]
|
||||
fn reset_creation_attempts() {
|
||||
CREATE_ATTEMPTS.with(|count| count.set(0));
|
||||
}
|
||||
|
||||
/// Whether `device_type` is known unavailable — a device-context
|
||||
/// creation failed once earlier in this process.
|
||||
@@ -178,9 +203,7 @@ pub fn open_hw_accel(
|
||||
return None;
|
||||
}
|
||||
#[cfg(test)]
|
||||
{
|
||||
CREATE_ATTEMPTS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
note_creation_attempt();
|
||||
let mut context = ffmpeg::codec::Context::from_parameters(params.clone()).ok()?;
|
||||
let mut device: *mut sys::AVBufferRef = std::ptr::null_mut();
|
||||
// SAFETY: `device` is a valid out-pointer; on success it owns the
|
||||
@@ -329,6 +352,10 @@ mod tests {
|
||||
/// boxes without the driver).
|
||||
#[test]
|
||||
fn negative_cache_skips_marked_device() {
|
||||
// Serialize with the media tests that hold the shared test lock
|
||||
// (the counter itself is thread-local, but the negative cache is
|
||||
// process-wide).
|
||||
let _lock = crate::lock_tests();
|
||||
// VDPAU is not a candidate on any supported platform, so marking
|
||||
// it cannot disturb the platform tests in this process (e.g. the
|
||||
// macOS VideoToolbox test above).
|
||||
@@ -342,11 +369,11 @@ mod tests {
|
||||
let params = fstream.parameters();
|
||||
let codec = ffmpeg::decoder::find(params.id()).expect("software h264 codec");
|
||||
|
||||
CREATE_ATTEMPTS.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||
reset_creation_attempts();
|
||||
let opened = open_hw_accel(¶ms, codec, dev);
|
||||
assert!(opened.is_none(), "marked device must not open");
|
||||
assert_eq!(
|
||||
CREATE_ATTEMPTS.load(std::sync::atomic::Ordering::Relaxed),
|
||||
creation_attempts(),
|
||||
0,
|
||||
"marked device must be skipped before any creation attempt"
|
||||
);
|
||||
@@ -365,6 +392,3 @@ mod tests {
|
||||
assert!(!device_unavailable(dev));
|
||||
}
|
||||
}
|
||||
+ /// would let their attempts perturb the negative-cache assertion on
|
||||
+#[cfg(test)]
|
||||
+#[cfg(test)]
|
||||
|
||||
@@ -769,4 +769,86 @@ mod tests_extra {
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests_coverage {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn proxy_state_numeric_and_string_conversions() {
|
||||
assert_eq!(ProxyState::try_from(0), Ok(ProxyState::Missing));
|
||||
assert_eq!(ProxyState::try_from(1), Ok(ProxyState::Generating));
|
||||
assert_eq!(ProxyState::try_from(2), Ok(ProxyState::Ready));
|
||||
assert_eq!(ProxyState::try_from(3), Ok(ProxyState::Failed));
|
||||
assert_eq!(ProxyState::try_from(4), Err(()));
|
||||
assert_eq!(ProxyState::try_from(-1), Err(()));
|
||||
|
||||
for state in [
|
||||
ProxyState::Missing,
|
||||
ProxyState::Generating,
|
||||
ProxyState::Ready,
|
||||
ProxyState::Failed,
|
||||
] {
|
||||
let name = ProxyManager::proxy_state_to_string(state);
|
||||
assert_eq!(ProxyManager::proxy_state_from_string(&name), state);
|
||||
assert_eq!(
|
||||
ProxyManager::proxy_state_from_string(&(state as i32).to_string()),
|
||||
state
|
||||
);
|
||||
}
|
||||
|
||||
// Whitespace is trimmed; unknown names fall back to Missing.
|
||||
assert_eq!(
|
||||
ProxyManager::proxy_state_from_string(" generating "),
|
||||
ProxyState::Generating
|
||||
);
|
||||
assert_eq!(
|
||||
ProxyManager::proxy_state_from_string("bogus"),
|
||||
ProxyState::Missing
|
||||
);
|
||||
assert_eq!(ProxyManager::proxy_state_from_string(""), ProxyState::Missing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proxy_filename_audio_tag_detection() {
|
||||
assert!(ProxyManager::proxy_filename_has_audio("/c/shot.a1.mp4"));
|
||||
assert!(ProxyManager::proxy_filename_has_audio("shot.a1.mov"));
|
||||
assert!(!ProxyManager::proxy_filename_has_audio("/c/shot.a2.mp4"));
|
||||
assert!(!ProxyManager::proxy_filename_has_audio("/c/shot.mp4"));
|
||||
assert!(!ProxyManager::proxy_filename_has_audio(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_proxy_state_reads_the_filesystem() {
|
||||
assert_eq!(ProxyManager::get_proxy_state(""), ProxyState::Missing);
|
||||
|
||||
let dir = std::env::temp_dir().join(format!("oakcodec-proxy-coverage-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
|
||||
// A finished proxy exists on disk.
|
||||
let proxy = dir.join("clip.mp4");
|
||||
std::fs::write(&proxy, b"proxy").unwrap();
|
||||
assert_eq!(
|
||||
ProxyManager::get_proxy_state(&proxy.to_string_lossy()),
|
||||
ProxyState::Ready
|
||||
);
|
||||
|
||||
// Only the working file exists: still generating.
|
||||
let pending = dir.join("pending.mp4");
|
||||
let working =
|
||||
ProxyManager::get_working_filename(&pending.to_string_lossy()).unwrap();
|
||||
std::fs::write(&working, b"partial").unwrap();
|
||||
assert_eq!(
|
||||
ProxyManager::get_proxy_state(&pending.to_string_lossy()),
|
||||
ProxyState::Generating
|
||||
);
|
||||
|
||||
// Neither file exists.
|
||||
assert_eq!(
|
||||
ProxyManager::get_proxy_state(&dir.join("none.mp4").to_string_lossy()),
|
||||
ProxyState::Missing
|
||||
);
|
||||
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user