- oakengine_sequence_move_clip implemented for real (oaktimeline TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple trim bugs the stub was hiding); same-track via the frozen C ABI, cross-track supported by the module command - oaknode clip blocks now declare a tex_in texture input and set effect_input to it, so timeline clips can host effect chains; facade test covers effect insert/remove on a real clip - oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a Homebrew upgrade left the system ffmpeg .pc pointing at a deleted dav1d Cellar path, breaking links); reads a git-ignored workspace .env for IDEs that cannot inject env vars (RustRover); links the C++ stdlib for C++ codec libs (svt-av1) - oakengine re-exports oaknode so tests share one crate instance; it_node uses the direct instance's value type where it calls the module FFI (the --workspace dev-dependency feature split builds oaknode twice)
228 lines
7.1 KiB
Rust
228 lines
7.1 KiB
Rust
// Oak Video Editor - Non-Linear Video Editor
|
|
// Copyright (C) 2026 Oak Team
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! Smoke tests for the common family (`engine/include/oakengine/config.h`
|
|
//! and `videoparams.h`). The oakcommon config store is a process-wide
|
|
//! singleton, so config tests are serialized inside single test
|
|
//! functions.
|
|
|
|
#[path = "common/mod.rs"]
|
|
mod common;
|
|
|
|
use std::ffi::{c_char, c_int};
|
|
|
|
use oakengine::common::{
|
|
oakengine_config_get_int, oakengine_config_get_string, oakengine_config_load,
|
|
oakengine_config_save, oakengine_config_set_error_handler, oakengine_config_set_int,
|
|
oakengine_config_set_string, oakengine_video_params_bytes_per_pixel,
|
|
oakengine_video_params_effective_size, oakengine_video_params_equal,
|
|
oakengine_video_params_format_is_float, oakengine_video_params_internal_channel_count,
|
|
oakengine_video_params_is_valid, oakengine_video_params_make,
|
|
oakengine_video_params_standard_pixel_aspect_at,
|
|
oakengine_video_params_standard_pixel_aspect_count,
|
|
oakengine_video_params_supported_divider_at, oakengine_video_params_supported_divider_count,
|
|
oakengine_video_params_supported_frame_rate_at,
|
|
oakengine_video_params_supported_frame_rate_count, OakVideoParamsPod,
|
|
};
|
|
|
|
/// Config: load/save, string and int round-trips, missing-key behavior.
|
|
#[test]
|
|
fn config_round_trip() {
|
|
assert_eq!(unsafe { oakengine_config_load() }, 0);
|
|
|
|
// Missing key reads as 0 / empty.
|
|
let mut buf = [0 as c_char; 64];
|
|
assert_eq!(
|
|
unsafe { oakengine_config_get_string(c"no/such/key".as_ptr(), buf.as_mut_ptr(), 64) },
|
|
0
|
|
);
|
|
assert_eq!(
|
|
unsafe { oakengine_config_get_int(c"no/such/key".as_ptr(), 7) },
|
|
7
|
|
);
|
|
|
|
// String round-trip.
|
|
assert_eq!(
|
|
unsafe { oakengine_config_set_string(c"facade/test".as_ptr(), c"hello".as_ptr()) },
|
|
0
|
|
);
|
|
let len = unsafe { oakengine_config_get_string(c"facade/test".as_ptr(), buf.as_mut_ptr(), 64) };
|
|
assert_eq!(len, 5);
|
|
assert_eq!(
|
|
unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) }
|
|
.to_str()
|
|
.unwrap(),
|
|
"hello"
|
|
);
|
|
|
|
// A too-small buffer is not written (the two-stage convention is:
|
|
// query the required size, allocate, copy) — the module reports the
|
|
// full length and leaves the buffer untouched.
|
|
let mut small = [0 as c_char; 3];
|
|
let len =
|
|
unsafe { oakengine_config_get_string(c"facade/test".as_ptr(), small.as_mut_ptr(), 3) };
|
|
assert_eq!(len, 5); // reported full length
|
|
assert_eq!(
|
|
unsafe { std::ffi::CStr::from_ptr(small.as_ptr()) }
|
|
.to_str()
|
|
.unwrap(),
|
|
""
|
|
);
|
|
|
|
// Int round-trip.
|
|
assert_eq!(
|
|
unsafe { oakengine_config_set_int(c"facade/n".as_ptr(), 1234) },
|
|
0
|
|
);
|
|
assert_eq!(
|
|
unsafe { oakengine_config_get_int(c"facade/n".as_ptr(), 0) },
|
|
1234
|
|
);
|
|
|
|
assert_eq!(unsafe { oakengine_config_save() }, 0);
|
|
}
|
|
|
|
/// Config error handler: registered, then invoked via report_error.
|
|
#[test]
|
|
fn config_error_handler() {
|
|
static CALLED: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
|
|
unsafe extern "C" fn handler(
|
|
_title: *const c_char,
|
|
_message: *const c_char,
|
|
_userdata: *mut std::ffi::c_void,
|
|
) {
|
|
CALLED.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
}
|
|
assert_eq!(
|
|
unsafe { oakengine_config_set_error_handler(Some(handler), std::ptr::null_mut()) },
|
|
0
|
|
);
|
|
// Report an error through the handler.
|
|
assert_eq!(
|
|
unsafe { oakengine::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) },
|
|
0
|
|
);
|
|
assert_eq!(CALLED.load(std::sync::atomic::Ordering::SeqCst), 1);
|
|
// NULL handler clears; reporting then does not invoke.
|
|
assert_eq!(
|
|
unsafe { oakengine_config_set_error_handler(None, std::ptr::null_mut()) },
|
|
0
|
|
);
|
|
unsafe { oakengine::common::oakengine_config_report_error(c"t".as_ptr(), c"m".as_ptr()) };
|
|
assert_eq!(CALLED.load(std::sync::atomic::Ordering::SeqCst), 1);
|
|
}
|
|
|
|
/// Videoparams static tables.
|
|
#[test]
|
|
fn videoparams_static_tables() {
|
|
// 12 standard frame rates; the 23.976 entry is 24000/1001.
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_supported_frame_rate_count() },
|
|
12
|
|
);
|
|
let (mut num, mut den) = (0, 0);
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_supported_frame_rate_at(2, &mut num, &mut den) },
|
|
0
|
|
);
|
|
assert_eq!((num, den), (24000, 1001));
|
|
// Out of range → E_INVALID.
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_supported_frame_rate_at(99, &mut num, &mut den) },
|
|
-1
|
|
);
|
|
|
|
// 6 standard pixel aspects; index 4 is PAL widescreen 64/45.
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_standard_pixel_aspect_count() },
|
|
6
|
|
);
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_standard_pixel_aspect_at(4, &mut num, &mut den) },
|
|
0
|
|
);
|
|
assert_eq!((num, den), (64, 45));
|
|
|
|
// Dividers 1..=8; out of range → -1.
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_supported_divider_count() },
|
|
8
|
|
);
|
|
assert_eq!(unsafe { oakengine_video_params_supported_divider_at(5) }, 8);
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_supported_divider_at(99) },
|
|
-1
|
|
);
|
|
|
|
// Format helpers (PixelFormat codes: F16 = 3, F32 = 4).
|
|
assert_eq!(unsafe { oakengine_video_params_format_is_float(4) }, 1); // F32
|
|
assert_eq!(unsafe { oakengine_video_params_format_is_float(3) }, 1); // F16
|
|
assert_eq!(unsafe { oakengine_video_params_format_is_float(0) }, 0); // U8
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_internal_channel_count() },
|
|
4
|
|
);
|
|
assert!(unsafe { oakengine_video_params_bytes_per_pixel(1, 4) } > 0);
|
|
}
|
|
|
|
/// Videoparams POD: make/equal/valid + effective size.
|
|
#[test]
|
|
fn videoparams_pod() {
|
|
let mut a: OakVideoParamsPod = unsafe { std::mem::zeroed() };
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_make(&mut a, 1920, 1080, 1001, 30000, 16, 1, 1, 0, 1, 1,) },
|
|
0
|
|
);
|
|
assert_eq!(a.width, 1920);
|
|
assert_eq!(a.height, 1080);
|
|
assert_eq!(a.time_base_num, 1001);
|
|
|
|
// A valid POD is valid.
|
|
assert_eq!(unsafe { oakengine_video_params_is_valid(&a) }, 1);
|
|
// Zero dimensions are not.
|
|
let mut bad = a;
|
|
bad.width = 0;
|
|
assert_eq!(unsafe { oakengine_video_params_is_valid(&bad) }, 0);
|
|
// NULL is invalid.
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_is_valid(std::ptr::null()) },
|
|
0
|
|
);
|
|
|
|
// Equality: identical PODs equal; differing field not.
|
|
let mut b = a;
|
|
assert_eq!(unsafe { oakengine_video_params_equal(&a, &b) }, 1);
|
|
b.divider = 2;
|
|
assert_eq!(unsafe { oakengine_video_params_equal(&a, &b) }, 0);
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_equal(std::ptr::null(), &a) },
|
|
0
|
|
);
|
|
|
|
// Effective size halves at divider 2.
|
|
let (mut w, mut h) = (0, 0);
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_effective_size(1920, 1080, 2, &mut w, &mut h) },
|
|
0
|
|
);
|
|
assert_eq!((w, h), (960, 540));
|
|
// Invalid divider.
|
|
assert_eq!(
|
|
unsafe { oakengine_video_params_effective_size(1920, 1080, 0, &mut w, &mut h) },
|
|
-1
|
|
);
|
|
}
|