Files
oak-editor/crates/oaktask/tests/project_task_test.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

520 lines
20 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/>.
//! Project task factory tests (project.rs + ffi/project.rs): load, save,
//! import, OTIO load/save and pre-cache/export factories.
mod common;
use common::*;
use std::ffi::{c_char, c_int, c_void};
use std::sync::atomic::Ordering;
use oaktask::error::{OAKTASK_E_INVALID, OAKTASK_E_NOT_FOUND};
use oaktask::ffi::manager::{oaktask_manager_init, oaktask_manager_shutdown};
use oaktask::ffi::project::{
oaktask_create_export, oaktask_create_precache, oaktask_create_project_import, oaktask_create_project_load,
oaktask_create_project_load_otio, oaktask_create_project_save, oaktask_create_project_save_otio,
oaktask_import_footage_at, oaktask_import_footage_count, oaktask_import_invalid_at, oaktask_import_invalid_count,
oaktask_import_set_image_sequence_confirm_cb, oaktask_import_take_command, oaktask_load_otio_set_confirm_cb,
oaktask_load_otio_take_project, oaktask_load_take_project,
};
use oaktask::ffi::task::{
oaktask_debug_alive_count, oaktask_task_error, oaktask_task_free, oaktask_task_is_finished, oaktask_task_start_sync,
oaktask_task_title,
};
use oaktask::handle::CHandle;
unsafe extern "C" fn accept_all(_seq: *const *const c_char, _count: c_int, _ud: *mut c_void) -> c_int {
1
}
unsafe extern "C" fn reject_all(_seq: *const *const c_char, _count: c_int, _ud: *mut c_void) -> c_int {
0
}
unsafe extern "C" fn image_seq_confirm(_filename: *const c_char, _ud: *mut c_void) -> c_int {
1
}
fn free(t: &mut CHandle) {
unsafe {
oaktask_task_free(t);
}
}
/// Given a valid project file, `create_project_load` returns a task whose
/// `start_sync` succeeds and whose `load_take_project` transfers an
/// initialized project with a root folder.
#[test]
fn project_load_transfers_initialized_project() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
let path = std::env::temp_dir().join("oak-load-ok.oakproj");
std::fs::write(&path, b"oakproj-stub").unwrap();
let path_str = path.to_string_lossy().into_owned();
let mut task = unsafe { oaktask_create_project_load(common::cstr_of(&path_str)) };
assert!(!task.ctx.is_null());
// Title embeds the filename.
let needed = unsafe { oaktask_task_title(task, std::ptr::null_mut(), 0) };
assert!(needed > 10);
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert_eq!(unsafe { oaktask_task_is_finished(task) }, 1);
// take_project transfers a non-empty project.
let project = unsafe { oaktask_load_take_project(task) };
assert!(!project.ctx.is_null(), "load_take_project returned empty");
// Second take is empty (ownership transferred).
let project2 = unsafe { oaktask_load_take_project(task) };
assert!(project2.ctx.is_null());
free(&mut task);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
let _ = std::fs::remove_file(&path);
}
/// Loading a missing file fails: start_sync=0, an error is set, and
/// take_project is empty.
#[test]
fn project_load_missing_file_fails() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
let missing = "/tmp/oak-does-not-exist-12345.oakproj";
let _ = std::fs::remove_file(missing);
let mut task = unsafe { oaktask_create_project_load(common::cstr_of(missing)) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 0);
assert_eq!(unsafe { oaktask_task_is_finished(task) }, 1);
let needed = unsafe { oaktask_task_error(task, std::ptr::null_mut(), 0) };
assert!(needed > 0);
let mut buf = vec![0i8; needed as usize];
unsafe { oaktask_task_error(task, buf.as_mut_ptr(), needed) };
assert!(cstr_read(&buf).contains("Failed to read file"));
let project = unsafe { oaktask_load_take_project(task) };
assert!(project.ctx.is_null());
// Null filename -> empty handle (factory failure path).
let empty = unsafe { oaktask_create_project_load(std::ptr::null()) };
assert!(empty.ctx.is_null());
free(&mut task);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// Given a loaded project, `create_project_save` writes the project and the
/// task succeeds with compression on and off.
#[test]
fn project_save_writes_with_and_without_compression() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
let path = std::env::temp_dir().join("oak-save-compressed.oakproj");
let path_str = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
// Without compression.
let mut task = unsafe { oaktask_create_project_save(fake_handle(), common::cstr_of(&path_str), 0) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert!(path.exists(), "saved file was not written");
free(&mut task);
// With compression (overwrites).
let _ = std::fs::remove_file(&path);
let mut task = unsafe { oaktask_create_project_save(fake_handle(), common::cstr_of(&path_str), 1) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert!(path.exists());
free(&mut task);
// Null project -> empty handle.
let empty = unsafe { oaktask_create_project_save(CHandle::null(), common::cstr_of(&path_str), 0) };
assert!(empty.ctx.is_null());
let _ = std::fs::remove_file(&path);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// Given an import folder and a media URL, `create_project_import` runs to
/// completion, records one footage and exposes it via `import_footage_at`.
#[test]
fn project_import_records_and_exposes_footage() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
let file = std::env::temp_dir().join("oak-import-ok.mp4");
std::fs::write(&file, b"media").unwrap();
let file_str = file.to_string_lossy().into_owned();
let urls = [common::cstr_of(&file_str)];
let mut task =
unsafe { oaktask_create_project_import(fake_handle(), fake_handle(), urls.as_ptr(), urls.len() as c_int) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert_eq!(unsafe { oaktask_import_footage_count(task) }, 1);
assert_eq!(unsafe { oaktask_import_invalid_count(task) }, 0);
// footage_at returns an addref'd handle for in-range indices.
let footage = unsafe { oaktask_import_footage_at(task, 0) };
assert!(!footage.ctx.is_null());
// Out of range -> empty.
assert!(unsafe { oaktask_import_footage_at(task, 5) }.ctx.is_null());
assert!(unsafe { oaktask_import_footage_at(task, -1) }.ctx.is_null());
// The undo command is produced; redo adds the folder child, undo
// removes it again.
let mut command = unsafe { oaktask_import_take_command(task) };
assert!(!command.ctx.is_null());
// Second take is empty.
assert!(unsafe { oaktask_import_take_command(task) }.ctx.is_null());
assert_eq!(unsafe { oaktask::bridge::undo::oakundo_command_redo_now(command) }, 0);
assert_eq!(unsafe { oaktask::bridge::node::oaknode_folder_child_count(fake_handle()) }, 1);
assert_eq!(unsafe { oaktask::bridge::undo::oakundo_command_undo_now(command) }, 0);
assert_eq!(unsafe { oaktask::bridge::node::oaknode_folder_child_count(fake_handle()) }, 0);
unsafe {
oaktask::bridge::undo::oakundo_command_free(&mut command);
}
free(&mut task);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
let _ = std::fs::remove_file(&file);
}
/// Given an unreadable import URL, `create_project_import` records the
/// invalid filename and `import_invalid_at` reports it (two-stage).
#[test]
fn project_import_reports_invalid_urls() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
FOOTAGE_VALID.store(0, Ordering::SeqCst);
let bad = "/tmp/oak-invalid-media-12345.mp4";
let _ = std::fs::remove_file(bad);
let urls = [common::cstr_of(bad)];
let mut task =
unsafe { oaktask_create_project_import(fake_handle(), fake_handle(), urls.as_ptr(), urls.len() as c_int) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert_eq!(unsafe { oaktask_import_invalid_count(task) }, 1);
assert_eq!(unsafe { oaktask_import_footage_count(task) }, 0);
// Two-stage invalid filename getter.
let needed = unsafe { oaktask_import_invalid_at(task, 0, std::ptr::null_mut(), 0) };
assert!(needed > 0);
let mut buf = vec![0i8; needed as usize];
let r = unsafe { oaktask_import_invalid_at(task, 0, buf.as_mut_ptr(), needed) };
assert_eq!(r, needed);
assert_eq!(cstr_read(&buf), bad);
// Out of range -> E_NOT_FOUND.
assert_eq!(
unsafe { oaktask_import_invalid_at(task, 3, std::ptr::null_mut(), 0) },
OAKTASK_E_NOT_FOUND
);
// Factory failure paths: null folder, null project, negative count.
assert!(unsafe { oaktask_create_project_import(CHandle::null(), fake_handle(), std::ptr::null(), 0) }.ctx.is_null());
assert!(unsafe { oaktask_create_project_import(fake_handle(), CHandle::null(), std::ptr::null(), 0) }.ctx.is_null());
let urls_null = [std::ptr::null()];
assert!(
unsafe { oaktask_create_project_import(fake_handle(), fake_handle(), urls_null.as_ptr(), 1) }.ctx.is_null()
);
// Accessors on a non-import task -> E_INVALID / empty.
let mut save_task = unsafe { oaktask_create_project_save(fake_handle(), common::cstr_of("/tmp/x.oakproj"), 0) };
assert_eq!(unsafe { oaktask_import_footage_count(save_task) }, OAKTASK_E_INVALID);
assert!(unsafe { oaktask_import_take_command(save_task) }.ctx.is_null());
free(&mut save_task);
free(&mut task);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// An image sequence with a confirmation callback imports once and prunes
/// the remaining frames from the list.
#[test]
fn project_import_image_sequence_confirmed() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
let dir = std::env::temp_dir().join("oak-seq");
let _ = std::fs::create_dir_all(&dir);
let files = ["seq001.png", "seq002.png", "seq003.png"];
let mut paths = Vec::new();
for f in &files {
let p = dir.join(f);
std::fs::write(&p, b"png").unwrap();
paths.push(p.to_string_lossy().into_owned());
}
IMAGE_SEQUENCE_DIGIT_COUNT.store(3, Ordering::SeqCst);
CONFIG_DEFAULT_SEQ_FRAME_RATE.store(1, Ordering::SeqCst);
unsafe { oaktask_import_set_image_sequence_confirm_cb(Some(image_seq_confirm), std::ptr::null_mut()) };
let urls: Vec<*const c_char> = paths.iter().map(|p| common::cstr_of(p)).collect();
let mut task =
unsafe { oaktask_create_project_import(fake_handle(), fake_handle(), urls.as_ptr(), urls.len() as c_int) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
// The three frames collapse into a single footage import.
assert_eq!(unsafe { oaktask_import_footage_count(task) }, 1);
assert_eq!(unsafe { oaktask_import_invalid_count(task) }, 0);
unsafe { oaktask_import_set_image_sequence_confirm_cb(None, std::ptr::null_mut()) };
IMAGE_SEQUENCE_DIGIT_COUNT.store(0, Ordering::SeqCst);
free(&mut task);
for p in &paths {
let _ = std::fs::remove_file(p);
}
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// Given a valid OTIO file, `create_project_load_otio` runs to completion and
/// `load_otio_take_project` transfers an initialized project; a missing file
/// fails cleanly. (OTIO parsing now happens inside the task through the
/// `oakotio` binding — README decision #6.)
#[test]
fn otio_load_transfers_project_with_default_confirm() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
// A minimal Timeline document (no tracks).
let path = std::env::temp_dir().join("oak-load-ok.otio");
let otio = r#"{
"OTIO_SCHEMA": "Timeline.1",
"metadata": {},
"name": "T",
"global_start_time": null,
"tracks": {
"OTIO_SCHEMA": "Stack.1",
"metadata": {},
"name": "tracks",
"source_range": null,
"effects": [],
"markers": [],
"enabled": true,
"children": []
}
}"#;
std::fs::write(&path, otio).unwrap();
let path_str = path.to_string_lossy().into_owned();
let mut task = unsafe { oaktask_create_project_load_otio(common::cstr_of(&path_str)) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert_eq!(unsafe { oaktask_task_is_finished(task) }, 1);
assert!(!(unsafe { oaktask_load_otio_take_project(task) }).ctx.is_null());
free(&mut task);
// Missing file -> failure with a clear error.
let missing = std::env::temp_dir().join("oak-missing-otio.otio");
let missing_str = missing.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&missing);
let mut task = unsafe { oaktask_create_project_load_otio(common::cstr_of(&missing_str)) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 0);
let needed = unsafe { oaktask_task_error(task, std::ptr::null_mut(), 0) };
assert!(needed > 0);
let mut buf = vec![0i8; needed as usize];
unsafe { oaktask_task_error(task, buf.as_mut_ptr(), needed) };
assert!(cstr_read(&buf).contains("Failed to load OpenTimelineIO"), "error was: {}", cstr_read(&buf));
assert!(unsafe { oaktask_load_otio_take_project(task) }.ctx.is_null());
// The confirm callback setters are no-ops (null clears).
unsafe {
oaktask_load_otio_set_confirm_cb(Some(accept_all), std::ptr::null_mut());
oaktask_load_otio_set_confirm_cb(Some(reject_all), std::ptr::null_mut());
oaktask_load_otio_set_confirm_cb(None, std::ptr::null_mut());
}
free(&mut task);
let _ = std::fs::remove_file(&path);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// Given a project with a sequence, `create_project_save_otio` writes the
/// OTIO file and the task succeeds; a project without sequences fails.
#[test]
fn otio_save_writes_file() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
// One sequence in the root folder, one video track with a single clip.
FOLDER_CHILD_COUNT.store(1, Ordering::SeqCst);
*NODE_ID.lock().unwrap() = "org.olivevideoeditor.Olive.sequence".to_string();
*NODE_LABEL.lock().unwrap() = "My Sequence".to_string();
VIDEO_FRAME_RATE_NUM.store(25, Ordering::SeqCst);
VIDEO_FRAME_RATE_DEN.store(1, Ordering::SeqCst);
TRACK_LIST_TRACK_COUNT.store(1, Ordering::SeqCst);
TRACK_BLOCK_COUNT.store(1, Ordering::SeqCst);
BLOCK_KIND.store(1, Ordering::SeqCst); // OAKNODE_BLOCK_CLIP
BLOCK_IN_NUM.store(0, Ordering::SeqCst);
BLOCK_IN_DEN.store(1, Ordering::SeqCst);
BLOCK_LENGTH_NUM.store(25, Ordering::SeqCst);
BLOCK_LENGTH_DEN.store(1, Ordering::SeqCst);
*FOOTAGE_FILENAME.lock().unwrap() = "file:///tmp/video.mp4".to_string();
let out = std::env::temp_dir().join("oak-otio-out.otio");
let out_str = out.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&out);
let mut task = unsafe { oaktask_create_project_save_otio(fake_handle(), common::cstr_of(&out_str)) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert!(out.exists(), "OTIO file was not written");
// Failure paths: null project or null filename.
assert!(unsafe { oaktask_create_project_save_otio(CHandle::null(), common::cstr_of("/tmp/x.otio")) }.ctx.is_null());
assert!(unsafe { oaktask_create_project_save_otio(fake_handle(), std::ptr::null()) }.ctx.is_null());
free(&mut task);
let _ = std::fs::remove_file(&out);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// Given a footage/sequence pair, `create_precache` runs to completion and
/// succeeds.
#[test]
fn precache_runs_and_succeeds() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
FOOTAGE_FILENAME.lock().unwrap().clear();
let mut task = unsafe { oaktask_create_precache(fake_handle(), 0, fake_handle()) };
assert!(!task.ctx.is_null());
let needed = unsafe { oaktask_task_title(task, std::ptr::null_mut(), 0) };
assert!(needed > 0);
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert_eq!(unsafe { oaktask_task_is_finished(task) }, 1);
// Failure path: null footage/sequence.
assert!(unsafe { oaktask_create_precache(CHandle::null(), 0, fake_handle()) }.ctx.is_null());
assert!(unsafe { oaktask_create_precache(fake_handle(), 0, CHandle::null()) }.ctx.is_null());
free(&mut task);
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// create_export runs the full render+encoder path against the stubs and
/// succeeds; the encoder-failure path fails cleanly.
#[test]
fn export_runs_with_stubbed_encoder() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
*PROJECT_FILENAME.lock().unwrap() = "/tmp/oak-export-test.mp4".to_string();
NODE_LABEL.lock().unwrap().clear();
let mut params = oaktask::bridge::codec::OakCodecEncodingParams {
filename: [0; 1024],
format: 0,
video_enabled: 1,
video_codec: 0,
video_width: 1920,
video_height: 1080,
video_time_base_num: 1,
video_time_base_den: 25,
video_pixel_format: 0,
video_interlacing: 0,
video_pixel_aspect_num: 1,
video_pixel_aspect_den: 1,
video_bit_rate: 0,
video_min_bit_rate: 0,
video_max_bit_rate: 0,
video_buffer_size: 0,
video_threads: 0,
video_pix_fmt: [0; 64],
video_is_image_sequence: 0,
video_scaling_method: 0,
audio_enabled: 0,
audio_codec: 0,
audio_sample_rate: 0,
audio_channel_layout: 0,
audio_sample_format: 0,
audio_bit_rate: 0,
subtitles_enabled: 0,
subtitles_codec: 0,
subtitles_are_sidecar: 0,
subtitles_sidecar_format: 0,
color_transform_output: [0; 256],
export_length_num: 0,
export_length_den: 0,
has_custom_range: 0,
custom_range_in_num: 0,
custom_range_in_den: 0,
custom_range_out_num: 0,
custom_range_out_den: 0,
};
let filename = std::env::temp_dir().join("oak-export-test.mp4").to_string_lossy().into_owned();
write_cstr(&filename, params.filename.as_mut_ptr(), 1024);
ENCODER_INIT_NULL.store(0, Ordering::SeqCst);
ENCODER_OPEN_RESULT.store(0, Ordering::SeqCst);
ENCODER_WRITE_VIDEO_RESULT.store(0, Ordering::SeqCst);
SEQUENCE_LENGTH_NUM.store(25, Ordering::SeqCst);
SEQUENCE_LENGTH_DEN.store(1, Ordering::SeqCst);
VIDEO_TIME_BASE_NUM.store(1, Ordering::SeqCst);
VIDEO_TIME_BASE_DEN.store(1, Ordering::SeqCst); // 1s frames -> 25 frames
let mut task = unsafe { oaktask_create_export(fake_handle(), fake_handle(), &params) };
assert!(!task.ctx.is_null());
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 1);
assert_eq!(unsafe { oaktask_task_is_finished(task) }, 1);
free(&mut task);
// Failure path: encoder init fails.
ENCODER_INIT_NULL.store(1, Ordering::SeqCst);
let mut task = unsafe { oaktask_create_export(fake_handle(), fake_handle(), &params) };
assert_eq!(unsafe { oaktask_task_start_sync(task) }, 0);
let needed = unsafe { oaktask_task_error(task, std::ptr::null_mut(), 0) };
assert!(needed > 0);
let mut buf = vec![0i8; needed as usize];
unsafe { oaktask_task_error(task, buf.as_mut_ptr(), needed) };
assert!(cstr_read(&buf).contains("Failed to create encoder"));
free(&mut task);
// Failure path: null viewer / null params.
assert!(unsafe { oaktask_create_export(CHandle::null(), fake_handle(), &params) }.ctx.is_null());
assert!(unsafe { oaktask_create_export(fake_handle(), fake_handle(), std::ptr::null()) }.ctx.is_null());
reset_stubs();
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// The manager is needed for nothing in these factories; a fresh
/// init/shutdown cycle leaves the alive count at zero.
#[test]
fn manager_cycle_leaves_no_leaks() {
let _guard = MANAGER_LOCK.lock().unwrap();
reset_stubs();
unsafe { oaktask_manager_shutdown() };
assert_eq!(unsafe { oaktask_manager_init() }, 0);
unsafe { oaktask_manager_shutdown() };
assert_eq!(unsafe { oaktask_debug_alive_count() }, 0);
}
/// cstr_read helper: read a NUL-terminated c_char buffer.
fn cstr_read(buf: &[i8]) -> String {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
let bytes = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, len) };
String::from_utf8_lossy(bytes).into_owned()
}