docs: archive completed C ABI campaign docs under plans/completed/

Move the finished migration campaign docs (handoffs v3-v6, roadmap,
R5 guides, R6 cleanup, R7 pure-ABI) from docs/zh/ into
docs/zh/plans/completed/ with an archive README; fix all
cross-references; refresh plans/README.md index (active plans now
marked unlocked).
This commit is contained in:
2026-07-27 01:52:52 +08:00
parent d7c0970d91
commit dd5508e571
32 changed files with 1122 additions and 1438 deletions
+14
View File
@@ -298,6 +298,20 @@ endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third_party)
# Google Test discovery (shared by core/tests, engine/tests, tests/)
if (BUILD_TESTS)
include(FetchContent)
find_package(GTest QUIET)
if (NOT GTest_FOUND)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()
endif()
add_subdirectory(core)
set(KDDockWidgets_STATIC ON CACHE INTERNAL "Force KDDockWidgets to build statically")
+31 -7
View File
@@ -25,6 +25,7 @@
#include <QDebug>
#include <QMessageBox>
#include <QScreen>
#include <QToolBar>
#ifdef Q_OS_LINUX
#include <QOffscreenSurface>
@@ -43,6 +44,8 @@
#include "oakengine/undo.h"
#include "widget/viewer/vieweroutpututils.h"
#include "widget/toolbar/toolbar.h"
#include "core.h"
namespace olive
{
@@ -87,6 +90,29 @@ MainWindow::MainWindow(QWidget *parent)
&MainWindow::status_bar_double_clicked);
setStatusBar(status_bar);
// Create application toolbar (31px) — replaces the dockable ToolPanel by default
tool_bar_ = new QToolBar(this);
tool_bar_->setObjectName(QStringLiteral("AppToolbar"));
tool_bar_->setFixedHeight(31);
tool_bar_->setMovable(false);
tool_bar_->setFloatable(false);
Toolbar *toolbar_widget = new Toolbar(tool_bar_);
toolbar_widget->set_tool(Core::instance()->tool());
toolbar_widget->set_snapping(Core::instance()->snapping());
tool_bar_->addWidget(toolbar_widget);
addToolBar(Qt::TopToolBarArea, tool_bar_);
connect(toolbar_widget, &Toolbar::tool_changed, Core::instance(),
&Core::set_tool);
connect(Core::instance(), &Core::tool_changed, toolbar_widget,
&Toolbar::set_tool);
connect(toolbar_widget, &Toolbar::snapping_changed, Core::instance(),
&Core::set_snapping);
connect(Core::instance(), &Core::snapping_changed, toolbar_widget,
&Toolbar::set_snapping);
connect(toolbar_widget, &Toolbar::selected_transition_changed,
Core::instance(), &Core::set_selected_transition_object);
// Create standard panels
node_panel_ = new NodePanel();
footage_viewer_panel_ = new FootageViewerPanel();
@@ -938,22 +964,20 @@ void MainWindow::set_default_layout()
// Bottom center - timelines
addDockWidget(timeline_panels_.first(), KDDockWidgets::Location_OnBottom);
// Left of timeline - tool panel
o.preferredSize = QSize(1, 0);
addDockWidget(tool_panel_, KDDockWidgets::Location_OnLeft,
timeline_panels_.first(), o);
// Right of timeline - audio monitor
o.preferredSize = QSize(320, 0);
addDockWidget(audio_monitor_panel_, KDDockWidgets::Location_OnRight,
timeline_panels_.first(), o);
// Bottom left - project panel
addDockWidget(project_panel_, KDDockWidgets::Location_OnLeft, tool_panel_);
// Bottom left - project panel (tool panel is now the top toolbar;
// the dockable ToolPanel remains available via Window menu)
addDockWidget(project_panel_, KDDockWidgets::Location_OnLeft,
timeline_panels_.first());
project_panel_->addDockWidgetAsTab(history_panel_);
project_panel_->raise();
// Hidden panels
tool_panel_->close();
pixel_sampler_panel_->close();
task_man_panel_->close();
curve_panel_->close();
+5
View File
@@ -25,6 +25,8 @@
#include <kddockwidgets/Config.h>
#include <kddockwidgets/MainWindow.h>
#include <QToolBar>
#include "node/project/serializer/serializedlayoutinfo.h"
#include "node/project.h"
#include "panel/multicam/multicampanel.h"
@@ -152,6 +154,9 @@ private:
QByteArray premaximized_state_;
// Application toolbar (31px, replaces the dockable ToolPanel by default)
QToolBar *tool_bar_;
// Standard panels
ProjectPanel *project_panel_;
NodePanel *node_panel_;
+2 -24
View File
@@ -120,29 +120,7 @@ else ()
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif ()
if (OLIVECORE_BUILD_TESTS)
if (OLIVECORE_BUILD_TESTS AND BUILD_TESTS)
enable_testing()
function(make_test name)
add_executable(${name}
tests/${name}.cpp
)
target_link_libraries(${name} PRIVATE olivecore)
target_include_directories(${name} PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core"
)
add_test(${name} ${name})
endfunction()
# Pure C ABI tests for the liboakcore public interface
make_test(oakcore_audioparams_test)
make_test(oakcore_bezier_test)
make_test(oakcore_color_test)
make_test(oakcore_fractionutils_test)
make_test(oakcore_rational_test)
make_test(oakcore_samplebuffer_test)
make_test(oakcore_stringutils_test)
make_test(oakcore_timecodefunctions_test)
make_test(oakcore_timerange_test)
add_subdirectory(tests)
endif()
+34
View File
@@ -0,0 +1,34 @@
# oakcore GTest target — migrated from pure C assert tests
# 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.
include(GoogleTest)
add_executable(oakcore_gtest
oakcore_rational_test.cpp
oakcore_audioparams_test.cpp
oakcore_bezier_test.cpp
oakcore_color_test.cpp
oakcore_fractionutils_test.cpp
oakcore_samplebuffer_test.cpp
oakcore_stringutils_test.cpp
oakcore_timecodefunctions_test.cpp
oakcore_timerange_test.cpp
)
target_link_libraries(oakcore_gtest PRIVATE
olivecore
GTest::gtest
GTest::gtest_main
)
target_include_directories(oakcore_gtest PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../include"
"${CMAKE_CURRENT_SOURCE_DIR}/../include/olive/core"
)
gtest_discover_tests(oakcore_gtest)
+100 -186
View File
@@ -1,247 +1,161 @@
/***
Oak - 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/>.
***/
// Pure C API test for oakcore/audioparams.h: no gtest, no C++ wrappers.
// oakcore/audioparams.h includes oakcore/rational.h for the OakRational
// handles crossing the boundary.
#include <cassert>
#include <gtest/gtest.h>
#include <cstdint>
#include <cstdio>
#include "olive/core/oakcore/audioparams.h"
// Mirror of render/channellayout.h values, spelled out so this test only
// includes the C API header.
static const uint64_t k_layout_mono = 0x4;
static const uint64_t k_layout_stereo = 0x3;
static const uint64_t k_layout_2_1 = 0x103;
static const uint64_t k_layout_5_point1 = 0x60F;
static const uint64_t k_layout_7_point1 = 0x63F;
// Mirror of render/sampleformat.h enum values.
static const int k_format_invalid = -1;
static const int k_format_u8_p = 0;
static const int k_format_f32_p = 4;
static const int k_format_s16 = 7;
static const int k_format_f64 = 11;
int main()
TEST(OakcoreAudioParams, InvalidConstruction)
{
// Default constructor: invalid parameters with footage defaults
{
OakAudioParams *p = oakcore_audioparams_create_invalid();
assert(p != nullptr);
assert(oakcore_audioparams_is_valid(p) == 0);
assert(oakcore_audioparams_sample_rate(p) == 0);
assert(oakcore_audioparams_channel_layout(p) == 0);
assert(oakcore_audioparams_channel_count(p) == 0);
assert(oakcore_audioparams_format(p) == k_format_invalid);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 0);
assert(oakcore_audioparams_bits_per_sample(p) == 0);
assert(oakcore_audioparams_enabled(p) == 1);
assert(oakcore_audioparams_stream_index(p) == 0);
assert(oakcore_audioparams_duration(p) == 0);
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcore_audioparams_is_valid(p), 0);
EXPECT_EQ(oakcore_audioparams_sample_rate(p), 0);
EXPECT_EQ(oakcore_audioparams_channel_layout(p), (uint64_t)0);
EXPECT_EQ(oakcore_audioparams_channel_count(p), 0);
EXPECT_EQ(oakcore_audioparams_format(p), k_format_invalid);
EXPECT_EQ(oakcore_audioparams_bytes_per_sample_per_channel(p), 0);
EXPECT_EQ(oakcore_audioparams_bits_per_sample(p), 0);
EXPECT_EQ(oakcore_audioparams_enabled(p), 1);
EXPECT_EQ(oakcore_audioparams_stream_index(p), 0);
EXPECT_EQ(oakcore_audioparams_duration(p), 0);
OakRational *tb = oakcore_audioparams_time_base(p);
assert(oakcore_rational_is_null(tb) == 1);
EXPECT_EQ(oakcore_rational_is_null(tb), 1);
oakcore_rational_free(tb);
oakcore_audioparams_free(p);
}
}
// Full constructor: 48000 Hz stereo s16
OakAudioParams *p =
oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
assert(p != nullptr);
assert(oakcore_audioparams_is_valid(p) == 1);
assert(oakcore_audioparams_sample_rate(p) == 48000);
assert(oakcore_audioparams_channel_layout(p) == k_layout_stereo);
assert(oakcore_audioparams_channel_count(p) == 2);
assert(oakcore_audioparams_format(p) == k_format_s16);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 2);
assert(oakcore_audioparams_bits_per_sample(p) == 16);
assert(oakcore_audioparams_enabled(p) == 1);
assert(oakcore_audioparams_stream_index(p) == 0);
assert(oakcore_audioparams_duration(p) == 0);
TEST(OakcoreAudioParams, FullConstruction)
{
OakAudioParams *p = oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
ASSERT_NE(p, nullptr);
EXPECT_EQ(oakcore_audioparams_is_valid(p), 1);
EXPECT_EQ(oakcore_audioparams_sample_rate(p), 48000);
EXPECT_EQ(oakcore_audioparams_channel_layout(p), k_layout_stereo);
EXPECT_EQ(oakcore_audioparams_channel_count(p), 2);
EXPECT_EQ(oakcore_audioparams_format(p), k_format_s16);
EXPECT_EQ(oakcore_audioparams_bytes_per_sample_per_channel(p), 2);
EXPECT_EQ(oakcore_audioparams_bits_per_sample(p), 16);
// Timebase defaults to 1/sample_rate
{
OakRational *tb = oakcore_audioparams_time_base(p);
assert(oakcore_rational_numerator(tb) == 1);
assert(oakcore_rational_denominator(tb) == 48000);
EXPECT_EQ(oakcore_rational_numerator(tb), 1);
EXPECT_EQ(oakcore_rational_denominator(tb), 48000);
oakcore_rational_free(tb);
oakcore_audioparams_free(p);
}
OakRational *srtb = oakcore_audioparams_sample_rate_as_time_base(p);
assert(oakcore_rational_numerator(srtb) == 1);
assert(oakcore_rational_denominator(srtb) == 48000);
oakcore_rational_free(srtb);
}
TEST(OakcoreAudioParams, SettersGetters)
{
OakAudioParams *p = oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
// Setters / getters
oakcore_audioparams_set_sample_rate(p, 44100);
assert(oakcore_audioparams_sample_rate(p) == 44100);
EXPECT_EQ(oakcore_audioparams_sample_rate(p), 44100);
oakcore_audioparams_set_sample_rate(p, 48000);
assert(oakcore_audioparams_sample_rate(p) == 48000);
// Changing the layout recalculates the channel count
oakcore_audioparams_set_channel_layout(p, k_layout_mono);
assert(oakcore_audioparams_channel_layout(p) == k_layout_mono);
assert(oakcore_audioparams_channel_count(p) == 1);
EXPECT_EQ(oakcore_audioparams_channel_count(p), 1);
oakcore_audioparams_set_channel_layout(p, k_layout_5_point1);
assert(oakcore_audioparams_channel_count(p) == 6);
EXPECT_EQ(oakcore_audioparams_channel_count(p), 6);
oakcore_audioparams_set_channel_layout(p, k_layout_stereo);
assert(oakcore_audioparams_channel_count(p) == 2);
{
OakRational *tb = oakcore_rational_create_nd(1, 1000);
oakcore_audioparams_set_time_base(p, tb);
oakcore_rational_free(tb);
OakRational *got = oakcore_audioparams_time_base(p);
assert(oakcore_rational_numerator(got) == 1);
assert(oakcore_rational_denominator(got) == 1000);
oakcore_rational_free(got);
// Restore the default 1/48000 timebase
tb = oakcore_rational_create_nd(1, 48000);
oakcore_audioparams_set_time_base(p, tb);
oakcore_rational_free(tb);
}
EXPECT_EQ(oakcore_audioparams_channel_count(p), 2);
oakcore_audioparams_set_format(p, k_format_f32_p);
assert(oakcore_audioparams_format(p) == k_format_f32_p);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 4);
assert(oakcore_audioparams_bits_per_sample(p) == 32);
EXPECT_EQ(oakcore_audioparams_format(p), k_format_f32_p);
EXPECT_EQ(oakcore_audioparams_bytes_per_sample_per_channel(p), 4);
EXPECT_EQ(oakcore_audioparams_bits_per_sample(p), 32);
oakcore_audioparams_set_format(p, k_format_s16);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 2);
oakcore_audioparams_set_enabled(p, 0);
assert(oakcore_audioparams_enabled(p) == 0);
EXPECT_EQ(oakcore_audioparams_enabled(p), 0);
oakcore_audioparams_set_enabled(p, 1);
assert(oakcore_audioparams_enabled(p) == 1);
oakcore_audioparams_set_stream_index(p, 3);
assert(oakcore_audioparams_stream_index(p) == 3);
oakcore_audioparams_set_stream_index(p, 0);
EXPECT_EQ(oakcore_audioparams_stream_index(p), 3);
const int64_t big_duration = int64_t(1) << 40;
oakcore_audioparams_set_duration(p, big_duration);
assert(oakcore_audioparams_duration(p) == big_duration);
oakcore_audioparams_set_duration(p, 0);
EXPECT_EQ(oakcore_audioparams_duration(p), big_duration);
// Time/sample/byte conversions (48000 Hz, 2 channels, 2 bytes/sample)
assert(oakcore_audioparams_time_to_samples(p, 1.0) == 48000);
assert(oakcore_audioparams_time_to_samples(p, 0.5) == 24000);
assert(oakcore_audioparams_time_to_samples(p, -1.0) == -48000);
assert(oakcore_audioparams_time_to_samples(p, 0.0) == 0);
oakcore_audioparams_free(p);
}
assert(oakcore_audioparams_samples_to_bytes_per_channel(p, 100) == 200);
assert(oakcore_audioparams_samples_to_bytes(p, 100) == 400);
assert(oakcore_audioparams_samples_to_bytes(p, 0) == 0);
TEST(OakcoreAudioParams, TimeSampleByteConversions)
{
OakAudioParams *p = oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
assert(oakcore_audioparams_time_to_bytes_per_channel(p, 1.0) == 96000);
assert(oakcore_audioparams_time_to_bytes(p, 1.0) == 192000);
EXPECT_EQ(oakcore_audioparams_time_to_samples(p, 1.0), 48000);
EXPECT_EQ(oakcore_audioparams_time_to_samples(p, 0.5), 24000);
EXPECT_EQ(oakcore_audioparams_time_to_samples(p, -1.0), -48000);
EXPECT_EQ(oakcore_audioparams_time_to_samples(p, 0.0), 0);
assert(oakcore_audioparams_bytes_to_samples(p, 400) == 100);
assert(oakcore_audioparams_bytes_to_samples(p, 0) == 0);
EXPECT_EQ(oakcore_audioparams_samples_to_bytes_per_channel(p, 100), 200);
EXPECT_EQ(oakcore_audioparams_samples_to_bytes(p, 100), 400);
EXPECT_EQ(oakcore_audioparams_time_to_bytes_per_channel(p, 1.0), 96000);
EXPECT_EQ(oakcore_audioparams_time_to_bytes(p, 1.0), 192000);
EXPECT_EQ(oakcore_audioparams_bytes_to_samples(p, 400), 100);
// Rational-taking overloads
{
OakRational *half = oakcore_rational_create_nd(1, 2);
assert(oakcore_audioparams_time_to_samples_rational(p, half) == 24000);
assert(oakcore_audioparams_time_to_bytes_rational(p, half) == 96000);
assert(oakcore_audioparams_time_to_bytes_per_channel_rational(p, half) ==
48000);
EXPECT_EQ(oakcore_audioparams_time_to_samples_rational(p, half), 24000);
EXPECT_EQ(oakcore_audioparams_time_to_bytes_rational(p, half), 96000);
oakcore_rational_free(half);
}
// Rational-returning conversions
{
OakRational *t = oakcore_audioparams_samples_to_time(p, 48000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 1);
EXPECT_EQ(oakcore_rational_numerator(t), 1);
EXPECT_EQ(oakcore_rational_denominator(t), 1);
oakcore_rational_free(t);
t = oakcore_audioparams_samples_to_time(p, 24000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 2);
EXPECT_EQ(oakcore_rational_numerator(t), 1);
EXPECT_EQ(oakcore_rational_denominator(t), 2);
oakcore_rational_free(t);
t = oakcore_audioparams_bytes_to_time(p, 192000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 1);
oakcore_rational_free(t);
t = oakcore_audioparams_bytes_per_channel_to_time(p, 96000);
assert(oakcore_rational_numerator(t) == 1);
assert(oakcore_rational_denominator(t) == 1);
oakcore_rational_free(t);
}
// Copy + equality
{
OakAudioParams *copy = oakcore_audioparams_copy(p);
assert(copy != nullptr);
assert(oakcore_audioparams_equals(p, copy) == 1);
assert(oakcore_audioparams_equals(copy, copy) == 1);
oakcore_audioparams_set_sample_rate(copy, 96000);
assert(oakcore_audioparams_equals(p, copy) == 0);
oakcore_audioparams_set_sample_rate(copy, 48000);
assert(oakcore_audioparams_equals(p, copy) == 1);
OakAudioParams *invalid = oakcore_audioparams_create_invalid();
assert(oakcore_audioparams_equals(p, invalid) == 0);
assert(oakcore_audioparams_equals(invalid, invalid) == 1);
oakcore_audioparams_free(invalid);
oakcore_audioparams_free(copy);
}
// Supported channel layouts / sample rates
{
assert(oakcore_audioparams_supported_channel_layout_count() == 5);
assert(oakcore_audioparams_supported_channel_layout_at(0) == k_layout_mono);
assert(oakcore_audioparams_supported_channel_layout_at(1) ==
k_layout_stereo);
assert(oakcore_audioparams_supported_channel_layout_at(2) == k_layout_2_1);
assert(oakcore_audioparams_supported_channel_layout_at(3) ==
k_layout_5_point1);
assert(oakcore_audioparams_supported_channel_layout_at(4) ==
k_layout_7_point1);
// Out-of-range indices return 0
assert(oakcore_audioparams_supported_channel_layout_at(-1) == 0);
assert(oakcore_audioparams_supported_channel_layout_at(5) == 0);
assert(oakcore_audioparams_supported_sample_rate_count() == 10);
assert(oakcore_audioparams_supported_sample_rate_at(0) == 8000);
assert(oakcore_audioparams_supported_sample_rate_at(6) == 44100);
assert(oakcore_audioparams_supported_sample_rate_at(7) == 48000);
assert(oakcore_audioparams_supported_sample_rate_at(9) == 96000);
assert(oakcore_audioparams_supported_sample_rate_at(-1) == 0);
assert(oakcore_audioparams_supported_sample_rate_at(10) == 0);
}
oakcore_audioparams_free(p);
}
std::printf("oakcore_audioparams_test: all assertions passed\n");
return 0;
TEST(OakcoreAudioParams, CopyAndEquality)
{
OakAudioParams *p = oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
OakAudioParams *copy = oakcore_audioparams_copy(p);
ASSERT_NE(copy, nullptr);
EXPECT_EQ(oakcore_audioparams_equals(p, copy), 1);
oakcore_audioparams_set_sample_rate(copy, 96000);
EXPECT_EQ(oakcore_audioparams_equals(p, copy), 0);
oakcore_audioparams_set_sample_rate(copy, 48000);
EXPECT_EQ(oakcore_audioparams_equals(p, copy), 1);
OakAudioParams *invalid = oakcore_audioparams_create_invalid();
EXPECT_EQ(oakcore_audioparams_equals(p, invalid), 0);
EXPECT_EQ(oakcore_audioparams_equals(invalid, invalid), 1);
oakcore_audioparams_free(invalid);
oakcore_audioparams_free(copy);
oakcore_audioparams_free(p);
}
TEST(OakcoreAudioParams, SupportedLayoutsAndRates)
{
EXPECT_EQ(oakcore_audioparams_supported_channel_layout_count(), 5);
EXPECT_EQ(oakcore_audioparams_supported_channel_layout_at(0), k_layout_mono);
EXPECT_EQ(oakcore_audioparams_supported_channel_layout_at(1), k_layout_stereo);
EXPECT_EQ(oakcore_audioparams_supported_channel_layout_at(4), k_layout_7_point1);
EXPECT_EQ(oakcore_audioparams_supported_channel_layout_at(-1), (uint64_t)0);
EXPECT_EQ(oakcore_audioparams_supported_channel_layout_at(5), (uint64_t)0);
EXPECT_EQ(oakcore_audioparams_supported_sample_rate_count(), 10);
EXPECT_EQ(oakcore_audioparams_supported_sample_rate_at(0), 8000);
EXPECT_EQ(oakcore_audioparams_supported_sample_rate_at(6), 44100);
EXPECT_EQ(oakcore_audioparams_supported_sample_rate_at(7), 48000);
EXPECT_EQ(oakcore_audioparams_supported_sample_rate_at(9), 96000);
EXPECT_EQ(oakcore_audioparams_supported_sample_rate_at(-1), 0);
EXPECT_EQ(oakcore_audioparams_supported_sample_rate_at(10), 0);
}
+93 -113
View File
@@ -1,144 +1,124 @@
/***
Oak - 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/>.
***/
#include <assert.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdio.h>
#include "olive/core/oakcore/bezier.h"
static int double_eq(double a, double b)
TEST(OakcoreBezier, DefaultConstruction)
{
return fabs(a - b) < 1e-9;
OakBezier *b = oakcore_bezier_create();
ASSERT_NE(b, nullptr);
EXPECT_NEAR(oakcore_bezier_x(b), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_y(b), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_x(b), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_y(b), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_x(b), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_y(b), 0.0, 1e-9);
oakcore_bezier_free(b);
}
int main(void)
TEST(OakcoreBezier, SettersAndGetters)
{
// Default constructor: everything zeroed
OakBezier *b = oakcore_bezier_create();
assert(b != NULL);
assert(double_eq(oakcore_bezier_x(b), 0.0));
assert(double_eq(oakcore_bezier_y(b), 0.0));
assert(double_eq(oakcore_bezier_cp1_x(b), 0.0));
assert(double_eq(oakcore_bezier_cp1_y(b), 0.0));
assert(double_eq(oakcore_bezier_cp2_x(b), 0.0));
assert(double_eq(oakcore_bezier_cp2_y(b), 0.0));
// Setters write through to the getters
oakcore_bezier_set_x(b, 1.5);
oakcore_bezier_set_y(b, -2.25);
oakcore_bezier_set_cp1_x(b, 0.1);
oakcore_bezier_set_cp1_y(b, 0.2);
oakcore_bezier_set_cp2_x(b, 0.3);
oakcore_bezier_set_cp2_y(b, 0.4);
assert(double_eq(oakcore_bezier_x(b), 1.5));
assert(double_eq(oakcore_bezier_y(b), -2.25));
assert(double_eq(oakcore_bezier_cp1_x(b), 0.1));
assert(double_eq(oakcore_bezier_cp1_y(b), 0.2));
assert(double_eq(oakcore_bezier_cp2_x(b), 0.3));
assert(double_eq(oakcore_bezier_cp2_y(b), 0.4));
EXPECT_NEAR(oakcore_bezier_x(b), 1.5, 1e-9);
EXPECT_NEAR(oakcore_bezier_y(b), -2.25, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_x(b), 0.1, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_y(b), 0.2, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_x(b), 0.3, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_y(b), 0.4, 1e-9);
oakcore_bezier_free(b);
}
// x/y constructor: control points stay zeroed
TEST(OakcoreBezier, XYConstructor)
{
OakBezier *xy = oakcore_bezier_create_xy(3.0, 4.0);
assert(double_eq(oakcore_bezier_x(xy), 3.0));
assert(double_eq(oakcore_bezier_y(xy), 4.0));
assert(double_eq(oakcore_bezier_cp1_x(xy), 0.0));
assert(double_eq(oakcore_bezier_cp1_y(xy), 0.0));
assert(double_eq(oakcore_bezier_cp2_x(xy), 0.0));
assert(double_eq(oakcore_bezier_cp2_y(xy), 0.0));
EXPECT_NEAR(oakcore_bezier_x(xy), 3.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_y(xy), 4.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_x(xy), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_y(xy), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_x(xy), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_y(xy), 0.0, 1e-9);
oakcore_bezier_free(xy);
}
// Full constructor
OakBezier *full =
oakcore_bezier_create_full(1.0, 2.0, 0.25, 0.5, 0.75, 1.0);
assert(double_eq(oakcore_bezier_x(full), 1.0));
assert(double_eq(oakcore_bezier_y(full), 2.0));
assert(double_eq(oakcore_bezier_cp1_x(full), 0.25));
assert(double_eq(oakcore_bezier_cp1_y(full), 0.5));
assert(double_eq(oakcore_bezier_cp2_x(full), 0.75));
assert(double_eq(oakcore_bezier_cp2_y(full), 1.0));
TEST(OakcoreBezier, FullConstructor)
{
OakBezier *full = oakcore_bezier_create_full(1.0, 2.0, 0.25, 0.5, 0.75, 1.0);
EXPECT_NEAR(oakcore_bezier_x(full), 1.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_y(full), 2.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_x(full), 0.25, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_y(full), 0.5, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_x(full), 0.75, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_y(full), 1.0, 1e-9);
oakcore_bezier_free(full);
}
// Copy: same values, independent storage
TEST(OakcoreBezier, CopyIsIndependent)
{
OakBezier *full = oakcore_bezier_create_full(1.0, 2.0, 0.25, 0.5, 0.75, 1.0);
OakBezier *dup = oakcore_bezier_copy(full);
assert(double_eq(oakcore_bezier_x(dup), 1.0));
assert(double_eq(oakcore_bezier_cp2_y(dup), 1.0));
EXPECT_NEAR(oakcore_bezier_x(dup), 1.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp2_y(dup), 1.0, 1e-9);
oakcore_bezier_set_x(dup, 9.0);
oakcore_bezier_set_cp1_y(dup, 8.0);
assert(double_eq(oakcore_bezier_x(full), 1.0));
assert(double_eq(oakcore_bezier_cp1_y(full), 0.5));
EXPECT_NEAR(oakcore_bezier_x(full), 1.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cp1_y(full), 0.5, 1e-9);
oakcore_bezier_free(dup);
oakcore_bezier_free(full);
}
TEST(OakcoreBezier, QuadraticEvaluation)
{
EXPECT_NEAR(oakcore_bezier_quadratic_tto_y(0.0, 0.5, 1.0, 0.0), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_quadratic_tto_y(0.0, 0.5, 1.0, 1.0), 1.0, 1e-9);
// Quadratic evaluation: endpoints and x->t->y round trip
assert(double_eq(oakcore_bezier_quadratic_tto_y(0.0, 0.5, 1.0, 0.0), 0.0));
assert(double_eq(oakcore_bezier_quadratic_tto_y(0.0, 0.5, 1.0, 1.0), 1.0));
{
const double x = 0.3;
const double t =
oakcore_bezier_quadratic_xto_t(x, 0.0, 0.25, 1.0);
const double y =
oakcore_bezier_quadratic_tto_y(0.0, 0.75, 1.0, t);
assert(t >= 0.0 && t <= 1.0);
// xto_t solves on the x curve; feeding the same x back must hold
assert(fabs(oakcore_bezier_quadratic_tto_y(0.0, 0.25, 1.0, t) - x) <
1e-5);
(void) y;
}
// xto_t clamps x into [a, c] instead of diverging
{
const double t = oakcore_bezier_quadratic_xto_t(x, 0.0, 0.25, 1.0);
EXPECT_GE(t, 0.0);
EXPECT_LE(t, 1.0);
EXPECT_NEAR(oakcore_bezier_quadratic_tto_y(0.0, 0.25, 1.0, t), x, 1e-5);
}
TEST(OakcoreBezier, QuadraticClamps)
{
const double t_lo = oakcore_bezier_quadratic_xto_t(-5.0, 0.0, 0.5, 1.0);
const double t_hi = oakcore_bezier_quadratic_xto_t(5.0, 0.0, 0.5, 1.0);
assert(t_lo >= 0.0 && t_lo <= 1.0);
assert(t_hi >= 0.0 && t_hi <= 1.0);
}
EXPECT_GE(t_lo, 0.0);
EXPECT_LE(t_lo, 1.0);
EXPECT_GE(t_hi, 0.0);
EXPECT_LE(t_hi, 1.0);
}
// Cubic evaluation: endpoints and x->t->y round trip
assert(double_eq(oakcore_bezier_cubic_tto_y(0.0, 0.25, 0.75, 1.0, 0.0),
0.0));
assert(double_eq(oakcore_bezier_cubic_tto_y(0.0, 0.25, 0.75, 1.0, 1.0),
1.0));
{
TEST(OakcoreBezier, CubicEvaluation)
{
EXPECT_NEAR(oakcore_bezier_cubic_tto_y(0.0, 0.25, 0.75, 1.0, 0.0), 0.0, 1e-9);
EXPECT_NEAR(oakcore_bezier_cubic_tto_y(0.0, 0.25, 0.75, 1.0, 1.0), 1.0, 1e-9);
const double x = 0.6;
const double t =
oakcore_bezier_cubic_xto_t(x, 0.0, 0.1, 0.9, 1.0);
const double y =
oakcore_bezier_cubic_tto_y(0.0, 0.8, 0.2, 1.0, t);
assert(t >= 0.0 && t <= 1.0);
assert(fabs(oakcore_bezier_cubic_tto_y(0.0, 0.1, 0.9, 1.0, t) - x) <
1e-5);
(void) y;
}
// xto_t clamps x into [a, d] instead of diverging
{
const double t_lo =
oakcore_bezier_cubic_xto_t(-5.0, 0.0, 0.25, 0.75, 1.0);
const double t_hi =
oakcore_bezier_cubic_xto_t(5.0, 0.0, 0.25, 0.75, 1.0);
assert(t_lo >= 0.0 && t_lo <= 1.0);
assert(t_hi >= 0.0 && t_hi <= 1.0);
}
const double t = oakcore_bezier_cubic_xto_t(x, 0.0, 0.1, 0.9, 1.0);
EXPECT_GE(t, 0.0);
EXPECT_LE(t, 1.0);
EXPECT_NEAR(oakcore_bezier_cubic_tto_y(0.0, 0.1, 0.9, 1.0, t), x, 1e-5);
}
TEST(OakcoreBezier, CubicClamps)
{
const double t_lo = oakcore_bezier_cubic_xto_t(-5.0, 0.0, 0.25, 0.75, 1.0);
const double t_hi = oakcore_bezier_cubic_xto_t(5.0, 0.0, 0.25, 0.75, 1.0);
EXPECT_GE(t_lo, 0.0);
EXPECT_LE(t_lo, 1.0);
EXPECT_GE(t_hi, 0.0);
EXPECT_LE(t_hi, 1.0);
}
// Ownership: every handle released exactly once
oakcore_bezier_free(b);
oakcore_bezier_free(xy);
oakcore_bezier_free(full);
oakcore_bezier_free(dup);
TEST(OakcoreBezier, FreeNull)
{
oakcore_bezier_free(NULL);
printf("oakcore_bezier_test: all assertions passed\n");
return 0;
}
+99 -248
View File
@@ -1,351 +1,202 @@
/***
Oak - 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/>.
***/
/**
* @file oakcore_color_test.cpp
* @brief Pure C API test for the OakColor ABI
*
* Exercises every function of oakcore/color.h through the C boundary only:
* no C++ wrapper, no test framework, just main() + assert().
*/
#include <gtest/gtest.h>
#include <math.h>
#include <stdint.h>
#include <string.h>
#include "olive/core/oakcore/color.h"
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* PixelFormat::Format values (render/pixelformat.h) */
enum { PF_U8 = 0, PF_U10 = 1, PF_U16 = 2, PF_F16 = 3, PF_F32 = 4 };
static int feq(float a, float b)
{
return fabsf(a - b) < 1e-5f;
}
static bool feq(float a, float b) { return fabsf(a - b) < 1e-5f; }
static void test_create_and_channels(void)
TEST(OakcoreColor, CreateAndChannels)
{
/* Default construction: all channels zero */
OakColor *c = oakcore_color_create();
assert(c != NULL);
assert(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 0.0f));
ASSERT_NE(c, nullptr);
EXPECT_TRUE(feq(oakcore_color_red(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_green(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_blue(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_alpha(c), 0.0f));
oakcore_color_free(c);
/* RGBA construction + getters */
c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f);
assert(feq(oakcore_color_red(c), 0.25f));
assert(feq(oakcore_color_green(c), 0.5f));
assert(feq(oakcore_color_blue(c), 0.75f));
assert(feq(oakcore_color_alpha(c), 1.0f));
EXPECT_TRUE(feq(oakcore_color_red(c), 0.25f));
EXPECT_TRUE(feq(oakcore_color_green(c), 0.5f));
EXPECT_TRUE(feq(oakcore_color_blue(c), 0.75f));
EXPECT_TRUE(feq(oakcore_color_alpha(c), 1.0f));
/* Setters, including out-of-gamut HDR values */
oakcore_color_set_red(c, -0.5f);
oakcore_color_set_green(c, 2.0f);
oakcore_color_set_blue(c, 0.0f);
oakcore_color_set_alpha(c, 0.125f);
assert(feq(oakcore_color_red(c), -0.5f));
assert(feq(oakcore_color_green(c), 2.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 0.125f));
EXPECT_TRUE(feq(oakcore_color_red(c), -0.5f));
EXPECT_TRUE(feq(oakcore_color_green(c), 2.0f));
EXPECT_TRUE(feq(oakcore_color_blue(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_alpha(c), 0.125f));
oakcore_color_free(c);
}
static void test_copy_and_ownership(void)
TEST(OakcoreColor, CopyAndOwnership)
{
OakColor *c = oakcore_color_create_rgba(0.1f, 0.2f, 0.3f, 0.4f);
OakColor *copy = oakcore_color_copy(c);
assert(copy != NULL);
assert(feq(oakcore_color_red(copy), 0.1f));
assert(feq(oakcore_color_green(copy), 0.2f));
assert(feq(oakcore_color_blue(copy), 0.3f));
assert(feq(oakcore_color_alpha(copy), 0.4f));
ASSERT_NE(copy, nullptr);
EXPECT_TRUE(feq(oakcore_color_red(copy), 0.1f));
EXPECT_TRUE(feq(oakcore_color_alpha(copy), 0.4f));
/* The copy is independent from the original */
oakcore_color_set_red(copy, 0.9f);
assert(feq(oakcore_color_red(copy), 0.9f));
assert(feq(oakcore_color_red(c), 0.1f));
EXPECT_TRUE(feq(oakcore_color_red(copy), 0.9f));
EXPECT_TRUE(feq(oakcore_color_red(c), 0.1f));
oakcore_color_free(copy);
oakcore_color_free(c);
/* Releasing a NULL handle must be safe */
oakcore_color_free(NULL);
}
static void test_hsv(void)
TEST(OakcoreColor, HSV)
{
/* Primary colors from HSV */
OakColor *c = oakcore_color_from_hsv(0.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_red(c), 1.0f));
assert(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 1.0f));
EXPECT_TRUE(feq(oakcore_color_red(c), 1.0f));
EXPECT_TRUE(feq(oakcore_color_green(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_blue(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_alpha(c), 1.0f));
oakcore_color_free(c);
c = oakcore_color_from_hsv(120.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 1.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_red(c), 0.0f));
EXPECT_TRUE(feq(oakcore_color_green(c), 1.0f));
oakcore_color_free(c);
c = oakcore_color_from_hsv(240.0f, 1.0f, 0.5f);
assert(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.5f));
EXPECT_TRUE(feq(oakcore_color_blue(c), 0.5f));
oakcore_color_free(c);
/* to_hsv of pure red and gray */
c = oakcore_color_create_rgba(1.0f, 0.0f, 0.0f, 1.0f);
float h = -1.0f, s = -1.0f, v = -1.0f;
float h, s, v;
oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 0.0f));
assert(feq(s, 1.0f));
assert(feq(v, 1.0f));
assert(feq(oakcore_color_hsv_hue(c), h));
assert(feq(oakcore_color_hsv_saturation(c), s));
assert(feq(oakcore_color_value(c), v));
EXPECT_TRUE(feq(h, 0.0f));
EXPECT_TRUE(feq(s, 1.0f));
EXPECT_TRUE(feq(v, 1.0f));
EXPECT_TRUE(feq(oakcore_color_hsv_hue(c), h));
oakcore_color_free(c);
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f);
oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 0.0f));
assert(feq(s, 0.0f));
assert(feq(v, 0.5f));
oakcore_color_free(c);
/* Roundtrip: color -> hsv -> color */
// Roundtrip
c = oakcore_color_create_rgba(0.2f, 0.4f, 0.8f, 1.0f);
oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 220.0f));
assert(feq(s, 0.75f));
assert(feq(v, 0.8f));
EXPECT_TRUE(feq(h, 220.0f));
EXPECT_TRUE(feq(s, 0.75f));
EXPECT_TRUE(feq(v, 0.8f));
OakColor *rt = oakcore_color_from_hsv(h, s, v);
assert(feq(oakcore_color_red(rt), 0.2f));
assert(feq(oakcore_color_green(rt), 0.4f));
assert(feq(oakcore_color_blue(rt), 0.8f));
EXPECT_TRUE(feq(oakcore_color_red(rt), 0.2f));
EXPECT_TRUE(feq(oakcore_color_green(rt), 0.4f));
EXPECT_TRUE(feq(oakcore_color_blue(rt), 0.8f));
oakcore_color_free(rt);
oakcore_color_free(c);
}
static void test_hsl(void)
TEST(OakcoreColor, HSL)
{
/* Pure red: l = 0.5, s = 1, h = 0 */
OakColor *c = oakcore_color_create_rgba(1.0f, 0.0f, 0.0f, 1.0f);
float h = -1.0f, s = -1.0f, l = -1.0f;
float h, s, l;
oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 0.0f));
assert(feq(s, 1.0f));
assert(feq(l, 0.5f));
assert(feq(oakcore_color_hsl_hue(c), h));
assert(feq(oakcore_color_hsl_saturation(c), s));
assert(feq(oakcore_color_lightness(c), l));
EXPECT_TRUE(feq(h, 0.0f));
EXPECT_TRUE(feq(s, 1.0f));
EXPECT_TRUE(feq(l, 0.5f));
oakcore_color_free(c);
/* Gray: zero saturation */
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f);
oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 0.0f));
assert(feq(s, 0.0f));
assert(feq(l, 0.5f));
oakcore_color_free(c);
/* Arbitrary color */
c = oakcore_color_create_rgba(0.2f, 0.4f, 0.8f, 1.0f);
oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 220.0f));
assert(feq(s, 0.6f));
assert(feq(l, 0.5f));
EXPECT_TRUE(feq(h, 220.0f));
EXPECT_TRUE(feq(s, 0.6f));
EXPECT_TRUE(feq(l, 0.5f));
oakcore_color_free(c);
}
static void test_data_access(void)
TEST(OakcoreColor, DataAccess)
{
OakColor *c = oakcore_color_create_rgba(0.1f, 0.2f, 0.3f, 0.4f);
/* Const read access */
const float *cd = oakcore_color_const_data(c);
assert(cd != NULL);
assert(feq(cd[0], 0.1f));
assert(feq(cd[1], 0.2f));
assert(feq(cd[2], 0.3f));
assert(feq(cd[3], 0.4f));
ASSERT_NE(cd, nullptr);
EXPECT_TRUE(feq(cd[0], 0.1f));
EXPECT_TRUE(feq(cd[3], 0.4f));
/* Mutable write access */
float *d = oakcore_color_data(c);
assert(d != NULL);
ASSERT_NE(d, nullptr);
d[0] = 0.125f;
d[3] = 1.0f;
assert(feq(oakcore_color_red(c), 0.125f));
assert(feq(oakcore_color_alpha(c), 1.0f));
EXPECT_TRUE(feq(oakcore_color_red(c), 0.125f));
EXPECT_TRUE(feq(oakcore_color_alpha(c), 1.0f));
oakcore_color_free(c);
}
static void test_pixel_data(void)
TEST(OakcoreColor, PixelDataU8)
{
char buf[16];
memset(buf, 0, sizeof(buf));
/* u8 roundtrip, 4 channels */
OakColor *c = oakcore_color_create_rgba(1.0f, 0.0f, 1.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_U8, 4);
assert((uint8_t)buf[0] == 255);
assert((uint8_t)buf[1] == 0);
assert((uint8_t)buf[2] == 255);
assert((uint8_t)buf[3] == 255);
EXPECT_EQ((uint8_t)buf[0], 255);
EXPECT_EQ((uint8_t)buf[1], 0);
EXPECT_EQ((uint8_t)buf[2], 255);
OakColor *back = oakcore_color_from_data(buf, PF_U8, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.0f));
assert(feq(oakcore_color_blue(back), 1.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
/* u8 with only 3 channels: alpha stays 0 */
oakcore_color_to_data(c, buf, PF_U8, 3);
back = oakcore_color_from_data(buf, PF_U8, 3);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.0f));
assert(feq(oakcore_color_blue(back), 1.0f));
assert(feq(oakcore_color_alpha(back), 0.0f));
oakcore_color_free(back);
/* u16 roundtrip */
oakcore_color_to_data(c, buf, PF_U16, 4);
assert(((uint16_t *)buf)[0] == 65535);
assert(((uint16_t *)buf)[1] == 0);
back = oakcore_color_from_data(buf, PF_U16, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
oakcore_color_free(c);
/* f32 roundtrip, exact */
c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f);
oakcore_color_to_data(c, buf, PF_F32, 4);
assert(feq(((float *)buf)[0], 0.25f));
assert(feq(((float *)buf)[3], 1.0f));
back = oakcore_color_from_data(buf, PF_F32, 4);
assert(feq(oakcore_color_red(back), 0.25f));
assert(feq(oakcore_color_green(back), 0.5f));
assert(feq(oakcore_color_blue(back), 0.75f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
/* f16 roundtrip: 1.0 = 0x3C00, 0.5 = 0x3800 in IEEE half */
c = oakcore_color_create_rgba(1.0f, 0.5f, 0.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_F16, 4);
assert(((uint16_t *)buf)[0] == 0x3C00);
assert(((uint16_t *)buf)[1] == 0x3800);
back = oakcore_color_from_data(buf, PF_F16, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.5f));
assert(feq(oakcore_color_blue(back), 0.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
oakcore_color_free(back);
/* u10 packed 4-channel roundtrip: all ones packs to 0xFFFFFFFF */
c = oakcore_color_create_rgba(1.0f, 1.0f, 1.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_U10, 4);
assert(((uint32_t *)buf)[0] == 0xFFFFFFFFu);
back = oakcore_color_from_data(buf, PF_U10, 4);
assert(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 1.0f));
assert(feq(oakcore_color_blue(back), 1.0f));
assert(feq(oakcore_color_alpha(back), 1.0f));
EXPECT_TRUE(feq(oakcore_color_red(back), 1.0f));
EXPECT_TRUE(feq(oakcore_color_green(back), 0.0f));
oakcore_color_free(back);
oakcore_color_free(c);
}
static void test_luminance(void)
TEST(OakcoreColor, PixelDataF32AndF16)
{
/* (2r + b + 3g) / 6 */
OakColor *c = oakcore_color_create_rgba(1.0f, 1.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c), 1.0f));
char buf[16];
OakColor *c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f);
oakcore_color_to_data(c, buf, PF_F32, 4);
EXPECT_TRUE(feq(((float *)buf)[0], 0.25f));
OakColor *back = oakcore_color_from_data(buf, PF_F32, 4);
EXPECT_TRUE(feq(oakcore_color_green(back), 0.5f));
oakcore_color_free(back);
oakcore_color_free(c);
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c), 0.5f));
c = oakcore_color_create_rgba(1.0f, 0.5f, 0.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_F16, 4);
EXPECT_EQ(((uint16_t *)buf)[0], 0x3C00);
EXPECT_EQ(((uint16_t *)buf)[1], 0x3800);
back = oakcore_color_from_data(buf, PF_F16, 4);
EXPECT_TRUE(feq(oakcore_color_red(back), 1.0f));
EXPECT_TRUE(feq(oakcore_color_green(back), 0.5f));
oakcore_color_free(back);
oakcore_color_free(c);
}
TEST(OakcoreColor, Luminance)
{
OakColor *c = oakcore_color_create_rgba(1.0f, 1.0f, 1.0f, 1.0f);
EXPECT_TRUE(feq(oakcore_color_get_rough_luminance(c), 1.0f));
oakcore_color_free(c);
c = oakcore_color_create_rgba(0.6f, 0.2f, 0.4f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c),
EXPECT_TRUE(feq(oakcore_color_get_rough_luminance(c),
(2.0f * 0.6f + 0.4f + 3.0f * 0.2f) / 6.0f));
oakcore_color_free(c);
}
static void test_math(void)
TEST(OakcoreColor, MathOps)
{
OakColor *a = oakcore_color_create_rgba(0.1f, 0.2f, 0.3f, 0.4f);
OakColor *b = oakcore_color_create_rgba(0.4f, 0.3f, 0.2f, 0.1f);
/* Color +=/-= Color */
oakcore_color_add_assign(a, b);
assert(feq(oakcore_color_red(a), 0.5f));
assert(feq(oakcore_color_green(a), 0.5f));
assert(feq(oakcore_color_blue(a), 0.5f));
assert(feq(oakcore_color_alpha(a), 0.5f));
EXPECT_TRUE(feq(oakcore_color_red(a), 0.5f));
EXPECT_TRUE(feq(oakcore_color_alpha(a), 0.5f));
oakcore_color_sub_assign(a, b);
assert(feq(oakcore_color_red(a), 0.1f));
assert(feq(oakcore_color_green(a), 0.2f));
assert(feq(oakcore_color_blue(a), 0.3f));
assert(feq(oakcore_color_alpha(a), 0.4f));
EXPECT_TRUE(feq(oakcore_color_red(a), 0.1f));
/* Scalar assign operators (apply to all channels) */
oakcore_color_add_scalar_assign(a, 1.0f);
assert(feq(oakcore_color_red(a), 1.1f));
assert(feq(oakcore_color_alpha(a), 1.4f));
oakcore_color_sub_scalar_assign(a, 0.6f);
assert(feq(oakcore_color_red(a), 0.5f));
assert(feq(oakcore_color_alpha(a), 0.8f));
oakcore_color_mul_scalar_assign(a, 2.0f);
assert(feq(oakcore_color_red(a), 1.0f));
assert(feq(oakcore_color_green(a), 1.2f));
assert(feq(oakcore_color_blue(a), 1.4f));
assert(feq(oakcore_color_alpha(a), 1.6f));
oakcore_color_div_scalar_assign(a, 4.0f);
assert(feq(oakcore_color_red(a), 0.25f));
assert(feq(oakcore_color_green(a), 0.3f));
assert(feq(oakcore_color_blue(a), 0.35f));
assert(feq(oakcore_color_alpha(a), 0.4f));
EXPECT_TRUE(feq(oakcore_color_red(a), 0.2f));
EXPECT_TRUE(feq(oakcore_color_green(a), 0.4f));
oakcore_color_div_scalar_assign(a, 2.0f);
EXPECT_TRUE(feq(oakcore_color_red(a), 0.1f));
oakcore_color_free(b);
oakcore_color_free(a);
}
int main(void)
{
test_create_and_channels();
test_copy_and_ownership();
test_hsv();
test_hsl();
test_data_access();
test_pixel_data();
test_luminance();
test_math();
printf("oakcore_color_test: all assertions passed\n");
return 0;
}
+83 -96
View File
@@ -1,126 +1,113 @@
/***
Oak - 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/>.
***/
#include "oakcore/fractionutils.h"
#include <assert.h>
#include <gtest/gtest.h>
#include <limits.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
/**
* @file oakcore_fractionutils_test.cpp
* @brief Pure C API test for the fraction utility functions
*
* Exercises every oakcore_fractionutils_* function directly, without the
* C++ wrapper and without a test framework.
*/
int main()
#include "oakcore/fractionutils.h"
TEST(OakcoreFractionUtils, ReduceFraction)
{
// reduce_fraction: greatest common divisor is divided out
{
int64_t num = 6, den = 9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == 2 && den == 3);
}
EXPECT_EQ(num, 2);
EXPECT_EQ(den, 3);
}
// reduce_fraction: the sign is normalized into the numerator
TEST(OakcoreFractionUtils, ReduceFractionSignNormalization)
{
{
int64_t num = -6, den = 9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == -2 && den == 3);
EXPECT_EQ(num, -2);
EXPECT_EQ(den, 3);
}
{
int64_t num = 6, den = -9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == -2 && den == 3);
EXPECT_EQ(num, -2);
EXPECT_EQ(den, 3);
}
}
// reduce_fraction: a zero denominator is preserved, numerator zeroed
{
TEST(OakcoreFractionUtils, ReduceFractionZeroDenominator)
{
int64_t num = 42, den = 0;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == 0 && den == 0);
}
EXPECT_EQ(num, 0);
EXPECT_EQ(den, 0);
}
// reduce_fraction: a zero numerator reduces to 0/1
{
TEST(OakcoreFractionUtils, ReduceFractionZeroNumerator)
{
int64_t num = 0, den = 7;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX);
assert(num == 0 && den == 1);
}
EXPECT_EQ(num, 0);
EXPECT_EQ(den, 1);
}
// reduce_fraction: exact reduction well within max
{
TEST(OakcoreFractionUtils, ReduceFractionExactWithinMax)
{
int64_t num = 1000000, den = 1000000;
oakcore_fractionutils_reduce_fraction(&num, &den, 100);
assert(num == 1 && den == 1);
}
EXPECT_EQ(num, 1);
EXPECT_EQ(den, 1);
}
// reduce_fraction: continued-fraction approximation respects max
{
TEST(OakcoreFractionUtils, ReduceFractionApproximation)
{
int64_t num = 1048576, den = 1000000;
oakcore_fractionutils_reduce_fraction(&num, &den, 10000);
assert(num > 0 && num <= 10000 && den > 0 && den <= 10000);
EXPECT_GT(num, 0);
EXPECT_LE(num, 10000);
EXPECT_GT(den, 0);
EXPECT_LE(den, 10000);
const double approx = double(num) / double(den);
assert(fabs(approx - 1.048576) < 0.001);
}
// compare_fractions: three-way result -1 / 0 / 1
assert(oakcore_fractionutils_compare_fractions(1, 3, 1, 2) == -1);
assert(oakcore_fractionutils_compare_fractions(1, 2, 2, 4) == 0);
assert(oakcore_fractionutils_compare_fractions(2, 3, 1, 2) == 1);
// compare_fractions: meaningless degenerate comparison returns INT_MIN
assert(oakcore_fractionutils_compare_fractions(0, 0, 0, 5) == INT_MIN);
// rescale_rnd: exact division needs no rounding
assert(oakcore_fractionutils_rescale_rnd(100, 3, 4,
OAK_FRACTION_ROUNDING_NEAR_INF) == 75);
// rescale_rnd: round to nearest, halfway cases away from zero
assert(oakcore_fractionutils_rescale_rnd(5, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF) == 3);
assert(oakcore_fractionutils_rescale_rnd(-5, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF) == -3);
assert(oakcore_fractionutils_rescale_rnd(7, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF) == 4);
// rescale_rnd: round toward positive infinity
assert(oakcore_fractionutils_rescale_rnd(5, 1, 2,
OAK_FRACTION_ROUNDING_UP) == 3);
assert(oakcore_fractionutils_rescale_rnd(-5, 1, 2,
OAK_FRACTION_ROUNDING_UP) == -2);
assert(oakcore_fractionutils_rescale_rnd(4, 1, 2,
OAK_FRACTION_ROUNDING_UP) == 2);
// rescale_rnd: a negative divisor is normalized into the multiplier
assert(oakcore_fractionutils_rescale_rnd(10, 1, -2,
OAK_FRACTION_ROUNDING_NEAR_INF) == -5);
// rescale_rnd: large intermediates stay exact (128-bit path)
assert(oakcore_fractionutils_rescale_rnd(INT64_MAX / 2, 4, 2,
OAK_FRACTION_ROUNDING_NEAR_INF)
== INT64_MAX - 1);
printf("oakcore_fractionutils_test: all assertions passed\n");
return 0;
EXPECT_NEAR(approx, 1.048576, 0.001);
}
TEST(OakcoreFractionUtils, CompareFractions)
{
EXPECT_EQ(oakcore_fractionutils_compare_fractions(1, 3, 1, 2), -1);
EXPECT_EQ(oakcore_fractionutils_compare_fractions(1, 2, 2, 4), 0);
EXPECT_EQ(oakcore_fractionutils_compare_fractions(2, 3, 1, 2), 1);
EXPECT_EQ(oakcore_fractionutils_compare_fractions(0, 0, 0, 5), INT_MIN);
}
TEST(OakcoreFractionUtils, RescaleRndExact)
{
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(100, 3, 4,
OAK_FRACTION_ROUNDING_NEAR_INF), 75);
}
TEST(OakcoreFractionUtils, RescaleRndNearInf)
{
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(5, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF), 3);
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(-5, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF), -3);
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(7, 1, 2,
OAK_FRACTION_ROUNDING_NEAR_INF), 4);
}
TEST(OakcoreFractionUtils, RescaleRndUp)
{
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(5, 1, 2,
OAK_FRACTION_ROUNDING_UP), 3);
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(-5, 1, 2,
OAK_FRACTION_ROUNDING_UP), -2);
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(4, 1, 2,
OAK_FRACTION_ROUNDING_UP), 2);
}
TEST(OakcoreFractionUtils, RescaleRndNegativeDivisor)
{
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(10, 1, -2,
OAK_FRACTION_ROUNDING_NEAR_INF), -5);
}
TEST(OakcoreFractionUtils, RescaleRndLargeIntermediates)
{
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(INT64_MAX / 2, 4, 2,
OAK_FRACTION_ROUNDING_NEAR_INF),
INT64_MAX - 1);
}
+98 -66
View File
@@ -1,110 +1,142 @@
#include <assert.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "olive/core/oakcore/rational.h"
int main()
TEST(OakcoreRational, CreateNdReducesAndNormalizesSigns)
{
// create_nd reduces and normalizes signs
OakRational *r = oakcore_rational_create_nd(2, 4);
assert(r != NULL);
assert(oakcore_rational_numerator(r) == 1);
assert(oakcore_rational_denominator(r) == 2);
ASSERT_NE(r, nullptr);
EXPECT_EQ(oakcore_rational_numerator(r), 1);
EXPECT_EQ(oakcore_rational_denominator(r), 2);
OakRational *neg = oakcore_rational_create_nd(1, -2);
assert(oakcore_rational_numerator(neg) == -1);
assert(oakcore_rational_denominator(neg) == 2);
EXPECT_EQ(oakcore_rational_numerator(neg), -1);
EXPECT_EQ(oakcore_rational_denominator(neg), 2);
oakcore_rational_free(neg);
oakcore_rational_free(r);
}
// create defaults to n/1
TEST(OakcoreRational, CreateDefaultsToNOver1)
{
OakRational *five = oakcore_rational_create(5);
assert(oakcore_rational_numerator(five) == 5);
assert(oakcore_rational_denominator(five) == 1);
EXPECT_EQ(oakcore_rational_numerator(five), 5);
EXPECT_EQ(oakcore_rational_denominator(five), 1);
oakcore_rational_free(five);
}
// NaN
TEST(OakcoreRational, NaN)
{
OakRational *nan = oakcore_rational_create_nan();
assert(oakcore_rational_is_nan(nan) == 1);
assert(oakcore_rational_is_null(nan) == 1);
assert(isnan(oakcore_rational_to_double(nan)));
EXPECT_EQ(oakcore_rational_is_nan(nan), 1);
EXPECT_EQ(oakcore_rational_is_null(nan), 1);
EXPECT_TRUE(isnan(oakcore_rational_to_double(nan)));
oakcore_rational_free(nan);
}
// copy is deep
TEST(OakcoreRational, CopyIsDeep)
{
OakRational *r = oakcore_rational_create_nd(1, 2);
OakRational *copy = oakcore_rational_copy(r);
assert(copy != r);
assert(oakcore_rational_compare(copy, r) == 0);
EXPECT_NE(copy, r);
EXPECT_EQ(oakcore_rational_compare(copy, r), 0);
oakcore_rational_add_assign(copy, r);
assert(oakcore_rational_numerator(copy) == 1);
assert(oakcore_rational_denominator(copy) == 1);
assert(oakcore_rational_numerator(r) == 1);
assert(oakcore_rational_denominator(r) == 2);
EXPECT_EQ(oakcore_rational_numerator(copy), 1);
EXPECT_EQ(oakcore_rational_denominator(copy), 1);
// Original unchanged
EXPECT_EQ(oakcore_rational_numerator(r), 1);
EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_free(copy);
oakcore_rational_free(r);
}
TEST(OakcoreRational, ToDoubleAndToString)
{
OakRational *r = oakcore_rational_create_nd(1, 2);
EXPECT_NEAR(oakcore_rational_to_double(r), 0.5, 1e-12);
EXPECT_EQ(oakcore_rational_to_string(r, NULL, 0), 3);
// to_double / to_string
assert(fabs(oakcore_rational_to_double(r) - 0.5) < 1e-12);
char buf[32];
int needed = oakcore_rational_to_string(r, NULL, 0);
assert(needed == 3);
assert(oakcore_rational_to_string(r, buf, sizeof(buf)) == 3);
assert(strcmp(buf, "1/2") == 0);
assert(oakcore_rational_to_string(r, buf, 2) == 3); // truncated write
assert(strcmp(buf, "1") == 0);
EXPECT_EQ(oakcore_rational_to_string(r, buf, sizeof(buf)), 3);
EXPECT_STREQ(buf, "1/2");
// from_double / from_string
// Truncated write
EXPECT_EQ(oakcore_rational_to_string(r, buf, 2), 3);
EXPECT_STREQ(buf, "1");
oakcore_rational_free(r);
}
TEST(OakcoreRational, FromDoubleAndFromString)
{
int ok = 0;
OakRational *r = oakcore_rational_create_nd(1, 2);
OakRational *half = oakcore_rational_from_double(0.5, &ok);
assert(ok == 1);
assert(oakcore_rational_compare(half, r) == 0);
EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_rational_compare(half, r), 0);
oakcore_rational_free(half);
OakRational *bad = oakcore_rational_from_double(NAN, &ok);
assert(ok == 0);
assert(oakcore_rational_is_nan(bad));
EXPECT_EQ(ok, 0);
EXPECT_TRUE(oakcore_rational_is_nan(bad));
oakcore_rational_free(bad);
OakRational *parsed = oakcore_rational_from_string("3/4", &ok);
assert(ok == 1);
assert(fabs(oakcore_rational_to_double(parsed) - 0.75) < 1e-12);
EXPECT_EQ(ok, 1);
EXPECT_NEAR(oakcore_rational_to_double(parsed), 0.75, 1e-12);
oakcore_rational_free(parsed);
OakRational *unparsed = oakcore_rational_from_string("1/2/3", &ok);
assert(ok == 0);
EXPECT_EQ(ok, 0);
oakcore_rational_free(unparsed);
// flip
OakRational *flip = oakcore_rational_flipped(r);
assert(oakcore_rational_numerator(flip) == 2);
assert(oakcore_rational_denominator(flip) == 1);
oakcore_rational_flip(flip);
assert(oakcore_rational_compare(flip, r) == 0);
oakcore_rational_free(flip);
oakcore_rational_free(r);
}
// arithmetic assignments
TEST(OakcoreRational, Flip)
{
OakRational *r = oakcore_rational_create_nd(1, 2);
OakRational *flip = oakcore_rational_flipped(r);
EXPECT_EQ(oakcore_rational_numerator(flip), 2);
EXPECT_EQ(oakcore_rational_denominator(flip), 1);
oakcore_rational_flip(flip);
EXPECT_EQ(oakcore_rational_compare(flip, r), 0);
oakcore_rational_free(flip);
oakcore_rational_free(r);
}
TEST(OakcoreRational, ArithmeticAssignments)
{
OakRational *a = oakcore_rational_create_nd(1, 3);
OakRational *b = oakcore_rational_create_nd(1, 6);
oakcore_rational_add_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 2);
oakcore_rational_sub_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 3);
oakcore_rational_mul_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 18);
oakcore_rational_div_assign(a, b);
assert(oakcore_rational_numerator(a) == 1);
assert(oakcore_rational_denominator(a) == 3);
// compare ordering
assert(oakcore_rational_compare(b, a) < 0);
assert(oakcore_rational_compare(a, b) > 0);
oakcore_rational_add_assign(a, b);
EXPECT_EQ(oakcore_rational_numerator(a), 1);
EXPECT_EQ(oakcore_rational_denominator(a), 2);
oakcore_rational_sub_assign(a, b);
EXPECT_EQ(oakcore_rational_numerator(a), 1);
EXPECT_EQ(oakcore_rational_denominator(a), 3);
oakcore_rational_mul_assign(a, b);
EXPECT_EQ(oakcore_rational_numerator(a), 1);
EXPECT_EQ(oakcore_rational_denominator(a), 18);
oakcore_rational_div_assign(a, b);
EXPECT_EQ(oakcore_rational_numerator(a), 1);
EXPECT_EQ(oakcore_rational_denominator(a), 3);
// Compare ordering
EXPECT_LT(oakcore_rational_compare(b, a), 0);
EXPECT_GT(oakcore_rational_compare(a, b), 0);
oakcore_rational_free(a);
oakcore_rational_free(b);
oakcore_rational_free(r);
printf("oakcore_rational_test: all assertions passed\n");
return 0;
}
+195 -181
View File
@@ -1,33 +1,9 @@
/***
Oak - 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/>.
***/
#include <assert.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include "olive/core/oakcore/samplebuffer.h"
/* These functions are declared in oakcore/audioparams.h (a parallel
* delivery); they are re-declared here so that this test only includes the
* samplebuffer C header it is meant to exercise. */
extern "C" {
OakAudioParams *oakcore_audioparams_create(int sample_rate,
uint64_t channel_layout,
@@ -37,163 +13,186 @@ int oakcore_audioparams_sample_rate(const OakAudioParams *self);
int oakcore_audioparams_channel_count(const OakAudioParams *self);
}
/* Values mirror render/channellayout.h (k_channel_layout_stereo) and
* render/sampleformat.h (SampleFormat::f32_p) on the library side. */
#define SAMPLE_RATE 48000
#define CHANNEL_LAYOUT_STEREO 0x3
#define FORMAT_F32P 4
static int float_eq(float a, float b)
{
return fabsf(a - b) < 1e-6f;
}
static bool feq(float a, float b) { return fabsf(a - b) < 1e-6f; }
int main(void)
class OakcoreSampleBufferTest : public ::testing::Test {
protected:
void SetUp() override
{
stereo_ = oakcore_audioparams_create(SAMPLE_RATE, CHANNEL_LAYOUT_STEREO, FORMAT_F32P);
}
void TearDown() override
{
oakcore_audioparams_free(stereo_);
}
OakAudioParams *stereo_;
};
TEST(OakcoreSampleBuffer, DefaultConstruction)
{
/* --- Default construction: empty, unallocated --- */
OakSampleBuffer *def = oakcore_samplebuffer_create();
assert(def != NULL);
assert(oakcore_samplebuffer_is_allocated(def) == 0);
assert(oakcore_samplebuffer_sample_count(def) == 0);
assert(oakcore_samplebuffer_channel_count(def) == 0);
assert(oakcore_samplebuffer_data(def, 0) == NULL);
ASSERT_NE(def, nullptr);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(def), 0);
EXPECT_EQ(oakcore_samplebuffer_sample_count(def), 0);
EXPECT_EQ(oakcore_samplebuffer_channel_count(def), 0);
EXPECT_EQ(oakcore_samplebuffer_data(def, 0), nullptr);
/* audio_params() hands back an owned copy of the (invalid) defaults */
OakAudioParams *def_params = oakcore_samplebuffer_audio_params(def);
assert(def_params != NULL);
assert(oakcore_audioparams_sample_rate(def_params) == 0);
ASSERT_NE(def_params, nullptr);
EXPECT_EQ(oakcore_audioparams_sample_rate(def_params), 0);
oakcore_audioparams_free(def_params);
/* Operations that need an allocation warn and leave the buffer alone */
// Operations that need an allocation warn and leave the buffer alone
oakcore_samplebuffer_allocate(def);
assert(oakcore_samplebuffer_is_allocated(def) == 0);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(def), 0);
oakcore_samplebuffer_silence(def);
oakcore_samplebuffer_reverse(def);
assert(oakcore_samplebuffer_is_allocated(def) == 0);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(def), 0);
oakcore_samplebuffer_free(def);
}
/* --- Construction from audio params + sample count --- */
OakAudioParams *stereo = oakcore_audioparams_create(
SAMPLE_RATE, CHANNEL_LAYOUT_STEREO, FORMAT_F32P);
assert(stereo != NULL);
TEST_F(OakcoreSampleBufferTest, CreateFromParamsAndCount)
{
OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo_, 100);
ASSERT_NE(buf, nullptr);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(buf), 1);
EXPECT_EQ(oakcore_samplebuffer_sample_count(buf), 100);
EXPECT_EQ(oakcore_samplebuffer_channel_count(buf), 2);
OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo, 100);
assert(oakcore_samplebuffer_is_allocated(buf) == 1);
assert(oakcore_samplebuffer_sample_count(buf) == 100);
assert(oakcore_samplebuffer_channel_count(buf) == 2);
/* A fresh allocation is silent */
// A fresh allocation is silent
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(buf, ch);
assert(d != NULL);
ASSERT_NE(d, nullptr);
for (int i = 0; i < 100; i++) {
assert(float_eq(d[i], 0.0f));
EXPECT_TRUE(feq(d[i], 0.0f));
}
}
/* audio_params() copy reflects the construction parameters */
OakAudioParams *p = oakcore_samplebuffer_audio_params(buf);
assert(oakcore_audioparams_sample_rate(p) == SAMPLE_RATE);
assert(oakcore_audioparams_channel_count(p) == 2);
EXPECT_EQ(oakcore_audioparams_sample_rate(p), SAMPLE_RATE);
EXPECT_EQ(oakcore_audioparams_channel_count(p), 2);
oakcore_audioparams_free(p);
/* data() rejects out-of-range channels */
assert(oakcore_samplebuffer_data(buf, -1) == NULL);
assert(oakcore_samplebuffer_data(buf, 2) == NULL);
// data() rejects out-of-range channels
EXPECT_EQ(oakcore_samplebuffer_data(buf, -1), nullptr);
EXPECT_EQ(oakcore_samplebuffer_data(buf, 2), nullptr);
oakcore_samplebuffer_free(buf);
}
/* --- Construction from audio params + rational length (0.5s) --- */
TEST_F(OakcoreSampleBufferTest, CreateFromRationalLength)
{
OakRational *half_sec = oakcore_rational_create_nd(1, 2);
OakSampleBuffer *timed =
oakcore_samplebuffer_create_length(stereo, half_sec);
assert(oakcore_samplebuffer_is_allocated(timed) == 1);
assert(oakcore_samplebuffer_sample_count(timed) == 24000);
OakSampleBuffer *timed = oakcore_samplebuffer_create_length(stereo_, half_sec);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(timed), 1);
EXPECT_EQ(oakcore_samplebuffer_sample_count(timed), 24000);
oakcore_rational_free(half_sec);
oakcore_samplebuffer_free(timed);
}
/* --- Manual lifecycle: params + count + allocate, destroy, repeat --- */
TEST_F(OakcoreSampleBufferTest, ManualLifecycle)
{
OakSampleBuffer *manual = oakcore_samplebuffer_create();
oakcore_samplebuffer_set_audio_params(manual, stereo);
oakcore_samplebuffer_set_audio_params(manual, stereo_);
oakcore_samplebuffer_set_sample_count(manual, 50);
oakcore_samplebuffer_allocate(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 1);
assert(oakcore_samplebuffer_sample_count(manual) == 50);
assert(oakcore_samplebuffer_channel_count(manual) == 2);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(manual), 1);
EXPECT_EQ(oakcore_samplebuffer_sample_count(manual), 50);
EXPECT_EQ(oakcore_samplebuffer_channel_count(manual), 2);
oakcore_samplebuffer_destroy(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 0);
assert(oakcore_samplebuffer_channel_count(manual) == 0);
assert(oakcore_samplebuffer_data(manual, 0) == NULL);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(manual), 0);
EXPECT_EQ(oakcore_samplebuffer_channel_count(manual), 0);
EXPECT_EQ(oakcore_samplebuffer_data(manual, 0), nullptr);
oakcore_samplebuffer_set_sample_count(manual, 30);
oakcore_samplebuffer_allocate(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 1);
assert(oakcore_samplebuffer_sample_count(manual) == 30);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(manual), 1);
EXPECT_EQ(oakcore_samplebuffer_sample_count(manual), 30);
oakcore_samplebuffer_free(manual);
/* set_sample_count via a rational length: 0.001s at 48kHz = 48 samples */
// set_sample_count via a rational length: 0.001s at 48kHz = 48 samples
OakSampleBuffer *manual2 = oakcore_samplebuffer_create();
oakcore_samplebuffer_set_audio_params(manual2, stereo);
oakcore_samplebuffer_set_audio_params(manual2, stereo_);
OakRational *one_ms = oakcore_rational_create_nd(1, 1000);
oakcore_samplebuffer_set_sample_count_length(manual2, one_ms);
oakcore_rational_free(one_ms);
oakcore_samplebuffer_allocate(manual2);
assert(oakcore_samplebuffer_sample_count(manual2) == 48);
EXPECT_EQ(oakcore_samplebuffer_sample_count(manual2), 48);
oakcore_samplebuffer_free(manual2);
}
/* --- data() direct access, set(), to_raw_ptrs() --- */
TEST_F(OakcoreSampleBufferTest, DataAccessSetAndRawPtrs)
{
OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo_, 100);
float *ch0 = oakcore_samplebuffer_data(buf, 0);
float *ch1 = oakcore_samplebuffer_data(buf, 1);
for (int i = 0; i < 100; i++) {
ch0[i] = (float)i; /* ramp 0..99 on channel 0 */
ch1[i] = (float)(i * 10); /* ramp 0..990 on channel 1 */
ch0[i] = (float)i;
ch1[i] = (float)(i * 10);
}
const float ins[4] = { -1.0f, -2.0f, -3.0f, -4.0f };
oakcore_samplebuffer_set(buf, 0, ins, 10, 4); /* write at offset 10 */
oakcore_samplebuffer_set(buf, 0, ins, 10, 4);
for (int k = 0; k < 4; k++) {
assert(float_eq(ch0[10 + k], ins[k]));
EXPECT_TRUE(feq(ch0[10 + k], ins[k]));
}
assert(float_eq(ch0[9], 9.0f)); /* neighbours untouched */
assert(float_eq(ch0[14], 14.0f));
EXPECT_TRUE(feq(ch0[9], 9.0f));
EXPECT_TRUE(feq(ch0[14], 14.0f));
oakcore_samplebuffer_set(buf, 1, ins, 0, 4); /* write at offset 0 */
oakcore_samplebuffer_set(buf, 1, ins, 0, 4);
for (int k = 0; k < 4; k++) {
assert(float_eq(ch1[k], ins[k]));
EXPECT_TRUE(feq(ch1[k], ins[k]));
}
for (int k = 0; k < 4; k++) { /* restore the channel 1 ramp */
for (int k = 0; k < 4; k++) {
ch1[k] = (float)(k * 10);
}
float *ptrs[2] = { NULL, NULL };
float *ptrs[2] = { nullptr, nullptr };
oakcore_samplebuffer_to_raw_ptrs(buf, ptrs);
assert(ptrs[0] == ch0);
assert(ptrs[1] == ch1);
ptrs[1][50] = 123.0f; /* writes through the raw pointer land in the buffer */
assert(float_eq(oakcore_samplebuffer_data(buf, 1)[50], 123.0f));
ch1[50] = 500.0f; /* restore ramp value (50 * 10) */
EXPECT_EQ(ptrs[0], ch0);
EXPECT_EQ(ptrs[1], ch1);
ptrs[1][50] = 123.0f;
EXPECT_TRUE(feq(oakcore_samplebuffer_data(buf, 1)[50], 123.0f));
ch1[50] = 500.0f;
oakcore_samplebuffer_free(buf);
}
TEST_F(OakcoreSampleBufferTest, CopyAndVolumeTransform)
{
OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo_, 100);
float *ch0 = oakcore_samplebuffer_data(buf, 0);
float *ch1 = oakcore_samplebuffer_data(buf, 1);
for (int i = 0; i < 100; i++) {
ch0[i] = (float)i;
ch1[i] = (float)(i * 10);
}
/* --- copy: an independent deep copy --- */
OakSampleBuffer *cp = oakcore_samplebuffer_copy(buf);
assert(oakcore_samplebuffer_is_allocated(cp) == 1);
assert(oakcore_samplebuffer_sample_count(cp) == 100);
assert(oakcore_samplebuffer_channel_count(cp) == 2);
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 9.0f));
EXPECT_EQ(oakcore_samplebuffer_is_allocated(cp), 1);
EXPECT_EQ(oakcore_samplebuffer_sample_count(cp), 100);
EXPECT_EQ(oakcore_samplebuffer_channel_count(cp), 2);
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cp, 0)[9], 9.0f));
/* --- transform_volume (in place, all channels) --- */
oakcore_samplebuffer_transform_volume(cp, 2.0f);
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f));
assert(float_eq(oakcore_samplebuffer_data(cp, 1)[9], 180.0f));
assert(float_eq(oakcore_samplebuffer_data(buf, 0)[9], 9.0f)); /* source keeps its data */
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cp, 1)[9], 180.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(buf, 0)[9], 9.0f));
/* --- transform_volume_for_channel (in place, one channel) --- */
oakcore_samplebuffer_transform_volume_for_channel(cp, 1, 0.5f);
assert(float_eq(oakcore_samplebuffer_data(cp, 1)[9], 90.0f));
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f)); /* other channel untouched */
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cp, 1)[9], 90.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f));
oakcore_samplebuffer_free(cp);
oakcore_samplebuffer_free(buf);
}
/* --- static-style transforms: input -> output --- */
OakSampleBuffer *tin = oakcore_samplebuffer_create_samples(stereo, 10);
OakSampleBuffer *tout = oakcore_samplebuffer_create_samples(stereo, 10);
TEST_F(OakcoreSampleBufferTest, StaticVolumeTransforms)
{
OakSampleBuffer *tin = oakcore_samplebuffer_create_samples(stereo_, 10);
OakSampleBuffer *tout = oakcore_samplebuffer_create_samples(stereo_, 10);
for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(tin, 0)[i] = (float)i;
oakcore_samplebuffer_data(tin, 1)[i] = (float)(100 + i);
@@ -201,159 +200,174 @@ int main(void)
oakcore_samplebuffer_transform_volume_to(0.5f, tin, tout);
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(tout, 0)[i], (float)i * 0.5f));
assert(float_eq(oakcore_samplebuffer_data(tout, 1)[i],
(float)(100 + i) * 0.5f));
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[i], (float)i)); /* input unchanged */
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 0)[i], (float)i * 0.5f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 1)[i], (float)(100 + i) * 0.5f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 0)[i], (float)i));
}
oakcore_samplebuffer_silence(tout);
oakcore_samplebuffer_transform_volume_for_channel_to(1, 2.0f, tin, tout);
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(tout, 1)[i],
(float)(100 + i) * 2.0f));
assert(float_eq(oakcore_samplebuffer_data(tout, 0)[i], 0.0f)); /* channel 0 untouched */
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 1)[i], (float)(100 + i) * 2.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 0)[i], 0.0f));
}
/* --- per-sample volume transforms --- */
// per-sample volume transforms
oakcore_samplebuffer_transform_volume_for_sample(tin, 3, 10.0f);
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[3], 30.0f));
assert(float_eq(oakcore_samplebuffer_data(tin, 1)[3], 1030.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 0)[3], 30.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 1)[3], 1030.0f));
oakcore_samplebuffer_transform_volume_for_sample_on_channel(tin, 4, 0,
100.0f);
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[4], 400.0f));
assert(float_eq(oakcore_samplebuffer_data(tin, 1)[4], 104.0f)); /* channel 1 untouched */
oakcore_samplebuffer_transform_volume_for_sample_on_channel(tin, 4, 0, 100.0f);
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 0)[4], 400.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 1)[4], 104.0f));
oakcore_samplebuffer_free(tin);
oakcore_samplebuffer_free(tout);
}
/* --- clamp() limits every channel to [-1, 1] --- */
OakSampleBuffer *cl = oakcore_samplebuffer_create_samples(stereo, 4);
TEST_F(OakcoreSampleBufferTest, Clamp)
{
OakSampleBuffer *cl = oakcore_samplebuffer_create_samples(stereo_, 4);
oakcore_samplebuffer_data(cl, 0)[0] = 2.5f;
oakcore_samplebuffer_data(cl, 0)[1] = -2.5f;
oakcore_samplebuffer_data(cl, 1)[0] = 42.0f;
oakcore_samplebuffer_data(cl, 1)[1] = 0.25f;
oakcore_samplebuffer_clamp(cl);
assert(float_eq(oakcore_samplebuffer_data(cl, 0)[0], 1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 0)[1], -1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 1)[0], 1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 1)[1], 0.25f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cl, 0)[0], 1.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cl, 0)[1], -1.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cl, 1)[0], 1.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(cl, 1)[1], 0.25f));
oakcore_samplebuffer_free(cl);
}
/* --- silence / silence_range / silence_bytes --- */
OakSampleBuffer *si = oakcore_samplebuffer_create_samples(stereo, 10);
TEST_F(OakcoreSampleBufferTest, SilenceAndSilenceRange)
{
OakSampleBuffer *si = oakcore_samplebuffer_create_samples(stereo_, 10);
for (int ch = 0; ch < 2; ch++) {
for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(si, ch)[i] = 1.0f;
}
}
oakcore_samplebuffer_silence_range(si, 2, 5); /* samples [2, 5) */
oakcore_samplebuffer_silence_range(si, 2, 5);
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch);
assert(float_eq(d[1], 1.0f));
assert(float_eq(d[2], 0.0f));
assert(float_eq(d[4], 0.0f));
assert(float_eq(d[5], 1.0f));
EXPECT_TRUE(feq(d[1], 1.0f));
EXPECT_TRUE(feq(d[2], 0.0f));
EXPECT_TRUE(feq(d[4], 0.0f));
EXPECT_TRUE(feq(d[5], 1.0f));
}
oakcore_samplebuffer_silence_bytes(si, 0, 2 * sizeof(float)); /* samples [0, 2) */
oakcore_samplebuffer_silence_bytes(si, 0, 2 * sizeof(float));
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch);
assert(float_eq(d[0], 0.0f));
assert(float_eq(d[1], 0.0f));
assert(float_eq(d[5], 1.0f));
EXPECT_TRUE(feq(d[0], 0.0f));
EXPECT_TRUE(feq(d[1], 0.0f));
EXPECT_TRUE(feq(d[5], 1.0f));
}
oakcore_samplebuffer_silence(si); /* everything */
oakcore_samplebuffer_silence(si);
for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch);
for (int i = 0; i < 10; i++) {
assert(float_eq(d[i], 0.0f));
EXPECT_TRUE(feq(d[i], 0.0f));
}
}
oakcore_samplebuffer_free(si);
}
/* --- reverse() flips every channel --- */
OakSampleBuffer *rv = oakcore_samplebuffer_create_samples(stereo, 5);
TEST_F(OakcoreSampleBufferTest, Reverse)
{
OakSampleBuffer *rv = oakcore_samplebuffer_create_samples(stereo_, 5);
for (int i = 0; i < 5; i++) {
oakcore_samplebuffer_data(rv, 0)[i] = (float)i;
oakcore_samplebuffer_data(rv, 1)[i] = (float)(10 * i);
}
oakcore_samplebuffer_reverse(rv);
for (int i = 0; i < 5; i++) {
assert(float_eq(oakcore_samplebuffer_data(rv, 0)[i], (float)(4 - i)));
assert(float_eq(oakcore_samplebuffer_data(rv, 1)[i],
(float)(10 * (4 - i))));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(rv, 0)[i], (float)(4 - i)));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(rv, 1)[i], (float)(10 * (4 - i))));
}
oakcore_samplebuffer_free(rv);
}
/* --- speed(2.0) halves the sample count, sampling the ramp exactly --- */
OakSampleBuffer *sp = oakcore_samplebuffer_create_samples(stereo, 100);
TEST_F(OakcoreSampleBufferTest, Speed)
{
OakSampleBuffer *sp = oakcore_samplebuffer_create_samples(stereo_, 100);
for (int i = 0; i < 100; i++) {
oakcore_samplebuffer_data(sp, 0)[i] = (float)i;
}
oakcore_samplebuffer_speed(sp, 2.0);
assert(oakcore_samplebuffer_sample_count(sp) == 50);
EXPECT_EQ(oakcore_samplebuffer_sample_count(sp), 50);
for (int i = 0; i < 50; i++) {
assert(float_eq(oakcore_samplebuffer_data(sp, 0)[i], (float)(2 * i)));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(sp, 0)[i], (float)(2 * i)));
}
oakcore_samplebuffer_free(sp);
}
/* --- fast_set(): channel copy between buffers --- */
OakSampleBuffer *fa = oakcore_samplebuffer_create_samples(stereo, 10);
OakSampleBuffer *fb = oakcore_samplebuffer_create_samples(stereo, 10);
TEST_F(OakcoreSampleBufferTest, FastSet)
{
OakSampleBuffer *fa = oakcore_samplebuffer_create_samples(stereo_, 10);
OakSampleBuffer *fb = oakcore_samplebuffer_create_samples(stereo_, 10);
for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(fa, 0)[i] = (float)i;
oakcore_samplebuffer_data(fa, 1)[i] = (float)(100 + i);
}
oakcore_samplebuffer_fast_set(fb, fa, 1, 0); /* fb channel 1 <- fa channel 0 */
oakcore_samplebuffer_fast_set(fb, fa, 1, 0);
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(fb, 1)[i], (float)i));
assert(float_eq(oakcore_samplebuffer_data(fb, 0)[i], 0.0f));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(fb, 1)[i], (float)i));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(fb, 0)[i], 0.0f));
}
oakcore_samplebuffer_fast_set(fb, fa, 0, -1); /* from == -1 mirrors to */
oakcore_samplebuffer_fast_set(fb, fa, 0, -1);
for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(fb, 0)[i], (float)i));
EXPECT_TRUE(feq(oakcore_samplebuffer_data(fb, 0)[i], (float)i));
}
oakcore_samplebuffer_free(fa);
oakcore_samplebuffer_free(fb);
}
/* --- rip_channel(): a new mono buffer with one channel's samples --- */
OakSampleBuffer *rip = oakcore_samplebuffer_rip_channel(buf, 1);
assert(oakcore_samplebuffer_is_allocated(rip) == 1);
assert(oakcore_samplebuffer_channel_count(rip) == 1);
assert(oakcore_samplebuffer_sample_count(rip) == 100);
TEST_F(OakcoreSampleBufferTest, RipChannel)
{
OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo_, 100);
for (int i = 0; i < 100; i++) {
assert(float_eq(oakcore_samplebuffer_data(rip, 0)[i], (float)(i * 10)));
oakcore_samplebuffer_data(buf, 1)[i] = (float)(i * 10);
}
OakSampleBuffer *rip = oakcore_samplebuffer_rip_channel(buf, 1);
EXPECT_EQ(oakcore_samplebuffer_is_allocated(rip), 1);
EXPECT_EQ(oakcore_samplebuffer_channel_count(rip), 1);
EXPECT_EQ(oakcore_samplebuffer_sample_count(rip), 100);
for (int i = 0; i < 100; i++) {
EXPECT_TRUE(feq(oakcore_samplebuffer_data(rip, 0)[i], (float)(i * 10)));
}
OakAudioParams *rip_params = oakcore_samplebuffer_audio_params(rip);
assert(oakcore_audioparams_sample_rate(rip_params) == SAMPLE_RATE);
assert(oakcore_audioparams_channel_count(rip_params) == 1);
EXPECT_EQ(oakcore_audioparams_sample_rate(rip_params), SAMPLE_RATE);
EXPECT_EQ(oakcore_audioparams_channel_count(rip_params), 1);
oakcore_audioparams_free(rip_params);
oakcore_samplebuffer_free(rip);
oakcore_samplebuffer_free(buf);
}
/* --- rip_channel_vector(): query size, partial copy, full copy --- */
assert(oakcore_samplebuffer_rip_channel_vector(buf, 1, NULL, 0) == 100);
TEST_F(OakcoreSampleBufferTest, RipChannelVector)
{
OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo_, 100);
for (int i = 0; i < 100; i++) {
oakcore_samplebuffer_data(buf, 1)[i] = (float)(i * 10);
}
EXPECT_EQ(oakcore_samplebuffer_rip_channel_vector(buf, 1, nullptr, 0), 100);
float partial[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
assert(oakcore_samplebuffer_rip_channel_vector(buf, 1, partial, 4) == 100);
EXPECT_EQ(oakcore_samplebuffer_rip_channel_vector(buf, 1, partial, 4), 100);
for (int k = 0; k < 4; k++) {
assert(float_eq(partial[k], (float)(k * 10)));
EXPECT_TRUE(feq(partial[k], (float)(k * 10)));
}
float full[100];
assert(oakcore_samplebuffer_rip_channel_vector(buf, 1, full, 100) == 100);
EXPECT_EQ(oakcore_samplebuffer_rip_channel_vector(buf, 1, full, 100), 100);
for (int i = 0; i < 100; i++) {
assert(float_eq(full[i], (float)(i * 10)));
EXPECT_TRUE(feq(full[i], (float)(i * 10)));
}
/* --- Ownership: everything released --- */
oakcore_samplebuffer_free(buf);
oakcore_audioparams_free(stereo);
printf("oakcore_samplebuffer_test: all assertions passed\n");
return 0;
}
+74 -123
View File
@@ -1,129 +1,93 @@
/***
Oak - 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/>.
***/
/**
* @file oakcore_stringutils_test.cpp
* @brief Pure C API test for oakcore/stringutils.h
*
* Exercises every C function directly (no C++ wrapper, no test framework).
*/
#include <assert.h>
#include <gtest/gtest.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "olive/core/oakcore/stringutils.h"
static void test_split()
TEST(OakcoreStringUtils, SplitBasic)
{
int count = 0;
// Basic split
char **arr = oakcore_stringutils_split("a,b,c", ',', &count);
assert(arr != NULL);
assert(count == 3);
assert(strcmp(arr[0], "a") == 0);
assert(strcmp(arr[1], "b") == 0);
assert(strcmp(arr[2], "c") == 0);
oakcore_stringutils_free_string_array(arr, count);
// No separator present: single element equal to the whole string
arr = oakcore_stringutils_split("abc", ',', &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "abc") == 0);
oakcore_stringutils_free_string_array(arr, count);
// Trailing separator yields a trailing empty string
arr = oakcore_stringutils_split("a,", ',', &count);
assert(arr != NULL);
assert(count == 2);
assert(strcmp(arr[0], "a") == 0);
assert(strcmp(arr[1], "") == 0);
oakcore_stringutils_free_string_array(arr, count);
// Empty string yields one empty string
arr = oakcore_stringutils_split("", ',', &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "") == 0);
oakcore_stringutils_free_string_array(arr, count);
// NULL string behaves like an empty string
arr = oakcore_stringutils_split(NULL, ',', &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "") == 0);
ASSERT_NE(arr, nullptr);
ASSERT_EQ(count, 3);
EXPECT_STREQ(arr[0], "a");
EXPECT_STREQ(arr[1], "b");
EXPECT_STREQ(arr[2], "c");
oakcore_stringutils_free_string_array(arr, count);
}
static void test_split_regex()
TEST(OakcoreStringUtils, SplitNoSeparator)
{
int count = 0;
char **arr = oakcore_stringutils_split("abc", ',', &count);
ASSERT_NE(arr, nullptr);
ASSERT_EQ(count, 1);
EXPECT_STREQ(arr[0], "abc");
oakcore_stringutils_free_string_array(arr, count);
}
// Split on runs of digits
TEST(OakcoreStringUtils, SplitTrailingSeparator)
{
int count = 0;
char **arr = oakcore_stringutils_split("a,", ',', &count);
ASSERT_NE(arr, nullptr);
ASSERT_EQ(count, 2);
EXPECT_STREQ(arr[0], "a");
EXPECT_STREQ(arr[1], "");
oakcore_stringutils_free_string_array(arr, count);
}
TEST(OakcoreStringUtils, SplitEmptyAndNull)
{
int count = 0;
char **arr = oakcore_stringutils_split("", ',', &count);
ASSERT_NE(arr, nullptr);
ASSERT_EQ(count, 1);
EXPECT_STREQ(arr[0], "");
oakcore_stringutils_free_string_array(arr, count);
arr = oakcore_stringutils_split(NULL, ',', &count);
ASSERT_NE(arr, nullptr);
ASSERT_EQ(count, 1);
EXPECT_STREQ(arr[0], "");
oakcore_stringutils_free_string_array(arr, count);
}
TEST(OakcoreStringUtils, SplitRegex)
{
int count = 0;
char **arr = oakcore_stringutils_split_regex("a1b22c", "[0-9]+", &count);
assert(arr != NULL);
assert(count == 3);
assert(strcmp(arr[0], "a") == 0);
assert(strcmp(arr[1], "b") == 0);
assert(strcmp(arr[2], "c") == 0);
ASSERT_NE(arr, nullptr);
ASSERT_EQ(count, 3);
EXPECT_STREQ(arr[0], "a");
EXPECT_STREQ(arr[1], "b");
EXPECT_STREQ(arr[2], "c");
oakcore_stringutils_free_string_array(arr, count);
// Pattern that never matches yields the whole string
arr = oakcore_stringutils_split_regex("abc", "[0-9]+", &count);
assert(arr != NULL);
assert(count == 1);
assert(strcmp(arr[0], "abc") == 0);
ASSERT_NE(arr, nullptr);
ASSERT_EQ(count, 1);
EXPECT_STREQ(arr[0], "abc");
oakcore_stringutils_free_string_array(arr, count);
// Freeing NULL is safe
oakcore_stringutils_free_string_array(NULL, 0);
}
static void test_to_int()
TEST(OakcoreStringUtils, ToInt)
{
int ok = 0;
// Base 10
assert(oakcore_stringutils_to_int("42", 10, &ok) == 42);
assert(ok == 1);
// Negative
assert(oakcore_stringutils_to_int("-17", 10, &ok) == -17);
assert(ok == 1);
// Base 16
assert(oakcore_stringutils_to_int("ff", 16, &ok) == 255);
assert(ok == 1);
// Parser error: returns 0 and reports failure
assert(oakcore_stringutils_to_int("xyz", 10, &ok) == 0);
assert(ok == 0);
// ok output parameter is optional
assert(oakcore_stringutils_to_int("7", 10, NULL) == 7);
EXPECT_EQ(oakcore_stringutils_to_int("42", 10, &ok), 42);
EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_stringutils_to_int("-17", 10, &ok), -17);
EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_stringutils_to_int("ff", 16, &ok), 255);
EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_stringutils_to_int("xyz", 10, &ok), 0);
EXPECT_EQ(ok, 0);
EXPECT_EQ(oakcore_stringutils_to_int("7", 10, NULL), 7);
}
/* Variadic forwarder to exercise oakcore_stringutils_format_v() */
static int format_forward(char *buf, int buf_size, const char *fmt, ...)
{
va_list args;
@@ -133,42 +97,29 @@ static int format_forward(char *buf, int buf_size, const char *fmt, ...)
return r;
}
static void test_format()
TEST(OakcoreStringUtils, Format)
{
char buf[64];
// Format with mixed argument types
const int needed = oakcore_stringutils_format(buf, sizeof(buf), "%d-%s-%.2f",
42, "mid", 1.5);
assert(needed == 11);
assert(strcmp(buf, "42-mid-1.50") == 0);
EXPECT_EQ(needed, 11);
EXPECT_STREQ(buf, "42-mid-1.50");
// NULL buffer queries the required size
assert(oakcore_stringutils_format(NULL, 0, "%d-%s-%.2f", 42, "mid",
1.5) == needed);
// NULL buffer queries size
EXPECT_EQ(oakcore_stringutils_format(NULL, 0, "%d-%s-%.2f", 42, "mid", 1.5),
needed);
// Too-small buffer truncates but still reports the full required size
// Truncation
char small[5];
const int needed2 =
oakcore_stringutils_format(small, sizeof(small), "%s", "abcdefgh");
assert(needed2 == 8);
assert(strcmp(small, "abcd") == 0);
EXPECT_EQ(needed2, 8);
EXPECT_STREQ(small, "abcd");
// va_list form produces identical results
// va_list form
char buf2[64];
const int needed3 = format_forward(buf2, sizeof(buf2), "%d-%s-%.2f", 42,
"mid", 1.5);
assert(needed3 == needed);
assert(strcmp(buf2, buf) == 0);
}
int main()
{
test_split();
test_split_regex();
test_to_int();
test_format();
printf("oakcore_stringutils_test: all tests passed\n");
return 0;
EXPECT_EQ(needed3, needed);
EXPECT_STREQ(buf2, buf);
}
+185 -241
View File
@@ -1,254 +1,198 @@
/***
Oak - 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/>.
***/
#include <assert.h>
#include <gtest/gtest.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "olive/core/oakcore/timecodefunctions.h"
int main()
{
char buf[64];
int ok = 0;
OakRational *r = NULL;
/* Common timebases: 30 fps non-drop, 29.97 drop-frame, 60 fps */
OakRational *tb_30 = oakcore_rational_create_nd(1, 30);
OakRational *tb_df = oakcore_rational_create_nd(1001, 30000);
OakRational *tb_60 = oakcore_rational_create_nd(1, 60);
/* oakcore_timecode_time_to_timecode: drop-frame ( Olive's own test case ) */
class OakcoreTimecodeTest : public ::testing::Test {
protected:
void SetUp() override
{
OakRational *time = oakcore_rational_create(1);
const int size = oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, NULL, 0);
assert(size == 11);
assert(oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf,
sizeof(buf)) == 11);
assert(strcmp(buf, "00:00:01;00") == 0);
/* Truncating buffer still reports the full required size */
assert(oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf,
6) == 11);
assert(strcmp(buf, "00:00") == 0);
oakcore_rational_free(time);
tb_30 = oakcore_rational_create_nd(1, 30);
tb_df = oakcore_rational_create_nd(1001, 30000);
tb_60 = oakcore_rational_create_nd(1, 60);
}
/* oakcore_timecode_time_to_timecode: invalid timebase */
void TearDown() override
{
OakRational *time = oakcore_rational_create(0);
OakRational *bizarre = oakcore_rational_create(156632219);
assert(oakcore_timecode_time_to_timecode(
time, bizarre, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf,
sizeof(buf)) == 16);
assert(strcmp(buf, "INVALID TIMEBASE") == 0);
oakcore_rational_free(bizarre);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timecode: non-drop, plus sign, negative */
{
OakRational *time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf,
sizeof(buf)) == 11);
assert(strcmp(buf, "00:00:01:15") == 0);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 1, buf,
sizeof(buf)) == 12);
assert(strcmp(buf, "+00:00:01:15") == 0);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(-3, 2);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf,
sizeof(buf)) == 12);
assert(strcmp(buf, "-00:00:01:15") == 0);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timecode: seconds / frames / milliseconds */
{
OakRational *time = oakcore_rational_create_nd(123, 2); /* 61.5 s */
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_SECONDS, 0, buf,
sizeof(buf)) == 12);
assert(strcmp(buf, "00:01:01.500") == 0);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_FRAMES, 0, buf,
sizeof(buf)) == 2);
assert(strcmp(buf, "45") == 0);
assert(oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_MILLISECONDS, 0, buf,
sizeof(buf)) == 4);
assert(strcmp(buf, "1500") == 0);
oakcore_rational_free(time);
}
/* oakcore_timecode_timecode_to_time: non-drop */
r = oakcore_timecode_timecode_to_time("00:00:01:15", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME,
&ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
/* oakcore_timecode_timecode_to_time: drop-frame */
r = oakcore_timecode_timecode_to_time("00:00:01;00", tb_df,
OAK_TIMECODE_DISPLAY_DROP_FRAME,
&ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 1001 &&
oakcore_rational_denominator(r) == 1000);
oakcore_rational_free(r);
/* oakcore_timecode_timecode_to_time: seconds / frames / milliseconds */
r = oakcore_timecode_timecode_to_time("00:00:01.5", tb_30,
OAK_TIMECODE_DISPLAY_SECONDS, &ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time("45", tb_30,
OAK_TIMECODE_DISPLAY_FRAMES, &ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time("1500", tb_30,
OAK_TIMECODE_DISPLAY_MILLISECONDS,
&ok);
assert(ok == 1);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
/* oakcore_timecode_timecode_to_time: invalid and NULL input */
r = oakcore_timecode_timecode_to_time("abc", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME,
&ok);
assert(ok == 0);
assert(oakcore_rational_numerator(r) == 0);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time(NULL, tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME,
&ok);
assert(ok == 0);
oakcore_rational_free(r);
/* ok pointer itself may be NULL */
r = oakcore_timecode_timecode_to_time("45", tb_30,
OAK_TIMECODE_DISPLAY_FRAMES, NULL);
assert(oakcore_rational_to_double(r) == 1.5);
oakcore_rational_free(r);
/* oakcore_timecode_time_to_string */
assert(oakcore_timecode_time_to_string(3661000, NULL, 0) == 8);
assert(oakcore_timecode_time_to_string(3661000, buf, sizeof(buf)) == 8);
assert(strcmp(buf, "01:01:01") == 0);
assert(oakcore_timecode_time_to_string(0, buf, sizeof(buf)) == 8);
assert(strcmp(buf, "00:00:00") == 0);
/* oakcore_timecode_snap_time_to_timebase */
{
OakRational *time = oakcore_rational_create_nd(102, 100); /* 1.02 s */
r = oakcore_timecode_snap_time_to_timebase(
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND);
assert(oakcore_rational_numerator(r) == 31 &&
oakcore_rational_denominator(r) == 30);
oakcore_rational_free(r);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(151, 100); /* 1.51 s -> 45.3 frames */
r = oakcore_timecode_snap_time_to_timebase(
time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_snap_time_to_timebase(
time, tb_30, OAK_TIMECODE_ROUNDING_CEIL);
assert(oakcore_rational_numerator(r) == 23 &&
oakcore_rational_denominator(r) == 15);
oakcore_rational_free(r);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timestamp (Rational) */
{
OakRational *time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND) == 45);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(151, 100); /* 45.3 frames */
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND) == 45);
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR) == 45);
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_CEIL) == 46);
oakcore_rational_free(time);
}
/* oakcore_timecode_time_to_timestamp_d (double) */
assert(oakcore_timecode_time_to_timestamp_d(1.5, tb_30,
OAK_TIMECODE_ROUNDING_ROUND) ==
45);
assert(oakcore_timecode_time_to_timestamp_d(NAN, tb_30,
OAK_TIMECODE_ROUNDING_ROUND) ==
0);
/* oakcore_timecode_rescale_timestamp */
assert(oakcore_timecode_rescale_timestamp(30, tb_30, tb_60) == 60);
assert(oakcore_timecode_rescale_timestamp(30, tb_30, tb_30) == 30);
{
/* 1 * (1*4) / (5*3) = 0.2666... -> nearest is 0 */
OakRational *src = oakcore_rational_create_nd(1, 5);
OakRational *dst = oakcore_rational_create_nd(3, 4);
assert(oakcore_timecode_rescale_timestamp(1, src, dst) == 0);
/* ... but ceil rounds up to 1 */
assert(oakcore_timecode_rescale_timestamp_ceil(1, src, dst) == 1);
oakcore_rational_free(dst);
oakcore_rational_free(src);
}
assert(oakcore_timecode_rescale_timestamp_ceil(30, tb_30, tb_60) == 60);
/* oakcore_timecode_timestamp_to_time */
r = oakcore_timecode_timestamp_to_time(45, tb_30);
assert(oakcore_rational_numerator(r) == 3 &&
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r);
r = oakcore_timecode_timestamp_to_time(0, tb_30);
assert(oakcore_rational_to_double(r) == 0.0);
oakcore_rational_free(r);
/* oakcore_timecode_timebase_is_drop_frame */
assert(oakcore_timecode_timebase_is_drop_frame(tb_df) == 1);
assert(oakcore_timecode_timebase_is_drop_frame(tb_30) == 0);
oakcore_rational_free(tb_60);
oakcore_rational_free(tb_df);
oakcore_rational_free(tb_30);
}
OakRational *tb_30, *tb_df, *tb_60;
char buf[64];
};
printf("oakcore_timecodefunctions_test: all tests passed\n");
return 0;
TEST_F(OakcoreTimecodeTest, TimeToTimecodeDropFrame)
{
OakRational *time = oakcore_rational_create(1);
const int size = oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, NULL, 0);
EXPECT_EQ(size, 11);
oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, sizeof(buf));
EXPECT_STREQ(buf, "00:00:01;00");
// Truncating buffer
oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, 6);
EXPECT_STREQ(buf, "00:00");
oakcore_rational_free(time);
}
TEST_F(OakcoreTimecodeTest, TimeToTimecodeInvalidTimebase)
{
OakRational *time = oakcore_rational_create(0);
OakRational *bizarre = oakcore_rational_create(156632219);
oakcore_timecode_time_to_timecode(
time, bizarre, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, sizeof(buf));
EXPECT_STREQ(buf, "INVALID TIMEBASE");
oakcore_rational_free(bizarre);
oakcore_rational_free(time);
}
TEST_F(OakcoreTimecodeTest, TimeToTimecodeNonDrop)
{
OakRational *time = oakcore_rational_create_nd(3, 2);
oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf, sizeof(buf));
EXPECT_STREQ(buf, "00:00:01:15");
oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 1, buf, sizeof(buf));
EXPECT_STREQ(buf, "+00:00:01:15");
oakcore_rational_free(time);
time = oakcore_rational_create_nd(-3, 2);
oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf, sizeof(buf));
EXPECT_STREQ(buf, "-00:00:01:15");
oakcore_rational_free(time);
}
TEST_F(OakcoreTimecodeTest, TimeToTimecodeDisplayModes)
{
OakRational *time = oakcore_rational_create_nd(123, 2);
oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_SECONDS, 0, buf, sizeof(buf));
EXPECT_STREQ(buf, "00:01:01.500");
oakcore_rational_free(time);
time = oakcore_rational_create_nd(3, 2);
oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_FRAMES, 0, buf, sizeof(buf));
EXPECT_STREQ(buf, "45");
oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_MILLISECONDS, 0, buf, sizeof(buf));
EXPECT_STREQ(buf, "1500");
oakcore_rational_free(time);
}
TEST_F(OakcoreTimecodeTest, TimecodeToTime)
{
int ok = 0;
OakRational *r;
r = oakcore_timecode_timecode_to_time("00:00:01:15", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, &ok);
EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_rational_numerator(r), 3);
EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time("00:00:01;00", tb_df,
OAK_TIMECODE_DISPLAY_DROP_FRAME, &ok);
EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_rational_numerator(r), 1001);
EXPECT_EQ(oakcore_rational_denominator(r), 1000);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time("45", tb_30,
OAK_TIMECODE_DISPLAY_FRAMES, &ok);
EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_rational_numerator(r), 3);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time("abc", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, &ok);
EXPECT_EQ(ok, 0);
oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time(NULL, tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, &ok);
EXPECT_EQ(ok, 0);
oakcore_rational_free(r);
}
TEST_F(OakcoreTimecodeTest, TimeToString)
{
EXPECT_EQ(oakcore_timecode_time_to_string(3661000, NULL, 0), 8);
EXPECT_EQ(oakcore_timecode_time_to_string(3661000, buf, sizeof(buf)), 8);
EXPECT_STREQ(buf, "01:01:01");
oakcore_timecode_time_to_string(0, buf, sizeof(buf));
EXPECT_STREQ(buf, "00:00:00");
}
TEST_F(OakcoreTimecodeTest, SnapTimeToTimebase)
{
OakRational *time = oakcore_rational_create_nd(102, 100);
OakRational *r = oakcore_timecode_snap_time_to_timebase(
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND);
EXPECT_EQ(oakcore_rational_numerator(r), 31);
EXPECT_EQ(oakcore_rational_denominator(r), 30);
oakcore_rational_free(r);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(151, 100);
r = oakcore_timecode_snap_time_to_timebase(time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR);
EXPECT_EQ(oakcore_rational_numerator(r), 3);
EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_free(r);
r = oakcore_timecode_snap_time_to_timebase(time, tb_30, OAK_TIMECODE_ROUNDING_CEIL);
EXPECT_EQ(oakcore_rational_numerator(r), 23);
EXPECT_EQ(oakcore_rational_denominator(r), 15);
oakcore_rational_free(r);
oakcore_rational_free(time);
}
TEST_F(OakcoreTimecodeTest, TimeToTimestamp)
{
OakRational *time = oakcore_rational_create_nd(3, 2);
EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_ROUND), 45);
oakcore_rational_free(time);
time = oakcore_rational_create_nd(151, 100);
EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_ROUND), 45);
EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR), 45);
EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_CEIL), 46);
oakcore_rational_free(time);
EXPECT_EQ(oakcore_timecode_time_to_timestamp_d(1.5, tb_30, OAK_TIMECODE_ROUNDING_ROUND), 45);
EXPECT_EQ(oakcore_timecode_time_to_timestamp_d(NAN, tb_30, OAK_TIMECODE_ROUNDING_ROUND), 0);
}
TEST_F(OakcoreTimecodeTest, RescaleTimestamp)
{
EXPECT_EQ(oakcore_timecode_rescale_timestamp(30, tb_30, tb_60), 60);
EXPECT_EQ(oakcore_timecode_rescale_timestamp(30, tb_30, tb_30), 30);
OakRational *src = oakcore_rational_create_nd(1, 5);
OakRational *dst = oakcore_rational_create_nd(3, 4);
EXPECT_EQ(oakcore_timecode_rescale_timestamp(1, src, dst), 0);
EXPECT_EQ(oakcore_timecode_rescale_timestamp_ceil(1, src, dst), 1);
oakcore_rational_free(dst);
oakcore_rational_free(src);
}
TEST_F(OakcoreTimecodeTest, TimestampToTime)
{
OakRational *r = oakcore_timecode_timestamp_to_time(45, tb_30);
EXPECT_EQ(oakcore_rational_numerator(r), 3);
EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_free(r);
}
TEST_F(OakcoreTimecodeTest, TimebaseIsDropFrame)
{
EXPECT_EQ(oakcore_timecode_timebase_is_drop_frame(tb_df), 1);
EXPECT_EQ(oakcore_timecode_timebase_is_drop_frame(tb_30), 0);
}
+42 -105
View File
@@ -1,34 +1,5 @@
/***
Oak - 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/>.
***/
/**
* @file oakcore_timerange_test.cpp
* @brief Smoke test for the OakTimeRange C ABI
*
* Pure C API test: no gtest, no C++ wrapper classes. Every allocated handle
* is released with the matching free function.
*/
#include <assert.h>
#include <gtest/gtest.h>
#include <limits.h>
#include <stdio.h>
#include "olive/core/oakcore/timerange.h"
@@ -49,8 +20,8 @@ static OakTimeRange *mk_range(int in, int out)
static void expect_int_time(const OakRational *r, int expected)
{
assert(oakcore_rational_numerator(r) == expected);
assert(oakcore_rational_denominator(r) == 1);
EXPECT_EQ(oakcore_rational_numerator(r), expected);
EXPECT_EQ(oakcore_rational_denominator(r), 1);
}
static void expect_range(const OakTimeRange *r, int in, int out)
@@ -63,7 +34,7 @@ static void expect_range(const OakTimeRange *r, int in, int out)
oakcore_rational_free(ro);
}
static void test_create_default(void)
TEST(OakcoreTimeRange, CreateDefault)
{
OakTimeRange *r = oakcore_timerange_create();
expect_range(r, 0, 0);
@@ -75,7 +46,7 @@ static void test_create_default(void)
oakcore_timerange_free(r);
}
static void test_create_and_getters(void)
TEST(OakcoreTimeRange, CreateAndGetters)
{
OakTimeRange *r = mk_range(10, 20);
expect_range(r, 10, 20);
@@ -87,9 +58,8 @@ static void test_create_and_getters(void)
oakcore_timerange_free(r);
}
static void test_normalize_swaps_in_out(void)
TEST(OakcoreTimeRange, NormalizeSwapsInOut)
{
// out earlier than in must be swapped
OakTimeRange *r = mk_range(20, 10);
expect_range(r, 10, 20);
@@ -100,28 +70,27 @@ static void test_normalize_swaps_in_out(void)
oakcore_timerange_free(r);
}
static void test_length_nan_on_extremes(void)
TEST(OakcoreTimeRange, LengthNanOnExtremes)
{
// RATIONAL_MIN/RATIONAL_MAX endpoints make the length NaN
OakTimeRange *r = mk_range(INT_MIN, 0);
OakRational *length = oakcore_timerange_length(r);
assert(oakcore_rational_is_nan(length) == 1);
EXPECT_EQ(oakcore_rational_is_nan(length), 1);
oakcore_rational_free(length);
oakcore_timerange_free(r);
r = mk_range(0, INT_MAX);
length = oakcore_timerange_length(r);
assert(oakcore_rational_is_nan(length) == 1);
EXPECT_EQ(oakcore_rational_is_nan(length), 1);
oakcore_rational_free(length);
oakcore_timerange_free(r);
}
static void test_copy_and_equal(void)
TEST(OakcoreTimeRange, CopyAndEqual)
{
OakTimeRange *r = mk_range(3, 7);
OakTimeRange *copy = oakcore_timerange_copy(r);
assert(oakcore_timerange_equal(r, copy) == 1);
EXPECT_EQ(oakcore_timerange_equal(r, copy), 1);
expect_range(copy, 3, 7);
// Mutating the copy must not affect the original (deep ownership)
@@ -131,14 +100,14 @@ static void test_copy_and_equal(void)
// set_in(9) with out==7 normalizes by swapping to (7, 9)
expect_range(copy, 7, 9);
assert(oakcore_timerange_equal(r, copy) == 0);
EXPECT_EQ(oakcore_timerange_equal(r, copy), 0);
expect_range(r, 3, 7);
oakcore_timerange_free(copy);
oakcore_timerange_free(r);
}
static void test_setters(void)
TEST(OakcoreTimeRange, Setters)
{
OakTimeRange *r = mk_range(0, 10);
OakRational *t = mk_time(5);
@@ -147,7 +116,6 @@ static void test_setters(void)
expect_range(r, 5, 10);
oakcore_timerange_set_out(r, t);
// set_out(5) with in==5 collapses to a zero-length range
expect_range(r, 5, 5);
{
@@ -157,52 +125,46 @@ static void test_setters(void)
oakcore_rational_free(in);
oakcore_rational_free(out);
}
// set_range also normalizes
expect_range(r, 20, 30);
oakcore_rational_free(t);
oakcore_timerange_free(r);
}
static void test_overlaps_with(void)
TEST(OakcoreTimeRange, OverlapsWith)
{
OakTimeRange *a = mk_range(0, 10);
OakTimeRange *b = mk_range(10, 20);
OakTimeRange *c = mk_range(5, 15);
// Touching ranges (0,10) vs (10,20): in_inclusive governs the
// other.out-vs-self.in comparison, out_inclusive the other.in-vs-self.out
// one, so the results are asymmetric for mixed flags
assert(oakcore_timerange_overlaps_with(a, b, 1, 1) == 1);
assert(oakcore_timerange_overlaps_with(a, b, 0, 0) == 0);
assert(oakcore_timerange_overlaps_with(a, b, 1, 0) == 0);
assert(oakcore_timerange_overlaps_with(a, b, 0, 1) == 1);
EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 1, 1), 1);
EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 0, 0), 0);
EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 1, 0), 0);
EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 0, 1), 1);
// Genuinely overlapping ranges overlap under any inclusivity
assert(oakcore_timerange_overlaps_with(a, c, 1, 1) == 1);
assert(oakcore_timerange_overlaps_with(a, c, 0, 0) == 1);
EXPECT_EQ(oakcore_timerange_overlaps_with(a, c, 1, 1), 1);
EXPECT_EQ(oakcore_timerange_overlaps_with(a, c, 0, 0), 1);
oakcore_timerange_free(c);
oakcore_timerange_free(b);
oakcore_timerange_free(a);
}
static void test_contains_range(void)
TEST(OakcoreTimeRange, ContainsRange)
{
OakTimeRange *outer = mk_range(0, 30);
OakTimeRange *inner = mk_range(5, 10);
OakTimeRange *same = mk_range(0, 30);
OakTimeRange *partial = mk_range(5, 40);
assert(oakcore_timerange_contains_range(outer, inner, 1, 1) == 1);
assert(oakcore_timerange_contains_range(outer, inner, 0, 0) == 1);
EXPECT_EQ(oakcore_timerange_contains_range(outer, inner, 1, 1), 1);
EXPECT_EQ(oakcore_timerange_contains_range(outer, inner, 0, 0), 1);
// Identical ranges: contained inclusively, not exclusively
assert(oakcore_timerange_contains_range(outer, same, 1, 1) == 1);
assert(oakcore_timerange_contains_range(outer, same, 0, 0) == 0);
EXPECT_EQ(oakcore_timerange_contains_range(outer, same, 1, 1), 1);
EXPECT_EQ(oakcore_timerange_contains_range(outer, same, 0, 0), 0);
assert(oakcore_timerange_contains_range(outer, partial, 1, 1) == 0);
assert(oakcore_timerange_contains_range(inner, outer, 1, 1) == 0);
EXPECT_EQ(oakcore_timerange_contains_range(outer, partial, 1, 1), 0);
EXPECT_EQ(oakcore_timerange_contains_range(inner, outer, 1, 1), 0);
oakcore_timerange_free(partial);
oakcore_timerange_free(same);
@@ -210,7 +172,7 @@ static void test_contains_range(void)
oakcore_timerange_free(outer);
}
static void test_contains_time(void)
TEST(OakcoreTimeRange, ContainsTime)
{
OakTimeRange *r = mk_range(0, 10);
OakRational *inside = mk_time(5);
@@ -218,11 +180,10 @@ static void test_contains_time(void)
OakRational *edge_out = mk_time(10);
OakRational *outside = mk_time(-1);
// contains(time) is in-inclusive but out-exclusive
assert(oakcore_timerange_contains_time(r, inside) == 1);
assert(oakcore_timerange_contains_time(r, edge_in) == 1);
assert(oakcore_timerange_contains_time(r, edge_out) == 0);
assert(oakcore_timerange_contains_time(r, outside) == 0);
EXPECT_EQ(oakcore_timerange_contains_time(r, inside), 1);
EXPECT_EQ(oakcore_timerange_contains_time(r, edge_in), 1);
EXPECT_EQ(oakcore_timerange_contains_time(r, edge_out), 0);
EXPECT_EQ(oakcore_timerange_contains_time(r, outside), 0);
oakcore_rational_free(outside);
oakcore_rational_free(edge_out);
@@ -231,7 +192,7 @@ static void test_contains_time(void)
oakcore_timerange_free(r);
}
static void test_combine_and_intersect(void)
TEST(OakcoreTimeRange, CombineAndIntersect)
{
OakTimeRange *a = mk_range(0, 10);
OakTimeRange *b = mk_range(20, 30);
@@ -244,7 +205,6 @@ static void test_combine_and_intersect(void)
expect_range(combined, 0, 30);
oakcore_timerange_free(combined);
// Disjoint ranges intersect to (20, 10), which normalize() swaps to (10, 20)
OakTimeRange *intersected = oakcore_timerange_intersected(a, b);
expect_range(intersected, 10, 20);
oakcore_timerange_free(intersected);
@@ -267,7 +227,7 @@ static void test_combine_and_intersect(void)
oakcore_timerange_free(a);
}
static void test_arithmetic(void)
TEST(OakcoreTimeRange, Arithmetic)
{
OakTimeRange *r = mk_range(0, 10);
OakRational *five = mk_time(5);
@@ -294,17 +254,15 @@ static void test_arithmetic(void)
oakcore_timerange_free(r);
}
static void test_split(void)
TEST(OakcoreTimeRange, Split)
{
OakTimeRange *r = mk_range(0, 10);
// Size query, both directly and through the NULL-buffer form
assert(oakcore_timerange_split_count(r, 4) == 3);
assert(oakcore_timerange_split(r, 4, NULL, 0) == 3);
EXPECT_EQ(oakcore_timerange_split_count(r, 4), 3);
EXPECT_EQ(oakcore_timerange_split(r, 4, nullptr, 0), 3);
// Full split: (0,4), (4,8), (8,10)
OakTimeRange *chunks[3];
assert(oakcore_timerange_split(r, 4, chunks, 3) == 3);
EXPECT_EQ(oakcore_timerange_split(r, 4, chunks, 3), 3);
expect_range(chunks[0], 0, 4);
expect_range(chunks[1], 4, 8);
expect_range(chunks[2], 8, 10);
@@ -312,41 +270,20 @@ static void test_split(void)
oakcore_timerange_free(chunks[i]);
}
// A too-small buffer receives a prefix, the return value is the total
OakTimeRange *prefix[2];
assert(oakcore_timerange_split(r, 4, prefix, 2) == 3);
EXPECT_EQ(oakcore_timerange_split(r, 4, prefix, 2), 3);
expect_range(prefix[0], 0, 4);
expect_range(prefix[1], 4, 8);
oakcore_timerange_free(prefix[0]);
oakcore_timerange_free(prefix[1]);
// Zero-length range splits into exactly one chunk
OakTimeRange *zero = mk_range(5, 5);
assert(oakcore_timerange_split_count(zero, 4) == 1);
OakTimeRange *single = NULL;
assert(oakcore_timerange_split(zero, 4, &single, 1) == 1);
EXPECT_EQ(oakcore_timerange_split_count(zero, 4), 1);
OakTimeRange *single = nullptr;
EXPECT_EQ(oakcore_timerange_split(zero, 4, &single, 1), 1);
expect_range(single, 5, 5);
oakcore_timerange_free(single);
oakcore_timerange_free(zero);
oakcore_timerange_free(r);
}
int main(void)
{
test_create_default();
test_create_and_getters();
test_normalize_swaps_in_out();
test_length_nan_on_extremes();
test_copy_and_equal();
test_setters();
test_overlaps_with();
test_contains_range();
test_contains_time();
test_combine_and_intersect();
test_arithmetic();
test_split();
printf("oakcore_timerange_test: all tests passed\n");
return 0;
}
+1 -1
View File
@@ -89,7 +89,7 @@ ctest --test-dir build --output-on-failure
- 所有测试必须使用 **Google Test** 编写,
- 面向引擎代码的 C ABI 边界契约。
更多项目文档:[中文文档目录](./)、[`facade-migration-roadmap.md`](facade-migration-roadmap.md)、[`riir.md`](plans/riir.md)、[`gtest-migration-guide.md`](plans/gtest-migration-guide.md)。
更多项目文档:[中文文档目录](./)、[`facade-migration-roadmap.md`](plans/completed/facade-migration-roadmap.md)、[`riir.md`](plans/riir.md)、[`gtest-migration-guide.md`](plans/gtest-migration-guide.md)。
## 许可证
+16 -10
View File
@@ -1,25 +1,31 @@
# 长期计划(plans
本目录收纳 Oak 的**中长期规划文档与并行执行计划**。当前正在进行的
**C ABI 迁移战役**的文档在上一层(`docs/zh/`),见下方"当前进行中"
本目录收纳 Oak 的**中长期规划文档与并行执行计划**。已完成的战役
文档归档在 [`completed/`](completed/);进行中的计划在下方目录
## 目录
| 文档 | 内容 | 启动前提 |
|---|---|---|
| [`riir.md`](riir.md) | **RIIR 绞杀者模式执行计划**C ABI 迁移完成后,把 liboakengine 安全拆成若干小模块,再逐个用 Rust 重写;含 API 冻结保证、模块图、六步流程与验证门禁 | C ABI 迁移战役验收完成 |
| [`riir/`](riir/) | **模块拆分执行手册**(riir.md 第一阶段落地):00 总览 → 01 双层适配器规范 → 02 依赖矩阵与拆分顺序 → 03 测试规范 → M1-M9 逐模块手册(C API 冻结);只拆分不重写,模块间 C ABI | R7 完成后启动(可与 R7 并行准备 |
| [`riir/`](riir/) | **模块拆分执行手册**(riir.md 第一阶段落地):00 总览 → 01 双层适配器规范 → 02 依赖矩阵与拆分顺序 → 03 测试规范 → M1-M9 逐模块手册(C API 冻结);只拆分不重写,模块间 C ABI | 已解锁(R7 完成),随时启动 |
| [`ai-agent-design.md`](ai-agent-design.md) | **AI Agent 设计**:多模态 LLM 经 MCP 调用策展工具面自动剪辑,渲染帧回喂形成"编辑→看图→再编辑"视觉闭环;含工具面、回放回路、安全与测试 | RIIR 拆分完成(面对一堆小库) |
| [`gtest-migration-guide.md`](gtest-migration-guide.md) | **测试统一到 Google Test**:把 OAK_ADD_TEST 宏框架、纯 C assert、已有 gtest 三套收敛为单一 Google Testctest 仅作运行器 | R5 验收完成后启动(可与 UI 改版并行 |
| [`ui-redesign-plan.md`](ui-redesign-plan.md) | **主界面 UI 改版**:依据 `design/` 三张设计图落地 10 个工作包(工具条、双监看、效果栈检查器、节点编辑器移位、电平条、状态栏等),全部文字精确定义 | R5 验收完成后启动(可与 GTest 迁移并行 |
| [`gtest-migration-guide.md`](gtest-migration-guide.md) | **测试统一到 Google Test**:把 OAK_ADD_TEST 宏框架、纯 C assert、已有 gtest 三套收敛为单一 Google Testctest 仅作运行器 | 已解锁(R5-R7 完成),可与 UI 改版并行 |
| [`ui-redesign-plan.md`](ui-redesign-plan.md) | **主界面 UI 改版**:依据 `design/` 三张设计图落地 10 个工作包(工具条、双监看、效果栈检查器、节点编辑器移位、电平条、状态栏等),全部文字精确定义 | 已解锁(R5-R7 完成),可与 GTest 迁移并行 |
## 当前进行中(不在本目录
## 已完成(completed/
C ABI 迁移战役的执行文档在 `docs/zh/`
C ABI 迁移战役B1-R7nm 557→0 + visibility 收口 3486→19)的
全套文档归档在 [`completed/`](completed/)
- `c-abi-migration-handoff.md`v3 交接)、`c-abi-migration-handoff-v4.md`v4 重做计划)
- `facade-migration-roadmap.md`(批次记录)
- `r5-app-migration-guide.md``r5-phase2-detailed-guide.md`R5 app 侧迁移指引)
- 交接:[`c-abi-migration-handoff.md`](completed/c-abi-migration-handoff.md)v3)、`-v4``-v5``-v6`
- 战役记录:[`facade-migration-roadmap.md`](completed/facade-migration-roadmap.md)
- R5[`r5-app-migration-guide.md`](completed/r5-app-migration-guide.md)、
[`r5-phase2-detailed-guide.md`](completed/r5-phase2-detailed-guide.md)、
[`r5-phase3-final-guide.md`](completed/r5-phase3-final-guide.md)、
[`r5-final-sprint.md`](completed/r5-final-sprint.md)
- R6/R7[`r6-cleanup-plan.md`](completed/r6-cleanup-plan.md)、
[`r7-pure-abi-plan.md`](completed/r7-pure-abi-plan.md)
## 其他参考
+24
View File
@@ -0,0 +1,24 @@
# 已完成战役归档(completed
本目录归档 **C ABI 迁移战役(B1-R7** 的全套文档。战役已结束:
- 成果:oak-editor / oak-render-worker / oak-cli 的 `U _ZN5olive`
557 降为 **0**liboakengine.so 导出 C++ 符号从 3486 收至 19
oakgl/oakvulkan dlopen 插件 ABI);ctest 45/45。
- 合并:压合为 6 个主题提交合入 main(`6ea653e6d`)。
## 文档索引
- **战役记录**`facade-migration-roadmap.md`(各批次完成记录)
- **交接文档**(执行过程的快照,记录历任执行者状态):
`c-abi-migration-handoff.md`v3)、`-v4``-v5``-v6`
- **R5**app 侧符号消除 557→58):
`r5-app-migration-guide.md``r5-phase2-detailed-guide.md`
`r5-phase3-final-guide.md``r5-final-sprint.md`
- **R6**(豁免清单清零 58→0):`r6-cleanup-plan.md`
- **R7**display.h POD 化 + visibility 收口):
`r7-pure-abi-plan.md`
这些文档**只作历史参考**,其中的"当前状态/进行中"描述均已是
过去时。后续工作在上一层:`../riir/`(模块拆分)、
`../gtest-migration-guide.md``../ui-redesign-plan.md`
@@ -81,7 +81,7 @@ cd cmake-build-debug && ctest --output-on-failure -j$(nproc)
colorcodingapp、filefunctionsapp、hashstreamapp、htmlapp、xmlutilsapp、debugapp)、
各 handle 头(keyframehandle/markerhandle/cliphandle/trackhandle/colorprocessorhandle/
vieweroutpututils)、`markerpainting.*``app/timeline/``app/ui/icons/`
- **文档**v3 交接文档(`c-abi-migration-handoff.md`)、RIIR 计划(`plans/riir.md`)、
- **文档**v3 交接文档(`c-abi-migration-handoff.md`)、RIIR 计划(`../riir.md`)、
roadmap 批次记录(经 LocalHistory 恢复,`facade-migration-roadmap.md`)。
- **结构性改动**:B1 图标(engine 返回图标名 + app from_name 映射,已修复一致)、
B2 布局 PODSerializedLayoutInfo 全链路,已修复一致)、B3 coreengine.h、
@@ -102,7 +102,7 @@ UndoCommand(3)。
4. ✅ 反作弊审计:app 无 `dlfcn.h`/dlsym/QLibrary 解析 engine 符号;
engine 无 inline 化(oakengine/*.h 纯 C 声明)。
5. ✅ `facade-migration-roadmap.md` 附 C R6 节已记录;
`plans/riir.md` §1.1 状态已更新为"边界已纯"。
`../riir.md` §1.1 状态已更新为"边界已纯"。
> 已知遗留(已论证,不泄漏符号):app 仍 include 约 40 个 engine C++ 头
> node/render/timeline/undo/pluginSupport,用于类型与 inline 访问器),
@@ -4,7 +4,7 @@
> 所有架构决策、边界契约、禁止事项已在本文钉死,执行时不得另行发明新方案;
> 遇到本文未覆盖的决策点,按"§8 决策兜底原则"处理,不得自由发挥。
>
> 相关文档:`docs/zh/facade-migration-roadmap.md`(各批次完成记录 + 附 D 事件机制 SOP)。
> 相关文档:`facade-migration-roadmap.md`(各批次完成记录 + 附 D 事件机制 SOP)。
>
> v32026-07-23):K2.7 对 DeepSeek Flash 的产出做了验收,修复了 6 个真实缺陷
> (详见 §2.2,每条都附教训——**这些错误模式不得再犯**)。当前符号 39、
@@ -309,7 +309,7 @@ set_value_at_time/staticMetaObject)从 inline 函数拉入,进豁免清单
### R6:豁免清单清零(58 → 0100% C ABI)— 完成
> 详见 `docs/zh/r6-cleanup-plan.md`(各 P 节已标 ✅)。目标:把 R5 遗留的
> 详见 `r6-cleanup-plan.md`(各 P 节已标 ✅)。目标:把 R5 遗留的
> 58 个豁免符号全部消除到 0,为 engine 模块化拆分与 RIIR 打地基。
- **P1F 类 facade 补齐,17**NodeValue 静态方法、VideoParams 构造器、
@@ -1,8 +1,8 @@
# R5 终局计划:181 → 豁免清单(≤6)
> 面向执行者(DeepSeek Flash),自包含。工作分支:`c-abi-migration`
> 前置文档:`../facade-migration-roadmap.md`(批次记录)、
> `../r5-app-migration-guide.md`R5 总指引)、`../c-abi-migration-handoff.md`
> 前置文档:`facade-migration-roadmap.md`(批次记录)、
> `r5-app-migration-guide.md`R5 总指引)、`c-abi-migration-handoff.md`
> (v3,§6.4 豁免清单格式)。本文是 R5 的**最后一个阶段**:
> 处置当前 WIP → 修完已记录缺陷 → 把剩余 181 个 `olive::` 符号收到豁免清单。
> 每批闭环:全量构建 0 error + 全量 ctest 绿 + nm 复核 + 立即提交。
@@ -5,7 +5,7 @@
> **背景**R5 已把 app 对 engine 的 `olive::` C++ 符号从 557 降到 58
> 剩余 58 个以"豁免清单"形式记录在 `c-abi-migration-handoff.md` §6.4。
> 本计划的目标是把它们**全部消除到 0**——这是后续 engine 模块化拆分
> 与 Rust 重写(RIIR,见 `plans/riir.md`)的硬前提:C ABI 边界上不能
> 与 Rust 重写(RIIR,见 `../riir.md`)的硬前提:C ABI 边界上不能
> 残留任何 C++ 渗漏。
>
> **三条红线**(违反即返工):
@@ -476,7 +476,7 @@ app 侧:`manageddisplay`/`viewerdisplay` 不再 `new OpenGLRenderer`
`grep -rn '#include "' app/ | grep -E '"(node|undo|task|render|timeline|pluginSupport)/'`
应为空或只剩极个别已论证的)。
4. `c-abi-migration-handoff.md` §6.4 豁免清单清空(改为"无豁免"),
roadmap 补 R6 批次记录,`plans/riir.md` 状态更新为"边界已纯"。
roadmap 补 R6 批次记录,`../riir.md` 状态更新为"边界已纯"。
## 执行顺序与节奏建议
@@ -207,7 +207,7 @@ grep -E '"(node|render|timeline|undo|task|pluginSupport)/'`)。不产生
1. `display.h` 全文无 C++ 类型签名/契约注释;app 无 TexturePtr/FramePtr。
2. liboakengine.so ` T _Z` = 0oak-editor ` U _ZN5olive` 保持 0。
3. 全量构建 0 error;全量 ctest 绿。
4. 更新 `plans/riir.md` 状态(边界已纯 → 可进 Step 1 拆分)、
4. 更新 `../riir.md` 状态(边界已纯 → 可进 Step 1 拆分)、
`facade-migration-roadmap.md` R7 批次记录。
5. 向用户报告,由用户宣布进入 riir.md §4 的模块拆分阶段。
@@ -225,4 +225,4 @@ grep -E '"(node|render|timeline|undo|task|pluginSupport)/'`)。不产生
- R7-Capp 的 ~40 个 engine C++ 头清理)未做,转入 riir/ 模块
拆分阶段顺带处理(M 系列手册的适配头天然覆盖)。
- 验收:构建 0 error、ctest 45/45、双二进制 nm U _ZN5olive = 0。
**RIIR 模块拆分(plans/riir/ M1-M9)解锁。**
**RIIR 模块拆分(../riir/ M1-M9)解锁。**
+2 -2
View File
@@ -1,6 +1,6 @@
# RIIR 绞杀者模式执行计划:liboakengine 模块化拆分与渐进式 Rust 重写
> 本文档描述在 C ABI 迁移战役(见 `c-abi-migration-handoff.md`)完成之后,
> 本文档描述在 C ABI 迁移战役(见 `completed/c-abi-migration-handoff.md`)完成之后,
> 如何用绞杀者模式(Strangler Fig)把 liboakengine.so 安全地拆成若干小模块,
> 再逐个重写为 Rust。
> **核心约束:每一步都可验证、可回退;任何一步失败都不影响已验证的部分。**
@@ -103,7 +103,7 @@
### Step 1 — 冻结 ABI
- 评审模块对外 C ABI 头(公开 facade 已有部分直接复用;模块间内部调用需要的
新增内部头,按 `c-abi-migration-handoff.md` §6.1 的同一套规则写:纯 C 类型、
新增内部头,按 `completed/c-abi-migration-handoff.md` §6.1 的同一套规则写:纯 C 类型、
buf/size 约定、owned/borrowed 注释、错误码)。
- 用门禁脚本生成 ABI 快照(§5-G1)并入库。**此后该头的任何改动都是显式评审行为。**
+1 -1
View File
@@ -22,7 +22,7 @@ oakrender/
### 2.1 `oakrender/renderer.h`(渲染器/纹理/blit
签名照 R7-A`docs/zh/r7-pure-abi-plan.md` §A.2)的 display.h 重写版
签名照 R7-A`docs/zh/plans/completed/r7-pure-abi-plan.md` §A.2)的 display.h 重写版
**原样采用**——R7-A 先做的话,M7 直接把它从 facade 层搬进
oakrender 并改前缀 `oakrender_display_*`;本表不重复,以 R7-A 为准。
补充后端管理:
+2 -2
View File
@@ -1,7 +1,7 @@
# Oak 主界面 UI 改版计划
> 本文是主界面重新设计的执行手册,面向 DeepSeek Flash(**不识字图,本文全部
> 用文字精确定义目标形态**)。详细程度对齐 `../r5-app-migration-guide.md`
> 用文字精确定义目标形态**)。详细程度对齐 `completed/r5-app-migration-guide.md`
> 工作分支:`c-abi-migration`。**启动前提:R5(C ABI app 侧迁移)验收完成之
> 后**;启动后可与 Google Test 统一迁移并行——协调规则见 §2。
> 依据:`design/Oak-UI设计图-主界面-标注版.png``...-效果栈版.png`
@@ -13,7 +13,6 @@
```
┌ 菜单栏:文件(F) 编辑(E) 视图(V) 回放(P) 序列(S) 窗口(W) 工具(T) 帮助(H)
├ 工具条(31px):14 个工具图标 + 吸附开关 + 缩放滑块 + 轨道高度滑块
├────────────────────────────────────────────────────────────────────
│ 素材查看器(源) │ 序列查看器(节目) │ 检查器│历史记录
│ ·适合/安全框 │ ·适合/安全框 │ ┌──────────────────┐
@@ -24,6 +23,7 @@
│ │ │ [+ 添加效果] │
│ │ │ └──────────────────┘
├────────────────────────────────────────────────────────────────────
├ 工具条(31px):14 个工具图标 + 吸附开关 + 缩放滑块 + 轨道高度滑块
│ 时间线(全宽贯通) │
│ 轨道头180px │ 轨道区 │
│ V2 视频轨道1 [锁定][显示] ████████ │
-11
View File
@@ -84,17 +84,6 @@ function(olive_add_test GROUP NAME SOURCE)
endif()
endfunction()
include(FetchContent)
find_package(GTest QUIET)
if (NOT GTest_FOUND)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()
add_subdirectory(compositing)
add_subdirectory(general)
add_subdirectory(timeline)