#include #include #include #include #include #include #include "codec/conformmanager.h" #include "config/config.h" #include "core.h" #include "node/color/colormanager/colormanager.h" #include "node/project.h" #include "node/project/footage/footage.h" #include "node/project/sequence/sequence.h" #include "render/diskmanager.h" #include "render/rendermanager.h" #include "task/customcache/customcachetask.h" #include "task/precache/precachetask.h" TEST(TaskCustomCache, CancelBeforeRunReturnsImmediately) { olive::CustomCacheTask task(QStringLiteral("Sequence")); bool cancelled_emitted = false; QObject::connect(&task, &olive::CustomCacheTask::cancelled, &task, [&cancelled_emitted] { cancelled_emitted = true; }); task.Cancel(); EXPECT_TRUE(cancelled_emitted); // Run() sees the cancel flag on entry and returns without ever blocking EXPECT_TRUE(task.start()); } TEST(TaskCustomCache, RunBlocksUntilCancelled) { olive::CustomCacheTask task(QStringLiteral("Sequence")); bool run_returned = false; bool run_result = false; QThread *thread = QThread::create([&] { run_result = task.start(); run_returned = true; }); thread->start(); // The task waits on its condition variable until cancelled EXPECT_FALSE(thread->wait(200)); EXPECT_FALSE(run_returned); bool cancelled_emitted = false; QObject::connect(&task, &olive::CustomCacheTask::cancelled, &task, [&cancelled_emitted] { cancelled_emitted = true; }); task.Cancel(); ASSERT_TRUE(thread->wait(5000)); EXPECT_TRUE(run_returned); EXPECT_TRUE(run_result); EXPECT_TRUE(cancelled_emitted); delete thread; } TEST(TaskCustomCache, FinishWakesRunWithoutEmittingCancelled) { olive::CustomCacheTask task(QStringLiteral("Sequence")); QThread *thread = QThread::create([&] { task.start(); }); thread->start(); EXPECT_FALSE(thread->wait(200)); bool cancelled_emitted = false; QObject::connect(&task, &olive::CustomCacheTask::cancelled, &task, [&cancelled_emitted] { cancelled_emitted = true; }); task.finish(); ASSERT_TRUE(thread->wait(5000)); EXPECT_FALSE(cancelled_emitted); delete thread; } class TaskPreCacheTest : public ::testing::Test { protected: void SetUp() override { if (!olive::Core::instance()) { // Leaked intentionally: Core is process-wide (matches footage_probe_test) new olive::Core(olive::Core::CoreParams()); } olive::ColorManager::set_up_default_config(); // Use the dummy render backend so no GPU is touched (matches // preview_autocacher_test) olive::Config::current()[QStringLiteral("GraphicsBackend")] = QStringLiteral("dummy"); created_disk_manager_ = (olive::DiskManager::instance() == nullptr); if (created_disk_manager_) { olive::DiskManager::create_instance(); } created_conform_manager_ = (olive::ConformManager::instance() == nullptr); if (created_conform_manager_) { olive::ConformManager::create_instance(); } created_render_manager_ = (olive::RenderManager::instance() == nullptr); if (created_render_manager_) { olive::RenderManager::create_instance(); } // Sandbox the footage metadata cache so real probes write into the // temp dir instead of the user's cache old_cache_home_ = qgetenv("XDG_CACHE_HOME"); had_cache_home_ = qEnvironmentVariableIsSet("XDG_CACHE_HOME"); qputenv("XDG_CACHE_HOME", QDir(temp_dir_.path()).filePath(QStringLiteral("xdg")).toUtf8()); QDir().mkpath( QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); project_ = std::make_unique(); project_->initialize(); } void TearDown() override { project_.reset(); if (created_render_manager_) { olive::RenderManager::destroy_instance(); } if (created_conform_manager_) { olive::ConformManager::destroy_instance(); } if (created_disk_manager_) { olive::DiskManager::destroy_instance(); } if (had_cache_home_) { qputenv("XDG_CACHE_HOME", old_cache_home_); } else { qunsetenv("XDG_CACHE_HOME"); } } QTemporaryDir temp_dir_; QByteArray old_cache_home_; bool had_cache_home_ = false; bool created_disk_manager_ = false; bool created_conform_manager_ = false; bool created_render_manager_ = false; std::unique_ptr project_; }; namespace { // Exposes RenderTask's protected accessors so the test can inspect the // private viewer the task builds. class InspectablePreCacheTask : public olive::PreCacheTask { public: InspectablePreCacheTask(olive::Footage *footage, int index, olive::Sequence *sequence) : PreCacheTask(footage, index, sequence) { } using olive::RenderTask::video_params; using olive::RenderTask::viewer; }; } // namespace TEST_F(TaskPreCacheTest, ConstructorCopiesFootageIntoPrivateProject) { const QString path = QDir(QStringLiteral(OAK_TEST_SOURCE_DIR)) .filePath(QStringLiteral("tests/img.png")); ASSERT_TRUE(QFileInfo::exists(path)); auto *footage = new olive::Footage(path); footage->setParent(project_.get()); ASSERT_TRUE(footage->is_valid()); auto *sequence = new olive::Sequence(); sequence->setParent(project_.get()); sequence->set_default_parameters(); // Construction copies the footage into a private project and wires it to a // private viewer; it must not touch the render pipeline. Run() itself is // not exercised here since it requires live render workers. InspectablePreCacheTask task(footage, 0, sequence); EXPECT_TRUE(task.get_title().contains(path)); EXPECT_TRUE(task.get_title().contains(QStringLiteral(":0"))); // The private viewer must mirror the sequence's parameters olive::ViewerOutput *viewer = task.viewer(); ASSERT_NE(viewer, nullptr); EXPECT_EQ(task.video_params(), sequence->get_video_params()); EXPECT_EQ(viewer->get_video_params(), sequence->get_video_params()); // The viewer's texture input must be fed by a private copy of the footage: // same file, different node, living in the task's private project rather // than the caller's olive::Node *connected = viewer->get_connected_texture_output(); ASSERT_NE(connected, nullptr); EXPECT_NE(connected, footage); auto *copied_footage = dynamic_cast(connected); ASSERT_NE(copied_footage, nullptr); EXPECT_EQ(copied_footage->filename(), footage->filename()); EXPECT_NE(copied_footage->project(), project_.get()); EXPECT_EQ(copied_footage->project(), viewer->project()); }