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) set(CMAKE_INCLUDE_CURRENT_DIR ON)
list(APPEND OLIVE_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/third_party) 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) add_subdirectory(core)
set(KDDockWidgets_STATIC ON CACHE INTERNAL "Force KDDockWidgets to build statically") set(KDDockWidgets_STATIC ON CACHE INTERNAL "Force KDDockWidgets to build statically")
+31 -7
View File
@@ -25,6 +25,7 @@
#include <QDebug> #include <QDebug>
#include <QMessageBox> #include <QMessageBox>
#include <QScreen> #include <QScreen>
#include <QToolBar>
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include <QOffscreenSurface> #include <QOffscreenSurface>
@@ -43,6 +44,8 @@
#include "oakengine/undo.h" #include "oakengine/undo.h"
#include "widget/viewer/vieweroutpututils.h" #include "widget/viewer/vieweroutpututils.h"
#include "widget/toolbar/toolbar.h"
#include "core.h"
namespace olive namespace olive
{ {
@@ -87,6 +90,29 @@ MainWindow::MainWindow(QWidget *parent)
&MainWindow::status_bar_double_clicked); &MainWindow::status_bar_double_clicked);
setStatusBar(status_bar); 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 // Create standard panels
node_panel_ = new NodePanel(); node_panel_ = new NodePanel();
footage_viewer_panel_ = new FootageViewerPanel(); footage_viewer_panel_ = new FootageViewerPanel();
@@ -938,22 +964,20 @@ void MainWindow::set_default_layout()
// Bottom center - timelines // Bottom center - timelines
addDockWidget(timeline_panels_.first(), KDDockWidgets::Location_OnBottom); 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 // Right of timeline - audio monitor
o.preferredSize = QSize(320, 0); o.preferredSize = QSize(320, 0);
addDockWidget(audio_monitor_panel_, KDDockWidgets::Location_OnRight, addDockWidget(audio_monitor_panel_, KDDockWidgets::Location_OnRight,
timeline_panels_.first(), o); timeline_panels_.first(), o);
// Bottom left - project panel // Bottom left - project panel (tool panel is now the top toolbar;
addDockWidget(project_panel_, KDDockWidgets::Location_OnLeft, tool_panel_); // the dockable ToolPanel remains available via Window menu)
addDockWidget(project_panel_, KDDockWidgets::Location_OnLeft,
timeline_panels_.first());
project_panel_->addDockWidgetAsTab(history_panel_); project_panel_->addDockWidgetAsTab(history_panel_);
project_panel_->raise(); project_panel_->raise();
// Hidden panels // Hidden panels
tool_panel_->close();
pixel_sampler_panel_->close(); pixel_sampler_panel_->close();
task_man_panel_->close(); task_man_panel_->close();
curve_panel_->close(); curve_panel_->close();
+5
View File
@@ -25,6 +25,8 @@
#include <kddockwidgets/Config.h> #include <kddockwidgets/Config.h>
#include <kddockwidgets/MainWindow.h> #include <kddockwidgets/MainWindow.h>
#include <QToolBar>
#include "node/project/serializer/serializedlayoutinfo.h" #include "node/project/serializer/serializedlayoutinfo.h"
#include "node/project.h" #include "node/project.h"
#include "panel/multicam/multicampanel.h" #include "panel/multicam/multicampanel.h"
@@ -152,6 +154,9 @@ private:
QByteArray premaximized_state_; QByteArray premaximized_state_;
// Application toolbar (31px, replaces the dockable ToolPanel by default)
QToolBar *tool_bar_;
// Standard panels // Standard panels
ProjectPanel *project_panel_; ProjectPanel *project_panel_;
NodePanel *node_panel_; NodePanel *node_panel_;
+2 -24
View File
@@ -120,29 +120,7 @@ else ()
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif () endif ()
if (OLIVECORE_BUILD_TESTS) if (OLIVECORE_BUILD_TESTS AND BUILD_TESTS)
enable_testing() enable_testing()
add_subdirectory(tests)
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)
endif() 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)
+106 -192
View File
@@ -1,247 +1,161 @@
/*** #include <gtest/gtest.h>
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 <cstdint> #include <cstdint>
#include <cstdio>
#include "olive/core/oakcore/audioparams.h" #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_mono = 0x4;
static const uint64_t k_layout_stereo = 0x3; static const uint64_t k_layout_stereo = 0x3;
static const uint64_t k_layout_2_1 = 0x103; static const uint64_t k_layout_2_1 = 0x103;
static const uint64_t k_layout_5_point1 = 0x60F; static const uint64_t k_layout_5_point1 = 0x60F;
static const uint64_t k_layout_7_point1 = 0x63F; 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_invalid = -1;
static const int k_format_u8_p = 0; static const int k_format_u8_p = 0;
static const int k_format_f32_p = 4; static const int k_format_f32_p = 4;
static const int k_format_s16 = 7; 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(); OakAudioParams *p = oakcore_audioparams_create_invalid();
assert(p != nullptr); ASSERT_NE(p, nullptr);
assert(oakcore_audioparams_is_valid(p) == 0); EXPECT_EQ(oakcore_audioparams_is_valid(p), 0);
assert(oakcore_audioparams_sample_rate(p) == 0); EXPECT_EQ(oakcore_audioparams_sample_rate(p), 0);
assert(oakcore_audioparams_channel_layout(p) == 0); EXPECT_EQ(oakcore_audioparams_channel_layout(p), (uint64_t)0);
assert(oakcore_audioparams_channel_count(p) == 0); EXPECT_EQ(oakcore_audioparams_channel_count(p), 0);
assert(oakcore_audioparams_format(p) == k_format_invalid); EXPECT_EQ(oakcore_audioparams_format(p), k_format_invalid);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 0); EXPECT_EQ(oakcore_audioparams_bytes_per_sample_per_channel(p), 0);
assert(oakcore_audioparams_bits_per_sample(p) == 0); EXPECT_EQ(oakcore_audioparams_bits_per_sample(p), 0);
assert(oakcore_audioparams_enabled(p) == 1); EXPECT_EQ(oakcore_audioparams_enabled(p), 1);
assert(oakcore_audioparams_stream_index(p) == 0); EXPECT_EQ(oakcore_audioparams_stream_index(p), 0);
assert(oakcore_audioparams_duration(p) == 0); EXPECT_EQ(oakcore_audioparams_duration(p), 0);
OakRational *tb = oakcore_audioparams_time_base(p); 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_rational_free(tb);
oakcore_audioparams_free(p); oakcore_audioparams_free(p);
} }
// Full constructor: 48000 Hz stereo s16 TEST(OakcoreAudioParams, FullConstruction)
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);
// Timebase defaults to 1/sample_rate
{ {
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);
OakRational *tb = oakcore_audioparams_time_base(p); OakRational *tb = oakcore_audioparams_time_base(p);
assert(oakcore_rational_numerator(tb) == 1); EXPECT_EQ(oakcore_rational_numerator(tb), 1);
assert(oakcore_rational_denominator(tb) == 48000); EXPECT_EQ(oakcore_rational_denominator(tb), 48000);
oakcore_rational_free(tb); 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);
} }
// Setters / getters TEST(OakcoreAudioParams, SettersGetters)
oakcore_audioparams_set_sample_rate(p, 44100);
assert(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);
oakcore_audioparams_set_channel_layout(p, k_layout_5_point1);
assert(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); OakAudioParams *p = oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
oakcore_audioparams_set_time_base(p, tb);
oakcore_rational_free(tb);
OakRational *got = oakcore_audioparams_time_base(p); oakcore_audioparams_set_sample_rate(p, 44100);
assert(oakcore_rational_numerator(got) == 1); EXPECT_EQ(oakcore_audioparams_sample_rate(p), 44100);
assert(oakcore_rational_denominator(got) == 1000); oakcore_audioparams_set_sample_rate(p, 48000);
oakcore_rational_free(got);
// Restore the default 1/48000 timebase oakcore_audioparams_set_channel_layout(p, k_layout_mono);
tb = oakcore_rational_create_nd(1, 48000); EXPECT_EQ(oakcore_audioparams_channel_count(p), 1);
oakcore_audioparams_set_time_base(p, tb); oakcore_audioparams_set_channel_layout(p, k_layout_5_point1);
oakcore_rational_free(tb); EXPECT_EQ(oakcore_audioparams_channel_count(p), 6);
} oakcore_audioparams_set_channel_layout(p, k_layout_stereo);
EXPECT_EQ(oakcore_audioparams_channel_count(p), 2);
oakcore_audioparams_set_format(p, k_format_f32_p); oakcore_audioparams_set_format(p, k_format_f32_p);
assert(oakcore_audioparams_format(p) == k_format_f32_p); EXPECT_EQ(oakcore_audioparams_format(p), k_format_f32_p);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 4); EXPECT_EQ(oakcore_audioparams_bytes_per_sample_per_channel(p), 4);
assert(oakcore_audioparams_bits_per_sample(p) == 32); EXPECT_EQ(oakcore_audioparams_bits_per_sample(p), 32);
oakcore_audioparams_set_format(p, k_format_s16); oakcore_audioparams_set_format(p, k_format_s16);
assert(oakcore_audioparams_bytes_per_sample_per_channel(p) == 2);
oakcore_audioparams_set_enabled(p, 0); 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); oakcore_audioparams_set_enabled(p, 1);
assert(oakcore_audioparams_enabled(p) == 1);
oakcore_audioparams_set_stream_index(p, 3); oakcore_audioparams_set_stream_index(p, 3);
assert(oakcore_audioparams_stream_index(p) == 3); EXPECT_EQ(oakcore_audioparams_stream_index(p), 3);
oakcore_audioparams_set_stream_index(p, 0);
const int64_t big_duration = int64_t(1) << 40; const int64_t big_duration = int64_t(1) << 40;
oakcore_audioparams_set_duration(p, big_duration); oakcore_audioparams_set_duration(p, big_duration);
assert(oakcore_audioparams_duration(p) == big_duration); EXPECT_EQ(oakcore_audioparams_duration(p), big_duration);
oakcore_audioparams_set_duration(p, 0);
// Time/sample/byte conversions (48000 Hz, 2 channels, 2 bytes/sample) oakcore_audioparams_free(p);
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);
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);
assert(oakcore_audioparams_time_to_bytes_per_channel(p, 1.0) == 96000);
assert(oakcore_audioparams_time_to_bytes(p, 1.0) == 192000);
assert(oakcore_audioparams_bytes_to_samples(p, 400) == 100);
assert(oakcore_audioparams_bytes_to_samples(p, 0) == 0);
// 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);
oakcore_rational_free(half);
} }
// Rational-returning conversions TEST(OakcoreAudioParams, TimeSampleByteConversions)
{ {
OakAudioParams *p = oakcore_audioparams_create(48000, k_layout_stereo, k_format_s16);
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);
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);
OakRational *half = oakcore_rational_create_nd(1, 2);
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);
OakRational *t = oakcore_audioparams_samples_to_time(p, 48000); OakRational *t = oakcore_audioparams_samples_to_time(p, 48000);
assert(oakcore_rational_numerator(t) == 1); EXPECT_EQ(oakcore_rational_numerator(t), 1);
assert(oakcore_rational_denominator(t) == 1); EXPECT_EQ(oakcore_rational_denominator(t), 1);
oakcore_rational_free(t); oakcore_rational_free(t);
t = oakcore_audioparams_samples_to_time(p, 24000); t = oakcore_audioparams_samples_to_time(p, 24000);
assert(oakcore_rational_numerator(t) == 1); EXPECT_EQ(oakcore_rational_numerator(t), 1);
assert(oakcore_rational_denominator(t) == 2); EXPECT_EQ(oakcore_rational_denominator(t), 2);
oakcore_rational_free(t); 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); oakcore_audioparams_free(p);
}
std::printf("oakcore_audioparams_test: all assertions passed\n"); TEST(OakcoreAudioParams, CopyAndEquality)
return 0; {
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);
} }
+87 -107
View File
@@ -1,144 +1,124 @@
/*** #include <gtest/gtest.h>
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 <math.h> #include <math.h>
#include <stdio.h>
#include "olive/core/oakcore/bezier.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(); 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_x(b, 1.5);
oakcore_bezier_set_y(b, -2.25); oakcore_bezier_set_y(b, -2.25);
oakcore_bezier_set_cp1_x(b, 0.1); oakcore_bezier_set_cp1_x(b, 0.1);
oakcore_bezier_set_cp1_y(b, 0.2); oakcore_bezier_set_cp1_y(b, 0.2);
oakcore_bezier_set_cp2_x(b, 0.3); oakcore_bezier_set_cp2_x(b, 0.3);
oakcore_bezier_set_cp2_y(b, 0.4); oakcore_bezier_set_cp2_y(b, 0.4);
assert(double_eq(oakcore_bezier_x(b), 1.5)); EXPECT_NEAR(oakcore_bezier_x(b), 1.5, 1e-9);
assert(double_eq(oakcore_bezier_y(b), -2.25)); EXPECT_NEAR(oakcore_bezier_y(b), -2.25, 1e-9);
assert(double_eq(oakcore_bezier_cp1_x(b), 0.1)); EXPECT_NEAR(oakcore_bezier_cp1_x(b), 0.1, 1e-9);
assert(double_eq(oakcore_bezier_cp1_y(b), 0.2)); EXPECT_NEAR(oakcore_bezier_cp1_y(b), 0.2, 1e-9);
assert(double_eq(oakcore_bezier_cp2_x(b), 0.3)); EXPECT_NEAR(oakcore_bezier_cp2_x(b), 0.3, 1e-9);
assert(double_eq(oakcore_bezier_cp2_y(b), 0.4)); 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); OakBezier *xy = oakcore_bezier_create_xy(3.0, 4.0);
assert(double_eq(oakcore_bezier_x(xy), 3.0)); EXPECT_NEAR(oakcore_bezier_x(xy), 3.0, 1e-9);
assert(double_eq(oakcore_bezier_y(xy), 4.0)); EXPECT_NEAR(oakcore_bezier_y(xy), 4.0, 1e-9);
assert(double_eq(oakcore_bezier_cp1_x(xy), 0.0)); EXPECT_NEAR(oakcore_bezier_cp1_x(xy), 0.0, 1e-9);
assert(double_eq(oakcore_bezier_cp1_y(xy), 0.0)); EXPECT_NEAR(oakcore_bezier_cp1_y(xy), 0.0, 1e-9);
assert(double_eq(oakcore_bezier_cp2_x(xy), 0.0)); EXPECT_NEAR(oakcore_bezier_cp2_x(xy), 0.0, 1e-9);
assert(double_eq(oakcore_bezier_cp2_y(xy), 0.0)); EXPECT_NEAR(oakcore_bezier_cp2_y(xy), 0.0, 1e-9);
oakcore_bezier_free(xy);
}
// Full constructor TEST(OakcoreBezier, FullConstructor)
OakBezier *full = {
oakcore_bezier_create_full(1.0, 2.0, 0.25, 0.5, 0.75, 1.0); 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)); EXPECT_NEAR(oakcore_bezier_x(full), 1.0, 1e-9);
assert(double_eq(oakcore_bezier_y(full), 2.0)); EXPECT_NEAR(oakcore_bezier_y(full), 2.0, 1e-9);
assert(double_eq(oakcore_bezier_cp1_x(full), 0.25)); EXPECT_NEAR(oakcore_bezier_cp1_x(full), 0.25, 1e-9);
assert(double_eq(oakcore_bezier_cp1_y(full), 0.5)); EXPECT_NEAR(oakcore_bezier_cp1_y(full), 0.5, 1e-9);
assert(double_eq(oakcore_bezier_cp2_x(full), 0.75)); EXPECT_NEAR(oakcore_bezier_cp2_x(full), 0.75, 1e-9);
assert(double_eq(oakcore_bezier_cp2_y(full), 1.0)); 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); OakBezier *dup = oakcore_bezier_copy(full);
assert(double_eq(oakcore_bezier_x(dup), 1.0)); EXPECT_NEAR(oakcore_bezier_x(dup), 1.0, 1e-9);
assert(double_eq(oakcore_bezier_cp2_y(dup), 1.0)); EXPECT_NEAR(oakcore_bezier_cp2_y(dup), 1.0, 1e-9);
oakcore_bezier_set_x(dup, 9.0); oakcore_bezier_set_x(dup, 9.0);
oakcore_bezier_set_cp1_y(dup, 8.0); oakcore_bezier_set_cp1_y(dup, 8.0);
assert(double_eq(oakcore_bezier_x(full), 1.0)); EXPECT_NEAR(oakcore_bezier_x(full), 1.0, 1e-9);
assert(double_eq(oakcore_bezier_cp1_y(full), 0.5)); EXPECT_NEAR(oakcore_bezier_cp1_y(full), 0.5, 1e-9);
// Quadratic evaluation: endpoints and x->t->y round trip oakcore_bezier_free(dup);
assert(double_eq(oakcore_bezier_quadratic_tto_y(0.0, 0.5, 1.0, 0.0), 0.0)); oakcore_bezier_free(full);
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
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);
const double x = 0.3;
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_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); 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); EXPECT_GE(t_lo, 0.0);
assert(t_hi >= 0.0 && t_hi <= 1.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 TEST(OakcoreBezier, CubicEvaluation)
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));
{ {
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 x = 0.6;
const double t = const double t = oakcore_bezier_cubic_xto_t(x, 0.0, 0.1, 0.9, 1.0);
oakcore_bezier_cubic_xto_t(x, 0.0, 0.1, 0.9, 1.0); EXPECT_GE(t, 0.0);
const double y = EXPECT_LE(t, 1.0);
oakcore_bezier_cubic_tto_y(0.0, 0.8, 0.2, 1.0, t); EXPECT_NEAR(oakcore_bezier_cubic_tto_y(0.0, 0.1, 0.9, 1.0, t), x, 1e-5);
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
TEST(OakcoreBezier, CubicClamps)
{ {
const double t_lo = const double t_lo = oakcore_bezier_cubic_xto_t(-5.0, 0.0, 0.25, 0.75, 1.0);
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);
const double t_hi = EXPECT_GE(t_lo, 0.0);
oakcore_bezier_cubic_xto_t(5.0, 0.0, 0.25, 0.75, 1.0); EXPECT_LE(t_lo, 1.0);
assert(t_lo >= 0.0 && t_lo <= 1.0); EXPECT_GE(t_hi, 0.0);
assert(t_hi >= 0.0 && t_hi <= 1.0); EXPECT_LE(t_hi, 1.0);
} }
// Ownership: every handle released exactly once TEST(OakcoreBezier, FreeNull)
oakcore_bezier_free(b); {
oakcore_bezier_free(xy);
oakcore_bezier_free(full);
oakcore_bezier_free(dup);
oakcore_bezier_free(NULL); oakcore_bezier_free(NULL);
printf("oakcore_bezier_test: all assertions passed\n");
return 0;
} }
+99 -248
View File
@@ -1,351 +1,202 @@
/*** #include <gtest/gtest.h>
#include <math.h>
Oak - Non-Linear Video Editor #include <stdint.h>
Copyright (C) 2026 Oak Team #include <string.h>
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 "olive/core/oakcore/color.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 }; enum { PF_U8 = 0, PF_U10 = 1, PF_U16 = 2, PF_F16 = 3, PF_F32 = 4 };
static int feq(float a, float b) static bool feq(float a, float b) { return fabsf(a - b) < 1e-5f; }
{
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(); OakColor *c = oakcore_color_create();
assert(c != NULL); ASSERT_NE(c, nullptr);
assert(feq(oakcore_color_red(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_alpha(c), 0.0f));
oakcore_color_free(c); oakcore_color_free(c);
/* RGBA construction + getters */
c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f); c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f);
assert(feq(oakcore_color_red(c), 0.25f)); EXPECT_TRUE(feq(oakcore_color_red(c), 0.25f));
assert(feq(oakcore_color_green(c), 0.5f)); EXPECT_TRUE(feq(oakcore_color_green(c), 0.5f));
assert(feq(oakcore_color_blue(c), 0.75f)); EXPECT_TRUE(feq(oakcore_color_blue(c), 0.75f));
assert(feq(oakcore_color_alpha(c), 1.0f)); 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_red(c, -0.5f);
oakcore_color_set_green(c, 2.0f); oakcore_color_set_green(c, 2.0f);
oakcore_color_set_blue(c, 0.0f); oakcore_color_set_blue(c, 0.0f);
oakcore_color_set_alpha(c, 0.125f); oakcore_color_set_alpha(c, 0.125f);
assert(feq(oakcore_color_red(c), -0.5f)); EXPECT_TRUE(feq(oakcore_color_red(c), -0.5f));
assert(feq(oakcore_color_green(c), 2.0f)); EXPECT_TRUE(feq(oakcore_color_green(c), 2.0f));
assert(feq(oakcore_color_blue(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 0.125f)); EXPECT_TRUE(feq(oakcore_color_alpha(c), 0.125f));
oakcore_color_free(c); 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 *c = oakcore_color_create_rgba(0.1f, 0.2f, 0.3f, 0.4f);
OakColor *copy = oakcore_color_copy(c); OakColor *copy = oakcore_color_copy(c);
assert(copy != NULL); ASSERT_NE(copy, nullptr);
assert(feq(oakcore_color_red(copy), 0.1f)); EXPECT_TRUE(feq(oakcore_color_red(copy), 0.1f));
assert(feq(oakcore_color_green(copy), 0.2f)); EXPECT_TRUE(feq(oakcore_color_alpha(copy), 0.4f));
assert(feq(oakcore_color_blue(copy), 0.3f));
assert(feq(oakcore_color_alpha(copy), 0.4f));
/* The copy is independent from the original */
oakcore_color_set_red(copy, 0.9f); oakcore_color_set_red(copy, 0.9f);
assert(feq(oakcore_color_red(copy), 0.9f)); EXPECT_TRUE(feq(oakcore_color_red(copy), 0.9f));
assert(feq(oakcore_color_red(c), 0.1f)); EXPECT_TRUE(feq(oakcore_color_red(c), 0.1f));
oakcore_color_free(copy); oakcore_color_free(copy);
oakcore_color_free(c); oakcore_color_free(c);
/* Releasing a NULL handle must be safe */
oakcore_color_free(NULL); 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); OakColor *c = oakcore_color_from_hsv(0.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_red(c), 1.0f)); EXPECT_TRUE(feq(oakcore_color_red(c), 1.0f));
assert(feq(oakcore_color_green(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_blue(c), 0.0f));
assert(feq(oakcore_color_alpha(c), 1.0f)); EXPECT_TRUE(feq(oakcore_color_alpha(c), 1.0f));
oakcore_color_free(c); oakcore_color_free(c);
c = oakcore_color_from_hsv(120.0f, 1.0f, 1.0f); c = oakcore_color_from_hsv(120.0f, 1.0f, 1.0f);
assert(feq(oakcore_color_red(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_red(c), 0.0f));
assert(feq(oakcore_color_green(c), 1.0f)); EXPECT_TRUE(feq(oakcore_color_green(c), 1.0f));
assert(feq(oakcore_color_blue(c), 0.0f));
oakcore_color_free(c); oakcore_color_free(c);
c = oakcore_color_from_hsv(240.0f, 1.0f, 0.5f); c = oakcore_color_from_hsv(240.0f, 1.0f, 0.5f);
assert(feq(oakcore_color_red(c), 0.0f)); EXPECT_TRUE(feq(oakcore_color_blue(c), 0.5f));
assert(feq(oakcore_color_green(c), 0.0f));
assert(feq(oakcore_color_blue(c), 0.5f));
oakcore_color_free(c); oakcore_color_free(c);
/* to_hsv of pure red and gray */
c = oakcore_color_create_rgba(1.0f, 0.0f, 0.0f, 1.0f); 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); oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 0.0f)); EXPECT_TRUE(feq(h, 0.0f));
assert(feq(s, 1.0f)); EXPECT_TRUE(feq(s, 1.0f));
assert(feq(v, 1.0f)); EXPECT_TRUE(feq(v, 1.0f));
assert(feq(oakcore_color_hsv_hue(c), h)); EXPECT_TRUE(feq(oakcore_color_hsv_hue(c), h));
assert(feq(oakcore_color_hsv_saturation(c), s));
assert(feq(oakcore_color_value(c), v));
oakcore_color_free(c); oakcore_color_free(c);
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f); // Roundtrip
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 */
c = oakcore_color_create_rgba(0.2f, 0.4f, 0.8f, 1.0f); c = oakcore_color_create_rgba(0.2f, 0.4f, 0.8f, 1.0f);
oakcore_color_to_hsv(c, &h, &s, &v); oakcore_color_to_hsv(c, &h, &s, &v);
assert(feq(h, 220.0f)); EXPECT_TRUE(feq(h, 220.0f));
assert(feq(s, 0.75f)); EXPECT_TRUE(feq(s, 0.75f));
assert(feq(v, 0.8f)); EXPECT_TRUE(feq(v, 0.8f));
OakColor *rt = oakcore_color_from_hsv(h, s, v); OakColor *rt = oakcore_color_from_hsv(h, s, v);
assert(feq(oakcore_color_red(rt), 0.2f)); EXPECT_TRUE(feq(oakcore_color_red(rt), 0.2f));
assert(feq(oakcore_color_green(rt), 0.4f)); EXPECT_TRUE(feq(oakcore_color_green(rt), 0.4f));
assert(feq(oakcore_color_blue(rt), 0.8f)); EXPECT_TRUE(feq(oakcore_color_blue(rt), 0.8f));
oakcore_color_free(rt); oakcore_color_free(rt);
oakcore_color_free(c); 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); 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); oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 0.0f)); EXPECT_TRUE(feq(h, 0.0f));
assert(feq(s, 1.0f)); EXPECT_TRUE(feq(s, 1.0f));
assert(feq(l, 0.5f)); EXPECT_TRUE(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));
oakcore_color_free(c); 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); c = oakcore_color_create_rgba(0.2f, 0.4f, 0.8f, 1.0f);
oakcore_color_to_hsl(c, &h, &s, &l); oakcore_color_to_hsl(c, &h, &s, &l);
assert(feq(h, 220.0f)); EXPECT_TRUE(feq(h, 220.0f));
assert(feq(s, 0.6f)); EXPECT_TRUE(feq(s, 0.6f));
assert(feq(l, 0.5f)); EXPECT_TRUE(feq(l, 0.5f));
oakcore_color_free(c); 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); 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); const float *cd = oakcore_color_const_data(c);
assert(cd != NULL); ASSERT_NE(cd, nullptr);
assert(feq(cd[0], 0.1f)); EXPECT_TRUE(feq(cd[0], 0.1f));
assert(feq(cd[1], 0.2f)); EXPECT_TRUE(feq(cd[3], 0.4f));
assert(feq(cd[2], 0.3f));
assert(feq(cd[3], 0.4f));
/* Mutable write access */
float *d = oakcore_color_data(c); float *d = oakcore_color_data(c);
assert(d != NULL); ASSERT_NE(d, nullptr);
d[0] = 0.125f; d[0] = 0.125f;
d[3] = 1.0f; d[3] = 1.0f;
assert(feq(oakcore_color_red(c), 0.125f)); EXPECT_TRUE(feq(oakcore_color_red(c), 0.125f));
assert(feq(oakcore_color_alpha(c), 1.0f)); EXPECT_TRUE(feq(oakcore_color_alpha(c), 1.0f));
oakcore_color_free(c); oakcore_color_free(c);
} }
static void test_pixel_data(void) TEST(OakcoreColor, PixelDataU8)
{ {
char buf[16]; char buf[16];
memset(buf, 0, sizeof(buf)); memset(buf, 0, sizeof(buf));
/* u8 roundtrip, 4 channels */
OakColor *c = oakcore_color_create_rgba(1.0f, 0.0f, 1.0f, 1.0f); OakColor *c = oakcore_color_create_rgba(1.0f, 0.0f, 1.0f, 1.0f);
oakcore_color_to_data(c, buf, PF_U8, 4); oakcore_color_to_data(c, buf, PF_U8, 4);
assert((uint8_t)buf[0] == 255); EXPECT_EQ((uint8_t)buf[0], 255);
assert((uint8_t)buf[1] == 0); EXPECT_EQ((uint8_t)buf[1], 0);
assert((uint8_t)buf[2] == 255); EXPECT_EQ((uint8_t)buf[2], 255);
assert((uint8_t)buf[3] == 255);
OakColor *back = oakcore_color_from_data(buf, PF_U8, 4); OakColor *back = oakcore_color_from_data(buf, PF_U8, 4);
assert(feq(oakcore_color_red(back), 1.0f)); EXPECT_TRUE(feq(oakcore_color_red(back), 1.0f));
assert(feq(oakcore_color_green(back), 0.0f)); EXPECT_TRUE(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));
oakcore_color_free(back); oakcore_color_free(back);
oakcore_color_free(c); oakcore_color_free(c);
} }
static void test_luminance(void) TEST(OakcoreColor, PixelDataF32AndF16)
{ {
/* (2r + b + 3g) / 6 */ char buf[16];
OakColor *c = oakcore_color_create_rgba(1.0f, 1.0f, 1.0f, 1.0f); OakColor *c = oakcore_color_create_rgba(0.25f, 0.5f, 0.75f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c), 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); oakcore_color_free(c);
c = oakcore_color_create_rgba(0.5f, 0.5f, 0.5f, 1.0f); c = oakcore_color_create_rgba(1.0f, 0.5f, 0.0f, 1.0f);
assert(feq(oakcore_color_get_rough_luminance(c), 0.5f)); 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); oakcore_color_free(c);
c = oakcore_color_create_rgba(0.6f, 0.2f, 0.4f, 1.0f); 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)); (2.0f * 0.6f + 0.4f + 3.0f * 0.2f) / 6.0f));
oakcore_color_free(c); 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 *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); OakColor *b = oakcore_color_create_rgba(0.4f, 0.3f, 0.2f, 0.1f);
/* Color +=/-= Color */
oakcore_color_add_assign(a, b); oakcore_color_add_assign(a, b);
assert(feq(oakcore_color_red(a), 0.5f)); EXPECT_TRUE(feq(oakcore_color_red(a), 0.5f));
assert(feq(oakcore_color_green(a), 0.5f)); EXPECT_TRUE(feq(oakcore_color_alpha(a), 0.5f));
assert(feq(oakcore_color_blue(a), 0.5f));
assert(feq(oakcore_color_alpha(a), 0.5f));
oakcore_color_sub_assign(a, b); oakcore_color_sub_assign(a, b);
assert(feq(oakcore_color_red(a), 0.1f)); EXPECT_TRUE(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));
/* 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); oakcore_color_mul_scalar_assign(a, 2.0f);
assert(feq(oakcore_color_red(a), 1.0f)); EXPECT_TRUE(feq(oakcore_color_red(a), 0.2f));
assert(feq(oakcore_color_green(a), 1.2f)); EXPECT_TRUE(feq(oakcore_color_green(a), 0.4f));
assert(feq(oakcore_color_blue(a), 1.4f)); oakcore_color_div_scalar_assign(a, 2.0f);
assert(feq(oakcore_color_alpha(a), 1.6f)); EXPECT_TRUE(feq(oakcore_color_red(a), 0.1f));
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));
oakcore_color_free(b); oakcore_color_free(b);
oakcore_color_free(a); 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;
}
+68 -81
View File
@@ -1,126 +1,113 @@
/*** #include <gtest/gtest.h>
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 <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
/** #include "oakcore/fractionutils.h"
* @file oakcore_fractionutils_test.cpp
* @brief Pure C API test for the fraction utility functions TEST(OakcoreFractionUtils, ReduceFraction)
*
* Exercises every oakcore_fractionutils_* function directly, without the
* C++ wrapper and without a test framework.
*/
int main()
{
// reduce_fraction: greatest common divisor is divided out
{ {
int64_t num = 6, den = 9; int64_t num = 6, den = 9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX); 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; int64_t num = -6, den = 9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX); 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; int64_t num = 6, den = -9;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX); 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; int64_t num = 42, den = 0;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX); 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; int64_t num = 0, den = 7;
oakcore_fractionutils_reduce_fraction(&num, &den, INT64_MAX); 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; int64_t num = 1000000, den = 1000000;
oakcore_fractionutils_reduce_fraction(&num, &den, 100); 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; int64_t num = 1048576, den = 1000000;
oakcore_fractionutils_reduce_fraction(&num, &den, 10000); 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); const double approx = double(num) / double(den);
assert(fabs(approx - 1.048576) < 0.001); EXPECT_NEAR(approx, 1.048576, 0.001);
} }
// compare_fractions: three-way result -1 / 0 / 1 TEST(OakcoreFractionUtils, CompareFractions)
assert(oakcore_fractionutils_compare_fractions(1, 3, 1, 2) == -1); {
assert(oakcore_fractionutils_compare_fractions(1, 2, 2, 4) == 0); EXPECT_EQ(oakcore_fractionutils_compare_fractions(1, 3, 1, 2), -1);
assert(oakcore_fractionutils_compare_fractions(2, 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);
}
// compare_fractions: meaningless degenerate comparison returns INT_MIN TEST(OakcoreFractionUtils, RescaleRndExact)
assert(oakcore_fractionutils_compare_fractions(0, 0, 0, 5) == INT_MIN); {
EXPECT_EQ(oakcore_fractionutils_rescale_rnd(100, 3, 4,
OAK_FRACTION_ROUNDING_NEAR_INF), 75);
}
// rescale_rnd: exact division needs no rounding TEST(OakcoreFractionUtils, RescaleRndNearInf)
assert(oakcore_fractionutils_rescale_rnd(100, 3, 4, {
OAK_FRACTION_ROUNDING_NEAR_INF) == 75); 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);
}
// rescale_rnd: round to nearest, halfway cases away from zero TEST(OakcoreFractionUtils, RescaleRndUp)
assert(oakcore_fractionutils_rescale_rnd(5, 1, 2, {
OAK_FRACTION_ROUNDING_NEAR_INF) == 3); EXPECT_EQ(oakcore_fractionutils_rescale_rnd(5, 1, 2,
assert(oakcore_fractionutils_rescale_rnd(-5, 1, 2, OAK_FRACTION_ROUNDING_UP), 3);
OAK_FRACTION_ROUNDING_NEAR_INF) == -3); EXPECT_EQ(oakcore_fractionutils_rescale_rnd(-5, 1, 2,
assert(oakcore_fractionutils_rescale_rnd(7, 1, 2, OAK_FRACTION_ROUNDING_UP), -2);
OAK_FRACTION_ROUNDING_NEAR_INF) == 4); EXPECT_EQ(oakcore_fractionutils_rescale_rnd(4, 1, 2,
OAK_FRACTION_ROUNDING_UP), 2);
}
// rescale_rnd: round toward positive infinity TEST(OakcoreFractionUtils, RescaleRndNegativeDivisor)
assert(oakcore_fractionutils_rescale_rnd(5, 1, 2, {
OAK_FRACTION_ROUNDING_UP) == 3); EXPECT_EQ(oakcore_fractionutils_rescale_rnd(10, 1, -2,
assert(oakcore_fractionutils_rescale_rnd(-5, 1, 2, OAK_FRACTION_ROUNDING_NEAR_INF), -5);
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 TEST(OakcoreFractionUtils, RescaleRndLargeIntermediates)
assert(oakcore_fractionutils_rescale_rnd(10, 1, -2, {
OAK_FRACTION_ROUNDING_NEAR_INF) == -5); EXPECT_EQ(oakcore_fractionutils_rescale_rnd(INT64_MAX / 2, 4, 2,
OAK_FRACTION_ROUNDING_NEAR_INF),
// rescale_rnd: large intermediates stay exact (128-bit path) INT64_MAX - 1);
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;
} }
+98 -66
View File
@@ -1,110 +1,142 @@
#include <assert.h> #include <gtest/gtest.h>
#include <math.h> #include <math.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include "olive/core/oakcore/rational.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); OakRational *r = oakcore_rational_create_nd(2, 4);
assert(r != NULL); ASSERT_NE(r, nullptr);
assert(oakcore_rational_numerator(r) == 1); EXPECT_EQ(oakcore_rational_numerator(r), 1);
assert(oakcore_rational_denominator(r) == 2); EXPECT_EQ(oakcore_rational_denominator(r), 2);
OakRational *neg = oakcore_rational_create_nd(1, -2); OakRational *neg = oakcore_rational_create_nd(1, -2);
assert(oakcore_rational_numerator(neg) == -1); EXPECT_EQ(oakcore_rational_numerator(neg), -1);
assert(oakcore_rational_denominator(neg) == 2); EXPECT_EQ(oakcore_rational_denominator(neg), 2);
oakcore_rational_free(neg); oakcore_rational_free(neg);
oakcore_rational_free(r);
}
// create defaults to n/1 TEST(OakcoreRational, CreateDefaultsToNOver1)
{
OakRational *five = oakcore_rational_create(5); OakRational *five = oakcore_rational_create(5);
assert(oakcore_rational_numerator(five) == 5); EXPECT_EQ(oakcore_rational_numerator(five), 5);
assert(oakcore_rational_denominator(five) == 1); EXPECT_EQ(oakcore_rational_denominator(five), 1);
oakcore_rational_free(five); oakcore_rational_free(five);
}
// NaN TEST(OakcoreRational, NaN)
{
OakRational *nan = oakcore_rational_create_nan(); OakRational *nan = oakcore_rational_create_nan();
assert(oakcore_rational_is_nan(nan) == 1); EXPECT_EQ(oakcore_rational_is_nan(nan), 1);
assert(oakcore_rational_is_null(nan) == 1); EXPECT_EQ(oakcore_rational_is_null(nan), 1);
assert(isnan(oakcore_rational_to_double(nan))); EXPECT_TRUE(isnan(oakcore_rational_to_double(nan)));
oakcore_rational_free(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); OakRational *copy = oakcore_rational_copy(r);
assert(copy != r); EXPECT_NE(copy, r);
assert(oakcore_rational_compare(copy, r) == 0); EXPECT_EQ(oakcore_rational_compare(copy, r), 0);
oakcore_rational_add_assign(copy, r); oakcore_rational_add_assign(copy, r);
assert(oakcore_rational_numerator(copy) == 1); EXPECT_EQ(oakcore_rational_numerator(copy), 1);
assert(oakcore_rational_denominator(copy) == 1); EXPECT_EQ(oakcore_rational_denominator(copy), 1);
assert(oakcore_rational_numerator(r) == 1); // Original unchanged
assert(oakcore_rational_denominator(r) == 2); EXPECT_EQ(oakcore_rational_numerator(r), 1);
EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_free(copy); 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]; char buf[32];
int needed = oakcore_rational_to_string(r, NULL, 0); EXPECT_EQ(oakcore_rational_to_string(r, buf, sizeof(buf)), 3);
assert(needed == 3); EXPECT_STREQ(buf, "1/2");
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);
// 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; int ok = 0;
OakRational *r = oakcore_rational_create_nd(1, 2);
OakRational *half = oakcore_rational_from_double(0.5, &ok); OakRational *half = oakcore_rational_from_double(0.5, &ok);
assert(ok == 1); EXPECT_EQ(ok, 1);
assert(oakcore_rational_compare(half, r) == 0); EXPECT_EQ(oakcore_rational_compare(half, r), 0);
oakcore_rational_free(half); oakcore_rational_free(half);
OakRational *bad = oakcore_rational_from_double(NAN, &ok); OakRational *bad = oakcore_rational_from_double(NAN, &ok);
assert(ok == 0); EXPECT_EQ(ok, 0);
assert(oakcore_rational_is_nan(bad)); EXPECT_TRUE(oakcore_rational_is_nan(bad));
oakcore_rational_free(bad); oakcore_rational_free(bad);
OakRational *parsed = oakcore_rational_from_string("3/4", &ok); OakRational *parsed = oakcore_rational_from_string("3/4", &ok);
assert(ok == 1); EXPECT_EQ(ok, 1);
assert(fabs(oakcore_rational_to_double(parsed) - 0.75) < 1e-12); EXPECT_NEAR(oakcore_rational_to_double(parsed), 0.75, 1e-12);
oakcore_rational_free(parsed); oakcore_rational_free(parsed);
OakRational *unparsed = oakcore_rational_from_string("1/2/3", &ok); OakRational *unparsed = oakcore_rational_from_string("1/2/3", &ok);
assert(ok == 0); EXPECT_EQ(ok, 0);
oakcore_rational_free(unparsed); oakcore_rational_free(unparsed);
// flip oakcore_rational_free(r);
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);
// 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 *a = oakcore_rational_create_nd(1, 3);
OakRational *b = oakcore_rational_create_nd(1, 6); 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 oakcore_rational_add_assign(a, b);
assert(oakcore_rational_compare(b, a) < 0); EXPECT_EQ(oakcore_rational_numerator(a), 1);
assert(oakcore_rational_compare(a, b) > 0); 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(a);
oakcore_rational_free(b); oakcore_rational_free(b);
oakcore_rational_free(r);
printf("oakcore_rational_test: all assertions passed\n");
return 0;
} }
+194 -180
View File
@@ -1,33 +1,9 @@
/*** #include <gtest/gtest.h>
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 <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#include "olive/core/oakcore/samplebuffer.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" { extern "C" {
OakAudioParams *oakcore_audioparams_create(int sample_rate, OakAudioParams *oakcore_audioparams_create(int sample_rate,
uint64_t channel_layout, uint64_t channel_layout,
@@ -37,163 +13,186 @@ int oakcore_audioparams_sample_rate(const OakAudioParams *self);
int oakcore_audioparams_channel_count(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 SAMPLE_RATE 48000
#define CHANNEL_LAYOUT_STEREO 0x3 #define CHANNEL_LAYOUT_STEREO 0x3
#define FORMAT_F32P 4 #define FORMAT_F32P 4
static int float_eq(float a, float b) static bool feq(float a, float b) { return fabsf(a - b) < 1e-6f; }
class OakcoreSampleBufferTest : public ::testing::Test {
protected:
void SetUp() override
{ {
return fabsf(a - b) < 1e-6f; stereo_ = oakcore_audioparams_create(SAMPLE_RATE, CHANNEL_LAYOUT_STEREO, FORMAT_F32P);
} }
void TearDown() override
int main(void)
{ {
/* --- Default construction: empty, unallocated --- */ oakcore_audioparams_free(stereo_);
}
OakAudioParams *stereo_;
};
TEST(OakcoreSampleBuffer, DefaultConstruction)
{
OakSampleBuffer *def = oakcore_samplebuffer_create(); OakSampleBuffer *def = oakcore_samplebuffer_create();
assert(def != NULL); ASSERT_NE(def, nullptr);
assert(oakcore_samplebuffer_is_allocated(def) == 0); EXPECT_EQ(oakcore_samplebuffer_is_allocated(def), 0);
assert(oakcore_samplebuffer_sample_count(def) == 0); EXPECT_EQ(oakcore_samplebuffer_sample_count(def), 0);
assert(oakcore_samplebuffer_channel_count(def) == 0); EXPECT_EQ(oakcore_samplebuffer_channel_count(def), 0);
assert(oakcore_samplebuffer_data(def, 0) == NULL); 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); OakAudioParams *def_params = oakcore_samplebuffer_audio_params(def);
assert(def_params != NULL); ASSERT_NE(def_params, nullptr);
assert(oakcore_audioparams_sample_rate(def_params) == 0); EXPECT_EQ(oakcore_audioparams_sample_rate(def_params), 0);
oakcore_audioparams_free(def_params); 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); 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_silence(def);
oakcore_samplebuffer_reverse(def); oakcore_samplebuffer_reverse(def);
assert(oakcore_samplebuffer_is_allocated(def) == 0); EXPECT_EQ(oakcore_samplebuffer_is_allocated(def), 0);
oakcore_samplebuffer_free(def); oakcore_samplebuffer_free(def);
}
/* --- Construction from audio params + sample count --- */ TEST_F(OakcoreSampleBufferTest, CreateFromParamsAndCount)
OakAudioParams *stereo = oakcore_audioparams_create( {
SAMPLE_RATE, CHANNEL_LAYOUT_STEREO, FORMAT_F32P); OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo_, 100);
assert(stereo != NULL); 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); // A fresh allocation is silent
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 */
for (int ch = 0; ch < 2; ch++) { for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(buf, ch); const float *d = oakcore_samplebuffer_data(buf, ch);
assert(d != NULL); ASSERT_NE(d, nullptr);
for (int i = 0; i < 100; i++) { 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); OakAudioParams *p = oakcore_samplebuffer_audio_params(buf);
assert(oakcore_audioparams_sample_rate(p) == SAMPLE_RATE); EXPECT_EQ(oakcore_audioparams_sample_rate(p), SAMPLE_RATE);
assert(oakcore_audioparams_channel_count(p) == 2); EXPECT_EQ(oakcore_audioparams_channel_count(p), 2);
oakcore_audioparams_free(p); oakcore_audioparams_free(p);
/* data() rejects out-of-range channels */ // data() rejects out-of-range channels
assert(oakcore_samplebuffer_data(buf, -1) == NULL); EXPECT_EQ(oakcore_samplebuffer_data(buf, -1), nullptr);
assert(oakcore_samplebuffer_data(buf, 2) == NULL); 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); OakRational *half_sec = oakcore_rational_create_nd(1, 2);
OakSampleBuffer *timed = OakSampleBuffer *timed = oakcore_samplebuffer_create_length(stereo_, half_sec);
oakcore_samplebuffer_create_length(stereo, half_sec); EXPECT_EQ(oakcore_samplebuffer_is_allocated(timed), 1);
assert(oakcore_samplebuffer_is_allocated(timed) == 1); EXPECT_EQ(oakcore_samplebuffer_sample_count(timed), 24000);
assert(oakcore_samplebuffer_sample_count(timed) == 24000);
oakcore_rational_free(half_sec); oakcore_rational_free(half_sec);
oakcore_samplebuffer_free(timed); oakcore_samplebuffer_free(timed);
}
/* --- Manual lifecycle: params + count + allocate, destroy, repeat --- */ TEST_F(OakcoreSampleBufferTest, ManualLifecycle)
{
OakSampleBuffer *manual = oakcore_samplebuffer_create(); 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_set_sample_count(manual, 50);
oakcore_samplebuffer_allocate(manual); oakcore_samplebuffer_allocate(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 1); EXPECT_EQ(oakcore_samplebuffer_is_allocated(manual), 1);
assert(oakcore_samplebuffer_sample_count(manual) == 50); EXPECT_EQ(oakcore_samplebuffer_sample_count(manual), 50);
assert(oakcore_samplebuffer_channel_count(manual) == 2); EXPECT_EQ(oakcore_samplebuffer_channel_count(manual), 2);
oakcore_samplebuffer_destroy(manual); oakcore_samplebuffer_destroy(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 0); EXPECT_EQ(oakcore_samplebuffer_is_allocated(manual), 0);
assert(oakcore_samplebuffer_channel_count(manual) == 0); EXPECT_EQ(oakcore_samplebuffer_channel_count(manual), 0);
assert(oakcore_samplebuffer_data(manual, 0) == NULL); EXPECT_EQ(oakcore_samplebuffer_data(manual, 0), nullptr);
oakcore_samplebuffer_set_sample_count(manual, 30); oakcore_samplebuffer_set_sample_count(manual, 30);
oakcore_samplebuffer_allocate(manual); oakcore_samplebuffer_allocate(manual);
assert(oakcore_samplebuffer_is_allocated(manual) == 1); EXPECT_EQ(oakcore_samplebuffer_is_allocated(manual), 1);
assert(oakcore_samplebuffer_sample_count(manual) == 30); EXPECT_EQ(oakcore_samplebuffer_sample_count(manual), 30);
oakcore_samplebuffer_free(manual); 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(); 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); OakRational *one_ms = oakcore_rational_create_nd(1, 1000);
oakcore_samplebuffer_set_sample_count_length(manual2, one_ms); oakcore_samplebuffer_set_sample_count_length(manual2, one_ms);
oakcore_rational_free(one_ms); oakcore_rational_free(one_ms);
oakcore_samplebuffer_allocate(manual2); oakcore_samplebuffer_allocate(manual2);
assert(oakcore_samplebuffer_sample_count(manual2) == 48); EXPECT_EQ(oakcore_samplebuffer_sample_count(manual2), 48);
oakcore_samplebuffer_free(manual2); 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 *ch0 = oakcore_samplebuffer_data(buf, 0);
float *ch1 = oakcore_samplebuffer_data(buf, 1); float *ch1 = oakcore_samplebuffer_data(buf, 1);
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
ch0[i] = (float)i; /* ramp 0..99 on channel 0 */ ch0[i] = (float)i;
ch1[i] = (float)(i * 10); /* ramp 0..990 on channel 1 */ ch1[i] = (float)(i * 10);
} }
const float ins[4] = { -1.0f, -2.0f, -3.0f, -4.0f }; 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++) { 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 */ EXPECT_TRUE(feq(ch0[9], 9.0f));
assert(float_eq(ch0[14], 14.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++) { 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); ch1[k] = (float)(k * 10);
} }
float *ptrs[2] = { NULL, NULL }; float *ptrs[2] = { nullptr, nullptr };
oakcore_samplebuffer_to_raw_ptrs(buf, ptrs); oakcore_samplebuffer_to_raw_ptrs(buf, ptrs);
assert(ptrs[0] == ch0); EXPECT_EQ(ptrs[0], ch0);
assert(ptrs[1] == ch1); EXPECT_EQ(ptrs[1], ch1);
ptrs[1][50] = 123.0f; /* writes through the raw pointer land in the buffer */ ptrs[1][50] = 123.0f;
assert(float_eq(oakcore_samplebuffer_data(buf, 1)[50], 123.0f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(buf, 1)[50], 123.0f));
ch1[50] = 500.0f; /* restore ramp value (50 * 10) */ 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); OakSampleBuffer *cp = oakcore_samplebuffer_copy(buf);
assert(oakcore_samplebuffer_is_allocated(cp) == 1); EXPECT_EQ(oakcore_samplebuffer_is_allocated(cp), 1);
assert(oakcore_samplebuffer_sample_count(cp) == 100); EXPECT_EQ(oakcore_samplebuffer_sample_count(cp), 100);
assert(oakcore_samplebuffer_channel_count(cp) == 2); EXPECT_EQ(oakcore_samplebuffer_channel_count(cp), 2);
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 9.0f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(cp, 0)[9], 9.0f));
/* --- transform_volume (in place, all channels) --- */
oakcore_samplebuffer_transform_volume(cp, 2.0f); oakcore_samplebuffer_transform_volume(cp, 2.0f);
assert(float_eq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(cp, 0)[9], 18.0f));
assert(float_eq(oakcore_samplebuffer_data(cp, 1)[9], 180.0f)); EXPECT_TRUE(feq(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(buf, 0)[9], 9.0f));
/* --- transform_volume_for_channel (in place, one channel) --- */
oakcore_samplebuffer_transform_volume_for_channel(cp, 1, 0.5f); oakcore_samplebuffer_transform_volume_for_channel(cp, 1, 0.5f);
assert(float_eq(oakcore_samplebuffer_data(cp, 1)[9], 90.0f)); EXPECT_TRUE(feq(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, 0)[9], 18.0f));
oakcore_samplebuffer_free(cp); oakcore_samplebuffer_free(cp);
oakcore_samplebuffer_free(buf);
}
/* --- static-style transforms: input -> output --- */ TEST_F(OakcoreSampleBufferTest, StaticVolumeTransforms)
OakSampleBuffer *tin = oakcore_samplebuffer_create_samples(stereo, 10); {
OakSampleBuffer *tout = oakcore_samplebuffer_create_samples(stereo, 10); OakSampleBuffer *tin = oakcore_samplebuffer_create_samples(stereo_, 10);
OakSampleBuffer *tout = oakcore_samplebuffer_create_samples(stereo_, 10);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(tin, 0)[i] = (float)i; oakcore_samplebuffer_data(tin, 0)[i] = (float)i;
oakcore_samplebuffer_data(tin, 1)[i] = (float)(100 + 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); oakcore_samplebuffer_transform_volume_to(0.5f, tin, tout);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(tout, 0)[i], (float)i * 0.5f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 0)[i], (float)i * 0.5f));
assert(float_eq(oakcore_samplebuffer_data(tout, 1)[i], EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 1)[i], (float)(100 + i) * 0.5f));
(float)(100 + i) * 0.5f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 0)[i], (float)i));
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[i], (float)i)); /* input unchanged */
} }
oakcore_samplebuffer_silence(tout); oakcore_samplebuffer_silence(tout);
oakcore_samplebuffer_transform_volume_for_channel_to(1, 2.0f, tin, tout); oakcore_samplebuffer_transform_volume_for_channel_to(1, 2.0f, tin, tout);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(tout, 1)[i], EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 1)[i], (float)(100 + i) * 2.0f));
(float)(100 + i) * 2.0f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(tout, 0)[i], 0.0f));
assert(float_eq(oakcore_samplebuffer_data(tout, 0)[i], 0.0f)); /* channel 0 untouched */
} }
/* --- per-sample volume transforms --- */ // per-sample volume transforms
oakcore_samplebuffer_transform_volume_for_sample(tin, 3, 10.0f); oakcore_samplebuffer_transform_volume_for_sample(tin, 3, 10.0f);
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[3], 30.0f)); EXPECT_TRUE(feq(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, 1)[3], 1030.0f));
oakcore_samplebuffer_transform_volume_for_sample_on_channel(tin, 4, 0, oakcore_samplebuffer_transform_volume_for_sample_on_channel(tin, 4, 0, 100.0f);
100.0f); EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 0)[4], 400.0f));
assert(float_eq(oakcore_samplebuffer_data(tin, 0)[4], 400.0f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(tin, 1)[4], 104.0f));
assert(float_eq(oakcore_samplebuffer_data(tin, 1)[4], 104.0f)); /* channel 1 untouched */
oakcore_samplebuffer_free(tin); oakcore_samplebuffer_free(tin);
oakcore_samplebuffer_free(tout); oakcore_samplebuffer_free(tout);
}
/* --- clamp() limits every channel to [-1, 1] --- */ TEST_F(OakcoreSampleBufferTest, Clamp)
OakSampleBuffer *cl = oakcore_samplebuffer_create_samples(stereo, 4); {
OakSampleBuffer *cl = oakcore_samplebuffer_create_samples(stereo_, 4);
oakcore_samplebuffer_data(cl, 0)[0] = 2.5f; oakcore_samplebuffer_data(cl, 0)[0] = 2.5f;
oakcore_samplebuffer_data(cl, 0)[1] = -2.5f; oakcore_samplebuffer_data(cl, 0)[1] = -2.5f;
oakcore_samplebuffer_data(cl, 1)[0] = 42.0f; oakcore_samplebuffer_data(cl, 1)[0] = 42.0f;
oakcore_samplebuffer_data(cl, 1)[1] = 0.25f; oakcore_samplebuffer_data(cl, 1)[1] = 0.25f;
oakcore_samplebuffer_clamp(cl); oakcore_samplebuffer_clamp(cl);
assert(float_eq(oakcore_samplebuffer_data(cl, 0)[0], 1.0f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(cl, 0)[0], 1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 0)[1], -1.0f)); EXPECT_TRUE(feq(oakcore_samplebuffer_data(cl, 0)[1], -1.0f));
assert(float_eq(oakcore_samplebuffer_data(cl, 1)[0], 1.0f)); EXPECT_TRUE(feq(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, 1)[1], 0.25f));
oakcore_samplebuffer_free(cl); oakcore_samplebuffer_free(cl);
}
/* --- silence / silence_range / silence_bytes --- */ TEST_F(OakcoreSampleBufferTest, SilenceAndSilenceRange)
OakSampleBuffer *si = oakcore_samplebuffer_create_samples(stereo, 10); {
OakSampleBuffer *si = oakcore_samplebuffer_create_samples(stereo_, 10);
for (int ch = 0; ch < 2; ch++) { for (int ch = 0; ch < 2; ch++) {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(si, ch)[i] = 1.0f; 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++) { for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch); const float *d = oakcore_samplebuffer_data(si, ch);
assert(float_eq(d[1], 1.0f)); EXPECT_TRUE(feq(d[1], 1.0f));
assert(float_eq(d[2], 0.0f)); EXPECT_TRUE(feq(d[2], 0.0f));
assert(float_eq(d[4], 0.0f)); EXPECT_TRUE(feq(d[4], 0.0f));
assert(float_eq(d[5], 1.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++) { for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch); const float *d = oakcore_samplebuffer_data(si, ch);
assert(float_eq(d[0], 0.0f)); EXPECT_TRUE(feq(d[0], 0.0f));
assert(float_eq(d[1], 0.0f)); EXPECT_TRUE(feq(d[1], 0.0f));
assert(float_eq(d[5], 1.0f)); EXPECT_TRUE(feq(d[5], 1.0f));
} }
oakcore_samplebuffer_silence(si); /* everything */ oakcore_samplebuffer_silence(si);
for (int ch = 0; ch < 2; ch++) { for (int ch = 0; ch < 2; ch++) {
const float *d = oakcore_samplebuffer_data(si, ch); const float *d = oakcore_samplebuffer_data(si, ch);
for (int i = 0; i < 10; i++) { 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); oakcore_samplebuffer_free(si);
}
/* --- reverse() flips every channel --- */ TEST_F(OakcoreSampleBufferTest, Reverse)
OakSampleBuffer *rv = oakcore_samplebuffer_create_samples(stereo, 5); {
OakSampleBuffer *rv = oakcore_samplebuffer_create_samples(stereo_, 5);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
oakcore_samplebuffer_data(rv, 0)[i] = (float)i; oakcore_samplebuffer_data(rv, 0)[i] = (float)i;
oakcore_samplebuffer_data(rv, 1)[i] = (float)(10 * i); oakcore_samplebuffer_data(rv, 1)[i] = (float)(10 * i);
} }
oakcore_samplebuffer_reverse(rv); oakcore_samplebuffer_reverse(rv);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
assert(float_eq(oakcore_samplebuffer_data(rv, 0)[i], (float)(4 - i))); EXPECT_TRUE(feq(oakcore_samplebuffer_data(rv, 0)[i], (float)(4 - i)));
assert(float_eq(oakcore_samplebuffer_data(rv, 1)[i], EXPECT_TRUE(feq(oakcore_samplebuffer_data(rv, 1)[i], (float)(10 * (4 - i))));
(float)(10 * (4 - i))));
} }
oakcore_samplebuffer_free(rv); oakcore_samplebuffer_free(rv);
}
/* --- speed(2.0) halves the sample count, sampling the ramp exactly --- */ TEST_F(OakcoreSampleBufferTest, Speed)
OakSampleBuffer *sp = oakcore_samplebuffer_create_samples(stereo, 100); {
OakSampleBuffer *sp = oakcore_samplebuffer_create_samples(stereo_, 100);
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
oakcore_samplebuffer_data(sp, 0)[i] = (float)i; oakcore_samplebuffer_data(sp, 0)[i] = (float)i;
} }
oakcore_samplebuffer_speed(sp, 2.0); 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++) { 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); oakcore_samplebuffer_free(sp);
}
/* --- fast_set(): channel copy between buffers --- */ TEST_F(OakcoreSampleBufferTest, FastSet)
OakSampleBuffer *fa = oakcore_samplebuffer_create_samples(stereo, 10); {
OakSampleBuffer *fb = oakcore_samplebuffer_create_samples(stereo, 10); OakSampleBuffer *fa = oakcore_samplebuffer_create_samples(stereo_, 10);
OakSampleBuffer *fb = oakcore_samplebuffer_create_samples(stereo_, 10);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
oakcore_samplebuffer_data(fa, 0)[i] = (float)i; oakcore_samplebuffer_data(fa, 0)[i] = (float)i;
oakcore_samplebuffer_data(fa, 1)[i] = (float)(100 + 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++) { for (int i = 0; i < 10; i++) {
assert(float_eq(oakcore_samplebuffer_data(fb, 1)[i], (float)i)); EXPECT_TRUE(feq(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, 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++) { 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(fa);
oakcore_samplebuffer_free(fb); oakcore_samplebuffer_free(fb);
}
/* --- rip_channel(): a new mono buffer with one channel's samples --- */ TEST_F(OakcoreSampleBufferTest, RipChannel)
OakSampleBuffer *rip = oakcore_samplebuffer_rip_channel(buf, 1); {
assert(oakcore_samplebuffer_is_allocated(rip) == 1); OakSampleBuffer *buf = oakcore_samplebuffer_create_samples(stereo_, 100);
assert(oakcore_samplebuffer_channel_count(rip) == 1);
assert(oakcore_samplebuffer_sample_count(rip) == 100);
for (int i = 0; i < 100; i++) { 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); OakAudioParams *rip_params = oakcore_samplebuffer_audio_params(rip);
assert(oakcore_audioparams_sample_rate(rip_params) == SAMPLE_RATE); EXPECT_EQ(oakcore_audioparams_sample_rate(rip_params), SAMPLE_RATE);
assert(oakcore_audioparams_channel_count(rip_params) == 1); EXPECT_EQ(oakcore_audioparams_channel_count(rip_params), 1);
oakcore_audioparams_free(rip_params); oakcore_audioparams_free(rip_params);
oakcore_samplebuffer_free(rip); oakcore_samplebuffer_free(rip);
oakcore_samplebuffer_free(buf);
}
/* --- rip_channel_vector(): query size, partial copy, full copy --- */ TEST_F(OakcoreSampleBufferTest, RipChannelVector)
assert(oakcore_samplebuffer_rip_channel_vector(buf, 1, NULL, 0) == 100); {
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 }; 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++) { 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]; 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++) { 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_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 @@
/*** #include <gtest/gtest.h>
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 <stdarg.h> #include <stdarg.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include "olive/core/oakcore/stringutils.h" #include "olive/core/oakcore/stringutils.h"
static void test_split() TEST(OakcoreStringUtils, SplitBasic)
{ {
int count = 0; int count = 0;
// Basic split
char **arr = oakcore_stringutils_split("a,b,c", ',', &count); char **arr = oakcore_stringutils_split("a,b,c", ',', &count);
assert(arr != NULL); ASSERT_NE(arr, nullptr);
assert(count == 3); ASSERT_EQ(count, 3);
assert(strcmp(arr[0], "a") == 0); EXPECT_STREQ(arr[0], "a");
assert(strcmp(arr[1], "b") == 0); EXPECT_STREQ(arr[1], "b");
assert(strcmp(arr[2], "c") == 0); EXPECT_STREQ(arr[2], "c");
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);
oakcore_stringutils_free_string_array(arr, count); oakcore_stringutils_free_string_array(arr, count);
} }
static void test_split_regex() TEST(OakcoreStringUtils, SplitNoSeparator)
{ {
int count = 0; 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); char **arr = oakcore_stringutils_split_regex("a1b22c", "[0-9]+", &count);
assert(arr != NULL); ASSERT_NE(arr, nullptr);
assert(count == 3); ASSERT_EQ(count, 3);
assert(strcmp(arr[0], "a") == 0); EXPECT_STREQ(arr[0], "a");
assert(strcmp(arr[1], "b") == 0); EXPECT_STREQ(arr[1], "b");
assert(strcmp(arr[2], "c") == 0); EXPECT_STREQ(arr[2], "c");
oakcore_stringutils_free_string_array(arr, count); oakcore_stringutils_free_string_array(arr, count);
// Pattern that never matches yields the whole string
arr = oakcore_stringutils_split_regex("abc", "[0-9]+", &count); arr = oakcore_stringutils_split_regex("abc", "[0-9]+", &count);
assert(arr != NULL); ASSERT_NE(arr, nullptr);
assert(count == 1); ASSERT_EQ(count, 1);
assert(strcmp(arr[0], "abc") == 0); EXPECT_STREQ(arr[0], "abc");
oakcore_stringutils_free_string_array(arr, count); oakcore_stringutils_free_string_array(arr, count);
// Freeing NULL is safe // Freeing NULL is safe
oakcore_stringutils_free_string_array(NULL, 0); oakcore_stringutils_free_string_array(NULL, 0);
} }
static void test_to_int() TEST(OakcoreStringUtils, ToInt)
{ {
int ok = 0; int ok = 0;
EXPECT_EQ(oakcore_stringutils_to_int("42", 10, &ok), 42);
// Base 10 EXPECT_EQ(ok, 1);
assert(oakcore_stringutils_to_int("42", 10, &ok) == 42); EXPECT_EQ(oakcore_stringutils_to_int("-17", 10, &ok), -17);
assert(ok == 1); EXPECT_EQ(ok, 1);
EXPECT_EQ(oakcore_stringutils_to_int("ff", 16, &ok), 255);
// Negative EXPECT_EQ(ok, 1);
assert(oakcore_stringutils_to_int("-17", 10, &ok) == -17); EXPECT_EQ(oakcore_stringutils_to_int("xyz", 10, &ok), 0);
assert(ok == 1); EXPECT_EQ(ok, 0);
EXPECT_EQ(oakcore_stringutils_to_int("7", 10, NULL), 7);
// 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);
} }
/* Variadic forwarder to exercise oakcore_stringutils_format_v() */
static int format_forward(char *buf, int buf_size, const char *fmt, ...) static int format_forward(char *buf, int buf_size, const char *fmt, ...)
{ {
va_list args; va_list args;
@@ -133,42 +97,29 @@ static int format_forward(char *buf, int buf_size, const char *fmt, ...)
return r; return r;
} }
static void test_format() TEST(OakcoreStringUtils, Format)
{ {
char buf[64]; char buf[64];
// Format with mixed argument types
const int needed = oakcore_stringutils_format(buf, sizeof(buf), "%d-%s-%.2f", const int needed = oakcore_stringutils_format(buf, sizeof(buf), "%d-%s-%.2f",
42, "mid", 1.5); 42, "mid", 1.5);
assert(needed == 11); EXPECT_EQ(needed, 11);
assert(strcmp(buf, "42-mid-1.50") == 0); EXPECT_STREQ(buf, "42-mid-1.50");
// NULL buffer queries the required size // NULL buffer queries size
assert(oakcore_stringutils_format(NULL, 0, "%d-%s-%.2f", 42, "mid", EXPECT_EQ(oakcore_stringutils_format(NULL, 0, "%d-%s-%.2f", 42, "mid", 1.5),
1.5) == needed); needed);
// Too-small buffer truncates but still reports the full required size // Truncation
char small[5]; char small[5];
const int needed2 = const int needed2 =
oakcore_stringutils_format(small, sizeof(small), "%s", "abcdefgh"); oakcore_stringutils_format(small, sizeof(small), "%s", "abcdefgh");
assert(needed2 == 8); EXPECT_EQ(needed2, 8);
assert(strcmp(small, "abcd") == 0); EXPECT_STREQ(small, "abcd");
// va_list form produces identical results // va_list form
char buf2[64]; char buf2[64];
const int needed3 = format_forward(buf2, sizeof(buf2), "%d-%s-%.2f", 42, const int needed3 = format_forward(buf2, sizeof(buf2), "%d-%s-%.2f", 42,
"mid", 1.5); "mid", 1.5);
assert(needed3 == needed); EXPECT_EQ(needed3, needed);
assert(strcmp(buf2, buf) == 0); EXPECT_STREQ(buf2, buf);
}
int main()
{
test_split();
test_split_regex();
test_to_int();
test_format();
printf("oakcore_stringutils_test: all tests passed\n");
return 0;
} }
+123 -179
View File
@@ -1,254 +1,198 @@
/*** #include <gtest/gtest.h>
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 <math.h> #include <math.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include "olive/core/oakcore/timecodefunctions.h" #include "olive/core/oakcore/timecodefunctions.h"
int main() class OakcoreTimecodeTest : public ::testing::Test {
protected:
void SetUp() override
{ {
tb_30 = oakcore_rational_create_nd(1, 30);
tb_df = oakcore_rational_create_nd(1001, 30000);
tb_60 = oakcore_rational_create_nd(1, 60);
}
void TearDown() override
{
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]; char buf[64];
int ok = 0; };
OakRational *r = NULL;
/* Common timebases: 30 fps non-drop, 29.97 drop-frame, 60 fps */ TEST_F(OakcoreTimecodeTest, TimeToTimecodeDropFrame)
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 ) */
{ {
OakRational *time = oakcore_rational_create(1); OakRational *time = oakcore_rational_create(1);
const int size = oakcore_timecode_time_to_timecode( const int size = oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, NULL, 0); time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, NULL, 0);
assert(size == 11); EXPECT_EQ(size, 11);
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, sizeof(buf));
sizeof(buf)) == 11); EXPECT_STREQ(buf, "00:00:01;00");
assert(strcmp(buf, "00:00:01;00") == 0);
/* Truncating buffer still reports the full required size */ // Truncating buffer
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, time, tb_df, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, 6);
6) == 11); EXPECT_STREQ(buf, "00:00");
assert(strcmp(buf, "00:00") == 0);
oakcore_rational_free(time); oakcore_rational_free(time);
} }
/* oakcore_timecode_time_to_timecode: invalid timebase */ TEST_F(OakcoreTimecodeTest, TimeToTimecodeInvalidTimebase)
{ {
OakRational *time = oakcore_rational_create(0); OakRational *time = oakcore_rational_create(0);
OakRational *bizarre = oakcore_rational_create(156632219); OakRational *bizarre = oakcore_rational_create(156632219);
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, bizarre, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, time, bizarre, OAK_TIMECODE_DISPLAY_DROP_FRAME, 0, buf, sizeof(buf));
sizeof(buf)) == 16); EXPECT_STREQ(buf, "INVALID TIMEBASE");
assert(strcmp(buf, "INVALID TIMEBASE") == 0);
oakcore_rational_free(bizarre); oakcore_rational_free(bizarre);
oakcore_rational_free(time); oakcore_rational_free(time);
} }
/* oakcore_timecode_time_to_timecode: non-drop, plus sign, negative */ TEST_F(OakcoreTimecodeTest, TimeToTimecodeNonDrop)
{ {
OakRational *time = oakcore_rational_create_nd(3, 2); OakRational *time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf, time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf, sizeof(buf));
sizeof(buf)) == 11); EXPECT_STREQ(buf, "00:00:01:15");
assert(strcmp(buf, "00:00:01:15") == 0);
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 1, buf, time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 1, buf, sizeof(buf));
sizeof(buf)) == 12); EXPECT_STREQ(buf, "+00:00:01:15");
assert(strcmp(buf, "+00:00:01:15") == 0);
oakcore_rational_free(time); oakcore_rational_free(time);
time = oakcore_rational_create_nd(-3, 2); time = oakcore_rational_create_nd(-3, 2);
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf, time, tb_30, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, 0, buf, sizeof(buf));
sizeof(buf)) == 12); EXPECT_STREQ(buf, "-00:00:01:15");
assert(strcmp(buf, "-00:00:01:15") == 0);
oakcore_rational_free(time); oakcore_rational_free(time);
} }
/* oakcore_timecode_time_to_timecode: seconds / frames / milliseconds */ TEST_F(OakcoreTimecodeTest, TimeToTimecodeDisplayModes)
{ {
OakRational *time = oakcore_rational_create_nd(123, 2); /* 61.5 s */ OakRational *time = oakcore_rational_create_nd(123, 2);
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_SECONDS, 0, buf, time, tb_30, OAK_TIMECODE_DISPLAY_SECONDS, 0, buf, sizeof(buf));
sizeof(buf)) == 12); EXPECT_STREQ(buf, "00:01:01.500");
assert(strcmp(buf, "00:01:01.500") == 0);
oakcore_rational_free(time); oakcore_rational_free(time);
time = oakcore_rational_create_nd(3, 2); time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timecode( oakcore_timecode_time_to_timecode(
time, tb_30, OAK_TIMECODE_DISPLAY_FRAMES, 0, buf, time, tb_30, OAK_TIMECODE_DISPLAY_FRAMES, 0, buf, sizeof(buf));
sizeof(buf)) == 2); EXPECT_STREQ(buf, "45");
assert(strcmp(buf, "45") == 0); oakcore_timecode_time_to_timecode(
assert(oakcore_timecode_time_to_timecode( time, tb_30, OAK_TIMECODE_DISPLAY_MILLISECONDS, 0, buf, sizeof(buf));
time, tb_30, OAK_TIMECODE_DISPLAY_MILLISECONDS, 0, buf, EXPECT_STREQ(buf, "1500");
sizeof(buf)) == 4);
assert(strcmp(buf, "1500") == 0);
oakcore_rational_free(time); oakcore_rational_free(time);
} }
/* oakcore_timecode_timecode_to_time: non-drop */ TEST_F(OakcoreTimecodeTest, TimecodeToTime)
{
int ok = 0;
OakRational *r;
r = oakcore_timecode_timecode_to_time("00:00:01:15", tb_30, r = oakcore_timecode_timecode_to_time("00:00:01:15", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, &ok);
&ok); EXPECT_EQ(ok, 1);
assert(ok == 1); EXPECT_EQ(oakcore_rational_numerator(r), 3);
assert(oakcore_rational_numerator(r) == 3 && EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_denominator(r) == 2);
oakcore_rational_free(r); oakcore_rational_free(r);
/* oakcore_timecode_timecode_to_time: drop-frame */
r = oakcore_timecode_timecode_to_time("00:00:01;00", tb_df, r = oakcore_timecode_timecode_to_time("00:00:01;00", tb_df,
OAK_TIMECODE_DISPLAY_DROP_FRAME, OAK_TIMECODE_DISPLAY_DROP_FRAME, &ok);
&ok); EXPECT_EQ(ok, 1);
assert(ok == 1); EXPECT_EQ(oakcore_rational_numerator(r), 1001);
assert(oakcore_rational_numerator(r) == 1001 && EXPECT_EQ(oakcore_rational_denominator(r), 1000);
oakcore_rational_denominator(r) == 1000);
oakcore_rational_free(r); 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, r = oakcore_timecode_timecode_to_time("45", tb_30,
OAK_TIMECODE_DISPLAY_FRAMES, &ok); OAK_TIMECODE_DISPLAY_FRAMES, &ok);
assert(ok == 1); EXPECT_EQ(ok, 1);
assert(oakcore_rational_numerator(r) == 3 && EXPECT_EQ(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_rational_free(r);
/* oakcore_timecode_timecode_to_time: invalid and NULL input */
r = oakcore_timecode_timecode_to_time("abc", tb_30, r = oakcore_timecode_timecode_to_time("abc", tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, &ok);
&ok); EXPECT_EQ(ok, 0);
assert(ok == 0);
assert(oakcore_rational_numerator(r) == 0);
oakcore_rational_free(r); oakcore_rational_free(r);
r = oakcore_timecode_timecode_to_time(NULL, tb_30, r = oakcore_timecode_timecode_to_time(NULL, tb_30,
OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, OAK_TIMECODE_DISPLAY_NON_DROP_FRAME, &ok);
&ok); EXPECT_EQ(ok, 0);
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_rational_free(r);
}
/* oakcore_timecode_time_to_string */ TEST_F(OakcoreTimecodeTest, TimeToString)
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 */ EXPECT_EQ(oakcore_timecode_time_to_string(3661000, NULL, 0), 8);
r = oakcore_timecode_snap_time_to_timebase( 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); time, tb_30, OAK_TIMECODE_ROUNDING_ROUND);
assert(oakcore_rational_numerator(r) == 31 && EXPECT_EQ(oakcore_rational_numerator(r), 31);
oakcore_rational_denominator(r) == 30); EXPECT_EQ(oakcore_rational_denominator(r), 30);
oakcore_rational_free(r); oakcore_rational_free(r);
oakcore_rational_free(time); 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 = oakcore_rational_create_nd(151, 100);
time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR); r = oakcore_timecode_snap_time_to_timebase(time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR);
assert(oakcore_rational_numerator(r) == 3 && EXPECT_EQ(oakcore_rational_numerator(r), 3);
oakcore_rational_denominator(r) == 2); EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_free(r); oakcore_rational_free(r);
r = oakcore_timecode_snap_time_to_timebase( r = oakcore_timecode_snap_time_to_timebase(time, tb_30, OAK_TIMECODE_ROUNDING_CEIL);
time, tb_30, OAK_TIMECODE_ROUNDING_CEIL); EXPECT_EQ(oakcore_rational_numerator(r), 23);
assert(oakcore_rational_numerator(r) == 23 && EXPECT_EQ(oakcore_rational_denominator(r), 15);
oakcore_rational_denominator(r) == 15);
oakcore_rational_free(r); oakcore_rational_free(r);
oakcore_rational_free(time); oakcore_rational_free(time);
} }
/* oakcore_timecode_time_to_timestamp (Rational) */ TEST_F(OakcoreTimecodeTest, TimeToTimestamp)
{ {
OakRational *time = oakcore_rational_create_nd(3, 2); OakRational *time = oakcore_rational_create_nd(3, 2);
assert(oakcore_timecode_time_to_timestamp( EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_ROUND), 45);
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND) == 45);
oakcore_rational_free(time); oakcore_rational_free(time);
time = oakcore_rational_create_nd(151, 100); /* 45.3 frames */
assert(oakcore_timecode_time_to_timestamp( time = oakcore_rational_create_nd(151, 100);
time, tb_30, OAK_TIMECODE_ROUNDING_ROUND) == 45); EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_ROUND), 45);
assert(oakcore_timecode_time_to_timestamp( EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR), 45);
time, tb_30, OAK_TIMECODE_ROUNDING_FLOOR) == 45); EXPECT_EQ(oakcore_timecode_time_to_timestamp(time, tb_30, OAK_TIMECODE_ROUNDING_CEIL), 46);
assert(oakcore_timecode_time_to_timestamp(
time, tb_30, OAK_TIMECODE_ROUNDING_CEIL) == 46);
oakcore_rational_free(time); 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);
} }
/* oakcore_timecode_time_to_timestamp_d (double) */ TEST_F(OakcoreTimecodeTest, RescaleTimestamp)
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 */ 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 *src = oakcore_rational_create_nd(1, 5);
OakRational *dst = oakcore_rational_create_nd(3, 4); OakRational *dst = oakcore_rational_create_nd(3, 4);
assert(oakcore_timecode_rescale_timestamp(1, src, dst) == 0); EXPECT_EQ(oakcore_timecode_rescale_timestamp(1, src, dst), 0);
/* ... but ceil rounds up to 1 */ EXPECT_EQ(oakcore_timecode_rescale_timestamp_ceil(1, src, dst), 1);
assert(oakcore_timecode_rescale_timestamp_ceil(1, src, dst) == 1);
oakcore_rational_free(dst); oakcore_rational_free(dst);
oakcore_rational_free(src); oakcore_rational_free(src);
} }
assert(oakcore_timecode_rescale_timestamp_ceil(30, tb_30, tb_60) == 60);
/* oakcore_timecode_timestamp_to_time */ TEST_F(OakcoreTimecodeTest, TimestampToTime)
r = oakcore_timecode_timestamp_to_time(45, tb_30); {
assert(oakcore_rational_numerator(r) == 3 && OakRational *r = oakcore_timecode_timestamp_to_time(45, tb_30);
oakcore_rational_denominator(r) == 2); EXPECT_EQ(oakcore_rational_numerator(r), 3);
EXPECT_EQ(oakcore_rational_denominator(r), 2);
oakcore_rational_free(r); 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 */ TEST_F(OakcoreTimecodeTest, TimebaseIsDropFrame)
assert(oakcore_timecode_timebase_is_drop_frame(tb_df) == 1); {
assert(oakcore_timecode_timebase_is_drop_frame(tb_30) == 0); EXPECT_EQ(oakcore_timecode_timebase_is_drop_frame(tb_df), 1);
EXPECT_EQ(oakcore_timecode_timebase_is_drop_frame(tb_30), 0);
oakcore_rational_free(tb_60);
oakcore_rational_free(tb_df);
oakcore_rational_free(tb_30);
printf("oakcore_timecodefunctions_test: all tests passed\n");
return 0;
} }
+42 -105
View File
@@ -1,34 +1,5 @@
/*** #include <gtest/gtest.h>
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 <limits.h> #include <limits.h>
#include <stdio.h>
#include "olive/core/oakcore/timerange.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) static void expect_int_time(const OakRational *r, int expected)
{ {
assert(oakcore_rational_numerator(r) == expected); EXPECT_EQ(oakcore_rational_numerator(r), expected);
assert(oakcore_rational_denominator(r) == 1); EXPECT_EQ(oakcore_rational_denominator(r), 1);
} }
static void expect_range(const OakTimeRange *r, int in, int out) 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); oakcore_rational_free(ro);
} }
static void test_create_default(void) TEST(OakcoreTimeRange, CreateDefault)
{ {
OakTimeRange *r = oakcore_timerange_create(); OakTimeRange *r = oakcore_timerange_create();
expect_range(r, 0, 0); expect_range(r, 0, 0);
@@ -75,7 +46,7 @@ static void test_create_default(void)
oakcore_timerange_free(r); oakcore_timerange_free(r);
} }
static void test_create_and_getters(void) TEST(OakcoreTimeRange, CreateAndGetters)
{ {
OakTimeRange *r = mk_range(10, 20); OakTimeRange *r = mk_range(10, 20);
expect_range(r, 10, 20); expect_range(r, 10, 20);
@@ -87,9 +58,8 @@ static void test_create_and_getters(void)
oakcore_timerange_free(r); 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); OakTimeRange *r = mk_range(20, 10);
expect_range(r, 10, 20); expect_range(r, 10, 20);
@@ -100,28 +70,27 @@ static void test_normalize_swaps_in_out(void)
oakcore_timerange_free(r); 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); OakTimeRange *r = mk_range(INT_MIN, 0);
OakRational *length = oakcore_timerange_length(r); 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_rational_free(length);
oakcore_timerange_free(r); oakcore_timerange_free(r);
r = mk_range(0, INT_MAX); r = mk_range(0, INT_MAX);
length = oakcore_timerange_length(r); 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_rational_free(length);
oakcore_timerange_free(r); oakcore_timerange_free(r);
} }
static void test_copy_and_equal(void) TEST(OakcoreTimeRange, CopyAndEqual)
{ {
OakTimeRange *r = mk_range(3, 7); OakTimeRange *r = mk_range(3, 7);
OakTimeRange *copy = oakcore_timerange_copy(r); 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); expect_range(copy, 3, 7);
// Mutating the copy must not affect the original (deep ownership) // 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) // set_in(9) with out==7 normalizes by swapping to (7, 9)
expect_range(copy, 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); expect_range(r, 3, 7);
oakcore_timerange_free(copy); oakcore_timerange_free(copy);
oakcore_timerange_free(r); oakcore_timerange_free(r);
} }
static void test_setters(void) TEST(OakcoreTimeRange, Setters)
{ {
OakTimeRange *r = mk_range(0, 10); OakTimeRange *r = mk_range(0, 10);
OakRational *t = mk_time(5); OakRational *t = mk_time(5);
@@ -147,7 +116,6 @@ static void test_setters(void)
expect_range(r, 5, 10); expect_range(r, 5, 10);
oakcore_timerange_set_out(r, t); oakcore_timerange_set_out(r, t);
// set_out(5) with in==5 collapses to a zero-length range
expect_range(r, 5, 5); expect_range(r, 5, 5);
{ {
@@ -157,52 +125,46 @@ static void test_setters(void)
oakcore_rational_free(in); oakcore_rational_free(in);
oakcore_rational_free(out); oakcore_rational_free(out);
} }
// set_range also normalizes
expect_range(r, 20, 30); expect_range(r, 20, 30);
oakcore_rational_free(t); oakcore_rational_free(t);
oakcore_timerange_free(r); oakcore_timerange_free(r);
} }
static void test_overlaps_with(void) TEST(OakcoreTimeRange, OverlapsWith)
{ {
OakTimeRange *a = mk_range(0, 10); OakTimeRange *a = mk_range(0, 10);
OakTimeRange *b = mk_range(10, 20); OakTimeRange *b = mk_range(10, 20);
OakTimeRange *c = mk_range(5, 15); OakTimeRange *c = mk_range(5, 15);
// Touching ranges (0,10) vs (10,20): in_inclusive governs the EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 1, 1), 1);
// other.out-vs-self.in comparison, out_inclusive the other.in-vs-self.out EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 0, 0), 0);
// one, so the results are asymmetric for mixed flags EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 1, 0), 0);
assert(oakcore_timerange_overlaps_with(a, b, 1, 1) == 1); EXPECT_EQ(oakcore_timerange_overlaps_with(a, b, 0, 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);
// Genuinely overlapping ranges overlap under any inclusivity EXPECT_EQ(oakcore_timerange_overlaps_with(a, c, 1, 1), 1);
assert(oakcore_timerange_overlaps_with(a, c, 1, 1) == 1); EXPECT_EQ(oakcore_timerange_overlaps_with(a, c, 0, 0), 1);
assert(oakcore_timerange_overlaps_with(a, c, 0, 0) == 1);
oakcore_timerange_free(c); oakcore_timerange_free(c);
oakcore_timerange_free(b); oakcore_timerange_free(b);
oakcore_timerange_free(a); oakcore_timerange_free(a);
} }
static void test_contains_range(void) TEST(OakcoreTimeRange, ContainsRange)
{ {
OakTimeRange *outer = mk_range(0, 30); OakTimeRange *outer = mk_range(0, 30);
OakTimeRange *inner = mk_range(5, 10); OakTimeRange *inner = mk_range(5, 10);
OakTimeRange *same = mk_range(0, 30); OakTimeRange *same = mk_range(0, 30);
OakTimeRange *partial = mk_range(5, 40); OakTimeRange *partial = mk_range(5, 40);
assert(oakcore_timerange_contains_range(outer, inner, 1, 1) == 1); EXPECT_EQ(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, 0, 0), 1);
// Identical ranges: contained inclusively, not exclusively EXPECT_EQ(oakcore_timerange_contains_range(outer, same, 1, 1), 1);
assert(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, same, 0, 0) == 0);
assert(oakcore_timerange_contains_range(outer, partial, 1, 1) == 0); EXPECT_EQ(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(inner, outer, 1, 1), 0);
oakcore_timerange_free(partial); oakcore_timerange_free(partial);
oakcore_timerange_free(same); oakcore_timerange_free(same);
@@ -210,7 +172,7 @@ static void test_contains_range(void)
oakcore_timerange_free(outer); oakcore_timerange_free(outer);
} }
static void test_contains_time(void) TEST(OakcoreTimeRange, ContainsTime)
{ {
OakTimeRange *r = mk_range(0, 10); OakTimeRange *r = mk_range(0, 10);
OakRational *inside = mk_time(5); OakRational *inside = mk_time(5);
@@ -218,11 +180,10 @@ static void test_contains_time(void)
OakRational *edge_out = mk_time(10); OakRational *edge_out = mk_time(10);
OakRational *outside = mk_time(-1); OakRational *outside = mk_time(-1);
// contains(time) is in-inclusive but out-exclusive EXPECT_EQ(oakcore_timerange_contains_time(r, inside), 1);
assert(oakcore_timerange_contains_time(r, inside) == 1); EXPECT_EQ(oakcore_timerange_contains_time(r, edge_in), 1);
assert(oakcore_timerange_contains_time(r, edge_in) == 1); EXPECT_EQ(oakcore_timerange_contains_time(r, edge_out), 0);
assert(oakcore_timerange_contains_time(r, edge_out) == 0); EXPECT_EQ(oakcore_timerange_contains_time(r, outside), 0);
assert(oakcore_timerange_contains_time(r, outside) == 0);
oakcore_rational_free(outside); oakcore_rational_free(outside);
oakcore_rational_free(edge_out); oakcore_rational_free(edge_out);
@@ -231,7 +192,7 @@ static void test_contains_time(void)
oakcore_timerange_free(r); oakcore_timerange_free(r);
} }
static void test_combine_and_intersect(void) TEST(OakcoreTimeRange, CombineAndIntersect)
{ {
OakTimeRange *a = mk_range(0, 10); OakTimeRange *a = mk_range(0, 10);
OakTimeRange *b = mk_range(20, 30); OakTimeRange *b = mk_range(20, 30);
@@ -244,7 +205,6 @@ static void test_combine_and_intersect(void)
expect_range(combined, 0, 30); expect_range(combined, 0, 30);
oakcore_timerange_free(combined); oakcore_timerange_free(combined);
// Disjoint ranges intersect to (20, 10), which normalize() swaps to (10, 20)
OakTimeRange *intersected = oakcore_timerange_intersected(a, b); OakTimeRange *intersected = oakcore_timerange_intersected(a, b);
expect_range(intersected, 10, 20); expect_range(intersected, 10, 20);
oakcore_timerange_free(intersected); oakcore_timerange_free(intersected);
@@ -267,7 +227,7 @@ static void test_combine_and_intersect(void)
oakcore_timerange_free(a); oakcore_timerange_free(a);
} }
static void test_arithmetic(void) TEST(OakcoreTimeRange, Arithmetic)
{ {
OakTimeRange *r = mk_range(0, 10); OakTimeRange *r = mk_range(0, 10);
OakRational *five = mk_time(5); OakRational *five = mk_time(5);
@@ -294,17 +254,15 @@ static void test_arithmetic(void)
oakcore_timerange_free(r); oakcore_timerange_free(r);
} }
static void test_split(void) TEST(OakcoreTimeRange, Split)
{ {
OakTimeRange *r = mk_range(0, 10); OakTimeRange *r = mk_range(0, 10);
// Size query, both directly and through the NULL-buffer form EXPECT_EQ(oakcore_timerange_split_count(r, 4), 3);
assert(oakcore_timerange_split_count(r, 4) == 3); EXPECT_EQ(oakcore_timerange_split(r, 4, nullptr, 0), 3);
assert(oakcore_timerange_split(r, 4, NULL, 0) == 3);
// Full split: (0,4), (4,8), (8,10)
OakTimeRange *chunks[3]; 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[0], 0, 4);
expect_range(chunks[1], 4, 8); expect_range(chunks[1], 4, 8);
expect_range(chunks[2], 8, 10); expect_range(chunks[2], 8, 10);
@@ -312,41 +270,20 @@ static void test_split(void)
oakcore_timerange_free(chunks[i]); oakcore_timerange_free(chunks[i]);
} }
// A too-small buffer receives a prefix, the return value is the total
OakTimeRange *prefix[2]; 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[0], 0, 4);
expect_range(prefix[1], 4, 8); expect_range(prefix[1], 4, 8);
oakcore_timerange_free(prefix[0]); oakcore_timerange_free(prefix[0]);
oakcore_timerange_free(prefix[1]); oakcore_timerange_free(prefix[1]);
// Zero-length range splits into exactly one chunk
OakTimeRange *zero = mk_range(5, 5); OakTimeRange *zero = mk_range(5, 5);
assert(oakcore_timerange_split_count(zero, 4) == 1); EXPECT_EQ(oakcore_timerange_split_count(zero, 4), 1);
OakTimeRange *single = NULL; OakTimeRange *single = nullptr;
assert(oakcore_timerange_split(zero, 4, &single, 1) == 1); EXPECT_EQ(oakcore_timerange_split(zero, 4, &single, 1), 1);
expect_range(single, 5, 5); expect_range(single, 5, 5);
oakcore_timerange_free(single); oakcore_timerange_free(single);
oakcore_timerange_free(zero); oakcore_timerange_free(zero);
oakcore_timerange_free(r); 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** 编写, - 所有测试必须使用 **Google Test** 编写,
- 面向引擎代码的 C ABI 边界契约。 - 面向引擎代码的 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 # 长期计划(plans
本目录收纳 Oak 的**中长期规划文档与并行执行计划**。当前正在进行的 本目录收纳 Oak 的**中长期规划文档与并行执行计划**。已完成的战役
**C ABI 迁移战役**的文档在上一层(`docs/zh/`),见下方"当前进行中" 文档归档在 [`completed/`](completed/);进行中的计划在下方目录
## 目录 ## 目录
| 文档 | 内容 | 启动前提 | | 文档 | 内容 | 启动前提 |
|---|---|---| |---|---|---|
| [`riir.md`](riir.md) | **RIIR 绞杀者模式执行计划**C ABI 迁移完成后,把 liboakengine 安全拆成若干小模块,再逐个用 Rust 重写;含 API 冻结保证、模块图、六步流程与验证门禁 | C ABI 迁移战役验收完成 | | [`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 拆分完成(面对一堆小库) | | [`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 改版并行 | | [`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 验收完成后启动(可与 GTest 迁移并行 | | [`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 重做计划) - 交接:[`c-abi-migration-handoff.md`](completed/c-abi-migration-handoff.md)v3)、`-v4``-v5``-v6`
- `facade-migration-roadmap.md`(批次记录) - 战役记录:[`facade-migration-roadmap.md`](completed/facade-migration-roadmap.md)
- `r5-app-migration-guide.md``r5-phase2-detailed-guide.md`R5 app 侧迁移指引) - 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)、 colorcodingapp、filefunctionsapp、hashstreamapp、htmlapp、xmlutilsapp、debugapp)、
各 handle 头(keyframehandle/markerhandle/cliphandle/trackhandle/colorprocessorhandle/ 各 handle 头(keyframehandle/markerhandle/cliphandle/trackhandle/colorprocessorhandle/
vieweroutpututils)、`markerpainting.*``app/timeline/``app/ui/icons/` 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`)。 roadmap 批次记录(经 LocalHistory 恢复,`facade-migration-roadmap.md`)。
- **结构性改动**:B1 图标(engine 返回图标名 + app from_name 映射,已修复一致)、 - **结构性改动**:B1 图标(engine 返回图标名 + app from_name 映射,已修复一致)、
B2 布局 PODSerializedLayoutInfo 全链路,已修复一致)、B3 coreengine.h、 B2 布局 PODSerializedLayoutInfo 全链路,已修复一致)、B3 coreengine.h、
@@ -102,7 +102,7 @@ UndoCommand(3)。
4. ✅ 反作弊审计:app 无 `dlfcn.h`/dlsym/QLibrary 解析 engine 符号; 4. ✅ 反作弊审计:app 无 `dlfcn.h`/dlsym/QLibrary 解析 engine 符号;
engine 无 inline 化(oakengine/*.h 纯 C 声明)。 engine 无 inline 化(oakengine/*.h 纯 C 声明)。
5. ✅ `facade-migration-roadmap.md` 附 C R6 节已记录; 5. ✅ `facade-migration-roadmap.md` 附 C R6 节已记录;
`plans/riir.md` §1.1 状态已更新为"边界已纯"。 `../riir.md` §1.1 状态已更新为"边界已纯"。
> 已知遗留(已论证,不泄漏符号):app 仍 include 约 40 个 engine C++ 头 > 已知遗留(已论证,不泄漏符号):app 仍 include 约 40 个 engine C++ 头
> node/render/timeline/undo/pluginSupport,用于类型与 inline 访问器), > node/render/timeline/undo/pluginSupport,用于类型与 inline 访问器),
@@ -4,7 +4,7 @@
> 所有架构决策、边界契约、禁止事项已在本文钉死,执行时不得另行发明新方案; > 所有架构决策、边界契约、禁止事项已在本文钉死,执行时不得另行发明新方案;
> 遇到本文未覆盖的决策点,按"§8 决策兜底原则"处理,不得自由发挥。 > 遇到本文未覆盖的决策点,按"§8 决策兜底原则"处理,不得自由发挥。
> >
> 相关文档:`docs/zh/facade-migration-roadmap.md`(各批次完成记录 + 附 D 事件机制 SOP)。 > 相关文档:`facade-migration-roadmap.md`(各批次完成记录 + 附 D 事件机制 SOP)。
> >
> v32026-07-23):K2.7 对 DeepSeek Flash 的产出做了验收,修复了 6 个真实缺陷 > v32026-07-23):K2.7 对 DeepSeek Flash 的产出做了验收,修复了 6 个真实缺陷
> (详见 §2.2,每条都附教训——**这些错误模式不得再犯**)。当前符号 39、 > (详见 §2.2,每条都附教训——**这些错误模式不得再犯**)。当前符号 39、
@@ -309,7 +309,7 @@ set_value_at_time/staticMetaObject)从 inline 函数拉入,进豁免清单
### R6:豁免清单清零(58 → 0100% C ABI)— 完成 ### R6:豁免清单清零(58 → 0100% C ABI)— 完成
> 详见 `docs/zh/r6-cleanup-plan.md`(各 P 节已标 ✅)。目标:把 R5 遗留的 > 详见 `r6-cleanup-plan.md`(各 P 节已标 ✅)。目标:把 R5 遗留的
> 58 个豁免符号全部消除到 0,为 engine 模块化拆分与 RIIR 打地基。 > 58 个豁免符号全部消除到 0,为 engine 模块化拆分与 RIIR 打地基。
- **P1F 类 facade 补齐,17**NodeValue 静态方法、VideoParams 构造器、 - **P1F 类 facade 补齐,17**NodeValue 静态方法、VideoParams 构造器、
@@ -1,8 +1,8 @@
# R5 终局计划:181 → 豁免清单(≤6) # R5 终局计划:181 → 豁免清单(≤6)
> 面向执行者(DeepSeek Flash),自包含。工作分支:`c-abi-migration` > 面向执行者(DeepSeek Flash),自包含。工作分支:`c-abi-migration`
> 前置文档:`../facade-migration-roadmap.md`(批次记录)、 > 前置文档:`facade-migration-roadmap.md`(批次记录)、
> `../r5-app-migration-guide.md`R5 总指引)、`../c-abi-migration-handoff.md` > `r5-app-migration-guide.md`R5 总指引)、`c-abi-migration-handoff.md`
> (v3,§6.4 豁免清单格式)。本文是 R5 的**最后一个阶段**: > (v3,§6.4 豁免清单格式)。本文是 R5 的**最后一个阶段**:
> 处置当前 WIP → 修完已记录缺陷 → 把剩余 181 个 `olive::` 符号收到豁免清单。 > 处置当前 WIP → 修完已记录缺陷 → 把剩余 181 个 `olive::` 符号收到豁免清单。
> 每批闭环:全量构建 0 error + 全量 ctest 绿 + nm 复核 + 立即提交。 > 每批闭环:全量构建 0 error + 全量 ctest 绿 + nm 复核 + 立即提交。
@@ -5,7 +5,7 @@
> **背景**R5 已把 app 对 engine 的 `olive::` C++ 符号从 557 降到 58 > **背景**R5 已把 app 对 engine 的 `olive::` C++ 符号从 557 降到 58
> 剩余 58 个以"豁免清单"形式记录在 `c-abi-migration-handoff.md` §6.4。 > 剩余 58 个以"豁免清单"形式记录在 `c-abi-migration-handoff.md` §6.4。
> 本计划的目标是把它们**全部消除到 0**——这是后续 engine 模块化拆分 > 本计划的目标是把它们**全部消除到 0**——这是后续 engine 模块化拆分
> 与 Rust 重写(RIIR,见 `plans/riir.md`)的硬前提:C ABI 边界上不能 > 与 Rust 重写(RIIR,见 `../riir.md`)的硬前提:C ABI 边界上不能
> 残留任何 C++ 渗漏。 > 残留任何 C++ 渗漏。
> >
> **三条红线**(违反即返工): > **三条红线**(违反即返工):
@@ -476,7 +476,7 @@ app 侧:`manageddisplay`/`viewerdisplay` 不再 `new OpenGLRenderer`
`grep -rn '#include "' app/ | grep -E '"(node|undo|task|render|timeline|pluginSupport)/'` `grep -rn '#include "' app/ | grep -E '"(node|undo|task|render|timeline|pluginSupport)/'`
应为空或只剩极个别已论证的)。 应为空或只剩极个别已论证的)。
4. `c-abi-migration-handoff.md` §6.4 豁免清单清空(改为"无豁免"), 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。 1. `display.h` 全文无 C++ 类型签名/契约注释;app 无 TexturePtr/FramePtr。
2. liboakengine.so ` T _Z` = 0oak-editor ` U _ZN5olive` 保持 0。 2. liboakengine.so ` T _Z` = 0oak-editor ` U _ZN5olive` 保持 0。
3. 全量构建 0 error;全量 ctest 绿。 3. 全量构建 0 error;全量 ctest 绿。
4. 更新 `plans/riir.md` 状态(边界已纯 → 可进 Step 1 拆分)、 4. 更新 `../riir.md` 状态(边界已纯 → 可进 Step 1 拆分)、
`facade-migration-roadmap.md` R7 批次记录。 `facade-migration-roadmap.md` R7 批次记录。
5. 向用户报告,由用户宣布进入 riir.md §4 的模块拆分阶段。 5. 向用户报告,由用户宣布进入 riir.md §4 的模块拆分阶段。
@@ -225,4 +225,4 @@ grep -E '"(node|render|timeline|undo|task|pluginSupport)/'`)。不产生
- R7-Capp 的 ~40 个 engine C++ 头清理)未做,转入 riir/ 模块 - R7-Capp 的 ~40 个 engine C++ 头清理)未做,转入 riir/ 模块
拆分阶段顺带处理(M 系列手册的适配头天然覆盖)。 拆分阶段顺带处理(M 系列手册的适配头天然覆盖)。
- 验收:构建 0 error、ctest 45/45、双二进制 nm U _ZN5olive = 0。 - 验收:构建 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 重写 # RIIR 绞杀者模式执行计划:liboakengine 模块化拆分与渐进式 Rust 重写
> 本文档描述在 C ABI 迁移战役(见 `c-abi-migration-handoff.md`)完成之后, > 本文档描述在 C ABI 迁移战役(见 `completed/c-abi-migration-handoff.md`)完成之后,
> 如何用绞杀者模式(Strangler Fig)把 liboakengine.so 安全地拆成若干小模块, > 如何用绞杀者模式(Strangler Fig)把 liboakengine.so 安全地拆成若干小模块,
> 再逐个重写为 Rust。 > 再逐个重写为 Rust。
> **核心约束:每一步都可验证、可回退;任何一步失败都不影响已验证的部分。** > **核心约束:每一步都可验证、可回退;任何一步失败都不影响已验证的部分。**
@@ -103,7 +103,7 @@
### Step 1 — 冻结 ABI ### Step 1 — 冻结 ABI
- 评审模块对外 C ABI 头(公开 facade 已有部分直接复用;模块间内部调用需要的 - 评审模块对外 C ABI 头(公开 facade 已有部分直接复用;模块间内部调用需要的
新增内部头,按 `c-abi-migration-handoff.md` §6.1 的同一套规则写:纯 C 类型、 新增内部头,按 `completed/c-abi-migration-handoff.md` §6.1 的同一套规则写:纯 C 类型、
buf/size 约定、owned/borrowed 注释、错误码)。 buf/size 约定、owned/borrowed 注释、错误码)。
- 用门禁脚本生成 ABI 快照(§5-G1)并入库。**此后该头的任何改动都是显式评审行为。** - 用门禁脚本生成 ABI 快照(§5-G1)并入库。**此后该头的任何改动都是显式评审行为。**
+1 -1
View File
@@ -22,7 +22,7 @@ oakrender/
### 2.1 `oakrender/renderer.h`(渲染器/纹理/blit ### 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 层搬进 **原样采用**——R7-A 先做的话,M7 直接把它从 facade 层搬进
oakrender 并改前缀 `oakrender_display_*`;本表不重复,以 R7-A 为准。 oakrender 并改前缀 `oakrender_display_*`;本表不重复,以 R7-A 为准。
补充后端管理: 补充后端管理:
+2 -2
View File
@@ -1,7 +1,7 @@
# Oak 主界面 UI 改版计划 # Oak 主界面 UI 改版计划
> 本文是主界面重新设计的执行手册,面向 DeepSeek Flash**不识字图,本文全部 > 本文是主界面重新设计的执行手册,面向 DeepSeek Flash**不识字图,本文全部
> 用文字精确定义目标形态**)。详细程度对齐 `../r5-app-migration-guide.md`。 > 用文字精确定义目标形态**)。详细程度对齐 `completed/r5-app-migration-guide.md`。
> 工作分支:`c-abi-migration`。**启动前提:R5C ABI app 侧迁移)验收完成之 > 工作分支:`c-abi-migration`。**启动前提:R5C ABI app 侧迁移)验收完成之
> 后**;启动后可与 Google Test 统一迁移并行——协调规则见 §2。 > 后**;启动后可与 Google Test 统一迁移并行——协调规则见 §2。
> 依据:`design/Oak-UI设计图-主界面-标注版.png`、`...-效果栈版.png`、 > 依据:`design/Oak-UI设计图-主界面-标注版.png`、`...-效果栈版.png`、
@@ -13,7 +13,6 @@
``` ```
┌ 菜单栏:文件(F) 编辑(E) 视图(V) 回放(P) 序列(S) 窗口(W) 工具(T) 帮助(H) ┌ 菜单栏:文件(F) 编辑(E) 视图(V) 回放(P) 序列(S) 窗口(W) 工具(T) 帮助(H)
├ 工具条(31px):14 个工具图标 + 吸附开关 + 缩放滑块 + 轨道高度滑块
├──────────────────────────────────────────────────────────────────── ├────────────────────────────────────────────────────────────────────
│ 素材查看器(源) │ 序列查看器(节目) │ 检查器│历史记录 │ 素材查看器(源) │ 序列查看器(节目) │ 检查器│历史记录
│ ·适合/安全框 │ ·适合/安全框 │ ┌──────────────────┐ │ ·适合/安全框 │ ·适合/安全框 │ ┌──────────────────┐
@@ -24,6 +23,7 @@
│ │ │ [+ 添加效果] │ │ │ │ [+ 添加效果] │
│ │ │ └──────────────────┘ │ │ │ └──────────────────┘
├──────────────────────────────────────────────────────────────────── ├────────────────────────────────────────────────────────────────────
├ 工具条(31px):14 个工具图标 + 吸附开关 + 缩放滑块 + 轨道高度滑块
│ 时间线(全宽贯通) │ │ 时间线(全宽贯通) │
│ 轨道头180px │ 轨道区 │ │ 轨道头180px │ 轨道区 │
│ V2 视频轨道1 [锁定][显示] ████████ │ │ V2 视频轨道1 [锁定][显示] ████████ │
-11
View File
@@ -84,17 +84,6 @@ function(olive_add_test GROUP NAME SOURCE)
endif() endif()
endfunction() 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(compositing)
add_subdirectory(general) add_subdirectory(general)
add_subdirectory(timeline) add_subdirectory(timeline)