-
Notifications
You must be signed in to change notification settings - Fork 47
test: 补充 dde-launchpad 单元测试 #813
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,151 @@ | ||
| # SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. | ||
| # SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. | ||
| # | ||
| # SPDX-License-Identifier: CC0-1.0 | ||
|
|
||
| find_package(Qt6 REQUIRED COMPONENTS Core Test) | ||
| find_package(Qt6 REQUIRED COMPONENTS Gui Qml Svg) # needed by tested-source OBJECT libs | ||
| find_package(Qt6Core CONFIG REQUIRED Private) # sortproxymodel.cpp uses QAbstractItemModelPrivate | ||
|
|
||
| qt_standard_project_setup() | ||
|
|
||
| # Coverage instrumentation (on by default so `coverage` produces a report). | ||
| # Only the test-directory targets (OBJECT libs + test executables) are affected; | ||
| # the production `launchpadcommon` shared library is left untouched. | ||
| option(ENABLE_COVERAGE "Enable coverage instrumentation for tests" ON) | ||
| if(ENABLE_COVERAGE) | ||
| add_compile_options(--coverage) | ||
| add_link_options(--coverage) | ||
| endif() | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Tested-source OBJECT libraries | ||
| # | ||
| # Per the long-term coverage rule: the tested source files (src/models/*.cpp, | ||
| # src/utils/*.cpp) are compiled as OBJECT libraries and linked *directly* | ||
| # into every test executable, instead of being reached only through the | ||
| # launchpadcommon.so shared library. This ensures gcov .gcda runtime data | ||
| # is written to the test build directory and can be captured even in | ||
| # cross-prefix environments where the shared-library build path is not | ||
| # writable at runtime. | ||
| # | ||
| # launchpadcommon is still linked afterwards for transitive dependencies | ||
| # (D-Bus adaptors, dde-integration AppMgr, etc.). Symbols defined by the | ||
| # OBJECT libs take precedence over the same symbols in the shared library, | ||
| # so the gcov-instrumented copies in the test executable are the ones that | ||
| # actually execute. | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| add_library(launchpad-test-models OBJECT | ||
| ${CMAKE_SOURCE_DIR}/src/models/appsmodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/categorizedsortproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/countlimitproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/favoritedproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/freesortproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/frequentlyusedproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/itemarrangementproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/itemspage.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/itemspagemodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/multipagesortfilterproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/recentlyinstalledproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/searchfilterproxymodel.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/models/sortproxymodel.cpp | ||
| ) | ||
| target_include_directories(launchpad-test-models PRIVATE | ||
| ${CMAKE_SOURCE_DIR}/src/models | ||
| ${CMAKE_SOURCE_DIR}/src/utils | ||
| ${CMAKE_SOURCE_DIR}/src/ddeintegration | ||
| ) | ||
| target_link_libraries(launchpad-test-models PUBLIC | ||
| Qt6::Core | ||
| Qt6::Gui | ||
| Qt6::Qml # QML_NAMED_ELEMENT / QML_SINGLETON in model headers (moc-generated registration) | ||
| Qt6::CorePrivate # sortproxymodel.cpp -> QAbstractItemModelPrivate | ||
| ${DTK_NS}::Core # DConfig, DFileWatcherManager, DPinyin (appsmodel.cpp, frequentlyusedproxymodel.cpp) | ||
| ) | ||
|
|
||
| add_library(launchpad-test-utils OBJECT | ||
| ${CMAKE_SOURCE_DIR}/src/utils/blurhash.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/utils/categoryutils.cpp | ||
| ${CMAKE_SOURCE_DIR}/src/utils/iconutils.cpp | ||
| ) | ||
| target_include_directories(launchpad-test-utils PRIVATE | ||
| ${CMAKE_SOURCE_DIR}/src/utils | ||
| ) | ||
| target_link_libraries(launchpad-test-utils PUBLIC | ||
| Qt6::Core | ||
| Qt6::Gui | ||
| Qt6::Svg | ||
| ${DTK_NS}::Gui # DIcon, DIconTheme, DSvgRenderer (iconutils.cpp) | ||
| ) | ||
|
|
||
| macro(launchpad_add_tests) | ||
| foreach(_testname ${ARGN}) | ||
| qt_add_executable(launchpad-${_testname} ${_testname}.cpp) | ||
| target_link_libraries(launchpad-${_testname} PRIVATE Qt6::Core Qt6::Test launchpadcommon) | ||
| # Test sources include the headers under test by bare name (e.g. | ||
| # `#include "categoryutils.h"`), matching how src/ includes its own | ||
| # headers — no `../src/...` relative paths. | ||
| target_include_directories(launchpad-${_testname} PRIVATE | ||
| ${CMAKE_SOURCE_DIR}/src/models | ||
| ${CMAKE_SOURCE_DIR}/src/utils | ||
| ${CMAKE_SOURCE_DIR}/src/gioutils | ||
| ) | ||
| # OBJECT libs first: their symbols take precedence over launchpadcommon.so, | ||
| # so the gcov-instrumented copies are the ones that execute at test time. | ||
| target_link_libraries(launchpad-${_testname} PRIVATE | ||
| Qt6::Core | ||
| Qt6::Test | ||
| launchpad-test-models | ||
| launchpad-test-utils | ||
| gio-utils # already OBJECT; covers src/gioutils/ (e.g. gioappinfotest) | ||
| launchpadcommon | ||
| ) | ||
| add_test(NAME launchpad-${_testname} COMMAND launchpad-${_testname}) | ||
| list(APPEND LAUNCHPAD_TEST_TARGETS launchpad-${_testname}) | ||
| endforeach() | ||
| endmacro() | ||
|
|
||
| launchpad_add_tests( | ||
| itemspagetest | ||
| gioappinfotest | ||
| searchfilterproxymodeltest | ||
| countlimitproxymodeltest | ||
| sortproxymodeltest | ||
| freesortproxymodeltest | ||
| recentlyinstalledproxymodeltest | ||
| multipagesortfilterproxymodeltest | ||
| categoryutilstest | ||
| blurhashtest | ||
| favoritedproxymodeltest | ||
| frequentlyusedproxymodeltest | ||
| itemarrangementproxymodeltest | ||
| iconutilstest | ||
| itemspagemodeltest | ||
| ) | ||
|
|
||
| # ---- coverage: execute all tests and generate a coverage report ---- | ||
| # Produces: ${CMAKE_BINARY_DIR}/coverage_report/index.html | ||
| find_program(LCOV_BIN lcov) | ||
| find_program(GENHTML_BIN genhtml) | ||
| set(_run_test_coverage "") | ||
| if(LCOV_BIN AND GENHTML_BIN) | ||
| set(_run_test_coverage | ||
| COMMAND ${LCOV_BIN} --capture --directory ${CMAKE_BINARY_DIR} | ||
| --rc lcov_branch_coverage=1 --output-file ${CMAKE_BINARY_DIR}/coverage.info | ||
| COMMAND ${LCOV_BIN} --extract ${CMAKE_BINARY_DIR}/coverage.info | ||
| --rc lcov_branch_coverage=1 | ||
| "${CMAKE_SOURCE_DIR}/src/models/*" | ||
| "${CMAKE_SOURCE_DIR}/src/utils/*" | ||
| "${CMAKE_SOURCE_DIR}/src/gioutils/*" | ||
| --output-file ${CMAKE_BINARY_DIR}/coverage_filtered.info | ||
| COMMAND ${GENHTML_BIN} ${CMAKE_BINARY_DIR}/coverage_filtered.info | ||
| --branch-coverage --output-directory ${CMAKE_BINARY_DIR}/coverage_report | ||
| ) | ||
| endif() | ||
| add_custom_target(coverage | ||
| COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure | ||
| ${_run_test_coverage} | ||
| DEPENDS ${LAUNCHPAD_TEST_TARGETS} | ||
| WORKING_DIRECTORY ${CMAKE_BINARY_DIR} | ||
| COMMENT "Run all tests and generate coverage report" | ||
| VERBATIM | ||
| ) | ||
|
Comment on lines
+131
to
151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): The Triggers: When lcov or genhtml is not installed on the build host. Suggested fix: Make the coverage target fail with a clear dependency error, or explicitly report that coverage generation was skipped instead of presenting the target as successful. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| // SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. | ||
| // | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
|
|
||
| #include <QTest> | ||
|
|
||
| #include "blurhash.hpp" | ||
|
|
||
| #include <vector> | ||
|
|
||
| namespace { | ||
|
|
||
| std::vector<unsigned char> solidImage(size_t width, size_t height, | ||
| unsigned char r, unsigned char g, unsigned char b) | ||
| { | ||
| std::vector<unsigned char> image(width * height * 3, 0); | ||
| for (size_t i = 0; i < width * height; ++i) { | ||
| image[i * 3 + 0] = r; | ||
| image[i * 3 + 1] = g; | ||
| image[i * 3 + 2] = b; | ||
| } | ||
| return image; | ||
| } | ||
|
|
||
| // blurhash layout: 1 (components) + 1 (maxAC) + 4 (DC) + 2 bytes per AC component | ||
| size_t expectedHashLength(int cx, int cy) | ||
| { | ||
| return size_t(1 + 1 + 4 + (cx * cy - 1) * 2); | ||
| } | ||
| } | ||
|
|
||
| class TestBlurhash : public QObject | ||
| { | ||
| Q_OBJECT | ||
| private slots: | ||
| void decodeEmptyStringReturnsEmpty(); | ||
| void decodeInvalidHashReturnsEmpty(); | ||
| void encodeRejectsInvalidInput(); | ||
| void encodedHashHasExpectedLength(); | ||
| void decodedImageHasExpectedDimensions(); | ||
| void roundTripPreservesAverageColor(); | ||
| void decodeWithFourBytesPerPixel(); | ||
| void decodeTooShortHashReturnsEmpty(); | ||
| void decodeWrongSizeForComponentsReturnsEmpty(); | ||
| }; | ||
|
|
||
| void TestBlurhash::decodeEmptyStringReturnsEmpty() | ||
| { | ||
| const auto img = blurhash::decode(std::string_view{}, 8, 8); | ||
| QVERIFY(img.image.empty()); | ||
| QCOMPARE(img.width, size_t(0)); | ||
| QCOMPARE(img.height, size_t(0)); | ||
| } | ||
|
|
||
| void TestBlurhash::decodeInvalidHashReturnsEmpty() | ||
| { | ||
| const auto img = blurhash::decode(std::string_view("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"), 8, 8); | ||
| QVERIFY(img.image.empty()); | ||
| } | ||
|
|
||
| void TestBlurhash::encodeRejectsInvalidInput() | ||
| { | ||
| std::vector<unsigned char> image = solidImage(4, 4, 255, 0, 0); | ||
| QVERIFY(blurhash::encode(image.data(), 0, 4, 1, 1).empty()); // width 0 | ||
| QVERIFY(blurhash::encode(nullptr, 4, 4, 1, 1).empty()); // null image | ||
| QVERIFY(blurhash::encode(image.data(), 4, 4, 0, 1).empty()); // components_x < 1 | ||
| QVERIFY(blurhash::encode(image.data(), 4, 4, 1, 10).empty()); // components_y > 9 | ||
| } | ||
|
|
||
| void TestBlurhash::encodedHashHasExpectedLength() | ||
| { | ||
| std::vector<unsigned char> image = solidImage(8, 8, 128, 64, 200); | ||
| QCOMPARE(blurhash::encode(image.data(), 8, 8, 1, 1).size(), size_t(6)); // no AC components | ||
| QCOMPARE(blurhash::encode(image.data(), 8, 8, 1, 3).size(), expectedHashLength(1, 3)); // 10 | ||
| QCOMPARE(blurhash::encode(image.data(), 8, 8, 2, 2).size(), expectedHashLength(2, 2)); // 12 | ||
| QCOMPARE(blurhash::encode(image.data(), 8, 8, 4, 4).size(), expectedHashLength(4, 4)); // 36 | ||
| } | ||
|
|
||
| void TestBlurhash::decodedImageHasExpectedDimensions() | ||
| { | ||
| constexpr size_t width = 16, height = 16; | ||
| std::vector<unsigned char> image = solidImage(width, height, 128, 64, 200); | ||
| const std::string hash = blurhash::encode(image.data(), width, height, 2, 2); | ||
| QVERIFY(!hash.empty()); | ||
|
|
||
| const auto decoded = blurhash::decode(hash, width, height); | ||
| QCOMPARE(decoded.width, width); | ||
| QCOMPARE(decoded.height, height); | ||
| QCOMPARE(decoded.image.size(), width * height * 3); | ||
| } | ||
|
|
||
| void TestBlurhash::roundTripPreservesAverageColor() | ||
| { | ||
| constexpr size_t width = 16, height = 16; | ||
| const unsigned char r = 128, g = 64, b = 200; | ||
| std::vector<unsigned char> image = solidImage(width, height, r, g, b); | ||
|
|
||
| const std::string hash = blurhash::encode(image.data(), width, height, 3, 3); | ||
| QVERIFY(!hash.empty()); | ||
|
|
||
| const auto decoded = blurhash::decode(hash, width, height); | ||
| QCOMPARE(decoded.image.size(), width * height * 3); | ||
|
|
||
| long rs = 0, gs = 0, bs = 0; | ||
| for (size_t i = 0; i < width * height; ++i) { | ||
| rs += decoded.image[i * 3 + 0]; | ||
| gs += decoded.image[i * 3 + 1]; | ||
| bs += decoded.image[i * 3 + 2]; | ||
| } | ||
| const long n = long(width * height); | ||
| const long avgR = rs / n, avgG = gs / n, avgB = bs / n; | ||
| // blurhash is lossy per-pixel, but the average (DC) stays close to the source color | ||
| QVERIFY(avgR >= long(r) - 15 && avgR <= long(r) + 15); | ||
| QVERIFY(avgG >= long(g) - 15 && avgG <= long(g) + 15); | ||
| QVERIFY(avgB >= long(b) - 15 && avgB <= long(b) + 15); | ||
| } | ||
|
|
||
| void TestBlurhash::decodeWithFourBytesPerPixel() | ||
| { | ||
| constexpr size_t width = 16, height = 16; | ||
| std::vector<unsigned char> image = solidImage(width, height, 128, 64, 200); | ||
| const std::string hash = blurhash::encode(image.data(), width, height, 3, 3); | ||
| QVERIFY(!hash.empty()); | ||
|
|
||
| const auto decoded = blurhash::decode(hash, width, height, 4); | ||
| QCOMPARE(decoded.width, width); | ||
| QCOMPARE(decoded.height, height); | ||
| // buffer should be width * height * 4 (not 3) | ||
| QCOMPARE(decoded.image.size(), width * height * 4); | ||
| // first 3 bytes of each pixel should still be the color channels | ||
| QVERIFY(decoded.image[0] != 255 || decoded.image[1] != 255 || decoded.image[2] != 255); | ||
| } | ||
|
|
||
| void TestBlurhash::decodeTooShortHashReturnsEmpty() | ||
| { | ||
| // hash must be >= 10 chars (1 + 1 + 4 + at least 4 for 2x2 components - 1 AC = 2 bytes -> 8, but 10 is the minimum check) | ||
| const auto img = blurhash::decode(std::string_view("short"), 8, 8); | ||
| QVERIFY(img.image.empty()); | ||
| } | ||
|
|
||
| void TestBlurhash::decodeWrongSizeForComponentsReturnsEmpty() | ||
| { | ||
| // 1 component char says 4x4 = 16 components -> needs 1+1+4+(16-1)*2 = 36 chars | ||
| // but we'll give only 12 chars | ||
| const auto img = blurhash::decode(std::string_view("LFE.}?a]a]a]a]a]a]a]"), 8, 8); | ||
| // this hash has wrong length for its declared components -> should return empty | ||
| QVERIFY(img.image.empty()); | ||
| } | ||
|
|
||
| QTEST_MAIN(TestBlurhash) | ||
| #include "blurhashtest.moc" | ||
Uh oh!
There was an error while loading. Please reload this page.