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