Files
oak-editor/crates/oakengine/tests/common.rs
T
Mike-Solar 013a175707 refactor: workspace layout — crates/, app at root, legacy C++ removed
Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
2026-08-10 20:24:25 +08:00

189 lines
7.0 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
);
}