tests: replace placebo assertions with real behavior checks
This commit is contained in:
@@ -688,8 +688,8 @@ fn node_behavior_defaults() {
|
||||
assert!(b.value_hint_for_input("in").is_none());
|
||||
assert_eq!(b.connected_render_output(&core, "in", -1), None);
|
||||
let tr = TimeRange::new(Rational::new(0, 1), Rational::new(5, 1));
|
||||
assert_eq!(b.input_time_adjustment("in", -1, tr, true), tr);
|
||||
assert_eq!(b.output_time_adjustment("in", -1, tr, false), tr);
|
||||
assert_eq!(b.input_time_adjustment(&core, "in", -1, tr, true), tr);
|
||||
assert_eq!(b.output_time_adjustment(&core, "in", -1, tr, false), tr);
|
||||
|
||||
// value / process_samples / generate_frame no-ops.
|
||||
let mut table = NodeValueTable::default();
|
||||
|
||||
@@ -248,10 +248,9 @@ fn add_entry_reclaims_the_free_slot() {
|
||||
let fresh = g.add_node(core, Box::new(TestNode { id: "fresh" }));
|
||||
assert_ne!(fresh, victim, "the fresh node takes a different slot");
|
||||
assert!(g.is_valid(victim), "the restored node survives add_node");
|
||||
assert_eq!(
|
||||
g.get(victim).map(|e| e.core.label.as_str()),
|
||||
g.get(victim).map(|e| e.core.label.as_str()),
|
||||
);
|
||||
// The fresh node must not clobber the restored node's slot: the
|
||||
// victim keeps its original (default empty) label.
|
||||
assert_eq!(g.get(victim).map(|e| e.core.label.as_str()), Some(""));
|
||||
assert_eq!(g.node_count(), count_before + 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +319,7 @@ fn connected_input_uses_adjusted_time() {
|
||||
}
|
||||
fn input_time_adjustment(
|
||||
&self,
|
||||
_core: &NodeCore,
|
||||
input: &str,
|
||||
_element: i32,
|
||||
time: TimeRange,
|
||||
@@ -353,3 +354,47 @@ fn connected_input_uses_adjusted_time() {
|
||||
"the upstream was evaluated at the doubled time"
|
||||
);
|
||||
}
|
||||
|
||||
/// The real TimeOffsetNode (not a test double) remaps its upstream's
|
||||
/// evaluation time through the trait: the keyframable offset lives on
|
||||
/// the node core, which the trait method now receives. Evaluating the
|
||||
/// offset at t=10 with `time_in` = 5 pulls the upstream at t=15 (C++
|
||||
/// `get_remapped_time`: `input + time_in`).
|
||||
#[test]
|
||||
fn time_offset_node_shifts_upstream_evaluation_time() {
|
||||
struct TimeEcho;
|
||||
impl NodeBehavior for TimeEcho {
|
||||
fn name(&self) -> &str {
|
||||
"TimeEcho"
|
||||
}
|
||||
fn type_id(&self) -> &str {
|
||||
"test.timeecho"
|
||||
}
|
||||
fn duplicate(&self, _c: &NodeCore) -> Option<Box<dyn NodeBehavior>> {
|
||||
Some(Box::new(TimeEcho))
|
||||
}
|
||||
fn value(&self, _c: &NodeCore, _i: &NodeValueRow, t: Rational, table: &mut NodeValueTable) {
|
||||
table.push(ValueType::Rational, NodeValue::Rational(t), None);
|
||||
}
|
||||
}
|
||||
|
||||
let mut g = Graph::new();
|
||||
let src = node_with_input(&mut g, Box::new(TimeEcho));
|
||||
let (mut core, behavior) = oak_node::factory::Factory::global()
|
||||
.create_any("org.olivevideoeditor.Olive.timeoffset")
|
||||
.expect("timeoffset registered");
|
||||
core.set_standard_value("time_in", -1, NodeValue::Rational(Rational::new(5, 1)));
|
||||
let offset = g.add_node(core, behavior);
|
||||
g.connect(src, offset, "input_in", -1).unwrap();
|
||||
|
||||
let mut t = Traverser::new();
|
||||
let mut hooks = Noop;
|
||||
let table = t
|
||||
.evaluate(&g, &EvalRequest::new(offset, Rational::new(10, 1)), &mut hooks)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
table.get(ValueType::Rational),
|
||||
Some(&NodeValue::Rational(Rational::new(15, 1))),
|
||||
"the upstream must be evaluated at the offset time (10 + 5)"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
//! 快照在 0 期由**现行 C++ 实现**抓取入库(tests/ofx/snapshots/ 与
|
||||
//! tests/ofx/frames/);本文件的测试断言 Rust 实现与之逐字段/逐像素
|
||||
//! 一致。**0 期基建尚未落地**(tests/ofx/ 目录为空)——依赖快照/
|
||||
//! 帧/GL 的用例一律 `#[ignore]`,落地后摘掉并补全断言(见
|
||||
//! 帧库的用例一律 `#[ignore]`,落地后摘掉并补全断言(见
|
||||
//! [`descriptor_snapshots_match`] 的说明)。无真实 bundle 的环境
|
||||
//! (CI)整文件 skip。
|
||||
//!
|
||||
//! [`cimg_full_describe_smoke`] 不依赖快照:真实 CImg bundle 存在时
|
||||
//! 全量 describe + 协商冒烟(健壮性,不比对),是本次唯一实际执行
|
||||
//! 的用例。
|
||||
//! 本文件当前没有实际执行的用例:渲染/GL/色彩链路本身已实现并经
|
||||
//! `gl_render_test.rs`/`colour_test.rs` 覆盖,这里缺的只是 golden
|
||||
//! 参照物。
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -82,11 +82,11 @@ fn cimg_full_describe_smoke() {
|
||||
///
|
||||
/// # ignore 原因
|
||||
///
|
||||
/// `tests/ofx/frames/`(C++ 实现抓取的 EXR + SHA256 库)尚未生成;
|
||||
/// 且 CPU 渲染链路依赖 renderer 桥(cargo 内无 liboakrender 真实现,
|
||||
/// 只有测试桩)。快照与真桥都落地后实现并摘除。
|
||||
/// `tests/ofx/frames/`(C++ 实现抓取的 EXR + SHA256 参照库)尚未生成
|
||||
/// ——渲染链路本身已是真实现(render_driver + oak-render eval),
|
||||
/// 缺的只是 golden 参照物。参照库落地后实现并摘除。
|
||||
#[test]
|
||||
#[ignore = "M11 0 期渲染 golden(tests/ofx/frames/)+ 真 liboakrender 缺失;落地后实现并摘除"]
|
||||
#[ignore = "M11 0 期渲染 golden 参照库(tests/ofx/frames/)尚未生成;落地后实现并摘除"]
|
||||
fn render_golden_cpu_bitexact() {
|
||||
todo!("0 期渲染 golden 落地后实现")
|
||||
}
|
||||
@@ -95,9 +95,11 @@ fn render_golden_cpu_bitexact() {
|
||||
///
|
||||
/// # ignore 原因
|
||||
///
|
||||
/// GL 路径属 M11 第 2 期(OpenGLRender suite 未实现),帧库亦未生成。
|
||||
/// GL 渲染路径已实现(suites/gl_render.rs,macOS CGL + CPU 回退),
|
||||
/// 缺的只是 golden 帧库(tests/ofx/frames/ 未生成)。帧库落地后实现
|
||||
/// 并摘除。
|
||||
#[test]
|
||||
#[ignore = "GL 路径属 M11 第 2 期,且帧库未生成;2 期后实现并摘除"]
|
||||
#[ignore = "golden 帧库(tests/ofx/frames/)未生成;落地后实现并摘除"]
|
||||
fn render_golden_gl_tolerant() {
|
||||
todo!("M11 第 2 期 GL 路径落地后实现")
|
||||
}
|
||||
@@ -108,10 +110,10 @@ fn render_golden_gl_tolerant() {
|
||||
///
|
||||
/// # ignore 原因
|
||||
///
|
||||
/// 同 [`render_golden_cpu_bitexact`]:依赖帧库与 OCIO 链路
|
||||
/// (ofxColour 属 M11 第 2 期)。
|
||||
/// 同 [`render_golden_cpu_bitexact`]:只缺 golden 参照帧库——F32
|
||||
/// 管线和 OCIO/ofxColour 链路均已实现(colour_test.rs 覆盖)。
|
||||
#[test]
|
||||
#[ignore = "M11 0 期帧库 + 第 2 期 ofxColour/OCIO 链路缺失;落地后实现并摘除"]
|
||||
#[ignore = "golden 帧库(tests/ofx/frames/)未生成;落地后实现并摘除"]
|
||||
fn pipeline_is_f32_acescg() {
|
||||
todo!("帧库与 OCIO 链路落地后实现")
|
||||
}
|
||||
|
||||
@@ -204,10 +204,15 @@ fn sequence_render_brackets() {
|
||||
max: 200.0,
|
||||
};
|
||||
assert!(inst.value.begin_sequence_render(range).is_ok());
|
||||
// begin 后实例记录该序列范围(timeline 上下文的 getTimeBounds
|
||||
// 读它)。
|
||||
let stored = *inst.value.sequence_range.lock().unwrap();
|
||||
let stored = stored.expect("begin_sequence_render 必须登记范围");
|
||||
assert_eq!((stored.min, stored.max), (10.0, 200.0));
|
||||
assert!(inst.value.end_sequence_render(range).is_ok());
|
||||
// end 后清除。
|
||||
assert!(inst.value.sequence_range.lock().unwrap().is_none());
|
||||
|
||||
// begin → timeline 上下文带范围(经 render 设置;单测直达
|
||||
// RenderCtx 的接线见 suites::timeline 测试)。
|
||||
Host::global().shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -249,9 +249,8 @@ fn memory_suite_ledger() {
|
||||
/// image effect suite:describe 期 clipDefine/clipGetPropertySet 与
|
||||
/// 属性读写;实例期 clipGetHandle。
|
||||
///
|
||||
/// clipGetImage/clipReleaseImage 配对依赖
|
||||
/// [`oak_plugin::clip::ClipInstance::fetch_image`](bridge::render 帧
|
||||
/// 访问 C ABI 未冻结)——随最小测试插件落地补全(`// TODO(plugin)`)。
|
||||
/// clipGetImage/clipReleaseImage 配对经
|
||||
/// [`oak_plugin::clip::ClipInstance::fetch_image`] 与存活表记账验证。
|
||||
#[test]
|
||||
fn image_effect_clip_image_pairing() {
|
||||
let mut desc = EffectDescriptor::new();
|
||||
@@ -331,23 +330,57 @@ fn image_effect_clip_image_pairing() {
|
||||
);
|
||||
}
|
||||
|
||||
// clipGetImage/clipReleaseImage 配对:依赖 clip fetch_image
|
||||
// (bridge::render 未冻结)——插件落地后补全。
|
||||
if common::test_plugin_dir().is_none() {
|
||||
common::skip("clipGetImage 配对随最小测试插件落地(M11 §2.4)");
|
||||
return;
|
||||
// clipGetImage/clipReleaseImage 配对:输入 clip 排入一帧后 get
|
||||
// 取到登记在存活表的图像;release 摘除;二次 release → BadHandle
|
||||
// (HS:2053-2068 的 releaseReference 配对)。
|
||||
let mut frame = oak_render::eval::generate_frame(
|
||||
oak_core::Rational::new(0, 1),
|
||||
(2, 2),
|
||||
oak_core::PixelFormat::F32,
|
||||
)
|
||||
.unwrap();
|
||||
for (i, v) in [0.1f32, 0.2, 0.3, 1.0].iter().enumerate() {
|
||||
frame.data[i * 4..i * 4 + 4].copy_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
inst.clips[0].set_input_texture(
|
||||
Some(oak_core::texture::Texture::wrap_frame(frame)),
|
||||
0.0,
|
||||
);
|
||||
// 注意另取句柄:上面未找到路径的 clip_get_handle 把 clip_h 置空了。
|
||||
let mut img_clip: *mut c_void = std::ptr::null_mut();
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
(s.clip_get_handle)(ih, name.as_ptr(), &mut img_clip, std::ptr::null_mut()),
|
||||
OK
|
||||
);
|
||||
}
|
||||
let mut img: *mut c_void = std::ptr::null_mut();
|
||||
unsafe {
|
||||
assert_eq!(
|
||||
(s.clip_get_image)(img_clip, 0.0, std::ptr::null(), &mut img),
|
||||
OK,
|
||||
"clipGetImage 抓取排入的输入帧"
|
||||
);
|
||||
}
|
||||
assert!(!img.is_null());
|
||||
assert_eq!(tag::kind(img), tag::IMAGE);
|
||||
unsafe {
|
||||
assert_eq!((s.clip_release_image)(img), OK);
|
||||
assert_eq!(
|
||||
(s.clip_release_image)(img),
|
||||
BAD_HANDLE,
|
||||
"二次 release 必须 BadHandle(存活表已摘除)"
|
||||
);
|
||||
}
|
||||
// TODO(plugin):插件驱动 clipGetImage → clipReleaseImage 配对 +
|
||||
// 不配对时的销毁记账断言。
|
||||
}
|
||||
|
||||
/// param suite:describe 期 define→getHandle→getValue(默认值);
|
||||
/// 实例期 int/double/bool/choice/string/RGBA/2D/3D 的
|
||||
/// setValue/getValue round-trip;AtTime == 当前值。
|
||||
///
|
||||
/// 声明原含"paramSetValue 触发 instanceChanged":通知走
|
||||
/// [`oak_plugin::param::notify_instance_changed`](bridge 期实现)——
|
||||
/// 随插件+桥落地补全(`// TODO(bridge)`)。
|
||||
/// 声明含"paramSetValue 触发 instanceChanged":通知走
|
||||
/// [`oak_plugin::param::notify_instance_changed`],插件自改经 undo
|
||||
/// 命令写回绑定节点的输入。
|
||||
#[test]
|
||||
fn param_suite_roundtrip_and_change_action() {
|
||||
// describe 期。
|
||||
@@ -454,8 +487,9 @@ fn param_suite_roundtrip_and_change_action() {
|
||||
assert_eq!(iv2, 9);
|
||||
}
|
||||
|
||||
// TODO(bridge):paramSetValue → instanceChanged 断言随
|
||||
// notify_instance_changed(bridge 期)落地。
|
||||
// paramSetValue → instanceChanged 回写(经 PARAM_OWNER 登记表,
|
||||
// pub(crate))由 crate 内测试
|
||||
// `suites::param::tests::set_value_writes_back_to_bound_node` 覆盖。
|
||||
}
|
||||
|
||||
/// paramGetValueAtTime/paramSetValueAtTime 与关键帧族
|
||||
|
||||
@@ -129,10 +129,12 @@ fn cancel_video_tasks_semantics() {
|
||||
d.shutdown();
|
||||
}
|
||||
|
||||
/// Change-record marshalling: every ChangeRecord kind survives the
|
||||
/// C struct round-trip (layout pinned by the C ABI header).
|
||||
/// Change-record layout: the C ABI header pins the struct to a 4-byte
|
||||
/// kind + 48-byte opaque payload (52 bytes total). The kind constants
|
||||
/// stay distinct so the wire tags never collide.
|
||||
#[test]
|
||||
fn change_record_marshalling() {
|
||||
fn change_record_layout_is_abi_pinned() {
|
||||
assert_eq!(std::mem::size_of::<oak_render::copier::ChangeRecord>(), 52);
|
||||
let kinds = [
|
||||
oak_render::copier::change_kind::NODE_ADD,
|
||||
oak_render::copier::change_kind::NODE_REMOVE,
|
||||
@@ -143,17 +145,10 @@ fn change_record_marshalling() {
|
||||
oak_render::copier::change_kind::PROJECT_SETTING_CHANGE,
|
||||
oak_render::copier::change_kind::FOOTAGE_PROXY,
|
||||
];
|
||||
for kind in kinds {
|
||||
let record = oak_render::copier::ChangeRecord {
|
||||
kind,
|
||||
payload: [0xAA; 48],
|
||||
};
|
||||
assert_eq!(record.kind, kind);
|
||||
assert_eq!(record.payload.len(), 48);
|
||||
assert_eq!(
|
||||
std::mem::size_of::<oak_render::copier::ChangeRecord>(),
|
||||
52
|
||||
);
|
||||
for (i, a) in kinds.iter().enumerate() {
|
||||
for b in &kinds[i + 1..] {
|
||||
assert_ne!(a, b, "change-kind tags must be distinct");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,9 +243,12 @@ fn snapshot_store_refcount() {
|
||||
);
|
||||
}
|
||||
|
||||
/// TimeRange sanity (used above).
|
||||
/// The arena hands back the time a submitted ticket carries (the
|
||||
/// metadata the playback clock queries while the job runs).
|
||||
#[test]
|
||||
fn range_sanity() {
|
||||
let r = TimeRange::new(Rational::new(0, 1), Rational::new(10, 1));
|
||||
assert_eq!(r.length(), Rational::new(10, 1));
|
||||
fn arena_time_roundtrips_submitted_ticket() {
|
||||
let (arena, d) = test_arena(ok_producer());
|
||||
let id = arena.submit_video(params(Rational::new(7, 1)), Box::new(|_| {}));
|
||||
assert_eq!(arena.time(id), Some(Rational::new(7, 1)));
|
||||
d.shutdown();
|
||||
}
|
||||
|
||||
@@ -87,13 +87,27 @@ fn marker_set_time_point_preserves_name_color() {
|
||||
assert_eq!(m.time().out(), Rational::new(40, 1));
|
||||
}
|
||||
|
||||
/// `has_sibling_at_time` is a De-Qt simplification that always reports
|
||||
/// `false`; sibling queries go through the list instead.
|
||||
/// Sibling detection goes through the owning list: two markers sharing
|
||||
/// an in point are both found by `get_marker_at_time` (the per-marker
|
||||
/// `has_sibling_at_time` is a documented De-Qt simplification with no
|
||||
/// parent pointer).
|
||||
#[test]
|
||||
fn marker_sibling_detection() {
|
||||
let m = TimelineMarker::new();
|
||||
assert!(!m.has_sibling_at_time(Rational::new(0, 1)));
|
||||
assert!(!m.has_sibling_at_time(Rational::new(42, 1)));
|
||||
let mut list = TimelineMarkerList::new();
|
||||
let mut a = TimelineMarker::new();
|
||||
a.set_time_point(Rational::new(42, 1));
|
||||
a.set_name("a");
|
||||
let mut b = TimelineMarker::new();
|
||||
b.set_time_point(Rational::new(42, 1));
|
||||
b.set_name("b");
|
||||
let mut other = TimelineMarker::new();
|
||||
other.set_time_point(Rational::new(7, 1));
|
||||
list.add_marker(a);
|
||||
list.add_marker(b);
|
||||
list.add_marker(other);
|
||||
assert_eq!(list.get_marker_at_time(Rational::new(42, 1)).map(|m| m.name()), Some("a"));
|
||||
assert!(list.get_marker_at_time(Rational::new(7, 1)).is_some());
|
||||
assert!(list.get_marker_at_time(Rational::new(8, 1)).is_none());
|
||||
}
|
||||
|
||||
/// The list starts empty and grows with each `add_marker`, preserving
|
||||
@@ -489,24 +503,3 @@ fn marker_commands_box_to_undo_command() {
|
||||
assert_eq!(list_of(&list_h).size(), 1);
|
||||
}
|
||||
|
||||
/// Loading a marker with a `color`/`in`/`out` attribute equal to the
|
||||
/// sentinel matches how `EditToInfo` consumers treat defaults.
|
||||
#[test]
|
||||
fn marker_defaults_map_to_edit_to_info() {
|
||||
// A default marker carries the null time and default color (0).
|
||||
let m = TimelineMarker::new();
|
||||
assert_eq!(m.color(), 0);
|
||||
assert_eq!(m.time().in_(), Rational::new(0, 1));
|
||||
|
||||
// `EditToInfo` defaults mirror those sentinels: null node references
|
||||
// (`None`, the single-lib replacement for the null `CHandle`) and the
|
||||
// null rational for `nearest_time`.
|
||||
let info = EditToInfo {
|
||||
track: None,
|
||||
nearest_time: m.time().in_(),
|
||||
nearest_block: None,
|
||||
};
|
||||
assert!(info.track.is_none());
|
||||
assert!(info.nearest_block.is_none());
|
||||
assert_eq!(info.nearest_time, Rational::new(0, 1));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user