From 1f679551f271f382efc01ee27034ba100bbe75a6 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Mon, 5 Jan 2026 02:55:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 86 +++++++++++++++++++++++++ docs/test-plan.md | 86 +++++++++++++++++++++++++ tests/CMakeLists.txt | 12 ++++ tests/gtest/CMakeLists.txt | 53 +++++++++++++++ tests/gtest/codec_frame_test.cpp | 11 ++++ tests/gtest/common_current_test.cpp | 27 ++++++++ tests/gtest/common_xmlutils_test.cpp | 20 ++++++ tests/gtest/config_test.cpp | 20 ++++++ tests/gtest/main.cpp | 9 +++ tests/gtest/module_smoke_test.cpp | 15 +++++ tests/gtest/node_keyframe_test.cpp | 47 ++++++++++++++ tests/gtest/node_serialization_test.cpp | 81 +++++++++++++++++++++++ tests/gtest/node_value_test.cpp | 50 ++++++++++++++ tests/gtest/plugin_support_test.cpp | 10 +++ tests/gtest/project_serializer_test.cpp | 43 +++++++++++++ tests/gtest/render_audioparams_test.cpp | 38 +++++++++++ tests/gtest/render_videoparams_test.cpp | 42 ++++++++++++ tests/gtest/task_taskmanager_test.cpp | 52 +++++++++++++++ tests/gtest/timeline_marker_test.cpp | 38 +++++++++++ tests/gtest/undo_stack_test.cpp | 44 +++++++++++++ 20 files changed, 784 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 docs/test-plan.md create mode 100644 tests/gtest/CMakeLists.txt create mode 100644 tests/gtest/codec_frame_test.cpp create mode 100644 tests/gtest/common_current_test.cpp create mode 100644 tests/gtest/common_xmlutils_test.cpp create mode 100644 tests/gtest/config_test.cpp create mode 100644 tests/gtest/main.cpp create mode 100644 tests/gtest/module_smoke_test.cpp create mode 100644 tests/gtest/node_keyframe_test.cpp create mode 100644 tests/gtest/node_serialization_test.cpp create mode 100644 tests/gtest/node_value_test.cpp create mode 100644 tests/gtest/plugin_support_test.cpp create mode 100644 tests/gtest/project_serializer_test.cpp create mode 100644 tests/gtest/render_audioparams_test.cpp create mode 100644 tests/gtest/render_videoparams_test.cpp create mode 100644 tests/gtest/task_taskmanager_test.cpp create mode 100644 tests/gtest/timeline_marker_test.cpp create mode 100644 tests/gtest/undo_stack_test.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..043bba6f9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +name: CI + +on: + push: + pull_request: + +jobs: + build-test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, macos-13, windows-2022] + env: + CMAKE_BUILD_TYPE: Release + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y \ + ninja-build pkg-config \ + qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools \ + libavcodec-dev libavformat-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev \ + libopencolorio-dev libopenimageio-dev libopenexr-dev libexpat1-dev \ + portaudio19-dev libgl1-mesa-dev + + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install ninja pkg-config qt@6 ffmpeg openimageio opencolorio openexr portaudio expat opentimelineio + echo "$(brew --prefix qt@6)/bin" >> "$GITHUB_PATH" + echo "CMAKE_PREFIX_PATH=$(brew --prefix qt@6)" >> "$GITHUB_ENV" + + - name: Install Qt (Windows) + if: runner.os == 'Windows' + uses: jurplel/install-qt-action@v4 + with: + version: 6.5.3 + cache: true + tools: 'tools_ninja' + + - name: Install dependencies (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + choco install -y ninja + $env:VCPKG_ROOT = "C:\vcpkg" + & "$env:VCPKG_ROOT\vcpkg.exe" install ffmpeg openimageio opencolorio openexr expat portaudio --triplet x64-windows + echo "VCPKG_ROOT=$env:VCPKG_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + echo "CMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Configure (Linux/macOS) + if: runner.os != 'Windows' + run: | + cmake -S . -B build -G Ninja \ + -DBUILD_TESTS=ON \ + -DBUILD_QT6=ON \ + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + + - name: Configure (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + cmake -S . -B build -G Ninja ` + -DBUILD_TESTS=ON ` + -DBUILD_QT6=ON ` + -DCMAKE_BUILD_TYPE=$env:CMAKE_BUILD_TYPE ` + -DCMAKE_TOOLCHAIN_FILE=$env:CMAKE_TOOLCHAIN_FILE ` + -DCMAKE_PREFIX_PATH=$env:Qt6_DIR + + - name: Build + run: cmake --build build --config ${{ env.CMAKE_BUILD_TYPE }} + + - name: Test (Linux) + if: runner.os == 'Linux' + env: + QT_QPA_PLATFORM: offscreen + run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }} + + - name: Test (macOS/Windows) + if: runner.os != 'Linux' + run: ctest --test-dir build --output-on-failure -C ${{ env.CMAKE_BUILD_TYPE }} diff --git a/docs/test-plan.md b/docs/test-plan.md new file mode 100644 index 000000000..1bbeba018 --- /dev/null +++ b/docs/test-plan.md @@ -0,0 +1,86 @@ +# Olive Testing Strategy and Plan + +This document describes the automated testing strategy for Olive, including unit tests, integration tests, and CI execution. + +## Goals + +- Maximize automation and reduce manual testing. +- Cover all modules with at least one automated test. +- Keep integration tests headless (no GUI interaction). +- Make failures reproducible on Windows/macOS/Linux CI. + +## Test Layers + +### 1) Unit Tests (GoogleTest) +- Focus: small units, deterministic behavior, no GUI. +- Location: `tests/gtest/`. +- Execution: `ctest` target `olive-gtest`. + +### 1.5) Module Smoke Tests (GoogleTest) +- Focus: compile-time and link-time coverage for GUI-heavy modules without instantiating widgets. +- Location: `tests/gtest/module_smoke_test.cpp`. +- Execution: `ctest` target `olive-gtest`. + +### 2) Integration Tests (GoogleTest) +- Focus: cross-module flows without GUI (e.g., serialize → deserialize → resolve). +- Location: `tests/gtest/` (prefixed with `ProjectSerializer`, `TaskManager`, etc.). + +### 3) Legacy Tests (Olive macro tests) +- Existing tests in `tests/general`, `tests/timeline`, `tests/compositing` remain. + +## Module Coverage Map + +Each top-level module has at least one test that exercises its core API or serialization path. + +- `app/common`: `common_current_test.cpp`, `common_xmlutils_test.cpp` +- `app/config`: `config_test.cpp` +- `app/node`: `node_value_test.cpp`, `node_keyframe_test.cpp`, `node_serialization_test.cpp` +- `app/node/project/serializer`: `project_serializer_test.cpp` +- `app/render`: `render_videoparams_test.cpp`, `render_audioparams_test.cpp` +- `app/timeline`: `timeline_marker_test.cpp` +- `app/undo`: `undo_stack_test.cpp` +- `app/task`: `task_taskmanager_test.cpp` +- `app/codec`: `codec_frame_test.cpp` +- `app/pluginSupport`: `plugin_support_test.cpp` +- `app/audio`, `app/cli`, `app/dialog`, `app/panel`, `app/tool`, `app/ui`, `app/widget`, `app/window`: `module_smoke_test.cpp` + +If a module has a GUI dependency (e.g., widgets), tests focus on non-visual data/model components. + +## Integration Test Details + +### Project Serializer Roundtrip +- Creates a minimal project with a built-in node. +- Saves to XML via `ProjectSerializer::Save`. +- Loads with `ProjectSerializer::Load`. +- Verifies that nodes are restored. + +### Task Manager Execution +- Adds a dummy task to `TaskManager`. +- Waits for completion using an event loop. +- Verifies the task ran. + +## Headless Execution + +- Tests avoid QWidget usage. +- CI sets `QT_QPA_PLATFORM=offscreen` to prevent GUI initialization issues. + +## Continuous Integration + +CI runs on Windows, macOS, and Linux: + +1. Install system dependencies (Qt, FFmpeg, OpenImageIO, OpenColorIO, OpenEXR, PortAudio, Expat). +2. Configure with `-DBUILD_TESTS=ON`. +3. Build with CMake + Ninja. +4. Run `ctest` with output on failure. + +### Dependency Installation Notes +- Linux: use distro packages (`apt` on Ubuntu) for Qt6, FFmpeg, OpenImageIO, OpenColorIO, OpenEXR, PortAudio, Expat, OpenGL headers. +- macOS: use Homebrew for Qt6 and media/color/image libraries. +- Windows: use system installers where available (Qt via `install-qt-action`), and vcpkg for the remaining C/C++ libraries. + +## Adding New Tests + +- Place new unit tests in `tests/gtest`. +- Use GoogleTest conventions. +- Prefer deterministic fixtures and local-only resources. +- When adding a new module, add at least one unit test and one integration scenario if applicable. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3dbbd2ccd..bb218f179 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -69,6 +69,18 @@ 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) +add_subdirectory(gtest) diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt new file mode 100644 index 000000000..4bbf4255f --- /dev/null +++ b/tests/gtest/CMakeLists.txt @@ -0,0 +1,53 @@ +add_executable(olive-gtest + main.cpp + common_current_test.cpp + common_xmlutils_test.cpp + config_test.cpp + node_value_test.cpp + node_keyframe_test.cpp + node_serialization_test.cpp + render_videoparams_test.cpp + render_audioparams_test.cpp + project_serializer_test.cpp + timeline_marker_test.cpp + undo_stack_test.cpp + plugin_support_test.cpp + codec_frame_test.cpp + task_taskmanager_test.cpp + module_smoke_test.cpp +) + +target_sources(olive-gtest PRIVATE $) + +target_include_directories( + olive-gtest + PRIVATE + ${CMAKE_SOURCE_DIR}/app + ${CMAKE_SOURCE_DIR}/tests + ${OLIVE_INCLUDE_DIRS} +) + +target_link_libraries( + olive-gtest + PRIVATE + ${OLIVE_LIBRARIES} + GTest::gtest +) + +target_compile_definitions( + olive-gtest + PRIVATE + ${OLIVE_DEFINITIONS} +) + +target_compile_options( + olive-gtest + PRIVATE + ${OLIVE_COMPILE_OPTIONS} +) + +if (MSVC) + add_test("Olive.gtest" olive-gtest) +else() + add_test(olive-gtest olive-gtest) +endif() diff --git a/tests/gtest/codec_frame_test.cpp b/tests/gtest/codec_frame_test.cpp new file mode 100644 index 000000000..17c29020f --- /dev/null +++ b/tests/gtest/codec_frame_test.cpp @@ -0,0 +1,11 @@ +#include + +#include "codec/frame.h" + +TEST(CodecFrame, DefaultState) +{ + olive::Frame frame; + EXPECT_EQ(frame.width(), 0); + EXPECT_EQ(frame.height(), 0); + EXPECT_EQ(frame.pixel_format(), olive::core::PixelFormat::Format::kFormatNone); +} diff --git a/tests/gtest/common_current_test.cpp b/tests/gtest/common_current_test.cpp new file mode 100644 index 000000000..6cbd96af9 --- /dev/null +++ b/tests/gtest/common_current_test.cpp @@ -0,0 +1,27 @@ +#include + +#include "common/Current.h" +#include "render/videoparams.h" +#include "render/audioparams.h" + +TEST(CommonCurrent, SetAndGetVideoParams) +{ + olive::VideoParams params; + params.set_width(1920); + params.set_height(1080); + Current::getInstance().setCurrentVideoParams(params); + + const olive::VideoParams &stored = Current::getInstance().currentVideoParams(); + EXPECT_EQ(stored.width(), 1920); + EXPECT_EQ(stored.height(), 1080); +} + +TEST(CommonCurrent, SetAndGetAudioParams) +{ + olive::AudioParams params; + params.set_sample_rate(48000); + Current::getInstance().setCurrentAudioParams(params); + + const olive::AudioParams &stored = Current::getInstance().currentAudioParams(); + EXPECT_EQ(stored.sample_rate(), 48000); +} diff --git a/tests/gtest/common_xmlutils_test.cpp b/tests/gtest/common_xmlutils_test.cpp new file mode 100644 index 000000000..b776ea2e5 --- /dev/null +++ b/tests/gtest/common_xmlutils_test.cpp @@ -0,0 +1,20 @@ +#include + +#include +#include +#include + +#include "common/xmlutils.h" + +TEST(CommonXmlUtils, ReadNextStartElement) +{ + QByteArray xml = "value"; + QBuffer buffer(&xml); + buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&buffer); + + EXPECT_TRUE(XMLReadNextStartElement(&reader)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("root")); + EXPECT_TRUE(XMLReadNextStartElement(&reader)); + EXPECT_EQ(reader.name().toString(), QStringLiteral("child")); +} diff --git a/tests/gtest/config_test.cpp b/tests/gtest/config_test.cpp new file mode 100644 index 000000000..94322588d --- /dev/null +++ b/tests/gtest/config_test.cpp @@ -0,0 +1,20 @@ +#include + +#include "config/config.h" + +TEST(Config, DefaultsPresent) +{ + olive::Config &cfg = olive::Config::Current(); + cfg.SetDefaults(); + + EXPECT_TRUE(cfg[QStringLiteral("Style")].isValid()); + EXPECT_TRUE(cfg[QStringLiteral("TimecodeDisplay")].isValid()); + EXPECT_TRUE(cfg[QStringLiteral("DefaultStillLength")].isValid()); +} + +TEST(Config, SetAndGetValues) +{ + olive::Config &cfg = olive::Config::Current(); + cfg[QStringLiteral("UnitTestValue")] = 42; + EXPECT_EQ(cfg[QStringLiteral("UnitTestValue")].toInt(), 42); +} diff --git a/tests/gtest/main.cpp b/tests/gtest/main.cpp new file mode 100644 index 000000000..a9d548259 --- /dev/null +++ b/tests/gtest/main.cpp @@ -0,0 +1,9 @@ +#include +#include + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/gtest/module_smoke_test.cpp b/tests/gtest/module_smoke_test.cpp new file mode 100644 index 000000000..aa13980c3 --- /dev/null +++ b/tests/gtest/module_smoke_test.cpp @@ -0,0 +1,15 @@ +#include + +#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) +{ + SUCCEED(); +} diff --git a/tests/gtest/node_keyframe_test.cpp b/tests/gtest/node_keyframe_test.cpp new file mode 100644 index 000000000..25817d736 --- /dev/null +++ b/tests/gtest/node_keyframe_test.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include + +#include "node/keyframe.h" +#include "node/value.h" + +TEST(NodeKeyframe, SaveLoadRoundTrip) +{ + olive::NodeKeyframe key; + key.set_input(QStringLiteral("Value")); + key.set_time(olive::core::rational(1, 24)); + key.set_type(olive::NodeKeyframe::kLinear); + key.set_value(42.0); + key.set_bezier_control_in(QPointF(0.1, 0.2)); + key.set_bezier_control_out(QPointF(0.3, 0.4)); + + QByteArray xml; + QBuffer buffer(&xml); + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter writer(&buffer); + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("key")); + key.save(&writer, olive::NodeValue::kFloat); + writer.writeEndElement(); + writer.writeEndDocument(); + buffer.close(); + + QBuffer read_buffer(&xml); + read_buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&read_buffer); + EXPECT_TRUE(reader.readNextStartElement()); + EXPECT_EQ(reader.name().toString(), QStringLiteral("key")); + + olive::NodeKeyframe loaded; + EXPECT_TRUE(loaded.load(&reader, olive::NodeValue::kFloat)); + EXPECT_EQ(loaded.input(), QStringLiteral("Value")); + EXPECT_EQ(loaded.time(), olive::core::rational(1, 24)); + EXPECT_EQ(loaded.type(), olive::NodeKeyframe::kLinear); + EXPECT_DOUBLE_EQ(loaded.value().toDouble(), 42.0); + EXPECT_DOUBLE_EQ(loaded.bezier_control_in().x(), 0.1); + EXPECT_DOUBLE_EQ(loaded.bezier_control_in().y(), 0.2); + EXPECT_DOUBLE_EQ(loaded.bezier_control_out().x(), 0.3); + EXPECT_DOUBLE_EQ(loaded.bezier_control_out().y(), 0.4); +} diff --git a/tests/gtest/node_serialization_test.cpp b/tests/gtest/node_serialization_test.cpp new file mode 100644 index 000000000..f527a0770 --- /dev/null +++ b/tests/gtest/node_serialization_test.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include +#include + +#include "node/node.h" +#include "node/serializeddata.h" +#include "node/value.h" + +namespace { +class TestNode final : public olive::Node { +public: + TestNode() + { + AddInput(QStringLiteral("Value"), olive::NodeValue::kFloat); + SetSplitStandardValue(QStringLiteral("Value"), 3.5, -1); + } + + TestNode *copy() const override + { + return new TestNode(); + } + + QString Name() const override + { + return QStringLiteral("TestNode"); + } + + QString id() const override + { + return QStringLiteral("org.olivevideoeditor.TestNode"); + } + + QVector Category() const override + { + return { kCategoryUnknown }; + } + + QString Description() const override + { + return QStringLiteral("Test node for serialization"); + } + + void Value(const NodeValueRow &, const NodeGlobals &, NodeValueTable *) const override + { + } +}; +} + +TEST(NodeSerialization, SaveAndLoadInput) +{ + TestNode node; + node.SetLabel(QStringLiteral("MyNode")); + node.SetOverrideColor(2); + + QByteArray xml; + QBuffer buffer(&xml); + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter writer(&buffer); + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("node")); + node.Save(&writer); + writer.writeEndElement(); + writer.writeEndDocument(); + buffer.close(); + + TestNode loaded; + olive::SerializedData data; + QBuffer read_buffer(&xml); + read_buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&read_buffer); + EXPECT_TRUE(reader.readNextStartElement()); + EXPECT_EQ(reader.name().toString(), QStringLiteral("node")); + EXPECT_TRUE(loaded.Load(&reader, &data)); + + EXPECT_EQ(loaded.GetLabel(), QStringLiteral("MyNode")); + EXPECT_EQ(loaded.GetOverrideColor(), 2); + EXPECT_DOUBLE_EQ(loaded.GetSplitStandardValue(QStringLiteral("Value"), -1) + .first().toDouble(), 3.5); +} diff --git a/tests/gtest/node_value_test.cpp b/tests/gtest/node_value_test.cpp new file mode 100644 index 000000000..91f094b51 --- /dev/null +++ b/tests/gtest/node_value_test.cpp @@ -0,0 +1,50 @@ +#include + +#include +#include +#include + +#include "node/value.h" + +TEST(NodeValue, VectorRoundTrip) +{ + QVector2D v2(1.5f, -2.0f); + QString encoded = olive::NodeValue::ValueToString( + olive::NodeValue::kVec2, QVariant::fromValue(v2), false); + QVariant decoded = olive::NodeValue::StringToValue( + olive::NodeValue::kVec2, encoded, false); + QVector2D v2_out = decoded.value(); + EXPECT_FLOAT_EQ(v2_out.x(), v2.x()); + EXPECT_FLOAT_EQ(v2_out.y(), v2.y()); + + QVector3D v3(1.0f, 2.0f, 3.0f); + encoded = olive::NodeValue::ValueToString( + olive::NodeValue::kVec3, QVariant::fromValue(v3), false); + decoded = olive::NodeValue::StringToValue( + olive::NodeValue::kVec3, encoded, false); + QVector3D v3_out = decoded.value(); + EXPECT_FLOAT_EQ(v3_out.x(), v3.x()); + EXPECT_FLOAT_EQ(v3_out.y(), v3.y()); + EXPECT_FLOAT_EQ(v3_out.z(), v3.z()); + + QVector4D v4(1.0f, 2.0f, 3.0f, 4.0f); + encoded = olive::NodeValue::ValueToString( + olive::NodeValue::kVec4, QVariant::fromValue(v4), false); + decoded = olive::NodeValue::StringToValue( + olive::NodeValue::kVec4, encoded, false); + QVector4D v4_out = decoded.value(); + EXPECT_FLOAT_EQ(v4_out.x(), v4.x()); + EXPECT_FLOAT_EQ(v4_out.y(), v4.y()); + EXPECT_FLOAT_EQ(v4_out.z(), v4.z()); + EXPECT_FLOAT_EQ(v4_out.w(), v4.w()); +} + +TEST(NodeValue, BinaryRoundTrip) +{ + QByteArray data("OliveTest"); + QString encoded = olive::NodeValue::ValueToString( + olive::NodeValue::kBinary, data, false); + QVariant decoded = olive::NodeValue::StringToValue( + olive::NodeValue::kBinary, encoded, false); + EXPECT_EQ(decoded.toByteArray(), data); +} diff --git a/tests/gtest/plugin_support_test.cpp b/tests/gtest/plugin_support_test.cpp new file mode 100644 index 000000000..5acad6e79 --- /dev/null +++ b/tests/gtest/plugin_support_test.cpp @@ -0,0 +1,10 @@ +#include + +#include "pluginSupport/OliveHost.h" + +TEST(PluginSupport, LoadPluginsEmptyPath) +{ + EXPECT_NO_THROW({ + olive::plugin::loadPlugins(QString()); + }); +} diff --git a/tests/gtest/project_serializer_test.cpp b/tests/gtest/project_serializer_test.cpp new file mode 100644 index 000000000..2f8d14149 --- /dev/null +++ b/tests/gtest/project_serializer_test.cpp @@ -0,0 +1,43 @@ +#include + +#include +#include +#include + +#include "node/factory.h" +#include "node/project.h" +#include "node/input/time/timeinput.h" +#include "node/project/serializer/serializer.h" + +TEST(ProjectSerializer, SaveLoadProjectRoundTrip) +{ + olive::NodeFactory::Initialize(); + + olive::Project project; + project.Initialize(); + + auto *node = new olive::TimeInput(); + node->SetLabel(QStringLiteral("TimeInput")); + node->setParent(&project); + + olive::ProjectSerializer::SaveData save_data( + olive::ProjectSerializer::kProject, &project, QString()); + + QByteArray xml; + QBuffer buffer(&xml); + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter writer(&buffer); + EXPECT_EQ(olive::ProjectSerializer::Save(&writer, save_data), + olive::ProjectSerializer::kSuccess); + buffer.close(); + + olive::Project loaded_project; + QBuffer read_buffer(&xml); + read_buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&read_buffer); + olive::ProjectSerializer::Result result = + olive::ProjectSerializer::Load(&loaded_project, &reader, + olive::ProjectSerializer::kProject); + EXPECT_EQ(result.code(), olive::ProjectSerializer::kSuccess); + EXPECT_FALSE(loaded_project.nodes().isEmpty()); +} diff --git a/tests/gtest/render_audioparams_test.cpp b/tests/gtest/render_audioparams_test.cpp new file mode 100644 index 000000000..60b28864d --- /dev/null +++ b/tests/gtest/render_audioparams_test.cpp @@ -0,0 +1,38 @@ +#include + +#include +#include +#include + +#include "node/project/serializer/typeserializer.h" +#include "render/audioparams.h" + +TEST(RenderAudioParams, SaveLoadRoundTrip) +{ + olive::AudioParams params; + params.set_sample_rate(48000); + params.set_enabled(true); + params.set_time_base(olive::core::rational(1, 48000)); + + QByteArray xml; + QBuffer buffer(&xml); + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter writer(&buffer); + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("audioparams")); + olive::TypeSerializer::SaveAudioParams(&writer, params); + writer.writeEndElement(); + writer.writeEndDocument(); + buffer.close(); + + QBuffer read_buffer(&xml); + read_buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&read_buffer); + EXPECT_TRUE(reader.readNextStartElement()); + EXPECT_EQ(reader.name().toString(), QStringLiteral("audioparams")); + olive::AudioParams loaded = olive::TypeSerializer::LoadAudioParams(&reader); + + EXPECT_EQ(loaded.sample_rate(), 48000); + EXPECT_TRUE(loaded.enabled()); + EXPECT_EQ(loaded.time_base(), olive::core::rational(1, 48000)); +} diff --git a/tests/gtest/render_videoparams_test.cpp b/tests/gtest/render_videoparams_test.cpp new file mode 100644 index 000000000..de4ab6503 --- /dev/null +++ b/tests/gtest/render_videoparams_test.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include +#include + +#include "render/videoparams.h" + +TEST(RenderVideoParams, SaveLoadRoundTrip) +{ + olive::VideoParams params; + params.set_width(1280); + params.set_height(720); + params.set_frame_rate(olive::core::rational(24, 1)); + params.set_pixel_aspect_ratio(olive::core::rational(1, 1)); + params.set_colorspace(QStringLiteral("test")); + + QByteArray xml; + QBuffer buffer(&xml); + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter writer(&buffer); + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("videoparams")); + params.Save(&writer); + writer.writeEndElement(); + writer.writeEndDocument(); + buffer.close(); + + olive::VideoParams loaded; + QBuffer read_buffer(&xml); + read_buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&read_buffer); + EXPECT_TRUE(reader.readNextStartElement()); + EXPECT_EQ(reader.name().toString(), QStringLiteral("videoparams")); + loaded.Load(&reader); + + EXPECT_EQ(loaded.width(), 1280); + EXPECT_EQ(loaded.height(), 720); + EXPECT_EQ(loaded.frame_rate(), olive::core::rational(24, 1)); + EXPECT_EQ(loaded.pixel_aspect_ratio(), olive::core::rational(1, 1)); + EXPECT_EQ(loaded.colorspace(), QStringLiteral("test")); +} diff --git a/tests/gtest/task_taskmanager_test.cpp b/tests/gtest/task_taskmanager_test.cpp new file mode 100644 index 000000000..452059075 --- /dev/null +++ b/tests/gtest/task_taskmanager_test.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include + +#include "task/taskmanager.h" + +namespace { +class DummyTask final : public olive::Task { +public: + explicit DummyTask(bool *ran) + : ran_(ran) + { + SetTitle(QStringLiteral("DummyTask")); + } + +protected: + bool Run() override + { + if (ran_) { + *ran_ = true; + } + return true; + } + +private: + bool *ran_ = nullptr; +}; +} + +TEST(TaskManager, AddAndRunTask) +{ + olive::TaskManager::CreateInstance(); + olive::TaskManager *mgr = olive::TaskManager::instance(); + ASSERT_NE(mgr, nullptr); + + bool ran = false; + DummyTask *task = new DummyTask(&ran); + + QEventLoop loop; + QObject::connect(task, &olive::Task::Finished, &loop, [&loop](olive::Task *, bool) { + loop.quit(); + }); + + mgr->AddTask(task); + + QTimer::singleShot(5000, &loop, &QEventLoop::quit); + loop.exec(); + + EXPECT_TRUE(ran); + olive::TaskManager::DestroyInstance(); +} diff --git a/tests/gtest/timeline_marker_test.cpp b/tests/gtest/timeline_marker_test.cpp new file mode 100644 index 000000000..e6d644e19 --- /dev/null +++ b/tests/gtest/timeline_marker_test.cpp @@ -0,0 +1,38 @@ +#include + +#include +#include +#include + +#include "timeline/timelinemarker.h" + +TEST(TimelineMarker, SaveLoadRoundTrip) +{ + olive::TimelineMarker marker; + marker.set_time(olive::core::rational(10, 1)); + marker.set_name(QStringLiteral("Marker")); + marker.set_color(5); + + QByteArray xml; + QBuffer buffer(&xml); + buffer.open(QIODevice::WriteOnly); + QXmlStreamWriter writer(&buffer); + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("marker")); + marker.save(&writer); + writer.writeEndElement(); + writer.writeEndDocument(); + buffer.close(); + + olive::TimelineMarker loaded; + QBuffer read_buffer(&xml); + read_buffer.open(QIODevice::ReadOnly); + QXmlStreamReader reader(&read_buffer); + EXPECT_TRUE(reader.readNextStartElement()); + EXPECT_EQ(reader.name().toString(), QStringLiteral("marker")); + loaded.load(&reader); + + EXPECT_EQ(loaded.time(), olive::core::rational(10, 1)); + EXPECT_EQ(loaded.name(), QStringLiteral("Marker")); + EXPECT_EQ(loaded.color(), 5); +} diff --git a/tests/gtest/undo_stack_test.cpp b/tests/gtest/undo_stack_test.cpp new file mode 100644 index 000000000..9dcd008ac --- /dev/null +++ b/tests/gtest/undo_stack_test.cpp @@ -0,0 +1,44 @@ +#include + +#include "undo/undostack.h" +#include "undo/undocommand.h" + +namespace { +class TestCommand final : public olive::UndoCommand { +public: + explicit TestCommand(int *value) + : value_(value) + { + } + +protected: + void redo() override + { + if (value_) { + (*value_)++; + } + } + + void undo() override + { + if (value_) { + (*value_)--; + } + } + +private: + int *value_ = nullptr; +}; +} + +TEST(UndoStack, PushUndoRedo) +{ + int counter = 0; + olive::UndoStack stack; + stack.push(new TestCommand(&counter), QStringLiteral("Test")); + EXPECT_EQ(counter, 1); + stack.undo(); + EXPECT_EQ(counter, 0); + stack.redo(); + EXPECT_EQ(counter, 1); +}