Files
oak-editor/src/common/rust/tests/ffi_commandlineparser.rs
T
Mike-Solar 4b24aa9d67 feat(rust): first green Rust crates — oakcore-rs, oakundo, oakcommon, oaktimeline, oakaudio
- oakcore-rs: Rational/TimeRange/TimeRangeList/PixelFormat/SampleFormat
  mirroring oakcore C++ semantics (91.6% coverage)
- oakundo: UndoCommand/UndoStack with vtable commands (99.6%)
- oakcommon: config/XML/VideoParams/logging etc. (89.8%, 516 tests);
  XML via quick-xml, logging via the log facade
- oaktimeline: markers/workarea/edit commands (95.4%)
- oakaudio: processor/manager/sync/waveform/levelmeter (88.4%)
- unified -MMCCCC error codes across all C ABI headers (registry in
  include/common/error.h; pass-through rule)
- olive::Variant moved node -> common (cross-module value type)
- oakcommon_videoparams_set_is_3d added to the C ABI
- M11 (oakplugin Rust rewrite) plan + notes.md freeze line for the
  inter-module C ABI wiring
2026-08-09 04:24:28 +08:00

868 lines
30 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/>.
//! Integration tests for the C ABI surface of
//! `oakcommon::ffi::commandlineparser`, which implements
//! `include/common/commandlineparser.h` (oracle: `src/common/c_api/commandlineparser.cpp`).
//!
//! Every exported function gets a success-path test and a failure-path test.
//! The failure paths cover the empty (NULL-ctx) handle plus the documented
//! invalid-argument codes; the string getters are TRUNCATING (see the C++
//! `copy_setting`), so all three two-stage buffer cases are exercised.
//!
//! These tests are pure: they create their own parser/option/argument
//! handles and touch no config files, so they need no `OAK_CONFIG_DIR`
//! locking and run independently of the config tests.
use std::ffi::{c_char, CStr, CString};
use std::ptr::null_mut;
use oakcommon::error::{OAKCOMMON_E_INVALID, OAKCOMMON_OK};
use oakcommon::ffi::commandlineparser::*;
use oakcommon::handle::{CHandle, OAKCOMMON_ABI_VERSION};
/// A fresh parser handle (reference count 1, non-null ctx).
fn new_parser() -> CHandle {
let h = oakcommon_commandlineparser_init();
assert!(!h.ctx.is_null());
h
}
/// Shallow-copy a handle, mirroring C's pass-by-value semantics. The FFI
/// exports take handles by value (a `CHandle` is a plain
/// `{ctx, addref, release, abi_version}` record), but the Rust type
/// deliberately does not implement `Copy`, so each call consumes its
/// argument. `dup` copies the four scalar fields; the exports never
/// release the handles they receive, so the reference count is untouched
/// and no `addref` is needed.
fn dup(h: &CHandle) -> CHandle {
CHandle {
ctx: h.ctx,
addref: h.addref,
release: h.release,
abi_version: h.abi_version,
}
}
/// Build a NUL-terminated C string.
fn c_str(s: &str) -> CString {
CString::new(s).unwrap()
}
/// Build a `char *const *`-style array: returns the CStrings (kept alive
/// for the caller) plus their pointer array.
fn c_strings(values: &[&str]) -> (Vec<CString>, Vec<*const c_char>) {
let cs: Vec<CString> = values.iter().map(|s| c_str(s)).collect();
let ptrs = cs.iter().map(|s| s.as_ptr()).collect();
(cs, ptrs)
}
/// Register a single `takes_arg` option and return its borrowed handle.
fn register_option(parser: CHandle, name: &str) -> CHandle {
let mut out = CHandle::null();
let (_names, ptrs) = c_strings(&[name]);
let r = oakcommon_commandlineparser_add_option(
parser,
ptrs.as_ptr(),
1,
c_str("desc").as_ptr(),
1,
c_str("ARG").as_ptr(),
0,
&mut out,
);
assert_eq!(r, OAKCOMMON_OK);
assert!(!out.ctx.is_null());
out
}
/// Register a single positional argument and return its borrowed handle.
fn register_positional(parser: CHandle, name: &str) -> CHandle {
let mut out = CHandle::null();
let r = oakcommon_commandlineparser_add_positional_argument(
parser,
c_str(name).as_ptr(),
c_str("desc").as_ptr(),
1,
&mut out,
);
assert_eq!(r, OAKCOMMON_OK);
assert!(!out.ctx.is_null());
out
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineparser_init
// ---------------------------------------------------------------------------
/// init returns a live handle with the standard vtable and ABI stamp, and
/// free() releases it cleanly. There is no testable failure path: the only
/// failure mode is allocation failure / panic inside `guard_handle`, both
/// of which return a NULL-ctx handle and cannot be triggered deterministically.
#[test]
fn init_returns_live_handle() {
let mut h = oakcommon_commandlineparser_init();
assert!(!h.ctx.is_null());
assert!(h.addref.is_some());
assert!(h.release.is_some());
assert_eq!(h.abi_version, OAKCOMMON_ABI_VERSION);
oakcommon_commandlineparser_free(&mut h);
assert!(h.ctx.is_null());
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineparser_free
// ---------------------------------------------------------------------------
#[test]
fn parser_free_null_pointer_is_noop() {
oakcommon_commandlineparser_free(null_mut());
}
#[test]
fn parser_free_twice_is_safe_and_nulls_ctx() {
let mut h = oakcommon_commandlineparser_init();
assert!(!h.ctx.is_null());
oakcommon_commandlineparser_free(&mut h);
assert!(h.ctx.is_null());
// Second free on the same (now empty) handle must be a no-op.
oakcommon_commandlineparser_free(&mut h);
assert!(h.ctx.is_null());
}
#[test]
fn parser_free_null_handle_struct_is_noop() {
let mut h = CHandle::null();
oakcommon_commandlineparser_free(&mut h);
assert!(h.ctx.is_null());
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineparser_set_app_info
// ---------------------------------------------------------------------------
#[test]
fn set_app_info_success() {
let mut p = new_parser();
let r = oakcommon_commandlineparser_set_app_info(dup(&p), c_str("myapp").as_ptr(), c_str("1.0").as_ptr());
assert_eq!(r, OAKCOMMON_OK);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn set_app_info_empty_handle_is_invalid() {
let e = CHandle::null();
let r = oakcommon_commandlineparser_set_app_info(e, c_str("n").as_ptr(), c_str("v").as_ptr());
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn set_app_info_null_name_is_invalid() {
let mut p = new_parser();
let r = oakcommon_commandlineparser_set_app_info(dup(&p), null_mut(), c_str("v").as_ptr());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn set_app_info_null_version_is_invalid() {
let mut p = new_parser();
// CPP-PARITY: the C++ oracle tolerates a NULL version (treated as ""),
// but the Rust export rejects it with E_INVALID.
let r = oakcommon_commandlineparser_set_app_info(dup(&p), c_str("n").as_ptr(), null_mut());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineparser_add_option
// ---------------------------------------------------------------------------
#[test]
fn add_option_success() {
let mut p = new_parser();
let mut out = CHandle::null();
let (_names, ptrs) = c_strings(&["o", "output"]);
let r = oakcommon_commandlineparser_add_option(
dup(&p),
ptrs.as_ptr(),
2,
c_str("Output file.").as_ptr(),
1,
c_str("FILE").as_ptr(),
0,
&mut out,
);
assert_eq!(r, OAKCOMMON_OK);
assert!(!out.ctx.is_null());
assert!(out.addref.is_some());
assert!(out.release.is_some());
oakcommon_commandlineoption_free(&mut out);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_option_empty_parser_is_invalid() {
let e = CHandle::null();
let mut out = CHandle::null();
let (_names, ptrs) = c_strings(&["o"]);
let r = oakcommon_commandlineparser_add_option(
e, ptrs.as_ptr(), 1, c_str("d").as_ptr(), 0, c_str("A").as_ptr(), 0, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn add_option_null_names_is_invalid() {
let mut p = new_parser();
let mut out = CHandle::null();
let r = oakcommon_commandlineparser_add_option(
dup(&p), null_mut(), 1, c_str("d").as_ptr(), 0, c_str("A").as_ptr(), 0, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_option_non_positive_name_count_is_invalid() {
let mut p = new_parser();
let mut out = CHandle::null();
let (_names, ptrs) = c_strings(&["o"]);
let r0 = oakcommon_commandlineparser_add_option(
dup(&p), ptrs.as_ptr(), 0, c_str("d").as_ptr(), 0, c_str("A").as_ptr(), 0, &mut out);
assert_eq!(r0, OAKCOMMON_E_INVALID);
let rn = oakcommon_commandlineparser_add_option(
dup(&p), ptrs.as_ptr(), -1, c_str("d").as_ptr(), 0, c_str("A").as_ptr(), 0, &mut out);
assert_eq!(rn, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_option_null_description_is_invalid() {
// CPP-PARITY: the header documents description as "may be NULL" (C++ maps
// it to ""), but the Rust export rejects a NULL description with E_INVALID.
let mut p = new_parser();
let mut out = CHandle::null();
let (_names, ptrs) = c_strings(&["o"]);
let r = oakcommon_commandlineparser_add_option(
dup(&p), ptrs.as_ptr(), 1, null_mut(), 0, c_str("A").as_ptr(), 0, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_option_null_arg_placeholder_is_invalid() {
// CPP-PARITY: the header documents arg_placeholder as "may be NULL" (C++
// maps it to ""), but the Rust export rejects it with E_INVALID.
let mut p = new_parser();
let mut out = CHandle::null();
let (_names, ptrs) = c_strings(&["o"]);
let r = oakcommon_commandlineparser_add_option(
dup(&p), ptrs.as_ptr(), 1, c_str("d").as_ptr(), 0, null_mut(), 0, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_option_null_out_option_is_invalid() {
// CPP-PARITY: the header says out_option "may be NULL if unused"; the Rust
// export requires it.
let mut p = new_parser();
let (_names, ptrs) = c_strings(&["o"]);
let r = oakcommon_commandlineparser_add_option(
dup(&p), ptrs.as_ptr(), 1, c_str("d").as_ptr(), 0, c_str("A").as_ptr(), 0, null_mut());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_option_non_utf8_name_is_invalid() {
let mut p = new_parser();
let mut out = CHandle::null();
// 0xFF is not valid UTF-8; the export rejects it with E_INVALID before
// the option is registered.
let bad = CString::new(vec![b'x', 0xFF]).unwrap();
let ptrs = [bad.as_ptr()];
let r = oakcommon_commandlineparser_add_option(
dup(&p), ptrs.as_ptr(), 1, c_str("d").as_ptr(), 0, c_str("A").as_ptr(), 0, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
// The failed registration must not leave a partial option behind: a
// subsequent valid registration still works and lands at index 0.
let mut out2 = CHandle::null();
let (_names, ptrs2) = c_strings(&["ok"]);
let r2 = oakcommon_commandlineparser_add_option(
dup(&p), ptrs2.as_ptr(), 1, c_str("d").as_ptr(), 0, c_str("A").as_ptr(), 0, &mut out2);
assert_eq!(r2, OAKCOMMON_OK);
assert!(!out2.ctx.is_null());
oakcommon_commandlineoption_free(&mut out2);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineparser_add_positional_argument
// ---------------------------------------------------------------------------
#[test]
fn add_positional_argument_success() {
let mut p = new_parser();
let mut out = CHandle::null();
let r = oakcommon_commandlineparser_add_positional_argument(
dup(&p), c_str("input").as_ptr(), c_str("Input file").as_ptr(), 1, &mut out);
assert_eq!(r, OAKCOMMON_OK);
assert!(!out.ctx.is_null());
assert!(out.release.is_some());
oakcommon_commandlinepositionalargument_free(&mut out);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_positional_argument_empty_parser_is_invalid() {
let e = CHandle::null();
let mut out = CHandle::null();
let r = oakcommon_commandlineparser_add_positional_argument(
e, c_str("in").as_ptr(), c_str("d").as_ptr(), 1, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn add_positional_argument_null_name_is_invalid() {
let mut p = new_parser();
let mut out = CHandle::null();
let r = oakcommon_commandlineparser_add_positional_argument(
dup(&p), null_mut(), c_str("d").as_ptr(), 1, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_positional_argument_null_description_is_invalid() {
// CPP-PARITY: C++ maps a NULL description to ""; the Rust export rejects
// it with E_INVALID.
let mut p = new_parser();
let mut out = CHandle::null();
let r = oakcommon_commandlineparser_add_positional_argument(
dup(&p), c_str("in").as_ptr(), null_mut(), 1, &mut out);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn add_positional_argument_null_out_is_invalid() {
// CPP-PARITY: C++ allows a NULL out_argument; the Rust export requires it.
let mut p = new_parser();
let r = oakcommon_commandlineparser_add_positional_argument(
dup(&p), c_str("in").as_ptr(), c_str("d").as_ptr(), 1, null_mut());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineparser_process
// ---------------------------------------------------------------------------
#[test]
fn process_success() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let mut pos = register_positional(dup(&p), "input");
let (_argv, argv_ptrs) = c_strings(&["prog", "-o", "out.mov", "in.mp4"]);
let r = oakcommon_commandlineparser_process(dup(&p), argv_ptrs.as_ptr(), 4);
assert_eq!(r, OAKCOMMON_OK);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn process_argc_zero_success() {
let mut p = new_parser();
// Non-null argv with argc == 0: the loop never runs, returns OK.
let (_argv, argv_ptrs) = c_strings(&["prog"]);
let r = oakcommon_commandlineparser_process(dup(&p), argv_ptrs.as_ptr(), 0);
assert_eq!(r, OAKCOMMON_OK);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn process_empty_parser_is_invalid() {
let e = CHandle::null();
let (_argv, argv_ptrs) = c_strings(&["prog"]);
let r = oakcommon_commandlineparser_process(e, argv_ptrs.as_ptr(), 1);
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn process_null_argv_is_invalid() {
let mut p = new_parser();
let r = oakcommon_commandlineparser_process(dup(&p), null_mut(), 1);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn process_negative_argc_is_invalid() {
let mut p = new_parser();
let (_argv, argv_ptrs) = c_strings(&["prog"]);
let r = oakcommon_commandlineparser_process(dup(&p), argv_ptrs.as_ptr(), -1);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineparser_print_help
// ---------------------------------------------------------------------------
#[test]
fn print_help_success() {
let mut p = new_parser();
let r = oakcommon_commandlineparser_print_help(dup(&p), c_str("oak").as_ptr());
assert_eq!(r, OAKCOMMON_OK);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn print_help_empty_parser_is_invalid() {
let e = CHandle::null();
let r = oakcommon_commandlineparser_print_help(e, c_str("oak").as_ptr());
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn print_help_null_filename_is_invalid() {
let mut p = new_parser();
let r = oakcommon_commandlineparser_print_help(dup(&p), null_mut());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineoption_is_set / _free
// ---------------------------------------------------------------------------
#[test]
fn option_is_set_success() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let mut is_set = true;
let r = oakcommon_commandlineoption_is_set(dup(&opt), &mut is_set);
assert_eq!(r, OAKCOMMON_OK);
// CPP-PARITY: the Rust borrowed handle is a registration-time snapshot
// (make_borrowed bit-copies), so is_set is always false on the handle;
// the C++ oracle stores a pointer to the live option and would report the
// parser's real state.
assert!(!is_set);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_is_set_empty_handle_is_invalid() {
let e = CHandle::null();
let mut is_set = true;
let r = oakcommon_commandlineoption_is_set(e, &mut is_set);
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn option_is_set_null_out_is_invalid() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let r = oakcommon_commandlineoption_is_set(dup(&opt), null_mut());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_free_null_pointer_is_noop() {
oakcommon_commandlineoption_free(null_mut());
}
#[test]
fn option_free_twice_is_safe_and_nulls_ctx() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
assert!(!opt.ctx.is_null());
oakcommon_commandlineoption_free(&mut opt);
assert!(opt.ctx.is_null());
// Second free on the same (now empty) handle must be a no-op.
oakcommon_commandlineoption_free(&mut opt);
assert!(opt.ctx.is_null());
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_free_null_handle_struct_is_noop() {
let mut h = CHandle::null();
oakcommon_commandlineoption_free(&mut h);
assert!(h.ctx.is_null());
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineoption_get_setting (TRUNCATING two-stage getter)
// ---------------------------------------------------------------------------
#[test]
fn option_get_setting_size_query() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
assert_eq!(
oakcommon_commandlineoption_set_setting(dup(&opt), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
// Stage 1: null buffer, size 0 -> required size len+1, nothing written.
let r = oakcommon_commandlineoption_get_setting(dup(&opt), null_mut(), 0);
assert_eq!(r, 7); // len("abcdef") + NUL
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_get_setting_short_buffer_truncates_and_returns_required() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
assert_eq!(
oakcommon_commandlineoption_set_setting(dup(&opt), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
// Stage 2: buf_size 4 < 7 -> writes "abc" + NUL, still returns 7.
let mut buf = [0xFFu8; 4];
let r = oakcommon_commandlineoption_get_setting(dup(&opt), buf.as_mut_ptr() as *mut c_char, 4);
assert_eq!(r, 7);
assert_eq!(&buf[..3], b"abc");
assert_eq!(buf[3], 0);
// buf_size 1 -> writes only the NUL terminator.
let mut tiny = [0xFFu8; 1];
let r = oakcommon_commandlineoption_get_setting(dup(&opt), tiny.as_mut_ptr() as *mut c_char, 1);
assert_eq!(r, 7);
assert_eq!(tiny[0], 0);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_get_setting_exact_fit_writes_full_string() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
assert_eq!(
oakcommon_commandlineoption_set_setting(dup(&opt), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
// Stage 3: buf_size == len+1 -> full string plus NUL, returns len+1.
let mut buf = [0xFFu8; 7];
let r = oakcommon_commandlineoption_get_setting(dup(&opt), buf.as_mut_ptr() as *mut c_char, 7);
assert_eq!(r, 7);
assert_eq!(buf[6], 0);
let s = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) };
assert_eq!(s.to_str().unwrap(), "abcdef");
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_get_setting_non_null_buf_size_zero_writes_nothing() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
assert_eq!(
oakcommon_commandlineoption_set_setting(dup(&opt), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
// A non-null buffer with size 0 is a valid size query: required returned,
// buffer untouched.
let mut buf = [0xFFu8; 8];
let r = oakcommon_commandlineoption_get_setting(dup(&opt), buf.as_mut_ptr() as *mut c_char, 0);
assert_eq!(r, 7);
assert_eq!(buf, [0xFFu8; 8]);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_get_setting_unset_returns_empty_string() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
// Never set: get_setting returns the empty string (required size 1).
let mut buf = [0xFFu8; 1];
let r = oakcommon_commandlineoption_get_setting(dup(&opt), buf.as_mut_ptr() as *mut c_char, 1);
assert_eq!(r, 1);
assert_eq!(buf[0], 0);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_get_setting_empty_handle_is_invalid() {
let e = CHandle::null();
let r = oakcommon_commandlineoption_get_setting(e, null_mut(), 0);
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn option_get_setting_negative_buf_size_is_invalid() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let mut buf = [0u8; 4];
let r = oakcommon_commandlineoption_get_setting(dup(&opt), buf.as_mut_ptr() as *mut c_char, -1);
assert_eq!(r, OAKCOMMON_E_INVALID);
let r2 = oakcommon_commandlineoption_get_setting(dup(&opt), null_mut(), -1);
assert_eq!(r2, OAKCOMMON_E_INVALID);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_get_setting_null_buf_with_size_is_invalid() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let r = oakcommon_commandlineoption_get_setting(dup(&opt), null_mut(), 1);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlineoption_set_setting
// ---------------------------------------------------------------------------
#[test]
fn option_set_setting_success() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let r = oakcommon_commandlineoption_set_setting(dup(&opt), c_str("value").as_ptr());
assert_eq!(r, OAKCOMMON_OK);
// Round-trip through the two-stage getter.
let mut buf = vec![0u8; 6];
let r = oakcommon_commandlineoption_get_setting(dup(&opt), buf.as_mut_ptr() as *mut c_char, 6);
assert_eq!(r, 6);
let s = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) };
assert_eq!(s.to_str().unwrap(), "value");
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn option_set_setting_empty_handle_is_invalid() {
let e = CHandle::null();
let r = oakcommon_commandlineoption_set_setting(e, c_str("v").as_ptr());
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn option_set_setting_null_value_is_invalid() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let r = oakcommon_commandlineoption_set_setting(dup(&opt), null_mut());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
/// CPP-PARITY: `process()` mutates the parser-owned option; the option handle
/// returned by `add_option` is a registration-time snapshot in Rust (C++
/// holds a pointer to the live option), so the handle does not observe the
/// parse: is_set stays false and get_setting stays empty.
#[test]
fn option_handle_does_not_observe_process() {
let mut p = new_parser();
let mut opt = register_option(dup(&p), "o");
let (_argv, argv_ptrs) = c_strings(&["prog", "-o", "out.mov"]);
let r = oakcommon_commandlineparser_process(dup(&p), argv_ptrs.as_ptr(), 3);
assert_eq!(r, OAKCOMMON_OK);
// The snapshot handle still reports the empty setting.
let r = oakcommon_commandlineoption_get_setting(dup(&opt), null_mut(), 0);
assert_eq!(r, 1); // empty string
let mut is_set = true;
assert_eq!(oakcommon_commandlineoption_is_set(dup(&opt), &mut is_set), OAKCOMMON_OK);
assert!(!is_set);
oakcommon_commandlineoption_free(&mut opt);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlinepositionalargument_get_setting (TRUNCATING)
// ---------------------------------------------------------------------------
#[test]
fn positional_get_setting_size_query() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
assert_eq!(
oakcommon_commandlinepositionalargument_set_setting(dup(&pos), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), null_mut(), 0);
assert_eq!(r, 7); // len("abcdef") + NUL
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_get_setting_short_buffer_truncates_and_returns_required() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
assert_eq!(
oakcommon_commandlinepositionalargument_set_setting(dup(&pos), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
let mut buf = [0xFFu8; 4];
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), buf.as_mut_ptr() as *mut c_char, 4);
assert_eq!(r, 7);
assert_eq!(&buf[..3], b"abc");
assert_eq!(buf[3], 0);
let mut tiny = [0xFFu8; 1];
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), tiny.as_mut_ptr() as *mut c_char, 1);
assert_eq!(r, 7);
assert_eq!(tiny[0], 0);
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_get_setting_exact_fit_writes_full_string() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
assert_eq!(
oakcommon_commandlinepositionalargument_set_setting(dup(&pos), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
let mut buf = [0xFFu8; 7];
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), buf.as_mut_ptr() as *mut c_char, 7);
assert_eq!(r, 7);
assert_eq!(buf[6], 0);
let s = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) };
assert_eq!(s.to_str().unwrap(), "abcdef");
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_get_setting_non_null_buf_size_zero_writes_nothing() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
assert_eq!(
oakcommon_commandlinepositionalargument_set_setting(dup(&pos), c_str("abcdef").as_ptr()),
OAKCOMMON_OK
);
let mut buf = [0xFFu8; 8];
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), buf.as_mut_ptr() as *mut c_char, 0);
assert_eq!(r, 7);
assert_eq!(buf, [0xFFu8; 8]);
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_get_setting_unset_returns_empty_string() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
let mut buf = [0xFFu8; 1];
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), buf.as_mut_ptr() as *mut c_char, 1);
assert_eq!(r, 1);
assert_eq!(buf[0], 0);
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_get_setting_empty_handle_is_invalid() {
let e = CHandle::null();
let r = oakcommon_commandlinepositionalargument_get_setting(e, null_mut(), 0);
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn positional_get_setting_negative_buf_size_is_invalid() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
let mut buf = [0u8; 4];
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), buf.as_mut_ptr() as *mut c_char, -1);
assert_eq!(r, OAKCOMMON_E_INVALID);
let r2 = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), null_mut(), -1);
assert_eq!(r2, OAKCOMMON_E_INVALID);
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_get_setting_null_buf_with_size_is_invalid() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), null_mut(), 1);
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
// ---------------------------------------------------------------------------
// oakcommon_commandlinepositionalargument_set_setting / _free
// ---------------------------------------------------------------------------
#[test]
fn positional_set_setting_success() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
let r = oakcommon_commandlinepositionalargument_set_setting(dup(&pos), c_str("file.mp4").as_ptr());
assert_eq!(r, OAKCOMMON_OK);
// Round-trip through the two-stage getter.
let mut buf = vec![0u8; 9];
let r = oakcommon_commandlinepositionalargument_get_setting(dup(&pos), buf.as_mut_ptr() as *mut c_char, 9);
assert_eq!(r, 9);
let s = unsafe { CStr::from_ptr(buf.as_ptr() as *const c_char) };
assert_eq!(s.to_str().unwrap(), "file.mp4");
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_set_setting_empty_handle_is_invalid() {
let e = CHandle::null();
let r = oakcommon_commandlinepositionalargument_set_setting(e, c_str("v").as_ptr());
assert_eq!(r, OAKCOMMON_E_INVALID);
}
#[test]
fn positional_set_setting_null_value_is_invalid() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
let r = oakcommon_commandlinepositionalargument_set_setting(dup(&pos), null_mut());
assert_eq!(r, OAKCOMMON_E_INVALID);
oakcommon_commandlinepositionalargument_free(&mut pos);
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_free_null_pointer_is_noop() {
oakcommon_commandlinepositionalargument_free(null_mut());
}
#[test]
fn positional_free_twice_is_safe_and_nulls_ctx() {
let mut p = new_parser();
let mut pos = register_positional(dup(&p), "input");
assert!(!pos.ctx.is_null());
oakcommon_commandlinepositionalargument_free(&mut pos);
assert!(pos.ctx.is_null());
// Second free on the same (now empty) handle must be a no-op.
oakcommon_commandlinepositionalargument_free(&mut pos);
assert!(pos.ctx.is_null());
oakcommon_commandlineparser_free(&mut p);
}
#[test]
fn positional_free_null_handle_struct_is_noop() {
let mut h = CHandle::null();
oakcommon_commandlinepositionalargument_free(&mut h);
assert!(h.ctx.is_null());
}