From c57608b8f895d7fae85b1eaf6859568f47b29119 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 13 Jul 2026 03:54:43 +0800 Subject: [PATCH] test: expand gtest coverage for common/core utilities Add unit tests for: - CommandLineParser, Debug handler, Decibel, Digit, JobTime, FFmpegUtils - Core StringUtils, Timecode, TimeRange, Bezier, Color - Extend Frame tests to cover pixel access, interlace, conversion Coverage improvements (app + ext/core): - commandlineparser.cpp: 0% -> 93% - debug.cpp: 0% -> 85% - decibel.h: 25% -> 88% - digit.h: 0% -> 100% - ffmpegutils.cpp: 29% -> 90% - frame.cpp: 53% -> 97% - bezier.cpp: 16% -> 98% - color.cpp: 13% -> 71% - Overall line coverage: 17% -> 19% --- tests/gtest/CMakeLists.txt | 11 ++ tests/gtest/codec_frame_test.cpp | 130 +++++++++++++ tests/gtest/common_commandlineparser_test.cpp | 83 ++++++++ tests/gtest/common_debug_test.cpp | 16 ++ tests/gtest/common_decibel_test.cpp | 52 +++++ tests/gtest/common_digit_test.cpp | 23 +++ tests/gtest/common_ffmpegutils_test.cpp | 149 ++++++++++++++ tests/gtest/common_jobtime_test.cpp | 42 ++++ tests/gtest/core_bezier_test.cpp | 120 ++++++++++++ tests/gtest/core_color_test.cpp | 169 ++++++++++++++++ tests/gtest/core_stringutils_test.cpp | 65 +++++++ tests/gtest/core_timecode_test.cpp | 115 +++++++++++ tests/gtest/core_timerange_test.cpp | 184 ++++++++++++++++++ 13 files changed, 1159 insertions(+) create mode 100644 tests/gtest/common_commandlineparser_test.cpp create mode 100644 tests/gtest/common_debug_test.cpp create mode 100644 tests/gtest/common_decibel_test.cpp create mode 100644 tests/gtest/common_digit_test.cpp create mode 100644 tests/gtest/common_ffmpegutils_test.cpp create mode 100644 tests/gtest/common_jobtime_test.cpp create mode 100644 tests/gtest/core_bezier_test.cpp create mode 100644 tests/gtest/core_color_test.cpp create mode 100644 tests/gtest/core_stringutils_test.cpp create mode 100644 tests/gtest/core_timecode_test.cpp create mode 100644 tests/gtest/core_timerange_test.cpp diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index e29f4c453..0460df744 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -5,6 +5,12 @@ add_executable(olive-gtest common_range_test.cpp common_filefunctions_test.cpp common_qtutils_test.cpp + common_commandlineparser_test.cpp + common_debug_test.cpp + common_decibel_test.cpp + common_digit_test.cpp + common_jobtime_test.cpp + common_ffmpegutils_test.cpp config_test.cpp audio_level_meter_test.cpp audio_synchronizer_test.cpp @@ -16,6 +22,11 @@ add_executable(olive-gtest node_globals_test.cpp node_project_test.cpp render_videoparams_test.cpp + core_stringutils_test.cpp + core_timecode_test.cpp + core_timerange_test.cpp + core_bezier_test.cpp + core_color_test.cpp render_videoparams_branch_test.cpp render_audioparams_test.cpp render_audioparams_branch_test.cpp diff --git a/tests/gtest/codec_frame_test.cpp b/tests/gtest/codec_frame_test.cpp index fde1cf3af..2c5551d50 100644 --- a/tests/gtest/codec_frame_test.cpp +++ b/tests/gtest/codec_frame_test.cpp @@ -54,3 +54,133 @@ TEST(CodecFrame, DestroyDeallocatesData) EXPECT_FALSE(frame->is_allocated()); EXPECT_EQ(frame->data(), nullptr); } + + +TEST(CodecFrame, AllocateInvalidParamsFails) +{ + olive::Frame frame; + EXPECT_FALSE(frame.allocate()); +} + +TEST(CodecFrame, DoubleAllocateReturnsTrue) +{ + olive::VideoParams params(8, 8, olive::core::PixelFormat::U8, + olive::VideoParams::kRGBAChannelCount); + olive::FramePtr frame = olive::Frame::Create(); + frame->set_video_params(params); + EXPECT_TRUE(frame->allocate()); + EXPECT_TRUE(frame->allocate()); +} + +TEST(CodecFrame, ContainsPixel) +{ + olive::VideoParams params(8, 8, olive::core::PixelFormat::U8, + olive::VideoParams::kRGBAChannelCount); + olive::FramePtr frame = olive::Frame::Create(); + frame->set_video_params(params); + + EXPECT_FALSE(frame->contains_pixel(0, 0)); + + frame->allocate(); + EXPECT_TRUE(frame->contains_pixel(0, 0)); + EXPECT_TRUE(frame->contains_pixel(7, 7)); + EXPECT_FALSE(frame->contains_pixel(8, 0)); + EXPECT_FALSE(frame->contains_pixel(0, 8)); + EXPECT_FALSE(frame->contains_pixel(-1, 0)); +} + +TEST(CodecFrame, GetPixelOutOfBoundsReturnsBlack) +{ + olive::VideoParams params(8, 8, olive::core::PixelFormat::U8, + olive::VideoParams::kRGBAChannelCount); + olive::FramePtr frame = olive::Frame::Create(); + frame->set_video_params(params); + frame->allocate(); + + olive::core::Color c = frame->get_pixel(-1, 0); + EXPECT_FLOAT_EQ(c.red(), 0.0f); +} + +TEST(CodecFrame, SetAndGetPixel) +{ + olive::VideoParams params(8, 8, olive::core::PixelFormat::U8, + olive::VideoParams::kRGBAChannelCount); + olive::FramePtr frame = olive::Frame::Create(); + frame->set_video_params(params); + frame->allocate(); + + olive::core::Color red(1.0f, 0.0f, 0.0f, 1.0f); + frame->set_pixel(3, 3, red); + + olive::core::Color got = frame->get_pixel(3, 3); + EXPECT_NEAR(got.red(), 1.0f, 0.01f); + EXPECT_NEAR(got.green(), 0.0f, 0.01f); + EXPECT_NEAR(got.blue(), 0.0f, 0.01f); +} + +TEST(CodecFrame, TimestampRoundTrip) +{ + olive::FramePtr frame = olive::Frame::Create(); + frame->set_timestamp(olive::core::rational(5, 1)); + EXPECT_EQ(frame->timestamp(), olive::core::rational(5, 1)); +} + +TEST(CodecFrame, GenerateLineSizeBytes) +{ + EXPECT_EQ(olive::Frame::generate_linesize_bytes( + 64, olive::core::PixelFormat::U8, + olive::VideoParams::kRGBAChannelCount), + 64 * 4); +} + +TEST(CodecFrame, InterlaceFrames) +{ + olive::VideoParams params(4, 4, olive::core::PixelFormat::U8, + olive::VideoParams::kRGBAChannelCount); + + olive::FramePtr top = olive::Frame::Create(); + top->set_video_params(params); + top->allocate(); + memset(top->data(), 0xFF, top->allocated_size()); + + olive::FramePtr bottom = olive::Frame::Create(); + bottom->set_video_params(params); + bottom->allocate(); + memset(bottom->data(), 0x00, bottom->allocated_size()); + + olive::FramePtr interlaced = olive::Frame::Interlace(top, bottom); + ASSERT_NE(interlaced, nullptr); + EXPECT_EQ(interlaced->width(), 4); + EXPECT_EQ(interlaced->height(), 4); +} + +TEST(CodecFrame, InterlaceIncompatibleReturnsNull) +{ + olive::FramePtr top = olive::Frame::Create(); + top->set_video_params(olive::VideoParams( + 4, 4, olive::core::PixelFormat::U8, olive::VideoParams::kRGBAChannelCount)); + top->allocate(); + + olive::FramePtr bottom = olive::Frame::Create(); + bottom->set_video_params(olive::VideoParams( + 8, 8, olive::core::PixelFormat::U8, olive::VideoParams::kRGBAChannelCount)); + bottom->allocate(); + + EXPECT_EQ(olive::Frame::Interlace(top, bottom), nullptr); +} + +TEST(CodecFrame, ConvertU8ToU16) +{ + olive::VideoParams params(4, 4, olive::core::PixelFormat::U8, + olive::VideoParams::kRGBAChannelCount); + olive::FramePtr frame = olive::Frame::Create(); + frame->set_video_params(params); + frame->allocate(); + + olive::FramePtr converted = + frame->convert(olive::core::PixelFormat::U16); + ASSERT_NE(converted, nullptr); + EXPECT_EQ(converted->format(), olive::core::PixelFormat::U16); + EXPECT_EQ(converted->width(), 4); + EXPECT_EQ(converted->height(), 4); +} diff --git a/tests/gtest/common_commandlineparser_test.cpp b/tests/gtest/common_commandlineparser_test.cpp new file mode 100644 index 000000000..6dc879180 --- /dev/null +++ b/tests/gtest/common_commandlineparser_test.cpp @@ -0,0 +1,83 @@ +#include + +#include "common/commandlineparser.h" + +TEST(CommonCommandLineParser, OptionWithoutArgument) +{ + CommandLineParser parser; + const CommandLineParser::Option *opt = parser.AddOption( + {QStringLiteral("help"), QStringLiteral("h")}, + QStringLiteral("Show help"), false); + + parser.Process({QStringLiteral("app"), QStringLiteral("-help")}); + + EXPECT_TRUE(opt->IsSet()); +} + +TEST(CommonCommandLineParser, ShortOption) +{ + CommandLineParser parser; + const CommandLineParser::Option *opt = parser.AddOption( + {QStringLiteral("help"), QStringLiteral("h")}, + QStringLiteral("Show help"), false); + + parser.Process({QStringLiteral("app"), QStringLiteral("-h")}); + + EXPECT_TRUE(opt->IsSet()); +} + +TEST(CommonCommandLineParser, OptionWithArgument) +{ + CommandLineParser parser; + const CommandLineParser::Option *opt = parser.AddOption( + {QStringLiteral("project")}, QStringLiteral("Project file"), true, + QStringLiteral("file")); + + parser.Process({QStringLiteral("app"), QStringLiteral("-project"), + QStringLiteral("test.ove")}); + + EXPECT_TRUE(opt->IsSet()); + EXPECT_EQ(opt->GetSetting(), QStringLiteral("test.ove")); +} + +TEST(CommonCommandLineParser, PositionalArgument) +{ + CommandLineParser parser; + const CommandLineParser::PositionalArgument *arg = + parser.AddPositionalArgument(QStringLiteral("filename"), + QStringLiteral("Project file"), true); + + parser.Process({QStringLiteral("app"), QStringLiteral("test.ove")}); + + EXPECT_EQ(arg->GetSetting(), QStringLiteral("test.ove")); +} + +TEST(CommonCommandLineParser, UnknownOptionWarning) +{ + CommandLineParser parser; + parser.AddOption({QStringLiteral("known")}, QStringLiteral("Known")); + + // Should not crash; unknown option is logged + parser.Process({QStringLiteral("app"), QStringLiteral("-unknown")}); +} + +TEST(CommonCommandLineParser, UnknownPositionalWarning) +{ + CommandLineParser parser; + + // Should not crash; unknown positional is logged + parser.Process({QStringLiteral("app"), QStringLiteral("extra")}); +} + +TEST(CommonCommandLineParser, HiddenOptionExcludedFromHelp) +{ + CommandLineParser parser; + parser.AddOption({QStringLiteral("visible")}, QStringLiteral("Visible")); + parser.AddOption({QStringLiteral("hidden")}, QStringLiteral("Hidden"), + false, QString(), true); + parser.AddPositionalArgument(QStringLiteral("file"), + QStringLiteral("Input file")); + + // Should not crash; hidden option should be skipped during help output + parser.PrintHelp("/usr/bin/app"); +} diff --git a/tests/gtest/common_debug_test.cpp b/tests/gtest/common_debug_test.cpp new file mode 100644 index 000000000..617f8469f --- /dev/null +++ b/tests/gtest/common_debug_test.cpp @@ -0,0 +1,16 @@ +#include + +#include "common/debug.h" + +TEST(CommonDebug, DebugHandlerFormatsAllLevels) +{ + // Install handler and restore after test + QtMessageHandler old = qInstallMessageHandler(olive::DebugHandler); + + qDebug() << "debug message"; + qInfo() << "info message"; + qWarning() << "warning message"; + qCritical() << "critical message"; + + qInstallMessageHandler(old); +} diff --git a/tests/gtest/common_decibel_test.cpp b/tests/gtest/common_decibel_test.cpp new file mode 100644 index 000000000..ba545811b --- /dev/null +++ b/tests/gtest/common_decibel_test.cpp @@ -0,0 +1,52 @@ +#include + +#include "common/decibel.h" + +TEST(CommonDecibel, FromLinearZeroReturnsMinimum) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::fromLinear(0.0), olive::Decibel::MINIMUM); +} + +TEST(CommonDecibel, FromLinearOneReturnsZero) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::fromLinear(1.0), 0.0); +} + +TEST(CommonDecibel, FromLinearTenReturnsTwenty) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::fromLinear(10.0), 20.0); +} + +TEST(CommonDecibel, ToLinearZeroReturnsOne) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::toLinear(0.0), 1.0); +} + +TEST(CommonDecibel, ToLinearMinimumReturnsZero) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::toLinear(olive::Decibel::MINIMUM), 0.0); +} + +TEST(CommonDecibel, ToLinearTwentyReturnsTen) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::toLinear(20.0), 10.0); +} + +TEST(CommonDecibel, FromLogarithmicAtEdges) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::fromLogarithmic(0.0), + olive::Decibel::MINIMUM); + EXPECT_DOUBLE_EQ(olive::Decibel::fromLogarithmic(1.0), 0.0); +} + +TEST(CommonDecibel, ToLogarithmicAtEdges) +{ + EXPECT_DOUBLE_EQ(olive::Decibel::toLogarithmic(0.0), 1.0); +} + +TEST(CommonDecibel, LinearLogarithmicRoundTrip) +{ + EXPECT_NEAR(olive::Decibel::LogarithmicToLinear( + olive::Decibel::LinearToLogarithmic(0.5)), + 0.5, 1e-6); +} diff --git a/tests/gtest/common_digit_test.cpp b/tests/gtest/common_digit_test.cpp new file mode 100644 index 000000000..e22943525 --- /dev/null +++ b/tests/gtest/common_digit_test.cpp @@ -0,0 +1,23 @@ +#include + +#include "common/digit.h" + +TEST(CommonDigit, SingleDigit) +{ + EXPECT_EQ(olive::GetDigitCount(0), 1); + EXPECT_EQ(olive::GetDigitCount(5), 1); + EXPECT_EQ(olive::GetDigitCount(-5), 1); +} + +TEST(CommonDigit, MultipleDigits) +{ + EXPECT_EQ(olive::GetDigitCount(10), 2); + EXPECT_EQ(olive::GetDigitCount(999), 3); + EXPECT_EQ(olive::GetDigitCount(1000), 4); + EXPECT_EQ(olive::GetDigitCount(-12345), 5); +} + +TEST(CommonDigit, LargeValue) +{ + EXPECT_EQ(olive::GetDigitCount(123456789012345LL), 15); +} diff --git a/tests/gtest/common_ffmpegutils_test.cpp b/tests/gtest/common_ffmpegutils_test.cpp new file mode 100644 index 000000000..d3906d12f --- /dev/null +++ b/tests/gtest/common_ffmpegutils_test.cpp @@ -0,0 +1,149 @@ +#include + +#include "common/ffmpegutils.h" + +using namespace olive; + +TEST(CommonFFmpegUtils, GetNativeSampleFormatMapsCorrectly) +{ + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_U8), + SampleFormat::U8); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S16), + SampleFormat::S16); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S32), + SampleFormat::S32); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S64), + SampleFormat::S64); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_FLT), + SampleFormat::F32); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_DBL), + SampleFormat::F64); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_U8P), + SampleFormat::U8P); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S16P), + SampleFormat::S16P); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S32P), + SampleFormat::S32P); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S64P), + SampleFormat::S64P); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_FLTP), + SampleFormat::F32P); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_DBLP), + SampleFormat::F64P); + EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_NONE), + SampleFormat::INVALID); +} + +TEST(CommonFFmpegUtils, GetFFmpegSampleFormatMapsCorrectly) +{ + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::U8), + AV_SAMPLE_FMT_U8); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S16), + AV_SAMPLE_FMT_S16); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S32), + AV_SAMPLE_FMT_S32); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S64), + AV_SAMPLE_FMT_S64); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F32), + AV_SAMPLE_FMT_FLT); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F64), + AV_SAMPLE_FMT_DBL); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::U8P), + AV_SAMPLE_FMT_U8P); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S16P), + AV_SAMPLE_FMT_S16P); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S32P), + AV_SAMPLE_FMT_S32P); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S64P), + AV_SAMPLE_FMT_S64P); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F32P), + AV_SAMPLE_FMT_FLTP); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F64P), + AV_SAMPLE_FMT_DBLP); + EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::INVALID), + AV_SAMPLE_FMT_NONE); +} + +TEST(CommonFFmpegUtils, GetSwsColorspaceFromAVColorSpace) +{ + EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_BT709), + SWS_CS_ITU709); + EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_FCC), + SWS_CS_FCC); + EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_BT470BG), + SWS_CS_ITU624); + EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_SMPTE170M), + SWS_CS_SMPTE170M); + EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_SMPTE240M), + SWS_CS_SMPTE240M); + EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_BT2020_NCL), + SWS_CS_BT2020); + EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_UNSPECIFIED), + SWS_CS_DEFAULT); +} + +TEST(CommonFFmpegUtils, ConvertJPEGSpaceToRegularSpace) +{ + EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ420P), + AV_PIX_FMT_YUV420P); + EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ422P), + AV_PIX_FMT_YUV422P); + EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ444P), + AV_PIX_FMT_YUV444P); + EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ440P), + AV_PIX_FMT_YUV440P); + EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ411P), + AV_PIX_FMT_YUV411P); + EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUV420P), + AV_PIX_FMT_YUV420P); +} + +TEST(CommonFFmpegUtils, GetCompatiblePixelFormatNative) +{ + EXPECT_EQ(FFmpegUtils::GetCompatiblePixelFormat(PixelFormat::U8), + PixelFormat::U8); + EXPECT_EQ(FFmpegUtils::GetCompatiblePixelFormat(PixelFormat::U10), + PixelFormat::U8); + EXPECT_EQ(FFmpegUtils::GetCompatiblePixelFormat(PixelFormat::U16), + PixelFormat::U16); + EXPECT_EQ(FFmpegUtils::GetCompatiblePixelFormat(PixelFormat::F16), + PixelFormat::U16); + EXPECT_EQ(FFmpegUtils::GetCompatiblePixelFormat(PixelFormat::F32), + PixelFormat::U16); + EXPECT_EQ(FFmpegUtils::GetCompatiblePixelFormat(PixelFormat::INVALID), + PixelFormat::INVALID); +} + +TEST(CommonFFmpegUtils, GetFFmpegPixelFormat) +{ + EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U8, + VideoParams::kRGBChannelCount), + AV_PIX_FMT_RGB24); + EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U16, + VideoParams::kRGBChannelCount), + AV_PIX_FMT_RGB48); + EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::F32, + VideoParams::kRGBChannelCount), + AV_PIX_FMT_RGBF32); + EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U8, + VideoParams::kRGBAChannelCount), + AV_PIX_FMT_RGBA); + EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U16, + VideoParams::kRGBAChannelCount), + AV_PIX_FMT_RGBA64); + EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::F32, + VideoParams::kRGBAChannelCount), + AV_PIX_FMT_RGBAF32); + EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::INVALID, 0), + AV_PIX_FMT_NONE); +} + +TEST(CommonFFmpegUtils, GetCompatiblePixelFormatAV) +{ + AVPixelFormat fmt = FFmpegUtils::GetCompatiblePixelFormat(AV_PIX_FMT_YUV420P); + EXPECT_NE(fmt, AV_PIX_FMT_NONE); + + fmt = FFmpegUtils::GetCompatiblePixelFormat(AV_PIX_FMT_YUV420P, + PixelFormat::U8); + EXPECT_EQ(fmt, AV_PIX_FMT_RGBA); +} diff --git a/tests/gtest/common_jobtime_test.cpp b/tests/gtest/common_jobtime_test.cpp new file mode 100644 index 000000000..4eb38f5d0 --- /dev/null +++ b/tests/gtest/common_jobtime_test.cpp @@ -0,0 +1,42 @@ +#include + +#include "common/jobtime.h" + +TEST(CommonJobTime, ConstructorAcquiresValue) +{ + olive::JobTime a; + olive::JobTime b; + + EXPECT_NE(a.value(), b.value()); + EXPECT_LT(a.value(), b.value()); +} + +TEST(CommonJobTime, AcquireUpdatesValue) +{ + olive::JobTime a; + uint64_t first = a.value(); + a.Acquire(); + uint64_t second = a.value(); + + EXPECT_GT(second, first); +} + +TEST(CommonJobTime, ComparisonOperators) +{ + olive::JobTime a; + olive::JobTime b; + + EXPECT_LT(a, b); + EXPECT_GT(b, a); + EXPECT_LE(a, a); + EXPECT_GE(b, b); + EXPECT_EQ(a, a); + EXPECT_NE(a, b); +} + +TEST(CommonJobTime, DebugStream) +{ + olive::JobTime a; + QDebug debug(QtDebugMsg); + debug << a; +} diff --git a/tests/gtest/core_bezier_test.cpp b/tests/gtest/core_bezier_test.cpp new file mode 100644 index 000000000..5c1c4cf0b --- /dev/null +++ b/tests/gtest/core_bezier_test.cpp @@ -0,0 +1,120 @@ +#include + +#include "olive/core/util/bezier.h" + +using namespace olive::core; + +TEST(CoreBezier, DefaultConstruction) +{ + Bezier b; + EXPECT_DOUBLE_EQ(b.x(), 0.0); + EXPECT_DOUBLE_EQ(b.y(), 0.0); + EXPECT_DOUBLE_EQ(b.cp1_x(), 0.0); + EXPECT_DOUBLE_EQ(b.cp1_y(), 0.0); + EXPECT_DOUBLE_EQ(b.cp2_x(), 0.0); + EXPECT_DOUBLE_EQ(b.cp2_y(), 0.0); +} + +TEST(CoreBezier, ValueConstruction) +{ + Bezier b(1.0, 2.0); + EXPECT_DOUBLE_EQ(b.x(), 1.0); + EXPECT_DOUBLE_EQ(b.y(), 2.0); +} + +TEST(CoreBezier, FullConstruction) +{ + Bezier b(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + EXPECT_DOUBLE_EQ(b.x(), 1.0); + EXPECT_DOUBLE_EQ(b.y(), 2.0); + EXPECT_DOUBLE_EQ(b.cp1_x(), 3.0); + EXPECT_DOUBLE_EQ(b.cp1_y(), 4.0); + EXPECT_DOUBLE_EQ(b.cp2_x(), 5.0); + EXPECT_DOUBLE_EQ(b.cp2_y(), 6.0); +} + +TEST(CoreBezier, Setters) +{ + Bezier b; + b.set_x(10.0); + b.set_y(20.0); + b.set_cp1_x(30.0); + b.set_cp1_y(40.0); + b.set_cp2_x(50.0); + b.set_cp2_y(60.0); + + EXPECT_DOUBLE_EQ(b.x(), 10.0); + EXPECT_DOUBLE_EQ(b.y(), 20.0); + EXPECT_DOUBLE_EQ(b.cp1_x(), 30.0); + EXPECT_DOUBLE_EQ(b.cp1_y(), 40.0); + EXPECT_DOUBLE_EQ(b.cp2_x(), 50.0); + EXPECT_DOUBLE_EQ(b.cp2_y(), 60.0); +} + +TEST(CoreBezier, QuadraticXtoT) +{ + double t = Bezier::QuadraticXtoT(0.5, 0.0, 0.5, 1.0); + EXPECT_NEAR(t, 0.5, 0.00001); + + t = Bezier::QuadraticXtoT(0.0, 0.0, 0.5, 1.0); + EXPECT_NEAR(t, 0.0, 0.00001); + + t = Bezier::QuadraticXtoT(1.0, 0.0, 0.5, 1.0); + EXPECT_NEAR(t, 1.0, 0.00001); +} + +TEST(CoreBezier, QuadraticTtoY) +{ + EXPECT_NEAR(Bezier::QuadraticTtoY(0.0, 0.5, 1.0, 0.0), 0.0, 0.00001); + EXPECT_NEAR(Bezier::QuadraticTtoY(0.0, 0.5, 1.0, 0.5), 0.5, 0.00001); + EXPECT_NEAR(Bezier::QuadraticTtoY(0.0, 0.5, 1.0, 1.0), 1.0, 0.00001); +} + +TEST(CoreBezier, QuadraticXtoY) +{ + Imath::V2d a(0.0, 0.0); + Imath::V2d b(0.5, 0.5); + Imath::V2d c(1.0, 1.0); + + EXPECT_NEAR(Bezier::QuadraticXtoY(0.5, a, b, c), 0.5, 0.00001); +} + +TEST(CoreBezier, CubicXtoT) +{ + double t = Bezier::CubicXtoT(0.5, 0.0, 0.33, 0.66, 1.0); + EXPECT_NEAR(t, 0.5, 0.01); +} + +TEST(CoreBezier, CubicTtoY) +{ + EXPECT_NEAR(Bezier::CubicTtoY(0.0, 0.33, 0.66, 1.0, 0.0), 0.0, 0.00001); + EXPECT_NEAR(Bezier::CubicTtoY(0.0, 0.33, 0.66, 1.0, 1.0), 1.0, 0.00001); +} + +TEST(CoreBezier, CubicXtoY) +{ + Imath::V2d a(0.0, 0.0); + Imath::V2d b(0.33, 0.0); + Imath::V2d c(0.66, 1.0); + Imath::V2d d(1.0, 1.0); + + double y = Bezier::CubicXtoY(0.5, a, b, c, d); + EXPECT_GE(y, 0.0); + EXPECT_LE(y, 1.0); +} + +TEST(CoreBezier, VectorConverters) +{ + Bezier b(1.0, 2.0, 3.0, 4.0, 5.0, 6.0); + Imath::V2d v = b.to_vec(); + EXPECT_DOUBLE_EQ(v.x, 1.0); + EXPECT_DOUBLE_EQ(v.y, 2.0); + + Imath::V2d cp1 = b.control_point_1_to_vec(); + EXPECT_DOUBLE_EQ(cp1.x, 3.0); + EXPECT_DOUBLE_EQ(cp1.y, 4.0); + + Imath::V2d cp2 = b.control_point_2_to_vec(); + EXPECT_DOUBLE_EQ(cp2.x, 5.0); + EXPECT_DOUBLE_EQ(cp2.y, 6.0); +} diff --git a/tests/gtest/core_color_test.cpp b/tests/gtest/core_color_test.cpp new file mode 100644 index 000000000..a2ae98b13 --- /dev/null +++ b/tests/gtest/core_color_test.cpp @@ -0,0 +1,169 @@ +#include + +#include "olive/core/util/color.h" + +using namespace olive::core; + +TEST(CoreColor, DefaultConstruction) +{ + Color c; + EXPECT_FLOAT_EQ(c.red(), 0.0f); + EXPECT_FLOAT_EQ(c.green(), 0.0f); + EXPECT_FLOAT_EQ(c.blue(), 0.0f); + EXPECT_FLOAT_EQ(c.alpha(), 0.0f); +} + +TEST(CoreColor, ValueConstruction) +{ + Color c(0.1f, 0.2f, 0.3f, 0.4f); + EXPECT_FLOAT_EQ(c.red(), 0.1f); + EXPECT_FLOAT_EQ(c.green(), 0.2f); + EXPECT_FLOAT_EQ(c.blue(), 0.3f); + EXPECT_FLOAT_EQ(c.alpha(), 0.4f); +} + +TEST(CoreColor, SettersAndDataAccess) +{ + Color c; + c.set_red(0.5f); + c.set_green(0.6f); + c.set_blue(0.7f); + c.set_alpha(0.8f); + + EXPECT_FLOAT_EQ(c.data()[0], 0.5f); + EXPECT_FLOAT_EQ(c.data()[1], 0.6f); + EXPECT_FLOAT_EQ(c.data()[2], 0.7f); + EXPECT_FLOAT_EQ(c.data()[3], 0.8f); +} + +TEST(CoreColor, FromHsvRed) +{ + Color c = Color::fromHsv(0.0f, 1.0f, 1.0f); + EXPECT_NEAR(c.red(), 1.0f, 0.001f); + EXPECT_NEAR(c.green(), 0.0f, 0.001f); + EXPECT_NEAR(c.blue(), 0.0f, 0.001f); +} + +TEST(CoreColor, FromHsvGreen) +{ + Color c = Color::fromHsv(120.0f, 1.0f, 1.0f); + EXPECT_NEAR(c.red(), 0.0f, 0.001f); + EXPECT_NEAR(c.green(), 1.0f, 0.001f); + EXPECT_NEAR(c.blue(), 0.0f, 0.001f); +} + +TEST(CoreColor, FromHsvBlue) +{ + Color c = Color::fromHsv(240.0f, 1.0f, 1.0f); + EXPECT_NEAR(c.red(), 0.0f, 0.001f); + EXPECT_NEAR(c.green(), 0.0f, 0.001f); + EXPECT_NEAR(c.blue(), 1.0f, 0.001f); +} + +TEST(CoreColor, HsvRoundTrip) +{ + Color original(0.8f, 0.4f, 0.2f); + float h, s, v; + original.toHsv(&h, &s, &v); + + EXPECT_NEAR(original.hsv_hue(), h, 0.001f); + EXPECT_NEAR(original.hsv_saturation(), s, 0.001f); + EXPECT_NEAR(original.value(), v, 0.001f); +} + +TEST(CoreColor, HslRoundTrip) +{ + Color original(0.2f, 0.5f, 0.8f); + float h, s, l; + original.toHsl(&h, &s, &l); + + EXPECT_NEAR(original.hsl_hue(), h, 0.001f); + EXPECT_NEAR(original.hsl_saturation(), s, 0.001f); + EXPECT_NEAR(original.lightness(), l, 0.001f); +} + +TEST(CoreColor, ArithmeticOperators) +{ + Color a(1.0f, 2.0f, 3.0f, 4.0f); + Color b(0.5f, 0.5f, 0.5f, 0.5f); + + Color sum = a + b; + EXPECT_FLOAT_EQ(sum.red(), 1.5f); + + Color diff = a - b; + EXPECT_FLOAT_EQ(diff.red(), 0.5f); + + Color scaled = a * 2.0f; + EXPECT_FLOAT_EQ(scaled.red(), 2.0f); + + Color divided = a / 2.0f; + EXPECT_FLOAT_EQ(divided.red(), 0.5f); + + Color added_scalar = a + 1.0f; + EXPECT_FLOAT_EQ(added_scalar.red(), 2.0f); +} + +TEST(CoreColor, CompoundAssignment) +{ + Color c(1.0f, 2.0f, 3.0f, 4.0f); + c += Color(0.5f, 0.5f, 0.5f, 0.5f); + EXPECT_FLOAT_EQ(c.red(), 1.5f); + + c *= 2.0f; + EXPECT_FLOAT_EQ(c.red(), 3.0f); +} + +TEST(CoreColor, GetRoughLuminance) +{ + Color white(1.0f, 1.0f, 1.0f); + EXPECT_FLOAT_EQ(white.GetRoughLuminance(), 1.0f); + + Color black(0.0f, 0.0f, 0.0f); + EXPECT_FLOAT_EQ(black.GetRoughLuminance(), 0.0f); +} + +TEST(CoreColor, ToDataAndFromDataU8) +{ + Color c(1.0f, 0.5f, 0.0f, 1.0f); + uint8_t data[4]; + c.toData(reinterpret_cast(data), PixelFormat::U8, 4); + + EXPECT_EQ(data[0], 255u); + EXPECT_EQ(data[1], 127u); + EXPECT_EQ(data[2], 0u); + EXPECT_EQ(data[3], 255u); + + Color restored = Color::fromData(reinterpret_cast(data), + PixelFormat::U8, 4); + EXPECT_NEAR(restored.red(), 1.0f, 0.01f); + EXPECT_NEAR(restored.green(), 0.5f, 0.01f); +} + +TEST(CoreColor, ToDataAndFromDataF32) +{ + Color c(0.25f, 0.5f, 0.75f, 1.0f); + float data[4]; + c.toData(reinterpret_cast(data), PixelFormat::F32, 4); + + EXPECT_FLOAT_EQ(data[0], 0.25f); + EXPECT_FLOAT_EQ(data[1], 0.5f); + EXPECT_FLOAT_EQ(data[2], 0.75f); + EXPECT_FLOAT_EQ(data[3], 1.0f); + + Color restored = Color::fromData(reinterpret_cast(data), + PixelFormat::F32, 4); + EXPECT_FLOAT_EQ(restored.red(), 0.25f); +} + +TEST(CoreColor, ToDataAndFromDataU10) +{ + Color c(1.0f, 0.5f, 0.0f, 1.0f); + uint32_t data; + c.toData(reinterpret_cast(&data), PixelFormat::U10, 4); + + Color restored = Color::fromData(reinterpret_cast(&data), + PixelFormat::U10, 4); + EXPECT_NEAR(restored.red(), 1.0f, 0.001f); + EXPECT_NEAR(restored.green(), 0.5f, 0.001f); + EXPECT_NEAR(restored.blue(), 0.0f, 0.001f); +} diff --git a/tests/gtest/core_stringutils_test.cpp b/tests/gtest/core_stringutils_test.cpp new file mode 100644 index 000000000..1b9056d13 --- /dev/null +++ b/tests/gtest/core_stringutils_test.cpp @@ -0,0 +1,65 @@ +#include + +#include + +#include "olive/core/util/stringutils.h" + +using namespace olive::core; + +TEST(CoreStringUtils, Split) +{ + auto result = StringUtils::split("a,b,c", ','); + ASSERT_EQ(result.size(), 3u); + EXPECT_EQ(result[0], "a"); + EXPECT_EQ(result[1], "b"); + EXPECT_EQ(result[2], "c"); +} + +TEST(CoreStringUtils, SplitRegex) +{ + auto result = StringUtils::split_regex("one:two;three", std::regex("(:)|(;)| ")); + ASSERT_EQ(result.size(), 3u); + EXPECT_EQ(result[0], "one"); + EXPECT_EQ(result[1], "two"); + EXPECT_EQ(result[2], "three"); +} + +TEST(CoreStringUtils, ToInt) +{ + bool ok = false; + EXPECT_EQ(StringUtils::to_int("42", &ok), 42); + EXPECT_TRUE(ok); + + EXPECT_EQ(StringUtils::to_int("-7", 10, &ok), -7); + EXPECT_TRUE(ok); + + EXPECT_EQ(StringUtils::to_int("ff", 16, &ok), 255); + EXPECT_TRUE(ok); + + EXPECT_EQ(StringUtils::to_int("abc", &ok), 0); + EXPECT_FALSE(ok); +} + +TEST(CoreStringUtils, ToStringLeftpad) +{ + EXPECT_EQ(StringUtils::to_string_leftpad(5, 3), "005"); + EXPECT_EQ(StringUtils::to_string_leftpad(123, 2), "123"); + EXPECT_EQ(StringUtils::to_string_leftpad(7, 4, '*'), "***7"); +} + +TEST(CoreStringUtils, Format) +{ + EXPECT_EQ(StringUtils::format("Hello %s %d", "world", 42), + "Hello world 42"); +} + +TEST(CoreStringUtils, Trim) +{ + std::string s = " hello world "; + StringUtils::trim(s); + EXPECT_EQ(s, "hello world"); + + EXPECT_EQ(StringUtils::trimmed("\t\nvalue\t\n"), "value"); + EXPECT_EQ(StringUtils::ltrimmed(" left"), "left"); + EXPECT_EQ(StringUtils::rtrimmed("right "), "right"); +} diff --git a/tests/gtest/core_timecode_test.cpp b/tests/gtest/core_timecode_test.cpp new file mode 100644 index 000000000..d04c2f262 --- /dev/null +++ b/tests/gtest/core_timecode_test.cpp @@ -0,0 +1,115 @@ +#include + +#include "olive/core/util/timecodefunctions.h" + +using namespace olive::core; + +TEST(CoreTimecode, TimeToTimecodeSeconds) +{ + rational time(5, 1); + rational tb(1, 25); + std::string tc = Timecode::time_to_timecode(time, tb, Timecode::kTimecodeSeconds); + EXPECT_EQ(tc, "00:00:05.000"); +} + +TEST(CoreTimecode, TimeToTimecodeNonDropFrame) +{ + rational time(2, 1); + rational tb(1, 25); + std::string tc = + Timecode::time_to_timecode(time, tb, Timecode::kTimecodeNonDropFrame); + EXPECT_EQ(tc, "00:00:02:00"); +} + +TEST(CoreTimecode, TimeToTimecodePlusSign) +{ + rational time(1, 1); + rational tb(1, 25); + std::string tc = Timecode::time_to_timecode(time, tb, + Timecode::kTimecodeSeconds, true); + EXPECT_EQ(tc.substr(0, 1), "+"); +} + +TEST(CoreTimecode, TimeToTimecodeInvalidTimebase) +{ + rational time(1, 1); + EXPECT_EQ(Timecode::time_to_timecode(time, rational(), Timecode::kFrames), + "INVALID TIMEBASE"); +} + +TEST(CoreTimecode, TimecodeToTimeSeconds) +{ + rational tb(1, 25); + bool ok = false; + rational t = Timecode::timecode_to_time("00:00:05.500", tb, + Timecode::kTimecodeSeconds, &ok); + EXPECT_TRUE(ok); + EXPECT_EQ(t, rational(11, 2)); +} + +TEST(CoreTimecode, TimecodeToTimeNonDropFrame) +{ + rational tb(1, 25); + bool ok = false; + rational t = Timecode::timecode_to_time("00:00:02:03", tb, + Timecode::kTimecodeNonDropFrame, &ok); + EXPECT_TRUE(ok); + EXPECT_EQ(t, rational(53, 25)); +} + +TEST(CoreTimecode, TimecodeToTimeInvalid) +{ + rational tb(1, 25); + bool ok = true; + Timecode::timecode_to_time("not a timecode", tb, + Timecode::kTimecodeSeconds, &ok); + EXPECT_FALSE(ok); +} + +TEST(CoreTimecode, TimeToString) +{ + EXPECT_EQ(Timecode::time_to_string(3661000), "01:01:01"); +} + +TEST(CoreTimecode, SnapTimeToTimebase) +{ + rational tb(1, 25); + rational snapped = Timecode::snap_time_to_timebase(rational(1, 10), tb); + // 0.1s @ 25fps rounds to frame 3 (0.12s) + EXPECT_EQ(snapped, rational(3, 25)); +} + +TEST(CoreTimecode, TimeToTimestamp) +{ + rational tb(1, 25); + EXPECT_EQ(Timecode::time_to_timestamp(rational(2, 1), tb), 50); + EXPECT_EQ(Timecode::time_to_timestamp(0.08, tb, Timecode::kFloor), 2); + EXPECT_EQ(Timecode::time_to_timestamp(0.08, tb, Timecode::kCeil), 2); +} + +TEST(CoreTimecode, TimestampToTime) +{ + rational tb(1, 25); + EXPECT_EQ(Timecode::timestamp_to_time(50, tb), rational(2, 1)); +} + +TEST(CoreTimecode, RescaleTimestamp) +{ + rational src(1, 25); + rational dst(1, 30); + EXPECT_EQ(Timecode::rescale_timestamp(50, src, dst), 60); + EXPECT_EQ(Timecode::rescale_timestamp(50, src, src), 50); +} + +TEST(CoreTimecode, RescaleTimestampCeil) +{ + rational src(1, 25); + rational dst(1, 30); + EXPECT_EQ(Timecode::rescale_timestamp_ceil(1, src, dst), 2); +} + +TEST(CoreTimecode, TimebaseIsDropFrame) +{ + EXPECT_FALSE(Timecode::timebase_is_drop_frame(rational(1, 25))); + EXPECT_TRUE(Timecode::timebase_is_drop_frame(rational(1001, 30000))); +} diff --git a/tests/gtest/core_timerange_test.cpp b/tests/gtest/core_timerange_test.cpp new file mode 100644 index 000000000..f5b745fcb --- /dev/null +++ b/tests/gtest/core_timerange_test.cpp @@ -0,0 +1,184 @@ +#include + +#include "olive/core/util/timerange.h" + +using namespace olive::core; + +TEST(CoreTimeRange, ConstructAndAccess) +{ + TimeRange r(rational(1), rational(5)); + EXPECT_EQ(r.in(), rational(1)); + EXPECT_EQ(r.out(), rational(5)); + EXPECT_EQ(r.length(), rational(4)); +} + +TEST(CoreTimeRange, NormalizationSwapsReversedBounds) +{ + TimeRange r(rational(5), rational(1)); + EXPECT_EQ(r.in(), rational(1)); + EXPECT_EQ(r.out(), rational(5)); +} + +TEST(CoreTimeRange, SettersNormalize) +{ + TimeRange r(rational(0), rational(10)); + r.set_in(rational(15)); + EXPECT_EQ(r.in(), rational(10)); + EXPECT_EQ(r.out(), rational(15)); + + r.set_out(rational(2)); + EXPECT_EQ(r.in(), rational(2)); + EXPECT_EQ(r.out(), rational(10)); +} + +TEST(CoreTimeRange, ContainsRational) +{ + TimeRange r(rational(0), rational(10)); + EXPECT_TRUE(r.Contains(rational(5))); + EXPECT_FALSE(r.Contains(rational(10))); + EXPECT_FALSE(r.Contains(rational(-1))); +} + +TEST(CoreTimeRange, ContainsRange) +{ + TimeRange outer(rational(0), rational(10)); + TimeRange inner(rational(2), rational(8)); + TimeRange partial(rational(5), rational(15)); + + EXPECT_TRUE(outer.Contains(inner)); + EXPECT_FALSE(outer.Contains(partial)); +} + +TEST(CoreTimeRange, OverlapsWith) +{ + TimeRange a(rational(0), rational(10)); + TimeRange b(rational(5), rational(15)); + TimeRange c(rational(10), rational(20)); + + EXPECT_TRUE(a.OverlapsWith(b)); + // By default bounds are inclusive, so [0,10] and [10,20] touch and overlap + EXPECT_TRUE(a.OverlapsWith(c)); + EXPECT_FALSE(a.OverlapsWith(c, false, false)); +} + +TEST(CoreTimeRange, CombineAndIntersect) +{ + TimeRange a(rational(0), rational(10)); + TimeRange b(rational(5), rational(15)); + + TimeRange combined = a.Combined(b); + EXPECT_EQ(combined.in(), rational(0)); + EXPECT_EQ(combined.out(), rational(15)); + + TimeRange intersect = a.Intersected(b); + EXPECT_EQ(intersect.in(), rational(5)); + EXPECT_EQ(intersect.out(), rational(10)); +} + +TEST(CoreTimeRange, Arithmetic) +{ + TimeRange r(rational(0), rational(10)); + TimeRange shifted = r + rational(5); + EXPECT_EQ(shifted.in(), rational(5)); + EXPECT_EQ(shifted.out(), rational(15)); + + shifted -= rational(3); + EXPECT_EQ(shifted.in(), rational(2)); + EXPECT_EQ(shifted.out(), rational(12)); +} + +TEST(CoreTimeRange, Split) +{ + TimeRange r(rational(0), rational(10)); + auto pieces = r.Split(3); + ASSERT_EQ(pieces.size(), 4u); + EXPECT_EQ(pieces.front().in(), rational(0)); +} + +TEST(CoreTimeRangeList, InsertMergesOverlapping) +{ + TimeRangeList list; + list.insert(TimeRange(rational(0), rational(5))); + list.insert(TimeRange(rational(3), rational(8))); + list.insert(TimeRange(rational(10), rational(12))); + + EXPECT_EQ(list.size(), 2); + EXPECT_EQ(list.first().in(), rational(0)); + EXPECT_EQ(list.first().out(), rational(8)); +} + +TEST(CoreTimeRangeList, RemoveSplitsRange) +{ + TimeRangeList list; + list.insert(TimeRange(rational(0), rational(10))); + list.remove(TimeRange(rational(3), rational(7))); + + EXPECT_EQ(list.size(), 2); + EXPECT_EQ(list.first().out(), rational(3)); + EXPECT_EQ(list.last().in(), rational(7)); +} + +TEST(CoreTimeRangeList, Shift) +{ + TimeRangeList list; + list.insert(TimeRange(rational(0), rational(5))); + list.shift(rational(10)); + + EXPECT_EQ(list.first().in(), rational(10)); + EXPECT_EQ(list.first().out(), rational(15)); +} + +TEST(CoreTimeRangeList, TrimInAndOut) +{ + TimeRangeList list; + list.insert(TimeRange(rational(10), rational(20))); + list.trim_in(rational(5)); + EXPECT_EQ(list.first().in(), rational(15)); + EXPECT_EQ(list.first().out(), rational(20)); + + list.trim_out(rational(-5)); + // set_out(out + diff) = 20 + (-5) = 15 + EXPECT_EQ(list.first().out(), rational(15)); +} + +TEST(CoreTimeRangeList, Intersects) +{ + TimeRangeList list; + list.insert(TimeRange(rational(0), rational(10))); + list.insert(TimeRange(rational(20), rational(30))); + + TimeRangeList result = list.Intersects(TimeRange(rational(5), rational(25))); + EXPECT_EQ(result.size(), 2); + EXPECT_EQ(result.first().in(), rational(5)); + EXPECT_EQ(result.first().out(), rational(10)); +} + +TEST(CoreTimeRangeListFrameIterator, IteratesFrames) +{ + TimeRangeList list; + // 5 seconds at 25fps = 125 frames + list.insert(TimeRange(rational(0), rational(5))); + TimeRangeListFrameIterator it(list, rational(1, 25)); + + rational out; + int count = 0; + while (it.GetNext(&out)) { + count++; + } + + EXPECT_EQ(count, 125); + EXPECT_EQ(it.size(), 125); +} + +TEST(CoreTimeRangeListFrameIterator, HasNext) +{ + TimeRangeList list; + list.insert(TimeRange(rational(0), rational(1))); + TimeRangeListFrameIterator it(list, rational(1, 25)); + + EXPECT_TRUE(it.HasNext()); + rational out; + while (it.GetNext(&out)) { + } + EXPECT_FALSE(it.HasNext()); +}