Files
oak-editor/crates/oaknode/tests/serializer_test.rs
T
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- 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)
2026-08-11 23:04:48 +08:00

144 lines
4.9 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/>.
//! Serializer tests. The C++ writer's golden files do not exist in this
//! tree (`tests/golden/` is empty), so the byte-exact comparison is
//! ignored with a doc reason; everything else (round-trip idempotence,
//! version rejection, corrupt input) is live.
/// Save format parity: a fixture project saves byte-identical XML to
/// the C++ 230220 writer output (attribute order included).
///
/// Ignored: the C++ golden fixtures are not committed in this tree
/// (src/node/tests/ has no .xml/.ove files) and the value text codecs
/// use Rust formatting, so byte-exact parity cannot be pinned here. The
/// C++ gtest suite (`src/node/tests/serializer_test.cpp`) pins the
/// writer; this crate's serializer_test covers structure + round-trip.
#[test]
#[ignore = "C++ golden fixtures unavailable in this tree"]
fn save_matches_cpp_byte_exact() {
todo!()
}
/// Round-trip: load(save(p)) yields a project whose re-saved XML is
/// identical (idempotence).
///
/// Needs the `test-stubs` feature: save/load route XML through the
/// oakcommon bridge, whose symbols only resolve in the test binary when
/// the in-crate stubs are compiled in.
#[cfg(feature = "test-stubs")]
#[test]
fn roundtrip_idempotent() {
use oaknode::input::Input;
use oaknode::node::NodeCore;
use oaknode::project::Project;
use oaknode::value::{NodeValue, ValueType};
let project = Project::new();
{
let mut p = project.lock().unwrap();
p.initialize().unwrap();
// A math node with a set value + a keyframe.
let (core, behavior) = (oaknode::factory::Factory::global()
.find("org.olivevideoeditor.Olive.math")
.unwrap()
.create)();
let id = p.graph.add_node(core, behavior);
p.graph.get_mut(id).unwrap().core.set_standard_value(
"param_a_in",
-1,
NodeValue::Float(2.5),
);
p.graph
.get_mut(id)
.unwrap()
.core
.keyframe_track_mut("param_a_in", -1)
.set_key(oaknode::keyframe::Keyframe {
time: oakcore_rs::Rational::new(0, 1),
value: NodeValue::Float(1.0),
interpolation: oaknode::keyframe::Interpolation::Linear,
bezier_in: (0.0, 0.0),
bezier_out: (0.0, 0.0),
});
// A second node connected to the first.
let (core2, behavior2) = (oaknode::factory::Factory::global()
.find("org.olivevideoeditor.Olive.math")
.unwrap()
.create)();
let id2 = p.graph.add_node(core2, behavior2);
p.graph.connect(id, id2, "param_a_in", -1).unwrap();
}
let xml = {
let p = project.lock().unwrap();
oaknode::serializer::save(&p).unwrap()
};
let loaded = oaknode::serializer::load(&xml).unwrap();
let xml2 = {
let p = loaded.lock().unwrap();
oaknode::serializer::save(&p).unwrap()
};
assert_eq!(xml, xml2, "re-save is idempotent");
// Structural equivalence: node count + edges.
{
let a = project.lock().unwrap();
let b = loaded.lock().unwrap();
assert_eq!(a.graph.node_count(), b.graph.node_count());
assert_eq!(
a.graph.output_connections_all().len(),
b.graph.output_connections_all().len()
);
}
}
/// Version ladder: historical `<olive ...>` roots with known versions
/// load (the body upgrades to the current model); unknown roots are
/// rejected.
///
/// Needs the `test-stubs` feature (same XML-bridge rationale as
/// [`roundtrip_idempotent`]).
#[cfg(feature = "test-stubs")]
#[test]
fn historical_versions_upgrade() {
// A current-format document round-trips.
let xml =
"<project version=\"1\"><uuid>{test}</uuid><nodes></nodes><settings></settings></project>";
let project = oaknode::serializer::load(xml).unwrap();
assert_eq!(project.lock().unwrap().uuid, "{test}");
// An unknown root is rejected.
assert!(oaknode::serializer::load("<olive-unknown/>").is_err());
assert!(oaknode::serializer::load("").is_err());
}
/// Corrupt XML and newer-than-build versions are rejected with the
/// documented error codes, never a panic.
#[test]
fn corrupt_and_future_files_rejected() {
// A newer-than-build version is rejected.
let future = "<olive version=\"999999\"></olive>";
assert!(oaknode::serializer::load(future).is_err());
// Malformed XML (unbalanced) is rejected without a panic.
let corrupt = "<project><nodes><node></project>";
assert!(oaknode::serializer::load(corrupt).is_err());
// Garbage text.
assert!(oaknode::serializer::load("not xml at all").is_err());
}