From ae095bbb0e8a071cc4e61dae7e074d679cf87545 Mon Sep 17 00:00:00 2001 From: zhang hongyuan <100520587+Metastarx@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:20:38 +0800 Subject: [PATCH] feat: add an Intel media (VAAPI) driver detector to ll-driver-detect Signed-off-by: zhang hongyuan <100520587+Metastarx@users.noreply.github.com> --- apps/ll-driver-detect/CMakeLists.txt | 1 + apps/ll-driver-detect/README.md | 6 +- .../src/driver_detection_manager.cpp | 4 +- .../src/intel_media_driver_detector.cpp | 262 ++++++++++++++++++ .../src/intel_media_driver_detector.h | 63 +++++ docs/pages/en/guide/reference/driver.md | 2 +- docs/pages/guide/reference/driver.md | 2 +- libs/linglong/tests/ll-tests/CMakeLists.txt | 2 + .../intel_media_driver_detector_test.cpp | 209 ++++++++++++++ 9 files changed, 546 insertions(+), 5 deletions(-) create mode 100644 apps/ll-driver-detect/src/intel_media_driver_detector.cpp create mode 100644 apps/ll-driver-detect/src/intel_media_driver_detector.h create mode 100644 libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/intel_media_driver_detector_test.cpp diff --git a/apps/ll-driver-detect/CMakeLists.txt b/apps/ll-driver-detect/CMakeLists.txt index 485de4720..ab0093fbe 100644 --- a/apps/ll-driver-detect/CMakeLists.txt +++ b/apps/ll-driver-detect/CMakeLists.txt @@ -11,6 +11,7 @@ pfl_add_executable( ./src/main.cpp ./src/driver_detection_config.cpp ./src/nvidia_driver_detector.cpp + ./src/intel_media_driver_detector.cpp ./src/application_singleton.cpp ./src/driver_detection_manager.cpp ./src/dbus_notifier.cpp diff --git a/apps/ll-driver-detect/README.md b/apps/ll-driver-detect/README.md index f08ed1875..21ccda133 100644 --- a/apps/ll-driver-detect/README.md +++ b/apps/ll-driver-detect/README.md @@ -4,7 +4,7 @@ ## 功能概述 -- **可扩展的驱动检测**: 采用插件式架构 (`DriverDetectionManager`),可以轻松扩展以支持多种类型的显卡驱动(目前已支持 NVIDIA)。 +- **可扩展的驱动检测**: 采用插件式架构 (`DriverDetectionManager`),可以轻松扩展以支持多种类型的显卡驱动(目前已支持 NVIDIA 与 Intel 媒体驱动)。 - **单例运行保证**: 通过文件锁 (`ApplicationSingleton`) 确保在任何时候只有一个检测进程在运行,避免资源浪费和重复通知。 - **简化的用户配置**: 提供一个简单的配置选项,允许用户选择“不再提醒”,配置被保存在 `~/.config/linglong/driver_detection.json` 中。 - **统一的用户通知**: 当检测到一个或多个可安装的驱动时,通过 DBus 向用户发送一个统一的通知。 @@ -57,7 +57,7 @@ ll-driver-detect --help 1. **获取单例锁**: 程序启动时,`ApplicationSingleton` 会尝试在 `$XDG_CONFIG_HOME/linglong/ll-driver-detect.lock`(如果 `XDG_CONFIG_HOME` 未设置,则默认为 `~/.config/linglong/ll-driver-detect.lock`)获取文件锁。如果锁已被另一实例持有,则当前进程会安静地退出。 2. **初始化驱动管理器**: 创建一个 `DriverDetectionManager` 实例。 -3. **注册与检测**: 管理器的构造函数会注册所有可用的 `DriverDetector` 子类(例如 `NVIDIADriverDetector`)。随后,管理器调用每个检测器的 `detect()` 方法。 +3. **注册与检测**: 管理器的构造函数会注册所有可用的 `DriverDetector` 子类(例如 `NVIDIADriverDetector`、`IntelMediaDriverDetector`)。随后,管理器调用每个检测器的 `detect()` 方法。 4. **结果聚合**: `detect()` 方法返回驱动信息(如果检测成功),包括驱动标识、版本、玲珑包名和当前安装状态。管理器收集所有成功检测到的驱动信息。 5. **执行操作**: - 如果没有检测到任何驱动,程序退出。 @@ -86,6 +86,7 @@ apps/ll-driver-detect/ │ ├── driver_detection_manager.h/.cpp # 驱动检测协调器 │ ├── driver_detector.h/.cpp # 驱动检测器基类 │ ├── nvidia_driver_detector.h/.cpp # NVIDIA 驱动检测器实现 +│ ├── intel_media_driver_detector.h/.cpp # Intel 媒体驱动检测器实现 │ ├── driver_detection_config.h/.cpp # 配置管理 │ └── dbus_notifier.h/.cpp # DBus 通知实现 ├── README.md # 本文档 @@ -98,6 +99,7 @@ apps/ll-driver-detect/ - **`DriverDetectionManager`**: 负责注册和运行所有具体的驱动检测器。 - **`DriverDetector`**: 驱动检测器的抽象基类,定义了 `detect()` 接口。 - **`NVIDIADriverDetector`**: `DriverDetector` 的一个实现,专门用于检测 NVIDIA 驱动。 +- **`IntelMediaDriverDetector`**: `DriverDetector` 的一个实现,用于检测 Intel 视频编解码(VAAPI)驱动。 - **`DriverDetectionConfigManager`**: 负责加载和保存 `driver_detection.json` 配置文件。 - **`DBusNotifier`**: 封装了通过 DBus 发送通知的逻辑。 diff --git a/apps/ll-driver-detect/src/driver_detection_manager.cpp b/apps/ll-driver-detect/src/driver_detection_manager.cpp index 0f03db9d3..0d97581c8 100644 --- a/apps/ll-driver-detect/src/driver_detection_manager.cpp +++ b/apps/ll-driver-detect/src/driver_detection_manager.cpp @@ -4,6 +4,7 @@ #include "driver_detection_manager.h" +#include "intel_media_driver_detector.h" #include "linglong/utils/cmd.h" #include "linglong/utils/log/log.h" #include "nvidia_driver_detector.h" @@ -49,7 +50,8 @@ void DriverDetectionManager::registerDetectors() // Register NVIDIA detector detectors_.push_back(std::make_unique()); - // TODO: Add other driver detector + // Register Intel media (VAAPI) detector + detectors_.push_back(std::make_unique()); LogD("Registered {} driver detectors", detectors_.size()); } diff --git a/apps/ll-driver-detect/src/intel_media_driver_detector.cpp b/apps/ll-driver-detect/src/intel_media_driver_detector.cpp new file mode 100644 index 000000000..1e356b94d --- /dev/null +++ b/apps/ll-driver-detect/src/intel_media_driver_detector.cpp @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "intel_media_driver_detector.h" + +#include + +#include "linglong/package/version.h" +#include "linglong/utils/cmd.h" +#include "linglong/utils/error/error.h" +#include "linglong/utils/log/log.h" + +#include + +#include +#include + +namespace linglong::driver::detect { + +IntelMediaDriverDetector::IntelMediaDriverDetector( + std::vector intelGpuModulePaths) + : intelGpuModulePaths_(std::move(intelGpuModulePaths)) +{ +} + +utils::error::Result IntelMediaDriverDetector::detect() +{ + LINGLONG_TRACE("Intel media driver detection started"); + + // Without an Intel GPU there is no reason to talk to the repository at all, so + // bail out before running any ll-cli command. + if (!isIntelGpuPresent()) { + return LINGLONG_ERR("No Intel GPU found on the host"); + } + + const std::string packageName = getDriverIdentify(); + + // Check if package exists in repo + auto packageInfoResult = getPackageInfoFromRemoteRepo(packageName); + if (!packageInfoResult) { + return LINGLONG_ERR("Failed to get package info from remote repo: " + + packageInfoResult.error().message()); + } + + auto installedResult = checkPackageInstalled(packageName); + if (!installedResult) { + return LINGLONG_ERR("Failed to check if package is installed: " + + installedResult.error().message()); + } + + if (*installedResult) { + if (!packageInfoResult->first) { + LogW("Intel media driver package is installed locally but not found in remote repo: {}", + packageName); + return LINGLONG_ERR("Cannot find Intel media driver package in remote repo."); + } + + auto upgradableResult = checkPackageUpgradable(packageName); + if (!upgradableResult) { + return LINGLONG_ERR("Failed to check if package is upgradable: " + + upgradableResult.error().message()); + } + if (*upgradableResult) { + LogD("Intel media driver package can be upgraded: {}, install from remote repo.", + packageName); + return packageInfoResult->second; + } + return LINGLONG_ERR("Intel media driver package is already installed and up-to-date."); + } + + if (!packageInfoResult->first) { + return LINGLONG_ERR("Intel media driver package not found in remote repo"); + } + + LogD("Intel media driver package is not installed, install from remote repo."); + return packageInfoResult->second; +} + +utils::error::Result> +IntelMediaDriverDetector::getPackageInfoFromRemoteRepo(const std::string &packageName) +{ + using json = nlohmann::json; + + LINGLONG_TRACE("Check if Intel media driver package exists in repository"); + + GraphicsDriverInfo driverInfo; + driverInfo.identify = getDriverIdentify(); + driverInfo.packageName = packageName; + + // Execute ll-cli search command to look for the driver package. The result is a + // map from repository alias to the packages found in that repository. + auto ret = linglong::utils::Cmd("ll-cli").exec({ "--json", "search", packageName }); + if (!ret) { + return LINGLONG_ERR("Search command failed: " + ret.error().message()); + } + + bool found = false; + + try { + json j = json::parse(*ret); + + for (const auto &[repoAlias, packages] : j.items()) { + if (!packages.is_array()) + continue; + + for (const auto &item : packages) { + if (!item.is_object()) + continue; + + // The search API performs a fuzzy match, so unrelated packages can + // show up here. Only accept the exact identify we are looking for. + auto idIter = item.find("id"); + if (idIter == item.end() || !idIter->is_string() + || idIter->get() != packageName) { + continue; + } + + auto versionIter = item.find("version"); + if (versionIter == item.end() || !versionIter->is_string()) + continue; + + std::string_view currentVersionStr = versionIter->get(); + + // A package may be published in several repositories; keep the newest + // version and remember which repository provides it. + if (!found || compareVersions(currentVersionStr, driverInfo.packageVersion)) { + driverInfo.repoName = repoAlias; + driverInfo.packageVersion = currentVersionStr; + found = true; + } + } + } + } catch (const nlohmann::json::parse_error &e) { + return LINGLONG_ERR("Failed to parse search result JSON: " + std::string(e.what())); + } + + return std::make_pair(found, driverInfo); +} + +utils::error::Result +IntelMediaDriverDetector::checkPackageInstalled(const std::string &packageName) +{ + LINGLONG_TRACE("Check if Intel media driver package is installed"); + + auto installedInfo = getInstalledGraphicsDriverInfo(packageName); + if (!installedInfo) { + return LINGLONG_ERR(installedInfo); + } + + return installedInfo->first; +} + +utils::error::Result +IntelMediaDriverDetector::checkPackageUpgradable(const std::string &packageName) +{ + LINGLONG_TRACE("Check if Intel media driver package is upgradable"); + + auto upgradableDriverInfo = getPackageInfoFromRemoteRepo(packageName); + if (!upgradableDriverInfo) { + return LINGLONG_ERR("Failed to get upgradable package info: " + + upgradableDriverInfo.error().message()); + } + + if (!upgradableDriverInfo->first) { + return LINGLONG_ERR("Intel media driver package not found in remote repo"); + } + + auto installedDriverInfo = getInstalledGraphicsDriverInfo(packageName); + if (!installedDriverInfo) { + return LINGLONG_ERR("Failed to get installed package info: " + + installedDriverInfo.error().message()); + } + + if (!installedDriverInfo->first) { + return LINGLONG_ERR("Intel media driver package is not installed"); + } + + LogD("{} current version:{}, latest version {}", + packageName, + installedDriverInfo->second.packageVersion, + upgradableDriverInfo->second.packageVersion); + + return compareVersions(upgradableDriverInfo->second.packageVersion, + installedDriverInfo->second.packageVersion); +} + +bool IntelMediaDriverDetector::isIntelGpuPresent() const +{ + // The i915 and xe kernel modules both expose an Intel GPU to userspace; the + // presence of either module directory is what we treat as "Intel GPU present". + return std::any_of(intelGpuModulePaths_.begin(), + intelGpuModulePaths_.end(), + [](const std::filesystem::path &modulePath) { + return std::filesystem::exists(modulePath); + }); +} + +utils::error::Result> +IntelMediaDriverDetector::getInstalledGraphicsDriverInfo(const std::string &packageName) const +{ + using json = nlohmann::json; + + LINGLONG_TRACE("Get installed Intel media graphics driver info"); + + GraphicsDriverInfo driverInfo; + driverInfo.identify = getDriverIdentify(); + driverInfo.packageName = packageName; + + auto listResult = linglong::utils::Cmd("ll-cli").exec({ "--json", "list", "--type=extension" }); + if (!listResult) { + return LINGLONG_ERR(listResult); + } + + bool installed = false; + try { + json installedExtensions = json::parse(*listResult); + if (!installedExtensions.is_array()) { + return LINGLONG_ERR("Invalid list result JSON: expected an array"); + } + + for (const auto &item : installedExtensions) { + if (!item.is_object()) + continue; + + auto idIter = item.find("id"); + if (idIter == item.end() || !idIter->is_string() + || idIter->get() != packageName) { + continue; + } + + auto versionIter = item.find("version"); + if (versionIter == item.end() || !versionIter->is_string()) { + return LINGLONG_ERR("Installed package found but version field is missing: " + + packageName); + } + + driverInfo.packageVersion = versionIter->get(); + installed = true; + break; + } + } catch (const nlohmann::json::parse_error &e) { + return LINGLONG_ERR("Failed to parse installed package JSON: " + std::string(e.what())); + } + + return std::make_pair(installed, driverInfo); +} + +bool IntelMediaDriverDetector::compareVersions(std::string_view v1, std::string_view v2) const noexcept +{ + auto ver1 = package::Version::parse(std::string(v1)); + auto ver2 = package::Version::parse(std::string(v2)); + + if (!ver1 || !ver2) { + LogW("Failed to parse versions: {} , {}", v1, v2); + return false; + } + + return *ver1 > *ver2; +} + +} // namespace linglong::driver::detect diff --git a/apps/ll-driver-detect/src/intel_media_driver_detector.h b/apps/ll-driver-detect/src/intel_media_driver_detector.h new file mode 100644 index 000000000..3280d8e70 --- /dev/null +++ b/apps/ll-driver-detect/src/intel_media_driver_detector.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include "driver_detector.h" + +#include +#include +#include +#include + +namespace linglong::driver::detect { + +// Detects the Intel video codec (VAAPI) driver package org.deepin.driver.media.intel. +// +// Unlike the NVIDIA package the Intel package carries no version segment in its +// identify: a single package covers both new and legacy Intel GPUs, so the package +// name is the identify itself and the version is resolved from the remote repository. +class IntelMediaDriverDetector : public DriverDetector +{ +public: + static constexpr auto kIntelMediaPackageIdentify = "org.deepin.driver.media.intel"; + + // Paths of the kernel modules that expose an Intel GPU. Both the legacy i915 + // driver and the newer xe driver are probed so detection works on old and new + // Intel hardware. The list is injectable, exactly like the NVIDIA version file + // path, so tests never depend on the hardware of the machine running them. + explicit IntelMediaDriverDetector( + std::vector intelGpuModulePaths = { "/sys/module/i915", + "/sys/module/xe" }); + ~IntelMediaDriverDetector() override = default; + + // Main detection method - returns driver info if detected + utils::error::Result detect() override; + + // Check if the driver package is installed + utils::error::Result checkPackageInstalled(const std::string &packageName) override; + + // Check if the driver package can be upgraded + utils::error::Result checkPackageUpgradable(const std::string &packageName) override; + + // Check if the driver package exists in repo + virtual utils::error::Result> + getPackageInfoFromRemoteRepo(const std::string &packageName); + + // Get the type of driver this detector handles + std::string getDriverIdentify() const override { return kIntelMediaPackageIdentify; } + +protected: + bool compareVersions(std::string_view v1, std::string_view v2) const noexcept; + +private: + // Returns true as soon as one of the configured Intel GPU module paths exists + bool isIntelGpuPresent() const; + utils::error::Result> + getInstalledGraphicsDriverInfo(const std::string &packageName) const; + + std::vector intelGpuModulePaths_; +}; + +} // namespace linglong::driver::detect diff --git a/docs/pages/en/guide/reference/driver.md b/docs/pages/en/guide/reference/driver.md index 434aa464b..2039f6adc 100644 --- a/docs/pages/en/guide/reference/driver.md +++ b/docs/pages/en/guide/reference/driver.md @@ -22,4 +22,4 @@ Drivers not included in the base that require additional installation: - NVIDIA proprietary drivers: Install via `sudo ll-cli install org.deepin.driver.display.nvidia.570-124-04`. The `570-124-04` is the driver version number, which must match the driver version installed on the host system. Check the host driver version through the `/sys/module/nvidia/version` file. - Glenfly graphics drivers: Install via `sudo ll-cli install com.glenfly.driver.display.arise`. -- Intel video codec drivers (VAAPI): Install via `sudo ll-cli install org.deepin.driver.media.intel`, which includes support for both new and legacy Intel graphics cards. +- Intel video codec drivers (VAAPI): Install via `sudo ll-cli install org.deepin.driver.media.intel`, which includes support for both new and legacy Intel graphics cards. When an Intel GPU is present on the host and the package is available in the configured remote repository, `ll-driver-detect` detects this driver and offers to install it. diff --git a/docs/pages/guide/reference/driver.md b/docs/pages/guide/reference/driver.md index 7876a72d2..f626a1a14 100644 --- a/docs/pages/guide/reference/driver.md +++ b/docs/pages/guide/reference/driver.md @@ -22,4 +22,4 @@ - 英伟达闭源驱动,通过 `sudo ll-cli install org.deepin.driver.display.nvidia.570-124-04` 安装。其中 `570-124-04` 是驱动版本号,需要与宿主机安装的驱动版本匹配,通过宿主机 `/sys/module/nvidia/version` 文件查看宿主机驱动的版本。 - 格兰菲显卡驱动,通过 `sudo ll-cli install com.glenfly.driver.display.arise` 安装。 -- 英特尔视频编解码驱动(VAAPI),通过 `sudo ll-cli install org.deepin.driver.media.intel` 安装,包含了新/旧 Intel 显卡的支持。 +- 英特尔视频编解码驱动(VAAPI),通过 `sudo ll-cli install org.deepin.driver.media.intel` 安装,包含了新/旧 Intel 显卡的支持。当宿主机上检测到 Intel 显卡、且配置的远端仓库中存在该软件包时,`ll-driver-detect` 会检测该驱动并提示安装。 diff --git a/libs/linglong/tests/ll-tests/CMakeLists.txt b/libs/linglong/tests/ll-tests/CMakeLists.txt index 58e11583b..9f3e000fc 100644 --- a/libs/linglong/tests/ll-tests/CMakeLists.txt +++ b/libs/linglong/tests/ll-tests/CMakeLists.txt @@ -89,11 +89,13 @@ pfl_add_executable( # ll-driver-detect tests ${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/nvidia_driver_detector.cpp + ${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/intel_media_driver_detector.cpp ${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/driver_detection_config.cpp ${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/dbus_notifier.cpp ${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/application_singleton.cpp ${PROJECT_SOURCE_DIR}/apps/ll-driver-detect/src/driver_detection_manager.cpp src/apps/ll-driver-detect/nvidia_driver_detector_test.cpp + src/apps/ll-driver-detect/intel_media_driver_detector_test.cpp src/apps/ll-driver-detect/driver_detection_config_test.cpp src/apps/ll-driver-detect/application_singleton_test.cpp src/apps/ll-driver-detect/driver_detection_manager_test.cpp diff --git a/libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/intel_media_driver_detector_test.cpp b/libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/intel_media_driver_detector_test.cpp new file mode 100644 index 000000000..ef742f2bb --- /dev/null +++ b/libs/linglong/tests/ll-tests/src/apps/ll-driver-detect/intel_media_driver_detector_test.cpp @@ -0,0 +1,209 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include +#include + +#include "common/tempdir.h" +#include "intel_media_driver_detector.h" + +#include +#include +#include +#include +#include + +using namespace linglong::driver::detect; +using ::testing::_; +using ::testing::Return; + +// Exposes the protected compareVersions helper and mocks every path that would +// otherwise shell out to ll-cli, so the whole detection flow can be exercised +// without a real repository or an Intel GPU. +class TestableIntelMediaDriverDetector : public IntelMediaDriverDetector +{ +public: + explicit TestableIntelMediaDriverDetector( + std::vector intelGpuModulePaths) + : IntelMediaDriverDetector(std::move(intelGpuModulePaths)) + { + } + + bool isVersionNewer(std::string_view candidate, std::string_view current) const noexcept + { + return compareVersions(candidate, current); + } + + MOCK_METHOD((linglong::utils::error::Result>), + getPackageInfoFromRemoteRepo, + (const std::string &packageName), + (override)); + + MOCK_METHOD(linglong::utils::error::Result, + checkPackageInstalled, + (const std::string &), + (override)); + MOCK_METHOD(linglong::utils::error::Result, + checkPackageUpgradable, + (const std::string &), + (override)); +}; + +class IntelMediaDriverDetectorTest : public ::testing::Test +{ +protected: + // The kernel module directory is the only hardware dependent input of the + // detector, so materialising it in a temp dir keeps these tests host agnostic. + static std::filesystem::path createGpuModulePath(const TempDir &tempDir) + { + auto modulePath = tempDir.path() / "i915"; + std::filesystem::create_directories(modulePath); + return modulePath; + } +}; + +TEST_F(IntelMediaDriverDetectorTest, CompareVersionsUsesSemanticOrdering) +{ + TestableIntelMediaDriverDetector detector{ std::vector{} }; + + // "1.0.10" is newer than "1.0.9" semantically, which a plain string compare + // would get wrong. + EXPECT_TRUE(detector.isVersionNewer("1.0.10", "1.0.9")); + EXPECT_FALSE(detector.isVersionNewer("1.0.9", "1.0.10")); +} + +TEST_F(IntelMediaDriverDetectorTest, Detect_NoIntelGpu) +{ + TempDir tempDir; + // None of the injected module paths exist, so no Intel GPU is present. The + // detector must report an error without ever querying the repository. + TestableIntelMediaDriverDetector detector({ tempDir.path() / "does-not-exist" }); + + EXPECT_CALL(detector, getPackageInfoFromRemoteRepo(_)).Times(0); + EXPECT_CALL(detector, checkPackageInstalled(_)).Times(0); + + auto result = detector.detect(); + + ASSERT_FALSE(result.has_value()); +} + +TEST_F(IntelMediaDriverDetectorTest, Detect_Success_PackageNotInstalled) +{ + TempDir tempDir; + TestableIntelMediaDriverDetector detector({ createGpuModulePath(tempDir) }); + + const std::string expectedPackageName = detector.getDriverIdentify(); + const std::string expectedVersion = "1.0.0"; + const std::string expectedRepo = "stable"; + + EXPECT_CALL(detector, getPackageInfoFromRemoteRepo(expectedPackageName)) + .WillOnce(Return(std::make_pair(true, + GraphicsDriverInfo{ + .identify = detector.getDriverIdentify(), + .packageName = expectedPackageName, + .packageVersion = expectedVersion, + .repoName = expectedRepo, + }))); + + EXPECT_CALL(detector, checkPackageInstalled(expectedPackageName)).WillOnce(Return(false)); + + auto result = detector.detect(); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->identify, detector.getDriverIdentify()); + EXPECT_EQ(result->packageName, expectedPackageName); + EXPECT_EQ(result->packageVersion, expectedVersion); + EXPECT_EQ(result->repoName, expectedRepo); +} + +TEST_F(IntelMediaDriverDetectorTest, Detect_Success_PackageUpgradable) +{ + TempDir tempDir; + TestableIntelMediaDriverDetector detector({ createGpuModulePath(tempDir) }); + + const std::string packageName = detector.getDriverIdentify(); + const std::string newerVersion = "1.0.1"; + + EXPECT_CALL(detector, getPackageInfoFromRemoteRepo(packageName)) + .WillOnce(Return(std::make_pair(true, + GraphicsDriverInfo{ + .identify = detector.getDriverIdentify(), + .packageName = packageName, + .packageVersion = newerVersion, + .repoName = "stable", + }))); + + EXPECT_CALL(detector, checkPackageInstalled(packageName)).WillOnce(Return(true)); + EXPECT_CALL(detector, checkPackageUpgradable(packageName)).WillOnce(Return(true)); + + auto result = detector.detect(); + + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(result->identify, detector.getDriverIdentify()); + EXPECT_EQ(result->packageName, packageName); + EXPECT_EQ(result->packageVersion, newerVersion); +} + +TEST_F(IntelMediaDriverDetectorTest, Detect_Success_PackageInstalledAndUpToDate) +{ + TempDir tempDir; + TestableIntelMediaDriverDetector detector({ createGpuModulePath(tempDir) }); + + const std::string packageName = detector.getDriverIdentify(); + + EXPECT_CALL(detector, getPackageInfoFromRemoteRepo(packageName)) + .WillOnce(Return(std::make_pair(true, + GraphicsDriverInfo{ + .identify = detector.getDriverIdentify(), + .packageName = packageName, + .packageVersion = "1.0.0", + .repoName = "stable", + }))); + + EXPECT_CALL(detector, checkPackageInstalled(packageName)).WillOnce(Return(true)); + EXPECT_CALL(detector, checkPackageUpgradable(packageName)).WillOnce(Return(false)); + + auto result = detector.detect(); + + // Nothing to offer when the installed package is already the newest one. + ASSERT_FALSE(result.has_value()); +} + +TEST_F(IntelMediaDriverDetectorTest, Detect_GpuPresentButPackageMissingInRepo) +{ + TempDir tempDir; + TestableIntelMediaDriverDetector detector({ createGpuModulePath(tempDir) }); + + const std::string packageName = detector.getDriverIdentify(); + + EXPECT_CALL(detector, getPackageInfoFromRemoteRepo(packageName)) + .WillOnce(Return(std::make_pair(false, + GraphicsDriverInfo{ + .identify = detector.getDriverIdentify(), + .packageName = packageName, + }))); + + EXPECT_CALL(detector, checkPackageInstalled(packageName)).WillOnce(Return(false)); + + auto result = detector.detect(); + + ASSERT_FALSE(result.has_value()); +} + +TEST_F(IntelMediaDriverDetectorTest, Detect_RemoteRepoQueryFailure) +{ + LINGLONG_TRACE("Testing remote repo query failure"); + + TempDir tempDir; + TestableIntelMediaDriverDetector detector({ createGpuModulePath(tempDir) }); + + const std::string packageName = detector.getDriverIdentify(); + + EXPECT_CALL(detector, getPackageInfoFromRemoteRepo(packageName)) + .WillOnce(Return(LINGLONG_ERR("ll-cli search failed"))); + + auto result = detector.detect(); + + ASSERT_FALSE(result.has_value()); +}