diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index 0460df744..a4030b827 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -27,6 +27,8 @@ add_executable(olive-gtest core_timerange_test.cpp core_bezier_test.cpp core_color_test.cpp + core_rational_test.cpp + core_samplebuffer_test.cpp render_videoparams_branch_test.cpp render_audioparams_test.cpp render_audioparams_branch_test.cpp diff --git a/tests/gtest/common_filefunctions_test.cpp b/tests/gtest/common_filefunctions_test.cpp index d23c650d0..defff8088 100644 --- a/tests/gtest/common_filefunctions_test.cpp +++ b/tests/gtest/common_filefunctions_test.cpp @@ -150,3 +150,67 @@ TEST(CommonFileFunctions, GetUniqueFileIdentifier) EXPECT_TRUE(olive::FileFunctions::GetUniqueFileIdentifier( QStringLiteral("/nonexistent")).isEmpty()); } + + +TEST(CommonFileFunctions, GetConfigurationLocation) +{ + QString loc = olive::FileFunctions::GetConfigurationLocation(); + EXPECT_FALSE(loc.isEmpty()); + EXPECT_TRUE(QDir(loc).exists()); +} + +TEST(CommonFileFunctions, GetTempFilePath) +{ + QString temp = olive::FileFunctions::GetTempFilePath(); + EXPECT_FALSE(temp.isEmpty()); + EXPECT_TRUE(QDir(temp).exists()); +} + +TEST(CommonFileFunctions, GetAutoRecoveryRoot) +{ + QString root = olive::FileFunctions::GetAutoRecoveryRoot(); + EXPECT_FALSE(root.isEmpty()); +} + +TEST(CommonFileFunctions, DirectoryIsValidExisting) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + EXPECT_TRUE(olive::FileFunctions::DirectoryIsValid(QDir(dir.path()), false)); +} + +TEST(CommonFileFunctions, CopyDirectoryWithOverwrite) +{ + QTemporaryDir src; + QTemporaryDir dst; + ASSERT_TRUE(src.isValid()); + ASSERT_TRUE(dst.isValid()); + + QString src_file = QDir(src.path()).filePath(QStringLiteral("file.txt")); + QFile f(src_file); + f.open(QIODevice::WriteOnly); + f.write("new content"); + f.close(); + + QString dst_file = QDir(dst.path()).filePath(QStringLiteral("file.txt")); + QFile g(dst_file); + g.open(QIODevice::WriteOnly); + g.write("old content"); + g.close(); + + olive::FileFunctions::CopyDirectory(src.path(), dst.path(), true); + + QFile result(dst_file); + result.open(QIODevice::ReadOnly); + EXPECT_EQ(result.readAll(), QByteArray("new content")); +} + +TEST(CommonFileFunctions, CopyDirectorySourceMissing) +{ + QTemporaryDir dst; + ASSERT_TRUE(dst.isValid()); + + // Should not crash even if source doesn't exist + olive::FileFunctions::CopyDirectory(QStringLiteral("/nonexistent/path"), + dst.path(), false); +} diff --git a/tests/gtest/common_qtutils_test.cpp b/tests/gtest/common_qtutils_test.cpp index 4a1f6b1fa..f1edbb9b3 100644 --- a/tests/gtest/common_qtutils_test.cpp +++ b/tests/gtest/common_qtutils_test.cpp @@ -111,3 +111,51 @@ TEST(CommonQtUtils, ToQColor) EXPECT_NEAR(qc.blueF(), 0.3, 0.001); EXPECT_NEAR(qc.alphaF(), 0.4, 0.001); } + + +TEST(CommonQtUtils, GetFormattedDateTime) +{ + QDateTime dt = QDateTime::fromString(QStringLiteral("2025-01-15T10:30:00"), + Qt::ISODate); + QString s = olive::QtUtils::GetFormattedDateTime(dt); + EXPECT_FALSE(s.isEmpty()); +} + +TEST(CommonQtUtils, WordWrapString) +{ + QFont font; + QFontMetrics fm(font); + + // Use a moderate width; long words may not split cleanly, so just verify no crash + QStringList wrapped = olive::QtUtils::WordWrapString( + QStringLiteral("hello world foo bar"), fm, 40); + EXPECT_GE(wrapped.size(), 1u); + + // Should preserve manual newlines + wrapped = olive::QtUtils::WordWrapString( + QStringLiteral("line1\nline2"), fm, 1000); + EXPECT_EQ(wrapped.size(), 2); +} + +TEST(CommonQtUtils, ToQColorClampsValues) +{ + olive::core::Color c(2.0f, -1.0f, 0.5f, 1.5f); + QColor qc = olive::QtUtils::toQColor(c); + EXPECT_NEAR(qc.redF(), 1.0, 0.001); + EXPECT_NEAR(qc.greenF(), 0.0, 0.001); + EXPECT_NEAR(qc.blueF(), 0.5, 0.001); + EXPECT_NEAR(qc.alphaF(), 1.0, 0.001); +} + +TEST(CommonQtUtils, qHashRational) +{ + olive::core::rational r(3, 4); + EXPECT_NO_THROW(qHash(r)); +} + +TEST(CommonQtUtils, qHashTimeRange) +{ + olive::core::TimeRange tr(olive::core::rational(1), + olive::core::rational(5)); + EXPECT_NO_THROW(qHash(tr)); +} diff --git a/tests/gtest/common_xmlutils_test.cpp b/tests/gtest/common_xmlutils_test.cpp index 8549d62e5..e0ad0ada5 100644 --- a/tests/gtest/common_xmlutils_test.cpp +++ b/tests/gtest/common_xmlutils_test.cpp @@ -58,3 +58,28 @@ TEST(CommonXmlUtils, ReadNextStartElementSkipsUnknown) EXPECT_TRUE(olive::XMLReadNextStartElement(&reader)); EXPECT_EQ(reader.name().toString(), QStringLiteral("known")); } + + +TEST(CommonXmlUtils, ReadNextStartElementWithCancel) +{ + QByteArray xml = ""; + QBuffer buffer(&xml); + buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&buffer); + + olive::CancelAtom atom; + EXPECT_TRUE(olive::XMLReadNextStartElement(&reader, &atom)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("root")); +} + +TEST(CommonXmlUtils, ReadNextStartElementRespectsCancel) +{ + QByteArray xml = ""; + QBuffer buffer(&xml); + buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&buffer); + + olive::CancelAtom atom; + atom.Cancel(); + EXPECT_FALSE(olive::XMLReadNextStartElement(&reader, &atom)); +} diff --git a/tests/gtest/core_rational_test.cpp b/tests/gtest/core_rational_test.cpp new file mode 100644 index 000000000..aaffe0f14 --- /dev/null +++ b/tests/gtest/core_rational_test.cpp @@ -0,0 +1,185 @@ +#include + +#include +#include +#include + +#include "olive/core/util/rational.h" + +using namespace olive::core; + +TEST(CoreRational, DefaultConstruction) +{ + rational r; + EXPECT_EQ(r.numerator(), 0); + EXPECT_EQ(r.denominator(), 1); +} + +TEST(CoreRational, IntegerConstruction) +{ + rational r(5); + EXPECT_EQ(r.numerator(), 5); + EXPECT_EQ(r.denominator(), 1); +} + +TEST(CoreRational, FractionConstructionReduces) +{ + rational r(4, 8); + EXPECT_EQ(r.numerator(), 1); + EXPECT_EQ(r.denominator(), 2); +} + +TEST(CoreRational, NegativeDenominatorNormalizes) +{ + rational r(1, -2); + EXPECT_EQ(r.numerator(), -1); + EXPECT_EQ(r.denominator(), 2); +} + +TEST(CoreRational, ZeroNormalizes) +{ + rational r(0, 5); + EXPECT_EQ(r.numerator(), 0); + EXPECT_EQ(r.denominator(), 1); +} + +TEST(CoreRational, FromDouble) +{ + bool ok = false; + rational r = rational::fromDouble(0.5, &ok); + EXPECT_TRUE(ok); + EXPECT_EQ(r, rational(1, 2)); + + r = rational::fromDouble(std::numeric_limits::quiet_NaN(), &ok); + EXPECT_FALSE(ok); + EXPECT_TRUE(r.isNaN()); +} + +TEST(CoreRational, FromString) +{ + bool ok = false; + rational r = rational::fromString("3/4", &ok); + EXPECT_TRUE(ok); + EXPECT_EQ(r, rational(3, 4)); + + r = rational::fromString("42", &ok); + EXPECT_TRUE(ok); + EXPECT_EQ(r, rational(42)); + + r = rational::fromString("1/2/3", &ok); + EXPECT_FALSE(ok); + EXPECT_TRUE(r.isNaN()); +} + +TEST(CoreRational, ToDouble) +{ + EXPECT_DOUBLE_EQ(rational(1, 2).toDouble(), 0.5); + EXPECT_TRUE(std::isnan(rational::NaN.toDouble())); +} + +TEST(CoreRational, ToString) +{ + EXPECT_EQ(rational(1, 2).toString(), "1/2"); +} + +TEST(CoreRational, ToAVRational) +{ + AVRational av = rational(3, 4).toAVRational(); + EXPECT_EQ(av.num, 3); + EXPECT_EQ(av.den, 4); +} + +TEST(CoreRational, Arithmetic) +{ + rational a(1, 2); + rational b(1, 3); + + EXPECT_EQ(a + b, rational(5, 6)); + EXPECT_EQ(a - b, rational(1, 6)); + EXPECT_EQ(a * b, rational(1, 6)); + EXPECT_EQ(a / b, rational(3, 2)); +} + +TEST(CoreRational, CompoundAssignment) +{ + rational a(1, 2); + a += rational(1, 4); + EXPECT_EQ(a, rational(3, 4)); + + a *= rational(2, 3); + EXPECT_EQ(a, rational(1, 2)); +} + +TEST(CoreRational, Comparisons) +{ + rational a(1, 2); + rational b(2, 4); + rational c(1, 3); + + EXPECT_TRUE(a == b); + EXPECT_FALSE(a != b); + EXPECT_TRUE(a > c); + EXPECT_TRUE(c < a); + EXPECT_TRUE(a >= b); + EXPECT_TRUE(c <= a); +} + +TEST(CoreRational, UnaryOperators) +{ + rational a(1, 2); + EXPECT_EQ(+a, a); + EXPECT_EQ(-a, rational(-1, 2)); + EXPECT_FALSE(!a); + + rational zero(0); + EXPECT_TRUE(!zero); +} + +TEST(CoreRational, Flip) +{ + rational a(2, 3); + a.flip(); + EXPECT_EQ(a, rational(3, 2)); + + rational zero(0); + zero.flip(); + EXPECT_EQ(zero, rational(0)); +} + +TEST(CoreRational, Flipped) +{ + EXPECT_EQ(rational(2, 3).flipped(), rational(3, 2)); +} + +TEST(CoreRational, IsNullAndIsNaN) +{ + rational zero(0); + EXPECT_TRUE(zero.isNull()); + EXPECT_FALSE(zero.isNaN()); + + rational nan = rational::NaN; + EXPECT_TRUE(nan.isNaN()); + EXPECT_TRUE(nan.isNull()); +} + +TEST(CoreRational, NaNPropagation) +{ + rational a(1, 2); + rational nan = rational::NaN; + + a += nan; + EXPECT_TRUE(a.isNaN()); +} + +TEST(CoreRational, StreamOutput) +{ + std::ostringstream oss; + oss << rational(3, 4); + EXPECT_EQ(oss.str(), "3/4"); +} + +TEST(CoreRational, MinMaxConstants) +{ + EXPECT_TRUE(RATIONAL_MIN < rational(0)); + EXPECT_TRUE(RATIONAL_MAX > rational(0)); +} diff --git a/tests/gtest/core_samplebuffer_test.cpp b/tests/gtest/core_samplebuffer_test.cpp new file mode 100644 index 000000000..57116e4b0 --- /dev/null +++ b/tests/gtest/core_samplebuffer_test.cpp @@ -0,0 +1,262 @@ +#include + +#include "olive/core/render/samplebuffer.h" + +using namespace olive::core; + +static AudioParams MakeParams(int channels = 2, int sample_rate = 48000) +{ + AudioParams params(sample_rate, AV_CH_LAYOUT_STEREO, SampleFormat::F32P); + return params; +} + +TEST(CoreSampleBuffer, DefaultConstruction) +{ + SampleBuffer b; + EXPECT_FALSE(b.is_allocated()); + EXPECT_EQ(b.channel_count(), 0); + EXPECT_EQ(b.sample_count(), 0u); +} + +TEST(CoreSampleBuffer, AllocateByLength) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, rational(1, 24)); + EXPECT_TRUE(b.is_allocated()); + EXPECT_EQ(b.channel_count(), 2); + EXPECT_EQ(b.sample_count(), 2000u); +} + +TEST(CoreSampleBuffer, AllocateBySampleCount) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 1024); + EXPECT_TRUE(b.is_allocated()); + EXPECT_EQ(b.sample_count(), 1024u); +} + +TEST(CoreSampleBuffer, AllocateInvalidParams) +{ + AudioParams params; + SampleBuffer b(params, 100); + EXPECT_FALSE(b.is_allocated()); +} + +TEST(CoreSampleBuffer, AllocateZeroSampleCount) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 0); + EXPECT_FALSE(b.is_allocated()); +} + +TEST(CoreSampleBuffer, Destroy) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 100); + EXPECT_TRUE(b.is_allocated()); + b.destroy(); + EXPECT_FALSE(b.is_allocated()); +} + +TEST(CoreSampleBuffer, SetAudioParamsBeforeAllocate) +{ + AudioParams params = MakeParams(); + SampleBuffer b; + b.set_audio_params(params); + b.set_sample_count(100); + b.allocate(); + EXPECT_TRUE(b.is_allocated()); +} + +TEST(CoreSampleBuffer, SetParamsOnAllocatedIsIgnored) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 100); + AudioParams other; + b.set_audio_params(other); + EXPECT_EQ(b.audio_params().sample_rate(), params.sample_rate()); +} + +TEST(CoreSampleBuffer, SetSampleCountOnAllocatedIsIgnored) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 100); + b.set_sample_count(200); + EXPECT_EQ(b.sample_count(), 100u); +} + +TEST(CoreSampleBuffer, DataAccess) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.1f; + b.data(0)[1] = 0.2f; + EXPECT_FLOAT_EQ(b.data(0)[0], 0.1f); + EXPECT_FLOAT_EQ(b.data(0)[1], 0.2f); +} + +TEST(CoreSampleBuffer, ToRawPtrs) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + std::vector ptrs = b.to_raw_ptrs(); + EXPECT_EQ(ptrs.size(), 2u); + EXPECT_NE(ptrs[0], nullptr); + EXPECT_NE(ptrs[1], nullptr); +} + +TEST(CoreSampleBuffer, RipChannel) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.5f; + b.data(1)[0] = 0.7f; + + SampleBuffer mono = b.rip_channel(1); + EXPECT_EQ(mono.channel_count(), 1); + EXPECT_FLOAT_EQ(mono.data(0)[0], 0.7f); +} + +TEST(CoreSampleBuffer, RipChannelVector) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.3f; + + std::vector v = b.rip_channel_vector(0); + EXPECT_EQ(v.size(), 4u); + EXPECT_FLOAT_EQ(v[0], 0.3f); +} + +TEST(CoreSampleBuffer, TransformVolume) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.5f; + b.transform_volume(2.0f); + EXPECT_FLOAT_EQ(b.data(0)[0], 1.0f); +} + +TEST(CoreSampleBuffer, TransformVolumeForChannel) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.5f; + b.data(1)[0] = 0.5f; + b.transform_volume_for_channel(1, 3.0f); + EXPECT_FLOAT_EQ(b.data(0)[0], 0.5f); + EXPECT_FLOAT_EQ(b.data(1)[0], 1.5f); +} + +TEST(CoreSampleBuffer, TransformVolumeStatic) +{ + AudioParams params = MakeParams(); + SampleBuffer in(params, 4); + SampleBuffer out(params, 4); + in.data(0)[0] = 0.5f; + SampleBuffer::transform_volume(2.0f, &in, &out); + EXPECT_FLOAT_EQ(out.data(0)[0], 1.0f); +} + +TEST(CoreSampleBuffer, TransformVolumeForSample) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.5f; + b.data(1)[0] = 0.5f; + b.transform_volume_for_sample(0, 2.0f); + EXPECT_FLOAT_EQ(b.data(0)[0], 1.0f); + EXPECT_FLOAT_EQ(b.data(1)[0], 1.0f); +} + +TEST(CoreSampleBuffer, Clamp) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 2.0f; + b.data(0)[1] = -2.0f; + b.clamp(); + EXPECT_FLOAT_EQ(b.data(0)[0], 1.0f); + EXPECT_FLOAT_EQ(b.data(0)[1], -1.0f); +} + +TEST(CoreSampleBuffer, Silence) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 1.0f; + b.silence(); + EXPECT_FLOAT_EQ(b.data(0)[0], 0.0f); +} + +TEST(CoreSampleBuffer, SilenceRange) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 1.0f; + b.data(0)[1] = 1.0f; + b.data(0)[2] = 1.0f; + b.data(0)[3] = 1.0f; + b.silence(1, 3); + EXPECT_FLOAT_EQ(b.data(0)[0], 1.0f); + EXPECT_FLOAT_EQ(b.data(0)[1], 0.0f); + EXPECT_FLOAT_EQ(b.data(0)[2], 0.0f); + EXPECT_FLOAT_EQ(b.data(0)[3], 1.0f); +} + +TEST(CoreSampleBuffer, Set) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + float data[2] = {0.3f, 0.4f}; + b.set(0, data, 1, 2); + EXPECT_FLOAT_EQ(b.data(0)[1], 0.3f); + EXPECT_FLOAT_EQ(b.data(0)[2], 0.4f); +} + +TEST(CoreSampleBuffer, FastSet) +{ + AudioParams params = MakeParams(); + SampleBuffer src(params, 4); + SampleBuffer dst(params, 4); + src.data(1)[0] = 0.9f; + dst.fast_set(src, 0, 1); + EXPECT_FLOAT_EQ(dst.data(0)[0], 0.9f); +} + +TEST(CoreSampleBuffer, Reverse) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.1f; + b.data(0)[1] = 0.2f; + b.data(0)[2] = 0.3f; + b.data(0)[3] = 0.4f; + b.reverse(); + EXPECT_FLOAT_EQ(b.data(0)[0], 0.4f); + EXPECT_FLOAT_EQ(b.data(0)[3], 0.1f); +} + +TEST(CoreSampleBuffer, Speed) +{ + AudioParams params = MakeParams(); + SampleBuffer b(params, 4); + b.data(0)[0] = 0.1f; + b.data(0)[1] = 0.2f; + b.data(0)[2] = 0.3f; + b.data(0)[3] = 0.4f; + b.speed(2.0); + EXPECT_EQ(b.sample_count(), 2u); + EXPECT_FLOAT_EQ(b.data(0)[0], 0.1f); + EXPECT_FLOAT_EQ(b.data(0)[1], 0.3f); +} + +TEST(CoreSampleBuffer, UnallocatedOperationsNoCrash) +{ + SampleBuffer b; + b.reverse(); + b.speed(2.0); + b.silence(); + b.set(0, nullptr, 0, 0); + b.destroy(); +}