Expand Google Test coverage across core subsystems

Add and extend unit tests for:
- Task manager (failed tasks, progress signals, multiple tasks)
- Undo stack (multiple undo/redo, push-after-undo, clear, multi-command)
- Config defaults and graphics backend string conversion
- Node keyframe serialization and default state
- Node value string round-trips and table operations
- Node globals and project settings/serialization
- Timeline coordinate, workarea, and marker commands
- Color LUT processor and OCIO transform handling
- Common utilities (range, file functions, Qt helpers, XML utils)
- Render enums (loop mode, alpha association)
- Shader resource availability
- Module smoke tests for Tool and HumanStrings

All tests pass including RenderWorkerFootage GPU worker tests after
rebuilding the stale olive-render-worker binary.
This commit is contained in:
2026-07-13 10:19:30 +08:00
parent b7ff687220
commit 748c53ac5c
20 changed files with 1075 additions and 11 deletions
+7
View File
@@ -2,6 +2,9 @@ add_executable(olive-gtest
main.cpp
common_current_test.cpp
common_xmlutils_test.cpp
common_range_test.cpp
common_filefunctions_test.cpp
common_qtutils_test.cpp
config_test.cpp
audio_level_meter_test.cpp
audio_synchronizer_test.cpp
@@ -10,12 +13,16 @@ add_executable(olive-gtest
node_value_test.cpp
node_keyframe_test.cpp
node_serialization_test.cpp
node_globals_test.cpp
node_project_test.cpp
render_videoparams_test.cpp
render_videoparams_branch_test.cpp
render_audioparams_test.cpp
render_audioparams_branch_test.cpp
render_sampleformat_test.cpp
render_pixelformat_test.cpp
render_loopmode_test.cpp
render_alphaassoc_test.cpp
render_ipc_test.cpp
render_ticket_test.cpp
render_worker_footage_test.cpp
+43
View File
@@ -171,6 +171,49 @@ TEST(ColorLut, OcioSupportsCubeAnd3dlExtensions)
EXPECT_FALSE(IsOakSupportedLutExtension("txt"));
}
TEST(ColorProcessor, CreateFromInvalidTransformReturnsNull)
{
olive::ColorManager::SetUpDefaultConfig();
OCIO::FileTransformRcPtr transform = OCIO::FileTransform::Create();
transform->setSrc("/nonexistent/lut.cube");
transform->setInterpolation(OCIO::INTERP_LINEAR);
transform->setDirection(OCIO::TRANSFORM_DIR_FORWARD);
olive::ColorProcessorPtr processor;
EXPECT_NO_THROW({
try {
processor = olive::ColorProcessor::Create(
olive::ColorManager::GetDefaultConfig()->getProcessor(
transform));
} catch (const std::exception &e) {
processor = nullptr;
}
});
EXPECT_EQ(processor, nullptr);
}
TEST(ColorProcessor, ConvertColorWithIdentityProcessor)
{
olive::ColorManager::SetUpDefaultConfig();
OCIO::MatrixTransformRcPtr transform = OCIO::MatrixTransform::Create();
transform->setDirection(OCIO::TRANSFORM_DIR_FORWARD);
olive::ColorProcessorPtr processor = olive::ColorProcessor::Create(
olive::ColorManager::GetDefaultConfig()->getProcessor(transform));
ASSERT_NE(processor, nullptr);
const olive::Color in(0.25f, 0.50f, 0.75f, 1.0f);
const olive::Color out = processor->ConvertColor(in);
EXPECT_NEAR(out.red(), in.red(), 0.001f);
EXPECT_NEAR(out.green(), in.green(), 0.001f);
EXPECT_NEAR(out.blue(), in.blue(), 0.001f);
EXPECT_NEAR(out.alpha(), in.alpha(), 0.001f);
}
TEST(ColorLut, CubeFileTransformConvertsColor)
{
QTemporaryDir dir;
+152
View File
@@ -0,0 +1,152 @@
#include <gtest/gtest.h>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include "common/filefunctions.h"
TEST(CommonFileFunctions, EnsureFilenameExtension)
{
EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension(
QStringLiteral("project"), QStringLiteral("ove")),
QStringLiteral("project.ove"));
EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension(
QStringLiteral("project.ove"), QStringLiteral("ove")),
QStringLiteral("project.ove"));
EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension(
QStringLiteral("PROJECT"), QStringLiteral("ove")),
QStringLiteral("PROJECT.ove"));
EXPECT_TRUE(olive::FileFunctions::EnsureFilenameExtension(QString(), QStringLiteral("ove")).isEmpty());
EXPECT_EQ(olive::FileFunctions::EnsureFilenameExtension(
QStringLiteral("project"), QString()),
QStringLiteral("project"));
}
TEST(CommonFileFunctions, GetSafeTemporaryFilename)
{
QTemporaryDir dir;
ASSERT_TRUE(dir.isValid());
QString base = dir.filePath(QStringLiteral("test.ove"));
QString first = olive::FileFunctions::GetSafeTemporaryFilename(base);
EXPECT_FALSE(QFileInfo::exists(first));
EXPECT_TRUE(first.contains(QStringLiteral(".tmp0.")));
QFile f(first);
f.open(QIODevice::WriteOnly);
f.close();
QString second = olive::FileFunctions::GetSafeTemporaryFilename(base);
EXPECT_NE(first, second);
EXPECT_TRUE(second.contains(QStringLiteral(".tmp1.")));
}
TEST(CommonFileFunctions, DirectoryIsValid)
{
QTemporaryDir dir;
ASSERT_TRUE(dir.isValid());
EXPECT_TRUE(olive::FileFunctions::DirectoryIsValid(QDir(dir.path()), false));
QDir nonexistent(dir.filePath(QStringLiteral("subdir/nested")));
EXPECT_TRUE(olive::FileFunctions::DirectoryIsValid(nonexistent, true));
EXPECT_TRUE(nonexistent.exists());
}
TEST(CommonFileFunctions, RenameFileAllowOverwrite)
{
QTemporaryDir dir;
ASSERT_TRUE(dir.isValid());
QString from = dir.filePath(QStringLiteral("from.txt"));
QString to = dir.filePath(QStringLiteral("to.txt"));
QFile f(from);
f.open(QIODevice::WriteOnly);
f.write("source");
f.close();
QFile t(to);
t.open(QIODevice::WriteOnly);
t.write("existing");
t.close();
EXPECT_TRUE(olive::FileFunctions::RenameFileAllowOverwrite(from, to));
EXPECT_FALSE(QFileInfo::exists(from));
QFile result(to);
result.open(QIODevice::ReadOnly);
EXPECT_EQ(result.readAll(), QByteArray("source"));
}
TEST(CommonFileFunctions, CanCopyDirectoryWithoutOverwriting)
{
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.close();
EXPECT_TRUE(olive::FileFunctions::CanCopyDirectoryWithoutOverwriting(
src.path(), dst.path()));
QString dst_file = QDir(dst.path()).filePath(QStringLiteral("file.txt"));
QFile g(dst_file);
g.open(QIODevice::WriteOnly);
g.close();
EXPECT_FALSE(olive::FileFunctions::CanCopyDirectoryWithoutOverwriting(
src.path(), dst.path()));
}
TEST(CommonFileFunctions, CopyDirectory)
{
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("copied");
f.close();
QString dst_dir = QDir(dst.path()).filePath(QStringLiteral("copied"));
olive::FileFunctions::CopyDirectory(src.path(), dst_dir, false);
QFile result(QDir(dst_dir).filePath(QStringLiteral("file.txt")));
EXPECT_TRUE(result.open(QIODevice::ReadOnly));
EXPECT_EQ(result.readAll(), QByteArray("copied"));
}
TEST(CommonFileFunctions, ReadFileAsString)
{
QTemporaryFile f;
ASSERT_TRUE(f.open());
f.write("hello world");
f.close();
EXPECT_EQ(olive::FileFunctions::ReadFileAsString(f.fileName()),
QStringLiteral("hello world"));
EXPECT_TRUE(olive::FileFunctions::ReadFileAsString(
QStringLiteral("/nonexistent/path")).isEmpty());
}
TEST(CommonFileFunctions, GetUniqueFileIdentifier)
{
QTemporaryFile f;
ASSERT_TRUE(f.open());
f.close();
QString id1 = olive::FileFunctions::GetUniqueFileIdentifier(f.fileName());
QString id2 = olive::FileFunctions::GetUniqueFileIdentifier(f.fileName());
EXPECT_FALSE(id1.isEmpty());
EXPECT_EQ(id1, id2);
EXPECT_TRUE(olive::FileFunctions::GetUniqueFileIdentifier(
QStringLiteral("/nonexistent")).isEmpty());
}
+113
View File
@@ -0,0 +1,113 @@
#include <gtest/gtest.h>
#include <QComboBox>
#include <QFontMetrics>
#include <QFont>
#include <QLabel>
#include "common/qtutils.h"
TEST(CommonQtUtils, PtrToValueAndBack)
{
int value = 42;
void *ptr = &value;
QVariant v = olive::QtUtils::PtrToValue(ptr);
EXPECT_EQ(olive::QtUtils::ValueToPtr<int>(v), &value);
}
TEST(CommonQtUtils, GetParentOfType)
{
QWidget root;
QLabel *child = new QLabel(&root);
EXPECT_EQ(olive::QtUtils::GetParentOfType<QLabel>(child), nullptr);
EXPECT_EQ(olive::QtUtils::GetParentOfType<QWidget>(child), &root);
}
TEST(CommonQtUtils, FlipControlAndShiftModifiers)
{
// NOTE: The early return for "both modifiers present" uses a broken condition
// (Qt::ControlModifier & Qt::ShiftModifier is always zero), so the function
// always swaps Control and Shift. This test documents current behavior.
Qt::KeyboardModifiers both = Qt::ControlModifier | Qt::ShiftModifier;
Qt::KeyboardModifiers flipped = olive::QtUtils::FlipControlAndShiftModifiers(both);
EXPECT_TRUE(flipped & Qt::ControlModifier);
EXPECT_FALSE(flipped & Qt::ShiftModifier);
Qt::KeyboardModifiers only_shift = Qt::ShiftModifier | Qt::AltModifier;
flipped = olive::QtUtils::FlipControlAndShiftModifiers(only_shift);
EXPECT_TRUE(flipped & Qt::ControlModifier);
EXPECT_FALSE(flipped & Qt::ShiftModifier);
EXPECT_TRUE(flipped & Qt::AltModifier);
Qt::KeyboardModifiers only_ctrl = Qt::ControlModifier | Qt::AltModifier;
flipped = olive::QtUtils::FlipControlAndShiftModifiers(only_ctrl);
EXPECT_FALSE(flipped & Qt::ControlModifier);
EXPECT_TRUE(flipped & Qt::ShiftModifier);
EXPECT_TRUE(flipped & Qt::AltModifier);
Qt::KeyboardModifiers none;
EXPECT_EQ(olive::QtUtils::FlipControlAndShiftModifiers(none), none);
}
TEST(CommonQtUtils, SetComboBoxDataByInt)
{
QComboBox cb;
cb.addItem(QStringLiteral("A"), 1);
cb.addItem(QStringLiteral("B"), 2);
cb.addItem(QStringLiteral("C"), 3);
olive::QtUtils::SetComboBoxData(&cb, 2);
EXPECT_EQ(cb.currentData().toInt(), 2);
EXPECT_EQ(cb.currentText(), QStringLiteral("B"));
olive::QtUtils::SetComboBoxData(&cb, 42);
EXPECT_EQ(cb.currentData().toInt(), 2);
}
TEST(CommonQtUtils, SetComboBoxDataByString)
{
QComboBox cb;
cb.addItem(QStringLiteral("A"), QStringLiteral("alpha"));
cb.addItem(QStringLiteral("B"), QStringLiteral("beta"));
olive::QtUtils::SetComboBoxData(&cb, QStringLiteral("beta"));
EXPECT_EQ(cb.currentData().toString(), QStringLiteral("beta"));
olive::QtUtils::SetComboBoxData(&cb, QStringLiteral("missing"));
EXPECT_EQ(cb.currentData().toString(), QStringLiteral("beta"));
}
TEST(CommonQtUtils, QFontMetricsWidth)
{
QFont font;
QFontMetrics fm(font);
int width = olive::QtUtils::QFontMetricsWidth(fm, QStringLiteral("Olive"));
EXPECT_GT(width, 0);
}
TEST(CommonQtUtils, CreateHorizontalLine)
{
QFrame *line = olive::QtUtils::CreateHorizontalLine();
ASSERT_NE(line, nullptr);
EXPECT_EQ(line->frameShape(), QFrame::HLine);
delete line;
}
TEST(CommonQtUtils, CreateVerticalLine)
{
QFrame *line = olive::QtUtils::CreateVerticalLine();
ASSERT_NE(line, nullptr);
EXPECT_EQ(line->frameShape(), QFrame::VLine);
delete line;
}
TEST(CommonQtUtils, ToQColor)
{
olive::core::Color c(0.1f, 0.2f, 0.3f, 0.4f);
QColor qc = olive::QtUtils::toQColor(c);
EXPECT_NEAR(qc.redF(), 0.1, 0.001);
EXPECT_NEAR(qc.greenF(), 0.2, 0.001);
EXPECT_NEAR(qc.blueF(), 0.3, 0.001);
EXPECT_NEAR(qc.alphaF(), 0.4, 0.001);
}
+26
View File
@@ -0,0 +1,26 @@
#include <gtest/gtest.h>
#include "common/range.h"
TEST(CommonRange, InRangeExact)
{
EXPECT_TRUE(InRange(5, 5, 0));
}
TEST(CommonRange, InRangeWithinTolerance)
{
EXPECT_TRUE(InRange(5.0, 5.5, 1.0));
EXPECT_TRUE(InRange(5.0, 4.5, 1.0));
}
TEST(CommonRange, OutOfRange)
{
EXPECT_FALSE(InRange(5.0, 7.0, 1.0));
EXPECT_FALSE(InRange(5.0, 3.0, 1.0));
}
TEST(CommonRange, BoundaryValues)
{
EXPECT_TRUE(InRange(5.0, 6.0, 1.0));
EXPECT_TRUE(InRange(5.0, 4.0, 1.0));
}
+40
View File
@@ -18,3 +18,43 @@ TEST(CommonXmlUtils, ReadNextStartElement)
EXPECT_TRUE(olive::XMLReadNextStartElement(&reader));
EXPECT_EQ(reader.name().toString(), QStringLiteral("child"));
}
TEST(CommonXmlUtils, ReadNextStartElementSkipsWhitespace)
{
QByteArray xml = "\n \n<root>\n\n<child/>\n</root>";
QBuffer buffer(&xml);
buffer.open(QIODevice::ReadOnly);
QXmlStreamReader reader(&buffer);
EXPECT_TRUE(olive::XMLReadNextStartElement(&reader));
EXPECT_EQ(reader.name().toString(), QStringLiteral("root"));
EXPECT_TRUE(olive::XMLReadNextStartElement(&reader));
EXPECT_EQ(reader.name().toString(), QStringLiteral("child"));
}
TEST(CommonXmlUtils, ReadNextStartElementReturnsFalseAtEnd)
{
QByteArray xml = "<root/>";
QBuffer buffer(&xml);
buffer.open(QIODevice::ReadOnly);
QXmlStreamReader reader(&buffer);
EXPECT_TRUE(olive::XMLReadNextStartElement(&reader));
EXPECT_EQ(reader.name().toString(), QStringLiteral("root"));
EXPECT_FALSE(olive::XMLReadNextStartElement(&reader));
}
TEST(CommonXmlUtils, ReadNextStartElementSkipsUnknown)
{
QByteArray xml = "<root><unknown/><known/></root>";
QBuffer buffer(&xml);
buffer.open(QIODevice::ReadOnly);
QXmlStreamReader reader(&buffer);
EXPECT_TRUE(olive::XMLReadNextStartElement(&reader));
EXPECT_TRUE(olive::XMLReadNextStartElement(&reader));
EXPECT_EQ(reader.name().toString(), QStringLiteral("unknown"));
reader.skipCurrentElement();
EXPECT_TRUE(olive::XMLReadNextStartElement(&reader));
EXPECT_EQ(reader.name().toString(), QStringLiteral("known"));
}
+47
View File
@@ -20,6 +20,22 @@ TEST(Config, SetAndGetValues)
olive::Config &cfg = olive::Config::Current();
cfg[QStringLiteral("UnitTestValue")] = 42;
EXPECT_EQ(cfg[QStringLiteral("UnitTestValue")].toInt(), 42);
cfg[QStringLiteral("UnitTestString")] = QStringLiteral("hello");
EXPECT_EQ(cfg[QStringLiteral("UnitTestString")].toString(),
QStringLiteral("hello"));
cfg[QStringLiteral("UnitTestBool")] = true;
EXPECT_TRUE(cfg[QStringLiteral("UnitTestBool")].toBool());
cfg[QStringLiteral("UnitTestDouble")] = 3.14;
EXPECT_NEAR(cfg[QStringLiteral("UnitTestDouble")].toDouble(), 3.14, 0.001);
}
TEST(Config, MissingKeyReturnsInvalidVariant)
{
olive::Config &cfg = olive::Config::Current();
EXPECT_FALSE(cfg[QStringLiteral("DefinitelyMissingKey")].isValid());
}
TEST(Config, GraphicsBackendStringConversion)
@@ -28,12 +44,43 @@ TEST(Config, GraphicsBackendStringConversion)
olive::RenderManager::kOpenGL);
EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("vulkan")),
olive::RenderManager::kVulkan);
EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("dummy")),
olive::RenderManager::kDummy);
EXPECT_EQ(olive::RenderManager::BackendFromString(
QStringLiteral("multiprocess")),
olive::RenderManager::kMultiProcess);
EXPECT_EQ(olive::RenderManager::BackendToString(
olive::RenderManager::kOpenGL),
QStringLiteral("opengl"));
EXPECT_EQ(olive::RenderManager::BackendToString(
olive::RenderManager::kVulkan),
QStringLiteral("vulkan"));
EXPECT_EQ(olive::RenderManager::BackendToString(
olive::RenderManager::kDummy),
QStringLiteral("dummy"));
EXPECT_EQ(olive::RenderManager::BackendToString(
olive::RenderManager::kMultiProcess),
QStringLiteral("multiprocess"));
EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("bad")),
olive::RenderManager::kOpenGL);
}
TEST(Config, SetDefaultsPopulatesRequiredKeys)
{
olive::Config &cfg = olive::Config::Current();
cfg.SetDefaults();
const QStringList required = {
QStringLiteral("Style"),
QStringLiteral("TimecodeDisplay"),
QStringLiteral("DefaultStillLength"),
QStringLiteral("GraphicsBackend"),
QStringLiteral("AutoCacheDelay"),
QStringLiteral("AudioOutput"),
QStringLiteral("AudioInput"),
};
foreach (const QString &key, required) {
EXPECT_TRUE(cfg[key].isValid()) << key.toStdString();
}
}
+37 -8
View File
@@ -1,15 +1,44 @@
#include <gtest/gtest.h>
#include "audio/audiomanager.h"
#include "cli/cliexport/cliexportmanager.h"
#include "dialog/progress/progress.h"
#include "panel/panelmanager.h"
#include "tool/tool.h"
#include "ui/humanstrings.h"
#include "widget/nodeview/nodeview.h"
#include "window/mainwindow/mainwindow.h"
TEST(ModuleSmoke, HeadersBuild)
TEST(ModuleSmoke, ToolAddableObjectNames)
{
SUCCEED();
EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableEmpty).isEmpty());
EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableBars).isEmpty());
EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableShape).isEmpty());
EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableSolid).isEmpty());
EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableTitle).isEmpty());
EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableTone).isEmpty());
EXPECT_FALSE(olive::Tool::GetAddableObjectName(olive::Tool::kAddableSubtitle).isEmpty());
}
TEST(ModuleSmoke, ToolAddableObjectIds)
{
EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableEmpty), QStringLiteral("empty"));
EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableBars), QStringLiteral("bars"));
EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableShape), QStringLiteral("shape"));
EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableSolid), QStringLiteral("solid"));
EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableTitle), QStringLiteral("title"));
EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableTone), QStringLiteral("tone"));
EXPECT_EQ(olive::Tool::GetAddableObjectID(olive::Tool::kAddableSubtitle), QStringLiteral("subtitle"));
}
TEST(ModuleSmoke, HumanStringsSampleRate)
{
EXPECT_FALSE(olive::HumanStrings::SampleRateToString(48000).isEmpty());
EXPECT_FALSE(olive::HumanStrings::SampleRateToString(44100).isEmpty());
}
TEST(ModuleSmoke, HumanStringsChannelLayout)
{
EXPECT_FALSE(olive::HumanStrings::ChannelLayoutToString(AV_CH_LAYOUT_MONO).isEmpty());
EXPECT_FALSE(olive::HumanStrings::ChannelLayoutToString(AV_CH_LAYOUT_STEREO).isEmpty());
}
TEST(ModuleSmoke, HumanStringsFormat)
{
EXPECT_FALSE(olive::HumanStrings::FormatToString(olive::SampleFormat::U8).isEmpty());
EXPECT_FALSE(olive::HumanStrings::FormatToString(olive::SampleFormat::F32).isEmpty());
}
+46
View File
@@ -0,0 +1,46 @@
#include <gtest/gtest.h>
#include "node/globals.h"
#include "render/videoparams.h"
#include "render/loopmode.h"
TEST(NodeGlobals, DefaultConstruction)
{
olive::NodeGlobals globals;
EXPECT_EQ(globals.vparams().width(), 0);
EXPECT_EQ(globals.vparams().height(), 0);
EXPECT_EQ(globals.aparams().sample_rate(), 0);
}
TEST(NodeGlobals, ConstructedWithParams)
{
olive::VideoParams video_params(1920, 1080, olive::PixelFormat::F32, 4);
olive::AudioParams audio_params;
audio_params.set_sample_rate(48000);
audio_params.set_channel_layout(AV_CH_LAYOUT_STEREO);
olive::TimeRange time(olive::core::rational(1, 24), olive::core::rational(2, 24));
olive::NodeGlobals globals(video_params, audio_params, time, olive::LoopMode::kLoopModeLoop);
EXPECT_EQ(globals.vparams().width(), 1920);
EXPECT_EQ(globals.vparams().height(), 1080);
EXPECT_EQ(globals.aparams().sample_rate(), 48000);
EXPECT_EQ(globals.loop_mode(), olive::LoopMode::kLoopModeLoop);
EXPECT_EQ(globals.time().in(), olive::core::rational(1, 24));
EXPECT_EQ(globals.time().out(), olive::core::rational(2, 24));
}
TEST(NodeGlobals, RationalConstructorExpandsToFrame)
{
olive::VideoParams video_params(1280, 720, olive::PixelFormat::F32, 4);
video_params.set_frame_rate(24);
olive::AudioParams audio_params;
audio_params.set_sample_rate(44100);
olive::NodeGlobals globals(video_params, audio_params,
olive::core::rational(0, 1),
olive::LoopMode::kLoopModeClamp);
EXPECT_EQ(globals.time().in(), olive::core::rational(0, 1));
EXPECT_EQ(globals.time().out(), olive::core::rational(1, 24));
}
+37
View File
@@ -45,3 +45,40 @@ TEST(NodeKeyframe, SaveLoadRoundTrip)
EXPECT_DOUBLE_EQ(loaded.bezier_control_out().x(), 0.3);
EXPECT_DOUBLE_EQ(loaded.bezier_control_out().y(), 0.4);
}
TEST(NodeKeyframe, TypeEnumeration)
{
using olive::NodeKeyframe;
EXPECT_NE(NodeKeyframe::kLinear, NodeKeyframe::kHold);
EXPECT_NE(NodeKeyframe::kLinear, NodeKeyframe::kBezier);
}
TEST(NodeKeyframe, DefaultState)
{
olive::NodeKeyframe key;
EXPECT_TRUE(key.input().isEmpty());
EXPECT_EQ(key.time(), olive::core::rational(0, 1));
EXPECT_EQ(key.type(), olive::NodeKeyframe::kLinear);
EXPECT_TRUE(key.value().isNull());
}
TEST(NodeKeyframe, SetValueRoundTrip)
{
olive::NodeKeyframe key;
key.set_value(QVariant::fromValue(olive::Color(0.1f, 0.2f, 0.3f, 1.0f)));
const olive::Color c = key.value().value<olive::Color>();
EXPECT_FLOAT_EQ(c.red(), 0.1f);
EXPECT_FLOAT_EQ(c.green(), 0.2f);
EXPECT_FLOAT_EQ(c.blue(), 0.3f);
}
TEST(NodeKeyframe, BezierControlDefaults)
{
olive::NodeKeyframe key;
EXPECT_DOUBLE_EQ(key.bezier_control_in().x(), 0.0);
EXPECT_DOUBLE_EQ(key.bezier_control_in().y(), 0.0);
EXPECT_DOUBLE_EQ(key.bezier_control_out().x(), 0.0);
EXPECT_DOUBLE_EQ(key.bezier_control_out().y(), 0.0);
}
+109
View File
@@ -0,0 +1,109 @@
#include <gtest/gtest.h>
#include "node/project.h"
TEST(NodeProject, DefaultsAfterConstruction)
{
olive::Project project;
EXPECT_EQ(project.filename(), QString());
EXPECT_EQ(project.name(), QStringLiteral("(untitled)"));
EXPECT_EQ(project.pretty_filename(), QStringLiteral("(untitled)"));
EXPECT_TRUE(project.is_new());
EXPECT_FALSE(project.is_modified());
EXPECT_TRUE(project.has_autorecovery_been_saved());
EXPECT_FALSE(project.GetUuid().isNull());
EXPECT_NE(project.color_manager(), nullptr);
EXPECT_EQ(project.root(), nullptr);
EXPECT_TRUE(project.nodes().isEmpty());
}
TEST(NodeProject, FilenameAndNameUpdate)
{
olive::Project project;
project.set_filename(QStringLiteral("/tmp/test_project.ove"));
EXPECT_EQ(project.filename(), QStringLiteral("/tmp/test_project.ove"));
EXPECT_EQ(project.name(), QStringLiteral("test_project"));
EXPECT_EQ(project.pretty_filename(), QStringLiteral("/tmp/test_project.ove"));
EXPECT_FALSE(project.is_new());
}
TEST(NodeProject, ModifiedState)
{
olive::Project project;
project.set_modified(true);
EXPECT_TRUE(project.is_modified());
EXPECT_FALSE(project.has_autorecovery_been_saved());
project.set_modified(false);
EXPECT_FALSE(project.is_modified());
EXPECT_TRUE(project.has_autorecovery_been_saved());
}
TEST(NodeProject, SettingsRoundTrip)
{
olive::Project project;
project.SetSetting(olive::Project::kCacheLocationSettingKey,
QString::number(olive::Project::kCacheStoreAlongsideProject));
EXPECT_EQ(project.GetCacheLocationSetting(),
olive::Project::kCacheStoreAlongsideProject);
project.SetCustomCachePath(QStringLiteral("/tmp/cache"));
EXPECT_EQ(project.GetCustomCachePath(), QStringLiteral("/tmp/cache"));
project.SetColorConfigFilename(QStringLiteral("config.ocio"));
EXPECT_EQ(project.GetColorConfigFilename(), QStringLiteral("config.ocio"));
project.SetDefaultInputColorSpace(QStringLiteral("ACEScg"));
EXPECT_EQ(project.GetDefaultInputColorSpace(), QStringLiteral("ACEScg"));
project.SetColorReferenceSpace(QStringLiteral("ACES - ACEScg"));
EXPECT_EQ(project.GetColorReferenceSpace(), QStringLiteral("ACES - ACEScg"));
}
TEST(NodeProject, InitializeCreatesRoot)
{
olive::Project project;
EXPECT_EQ(project.root(), nullptr);
project.Initialize();
EXPECT_NE(project.root(), nullptr);
EXPECT_EQ(project.root()->GetLabel(), QStringLiteral("Root"));
}
TEST(NodeProject, UuidCanBeRegenerated)
{
olive::Project project;
const QUuid original = project.GetUuid();
project.RegenerateUuid();
EXPECT_FALSE(project.GetUuid().isNull());
EXPECT_NE(project.GetUuid(), original);
}
TEST(NodeProject, GetProjectFromObject)
{
olive::Project project;
olive::ColorManager *cm = project.color_manager();
EXPECT_EQ(olive::Project::GetProjectFromObject(cm), &project);
}
TEST(NodeProject, SaveProducesXml)
{
olive::Project project;
project.Initialize();
QByteArray xml;
QXmlStreamWriter writer(&xml);
writer.writeStartDocument();
writer.writeStartElement(QStringLiteral("project"));
project.Save(&writer);
writer.writeEndElement();
writer.writeEndDocument();
EXPECT_FALSE(xml.isEmpty());
EXPECT_TRUE(xml.contains("uuid"));
}
+77
View File
@@ -48,3 +48,80 @@ TEST(NodeValue, BinaryRoundTrip)
olive::NodeValue::kBinary, encoded, false);
EXPECT_EQ(decoded.toByteArray(), data);
}
TEST(NodeValue, TypeClassification)
{
EXPECT_TRUE(olive::NodeValue::type_can_be_interpolated(olive::NodeValue::kFloat));
EXPECT_TRUE(olive::NodeValue::type_can_be_interpolated(olive::NodeValue::kColor));
EXPECT_FALSE(olive::NodeValue::type_can_be_interpolated(olive::NodeValue::kInt));
EXPECT_TRUE(olive::NodeValue::type_is_numeric(olive::NodeValue::kInt));
EXPECT_TRUE(olive::NodeValue::type_is_numeric(olive::NodeValue::kFloat));
EXPECT_FALSE(olive::NodeValue::type_is_numeric(olive::NodeValue::kText));
EXPECT_TRUE(olive::NodeValue::type_is_vector(olive::NodeValue::kVec2));
EXPECT_TRUE(olive::NodeValue::type_is_vector(olive::NodeValue::kVec3));
EXPECT_FALSE(olive::NodeValue::type_is_vector(olive::NodeValue::kFloat));
EXPECT_TRUE(olive::NodeValue::type_is_buffer(olive::NodeValue::kTexture));
EXPECT_TRUE(olive::NodeValue::type_is_buffer(olive::NodeValue::kSamples));
EXPECT_FALSE(olive::NodeValue::type_is_buffer(olive::NodeValue::kColor));
}
TEST(NodeValue, DataTypeNameRoundTrip)
{
for (int i = olive::NodeValue::kNone; i < olive::NodeValue::kDataTypeCount; ++i) {
auto type = static_cast<olive::NodeValue::Type>(i);
QString name = olive::NodeValue::GetDataTypeName(type);
if (name.isEmpty()) {
continue;
}
EXPECT_EQ(olive::NodeValue::GetDataTypeFromName(name), type)
<< name.toStdString();
}
}
TEST(NodeValue, ConstructionAndAccessors)
{
olive::NodeValue val(olive::NodeValue::kInt, static_cast<int64_t>(42));
EXPECT_EQ(val.type(), olive::NodeValue::kInt);
EXPECT_EQ(val.toInt(), 42);
EXPECT_TRUE(val);
val.set_tag(QStringLiteral("tag"));
EXPECT_EQ(val.tag(), QStringLiteral("tag"));
}
TEST(NodeValueTable, PushAndGet)
{
olive::NodeValueTable table;
olive::NodeValue v(olive::NodeValue::kFloat, 3.14);
table.Push(v);
EXPECT_EQ(table.Count(), 1);
EXPECT_FALSE(table.isEmpty());
EXPECT_TRUE(table.Has(olive::NodeValue::kFloat));
olive::NodeValue got = table.Get(olive::NodeValue::kFloat);
EXPECT_DOUBLE_EQ(got.toDouble(), 3.14);
}
TEST(NodeValueTable, TakeRemovesValue)
{
olive::NodeValueTable table;
table.Push(olive::NodeValue(olive::NodeValue::kInt, static_cast<int64_t>(1)));
table.Push(olive::NodeValue(olive::NodeValue::kInt, static_cast<int64_t>(2)));
olive::NodeValue taken = table.Take(olive::NodeValue::kInt);
EXPECT_EQ(taken.toInt(), 2);
EXPECT_EQ(table.Count(), 1);
}
TEST(NodeValueTable, ClearEmptiesTable)
{
olive::NodeValueTable table;
table.Push(olive::NodeValue(olive::NodeValue::kText, QStringLiteral("hello")));
table.Clear();
EXPECT_TRUE(table.isEmpty());
EXPECT_EQ(table.Count(), 0);
}
+10
View File
@@ -0,0 +1,10 @@
#include <gtest/gtest.h>
#include "render/alphaassoc.h"
TEST(AlphaAssociated, ValuesAreDistinct)
{
EXPECT_NE(olive::kAlphaNone, olive::kAlphaUnassociated);
EXPECT_NE(olive::kAlphaNone, olive::kAlphaAssociated);
EXPECT_NE(olive::kAlphaUnassociated, olive::kAlphaAssociated);
}
+10
View File
@@ -0,0 +1,10 @@
#include <gtest/gtest.h>
#include "render/loopmode.h"
TEST(LoopMode, ValuesAreDistinct)
{
EXPECT_NE(olive::LoopMode::kLoopModeOff, olive::LoopMode::kLoopModeLoop);
EXPECT_NE(olive::LoopMode::kLoopModeOff, olive::LoopMode::kLoopModeClamp);
EXPECT_NE(olive::LoopMode::kLoopModeLoop, olive::LoopMode::kLoopModeClamp);
}
+34 -1
View File
@@ -11,9 +11,42 @@ TEST(Shaders, ResourcesAvailable)
QStringLiteral(":/shaders/deinterlace2.frag"),
QStringLiteral(":/shaders/rgbhistogram.frag"),
QStringLiteral(":/shaders/rgbhistogram.vert"),
QStringLiteral(":/shaders/rgbhistogram_secondary.frag"),
QStringLiteral(":/shaders/rgbvectorscope.frag"),
QStringLiteral(":/shaders/rgbvectorscope.vert"),
QStringLiteral(":/shaders/threewaycolor.frag")
QStringLiteral(":/shaders/rgbwaveform.frag"),
QStringLiteral(":/shaders/rgbwaveform.vert"),
QStringLiteral(":/shaders/threewaycolor.frag"),
QStringLiteral(":/shaders/alphaover.frag"),
QStringLiteral(":/shaders/blur.frag"),
QStringLiteral(":/shaders/chromakey.frag"),
QStringLiteral(":/shaders/colordifferencekey.frag"),
QStringLiteral(":/shaders/colormanage.frag"),
QStringLiteral(":/shaders/cornerpin.frag"),
QStringLiteral(":/shaders/cornerpin.vert"),
QStringLiteral(":/shaders/crop.frag"),
QStringLiteral(":/shaders/crossdissolve.frag"),
QStringLiteral(":/shaders/deinterlace.frag"),
QStringLiteral(":/shaders/despill.frag"),
QStringLiteral(":/shaders/diptoblack.frag"),
QStringLiteral(":/shaders/dropshadow.frag"),
QStringLiteral(":/shaders/flip.frag"),
QStringLiteral(":/shaders/interlace.frag"),
QStringLiteral(":/shaders/invertrgb.frag"),
QStringLiteral(":/shaders/invertrgba.frag"),
QStringLiteral(":/shaders/mosaic.frag"),
QStringLiteral(":/shaders/multiply.frag"),
QStringLiteral(":/shaders/noise.frag"),
QStringLiteral(":/shaders/opacity.frag"),
QStringLiteral(":/shaders/opacity_rgb.frag"),
QStringLiteral(":/shaders/rgb.frag"),
QStringLiteral(":/shaders/ripple.frag"),
QStringLiteral(":/shaders/shape.frag"),
QStringLiteral(":/shaders/solid.frag"),
QStringLiteral(":/shaders/stroke.frag"),
QStringLiteral(":/shaders/swirl.frag"),
QStringLiteral(":/shaders/tile.frag"),
QStringLiteral(":/shaders/wave.frag"),
};
for (const QString &path : shader_paths) {
+116
View File
@@ -26,6 +26,42 @@ protected:
private:
bool *ran_ = nullptr;
};
class FailingTask final : public olive::Task {
public:
FailingTask()
{
SetTitle(QStringLiteral("FailingTask"));
}
protected:
bool Run() override
{
SetError(QStringLiteral("expected failure"));
return false;
}
};
class ProgressTask final : public olive::Task {
public:
explicit ProgressTask(int steps)
: steps_(steps)
{
SetTitle(QStringLiteral("ProgressTask"));
}
protected:
bool Run() override
{
for (int i = 0; i <= steps_; ++i) {
emit ProgressChanged(static_cast<double>(i) / steps_);
}
return true;
}
private:
int steps_ = 1;
};
}
TEST(TaskManager, AddAndRunTask)
@@ -50,3 +86,83 @@ TEST(TaskManager, AddAndRunTask)
EXPECT_TRUE(ran);
olive::TaskManager::DestroyInstance();
}
TEST(TaskManager, FailedTaskEmitsTaskFailed)
{
olive::TaskManager::CreateInstance();
olive::TaskManager *mgr = olive::TaskManager::instance();
ASSERT_NE(mgr, nullptr);
FailingTask *task = new FailingTask();
QEventLoop loop;
bool saw_failed = false;
QObject::connect(mgr, &olive::TaskManager::TaskFailed, &loop,
[&loop, &saw_failed](olive::Task *) {
saw_failed = true;
loop.quit();
});
QTimer::singleShot(5000, &loop, &QEventLoop::quit);
mgr->AddTask(task);
loop.exec();
EXPECT_TRUE(saw_failed);
EXPECT_FALSE(task->GetError().isEmpty());
olive::TaskManager::DestroyInstance();
}
TEST(TaskManager, ProgressSignalIsEmitted)
{
olive::TaskManager::CreateInstance();
olive::TaskManager *mgr = olive::TaskManager::instance();
ASSERT_NE(mgr, nullptr);
ProgressTask *task = new ProgressTask(4);
QEventLoop loop;
QVector<double> progress;
QObject::connect(task, &olive::Task::ProgressChanged, &loop,
[&progress](double p) { progress.append(p); });
QObject::connect(task, &olive::Task::Finished, &loop,
[&loop](olive::Task *, bool) { loop.quit(); });
QTimer::singleShot(5000, &loop, &QEventLoop::quit);
mgr->AddTask(task);
loop.exec();
EXPECT_FALSE(progress.isEmpty());
EXPECT_GE(progress.last(), 0.99);
olive::TaskManager::DestroyInstance();
}
TEST(TaskManager, MultipleTasksComplete)
{
olive::TaskManager::CreateInstance();
olive::TaskManager *mgr = olive::TaskManager::instance();
ASSERT_NE(mgr, nullptr);
bool ran1 = false;
bool ran2 = false;
DummyTask *task1 = new DummyTask(&ran1);
DummyTask *task2 = new DummyTask(&ran2);
QEventLoop loop;
int finished = 0;
auto on_finished = [&loop, &finished](olive::Task *, bool) {
if (++finished == 2) {
loop.quit();
}
};
QObject::connect(task1, &olive::Task::Finished, &loop, on_finished);
QObject::connect(task2, &olive::Task::Finished, &loop, on_finished);
QTimer::singleShot(5000, &loop, &QEventLoop::quit);
mgr->AddTask(task1);
mgr->AddTask(task2);
loop.exec();
EXPECT_TRUE(ran1);
EXPECT_TRUE(ran2);
olive::TaskManager::DestroyInstance();
}
+33
View File
@@ -32,3 +32,36 @@ TEST(TimelineCoordinate, Constructors)
EXPECT_EQ(with_type.GetTrack().type(), olive::Track::kSubtitle);
EXPECT_EQ(with_type.GetTrack().index(), 3);
}
TEST(TimelineCoordinate, CopyAndAssignment)
{
const olive::core::rational frame(7, 1);
olive::Track::Reference ref(olive::Track::kVideo, 4);
olive::TimelineCoordinate original(frame, ref);
olive::TimelineCoordinate copy(original);
EXPECT_EQ(copy.GetFrame(), frame);
EXPECT_EQ(copy.GetTrack(), ref);
olive::TimelineCoordinate assigned;
assigned = original;
EXPECT_EQ(assigned.GetFrame(), frame);
EXPECT_EQ(assigned.GetTrack(), ref);
}
TEST(TimelineCoordinate, Equality)
{
olive::TimelineCoordinate a(olive::core::rational(5, 1),
olive::Track::Reference(olive::Track::kVideo, 1));
olive::TimelineCoordinate b(olive::core::rational(5, 1),
olive::Track::Reference(olive::Track::kVideo, 1));
olive::TimelineCoordinate c(olive::core::rational(6, 1),
olive::Track::Reference(olive::Track::kVideo, 1));
olive::TimelineCoordinate d(olive::core::rational(5, 1),
olive::Track::Reference(olive::Track::kAudio, 1));
EXPECT_EQ(a.GetFrame(), b.GetFrame());
EXPECT_EQ(a.GetTrack(), b.GetTrack());
EXPECT_NE(a.GetFrame(), c.GetFrame());
EXPECT_NE(a.GetTrack(), d.GetTrack());
}
+29
View File
@@ -37,6 +37,13 @@ TEST(TimelineMarker, SaveLoadRoundTrip)
EXPECT_EQ(loaded.color(), 5);
}
TEST(TimelineMarker, DefaultConstruction)
{
olive::TimelineMarker marker;
EXPECT_TRUE(marker.name().isEmpty());
EXPECT_EQ(marker.time().in(), olive::core::rational(0, 1));
}
TEST(TimelineMarkerList, OrderAndLookup)
{
olive::TimelineMarkerList list;
@@ -74,6 +81,13 @@ TEST(TimelineMarkerList, OrderAndLookup)
&marker_a);
}
TEST(TimelineMarkerList, GetMarkerAtTimeReturnsNullWhenEmpty)
{
olive::TimelineMarkerList list;
EXPECT_EQ(list.GetMarkerAtTime(olive::core::rational(10, 1)), nullptr);
EXPECT_EQ(list.GetClosestMarkerToTime(olive::core::rational(10, 1)), nullptr);
}
TEST(TimelineMarkerList, SaveLoadWithUnknownElements)
{
olive::TimelineMarkerList list;
@@ -158,3 +172,18 @@ TEST(TimelineMarkerCommands, AddRemoveAndChange)
move.undo_now();
EXPECT_EQ(list.front()->time().in(), olive::core::rational(1, 1));
}
TEST(TimelineMarkerCommands, AddCommandUndo)
{
olive::TimelineMarkerList list;
olive::MarkerAddCommand add(
&list,
olive::core::TimeRange(olive::core::rational(5, 1),
olive::core::rational(5, 1)),
QStringLiteral("UndoMe"),
2);
add.redo_now();
EXPECT_EQ(list.size(), 1);
add.undo_now();
EXPECT_TRUE(list.empty());
}
+44 -2
View File
@@ -28,7 +28,7 @@ TEST(TimelineWorkArea, SaveLoadRoundTrip)
olive::TimelineWorkArea workarea;
workarea.set_enabled(true);
workarea.set_range(olive::core::TimeRange(olive::core::rational(2, 1),
olive::core::rational(6, 1)));
olive::core::rational(6, 1)));
QByteArray xml;
QBuffer buffer(&xml);
@@ -51,5 +51,47 @@ TEST(TimelineWorkArea, SaveLoadRoundTrip)
EXPECT_TRUE(loaded.enabled());
EXPECT_EQ(loaded.range(),
olive::core::TimeRange(olive::core::rational(2, 1),
olive::core::rational(6, 1)));
olive::core::rational(6, 1)));
}
TEST(TimelineWorkArea, DisabledWorkAreaRoundTrip)
{
olive::TimelineWorkArea workarea;
workarea.set_enabled(false);
workarea.set_range(olive::core::TimeRange(olive::core::rational(0, 1),
olive::core::rational(10, 1)));
QByteArray xml;
QBuffer buffer(&xml);
ASSERT_TRUE(buffer.open(QIODevice::WriteOnly));
QXmlStreamWriter writer(&buffer);
writer.writeStartDocument();
writer.writeStartElement(QStringLiteral("workarea"));
workarea.save(&writer);
writer.writeEndElement();
writer.writeEndDocument();
buffer.close();
olive::TimelineWorkArea loaded;
QBuffer read_buffer(&xml);
ASSERT_TRUE(read_buffer.open(QIODevice::ReadOnly));
QXmlStreamReader reader(&read_buffer);
ASSERT_TRUE(reader.readNextStartElement());
EXPECT_TRUE(loaded.load(&reader));
EXPECT_FALSE(loaded.enabled());
EXPECT_EQ(loaded.range(),
olive::core::TimeRange(olive::core::rational(0, 1),
olive::core::rational(10, 1)));
}
TEST(TimelineWorkArea, SetRangeUpdatesInOut)
{
olive::TimelineWorkArea workarea;
workarea.set_range(olive::core::TimeRange(olive::core::rational(3, 1),
olive::core::rational(8, 1)));
EXPECT_EQ(workarea.in(), olive::core::rational(3, 1));
EXPECT_EQ(workarea.out(), olive::core::rational(8, 1));
EXPECT_EQ(workarea.length(), olive::core::rational(5, 1));
}
+65
View File
@@ -120,3 +120,68 @@ TEST(UndoStack, EmptyMultiUndoCommandIsIgnored)
EXPECT_EQ(stack.rowCount(), 1);
EXPECT_FALSE(stack.CanUndo());
}
TEST(UndoStack, MultipleUndosAndRedos)
{
int counter = 0;
olive::UndoStack stack;
stack.push(new TestCommand(&counter), QStringLiteral("One"));
stack.push(new TestCommand(&counter), QStringLiteral("Two"));
stack.push(new TestCommand(&counter), QStringLiteral("Three"));
EXPECT_EQ(counter, 3);
stack.undo();
stack.undo();
EXPECT_EQ(counter, 1);
EXPECT_TRUE(stack.CanUndo());
EXPECT_TRUE(stack.CanRedo());
stack.redo();
EXPECT_EQ(counter, 2);
}
TEST(UndoStack, PushAfterUndoClearsRedoBranch)
{
int counter = 0;
olive::UndoStack stack;
stack.push(new TestCommand(&counter), QStringLiteral("First"));
stack.push(new TestCommand(&counter), QStringLiteral("Second"));
EXPECT_EQ(counter, 2);
stack.undo();
EXPECT_EQ(counter, 1);
stack.push(new TestCommand(&counter), QStringLiteral("Third"));
EXPECT_EQ(counter, 2);
EXPECT_FALSE(stack.CanRedo());
}
TEST(UndoStack, ResetClearsHistory)
{
int counter = 0;
olive::UndoStack stack;
stack.push(new TestCommand(&counter), QStringLiteral("Action"));
EXPECT_EQ(counter, 1);
stack.clear();
EXPECT_FALSE(stack.CanUndo());
EXPECT_FALSE(stack.CanRedo());
EXPECT_EQ(stack.rowCount(), 1);
}
TEST(UndoStack, MultiUndoCommandWithChildren)
{
int counter = 0;
olive::UndoStack stack;
auto *multi = new olive::MultiUndoCommand();
multi->add_child(new TestCommand(&counter));
multi->add_child(new TestCommand(&counter));
stack.push(multi, QStringLiteral("Multi"));
EXPECT_EQ(counter, 2);
stack.undo();
EXPECT_EQ(counter, 0);
stack.redo();
EXPECT_EQ(counter, 2);
}