Skip to content

Commit 2e71bba

Browse files
MyLeeJiEundeepin-bot[bot]
authored andcommitted
test: resolve conflicts with upstream master (rebase onto f7f8c96)
- Rebase PR #813 onto current upstream/master - Keep upstream's Qt DBus component in root CMakeLists.txt - Align test desktopId assertions with upstream's normalizedDesktopId semantics (pages store suffixed ids, AppsModel strips the suffix)
1 parent 457a040 commit 2e71bba

17 files changed

Lines changed: 2862 additions & 110 deletions

‎CMakeLists.txt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.10)
66

77
project(dde-launchpad VERSION 0.7.0)
88

9-
option(BUILD_TEST "Whether or not to build the tests" OFF)
9+
option(BUILD_TESTING "Whether or not to build the tests" OFF)
1010
option(CMAKE_EXPORT_COMPILE_COMMANDS "clangd support" ON)
1111

1212
set(CMAKE_CXX_STANDARD 17) # blurhash requires 17, otherwish we can still use 14
@@ -65,7 +65,7 @@ add_subdirectory(src/treelandintegration)
6565
add_subdirectory(src/models)
6666
add_subdirectory(qml/windowed)
6767

68-
if(BUILD_TEST)
68+
if(BUILD_TESTING)
6969
enable_testing()
7070
add_subdirectory(tests)
7171
endif()

‎tests/CMakeLists.txt‎

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,151 @@
1-
# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
1+
# SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd.
22
#
33
# SPDX-License-Identifier: CC0-1.0
44

55
find_package(Qt6 REQUIRED COMPONENTS Core Test)
6+
find_package(Qt6 REQUIRED COMPONENTS Gui Qml Svg) # needed by tested-source OBJECT libs
7+
find_package(Qt6Core CONFIG REQUIRED Private) # sortproxymodel.cpp uses QAbstractItemModelPrivate
68

79
qt_standard_project_setup()
810

11+
# Coverage instrumentation (on by default so `coverage` produces a report).
12+
# Only the test-directory targets (OBJECT libs + test executables) are affected;
13+
# the production `launchpadcommon` shared library is left untouched.
14+
option(ENABLE_COVERAGE "Enable coverage instrumentation for tests" ON)
15+
if(ENABLE_COVERAGE)
16+
add_compile_options(--coverage)
17+
add_link_options(--coverage)
18+
endif()
19+
20+
# ---------------------------------------------------------------------------
21+
# Tested-source OBJECT libraries
22+
#
23+
# Per the long-term coverage rule: the tested source files (src/models/*.cpp,
24+
# src/utils/*.cpp) are compiled as OBJECT libraries and linked *directly*
25+
# into every test executable, instead of being reached only through the
26+
# launchpadcommon.so shared library. This ensures gcov .gcda runtime data
27+
# is written to the test build directory and can be captured even in
28+
# cross-prefix environments where the shared-library build path is not
29+
# writable at runtime.
30+
#
31+
# launchpadcommon is still linked afterwards for transitive dependencies
32+
# (D-Bus adaptors, dde-integration AppMgr, etc.). Symbols defined by the
33+
# OBJECT libs take precedence over the same symbols in the shared library,
34+
# so the gcov-instrumented copies in the test executable are the ones that
35+
# actually execute.
36+
# ---------------------------------------------------------------------------
37+
38+
add_library(launchpad-test-models OBJECT
39+
${CMAKE_SOURCE_DIR}/src/models/appsmodel.cpp
40+
${CMAKE_SOURCE_DIR}/src/models/categorizedsortproxymodel.cpp
41+
${CMAKE_SOURCE_DIR}/src/models/countlimitproxymodel.cpp
42+
${CMAKE_SOURCE_DIR}/src/models/favoritedproxymodel.cpp
43+
${CMAKE_SOURCE_DIR}/src/models/freesortproxymodel.cpp
44+
${CMAKE_SOURCE_DIR}/src/models/frequentlyusedproxymodel.cpp
45+
${CMAKE_SOURCE_DIR}/src/models/itemarrangementproxymodel.cpp
46+
${CMAKE_SOURCE_DIR}/src/models/itemspage.cpp
47+
${CMAKE_SOURCE_DIR}/src/models/itemspagemodel.cpp
48+
${CMAKE_SOURCE_DIR}/src/models/multipagesortfilterproxymodel.cpp
49+
${CMAKE_SOURCE_DIR}/src/models/recentlyinstalledproxymodel.cpp
50+
${CMAKE_SOURCE_DIR}/src/models/searchfilterproxymodel.cpp
51+
${CMAKE_SOURCE_DIR}/src/models/sortproxymodel.cpp
52+
)
53+
target_include_directories(launchpad-test-models PRIVATE
54+
${CMAKE_SOURCE_DIR}/src/models
55+
${CMAKE_SOURCE_DIR}/src/utils
56+
${CMAKE_SOURCE_DIR}/src/ddeintegration
57+
)
58+
target_link_libraries(launchpad-test-models PUBLIC
59+
Qt6::Core
60+
Qt6::Gui
61+
Qt6::Qml # QML_NAMED_ELEMENT / QML_SINGLETON in model headers (moc-generated registration)
62+
Qt6::CorePrivate # sortproxymodel.cpp -> QAbstractItemModelPrivate
63+
${DTK_NS}::Core # DConfig, DFileWatcherManager, DPinyin (appsmodel.cpp, frequentlyusedproxymodel.cpp)
64+
)
65+
66+
add_library(launchpad-test-utils OBJECT
67+
${CMAKE_SOURCE_DIR}/src/utils/blurhash.cpp
68+
${CMAKE_SOURCE_DIR}/src/utils/categoryutils.cpp
69+
${CMAKE_SOURCE_DIR}/src/utils/iconutils.cpp
70+
)
71+
target_include_directories(launchpad-test-utils PRIVATE
72+
${CMAKE_SOURCE_DIR}/src/utils
73+
)
74+
target_link_libraries(launchpad-test-utils PUBLIC
75+
Qt6::Core
76+
Qt6::Gui
77+
Qt6::Svg
78+
${DTK_NS}::Gui # DIcon, DIconTheme, DSvgRenderer (iconutils.cpp)
79+
)
80+
981
macro(launchpad_add_tests)
1082
foreach(_testname ${ARGN})
1183
qt_add_executable(launchpad-${_testname} ${_testname}.cpp)
12-
target_link_libraries(launchpad-${_testname} PRIVATE Qt6::Core Qt6::Test launchpadcommon)
84+
# Test sources include the headers under test by bare name (e.g.
85+
# `#include "categoryutils.h"`), matching how src/ includes its own
86+
# headers — no `../src/...` relative paths.
87+
target_include_directories(launchpad-${_testname} PRIVATE
88+
${CMAKE_SOURCE_DIR}/src/models
89+
${CMAKE_SOURCE_DIR}/src/utils
90+
${CMAKE_SOURCE_DIR}/src/gioutils
91+
)
92+
# OBJECT libs first: their symbols take precedence over launchpadcommon.so,
93+
# so the gcov-instrumented copies are the ones that execute at test time.
94+
target_link_libraries(launchpad-${_testname} PRIVATE
95+
Qt6::Core
96+
Qt6::Test
97+
launchpad-test-models
98+
launchpad-test-utils
99+
gio-utils # already OBJECT; covers src/gioutils/ (e.g. gioappinfotest)
100+
launchpadcommon
101+
)
13102
add_test(NAME launchpad-${_testname} COMMAND launchpad-${_testname})
103+
list(APPEND LAUNCHPAD_TEST_TARGETS launchpad-${_testname})
14104
endforeach()
15105
endmacro()
16106

17107
launchpad_add_tests(
18108
itemspagetest
19109
gioappinfotest
20110
searchfilterproxymodeltest
111+
countlimitproxymodeltest
112+
sortproxymodeltest
113+
freesortproxymodeltest
114+
recentlyinstalledproxymodeltest
115+
multipagesortfilterproxymodeltest
116+
categoryutilstest
117+
blurhashtest
118+
favoritedproxymodeltest
119+
frequentlyusedproxymodeltest
120+
itemarrangementproxymodeltest
121+
iconutilstest
122+
itemspagemodeltest
123+
)
124+
125+
# ---- coverage: execute all tests and generate a coverage report ----
126+
# Produces: ${CMAKE_BINARY_DIR}/coverage_report/index.html
127+
find_program(LCOV_BIN lcov)
128+
find_program(GENHTML_BIN genhtml)
129+
set(_run_test_coverage "")
130+
if(LCOV_BIN AND GENHTML_BIN)
131+
set(_run_test_coverage
132+
COMMAND ${LCOV_BIN} --capture --directory ${CMAKE_BINARY_DIR}
133+
--rc lcov_branch_coverage=1 --output-file ${CMAKE_BINARY_DIR}/coverage.info
134+
COMMAND ${LCOV_BIN} --extract ${CMAKE_BINARY_DIR}/coverage.info
135+
--rc lcov_branch_coverage=1
136+
"${CMAKE_SOURCE_DIR}/src/models/*"
137+
"${CMAKE_SOURCE_DIR}/src/utils/*"
138+
"${CMAKE_SOURCE_DIR}/src/gioutils/*"
139+
--output-file ${CMAKE_BINARY_DIR}/coverage_filtered.info
140+
COMMAND ${GENHTML_BIN} ${CMAKE_BINARY_DIR}/coverage_filtered.info
141+
--branch-coverage --output-directory ${CMAKE_BINARY_DIR}/coverage_report
142+
)
143+
endif()
144+
add_custom_target(coverage
145+
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
146+
${_run_test_coverage}
147+
DEPENDS ${LAUNCHPAD_TEST_TARGETS}
148+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
149+
COMMENT "Run all tests and generate coverage report"
150+
VERBATIM
21151
)

‎tests/blurhashtest.cpp‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
#include <QTest>
6+
7+
#include "blurhash.hpp"
8+
9+
#include <vector>
10+
11+
namespace {
12+
13+
std::vector<unsigned char> solidImage(size_t width, size_t height,
14+
unsigned char r, unsigned char g, unsigned char b)
15+
{
16+
std::vector<unsigned char> image(width * height * 3, 0);
17+
for (size_t i = 0; i < width * height; ++i) {
18+
image[i * 3 + 0] = r;
19+
image[i * 3 + 1] = g;
20+
image[i * 3 + 2] = b;
21+
}
22+
return image;
23+
}
24+
25+
// blurhash layout: 1 (components) + 1 (maxAC) + 4 (DC) + 2 bytes per AC component
26+
size_t expectedHashLength(int cx, int cy)
27+
{
28+
return size_t(1 + 1 + 4 + (cx * cy - 1) * 2);
29+
}
30+
}
31+
32+
class TestBlurhash : public QObject
33+
{
34+
Q_OBJECT
35+
private slots:
36+
void decodeEmptyStringReturnsEmpty();
37+
void decodeInvalidHashReturnsEmpty();
38+
void encodeRejectsInvalidInput();
39+
void encodedHashHasExpectedLength();
40+
void decodedImageHasExpectedDimensions();
41+
void roundTripPreservesAverageColor();
42+
void decodeWithFourBytesPerPixel();
43+
void decodeTooShortHashReturnsEmpty();
44+
void decodeWrongSizeForComponentsReturnsEmpty();
45+
};
46+
47+
void TestBlurhash::decodeEmptyStringReturnsEmpty()
48+
{
49+
const auto img = blurhash::decode(std::string_view{}, 8, 8);
50+
QVERIFY(img.image.empty());
51+
QCOMPARE(img.width, size_t(0));
52+
QCOMPARE(img.height, size_t(0));
53+
}
54+
55+
void TestBlurhash::decodeInvalidHashReturnsEmpty()
56+
{
57+
const auto img = blurhash::decode(std::string_view("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"), 8, 8);
58+
QVERIFY(img.image.empty());
59+
}
60+
61+
void TestBlurhash::encodeRejectsInvalidInput()
62+
{
63+
std::vector<unsigned char> image = solidImage(4, 4, 255, 0, 0);
64+
QVERIFY(blurhash::encode(image.data(), 0, 4, 1, 1).empty()); // width 0
65+
QVERIFY(blurhash::encode(nullptr, 4, 4, 1, 1).empty()); // null image
66+
QVERIFY(blurhash::encode(image.data(), 4, 4, 0, 1).empty()); // components_x < 1
67+
QVERIFY(blurhash::encode(image.data(), 4, 4, 1, 10).empty()); // components_y > 9
68+
}
69+
70+
void TestBlurhash::encodedHashHasExpectedLength()
71+
{
72+
std::vector<unsigned char> image = solidImage(8, 8, 128, 64, 200);
73+
QCOMPARE(blurhash::encode(image.data(), 8, 8, 1, 1).size(), size_t(6)); // no AC components
74+
QCOMPARE(blurhash::encode(image.data(), 8, 8, 1, 3).size(), expectedHashLength(1, 3)); // 10
75+
QCOMPARE(blurhash::encode(image.data(), 8, 8, 2, 2).size(), expectedHashLength(2, 2)); // 12
76+
QCOMPARE(blurhash::encode(image.data(), 8, 8, 4, 4).size(), expectedHashLength(4, 4)); // 36
77+
}
78+
79+
void TestBlurhash::decodedImageHasExpectedDimensions()
80+
{
81+
constexpr size_t width = 16, height = 16;
82+
std::vector<unsigned char> image = solidImage(width, height, 128, 64, 200);
83+
const std::string hash = blurhash::encode(image.data(), width, height, 2, 2);
84+
QVERIFY(!hash.empty());
85+
86+
const auto decoded = blurhash::decode(hash, width, height);
87+
QCOMPARE(decoded.width, width);
88+
QCOMPARE(decoded.height, height);
89+
QCOMPARE(decoded.image.size(), width * height * 3);
90+
}
91+
92+
void TestBlurhash::roundTripPreservesAverageColor()
93+
{
94+
constexpr size_t width = 16, height = 16;
95+
const unsigned char r = 128, g = 64, b = 200;
96+
std::vector<unsigned char> image = solidImage(width, height, r, g, b);
97+
98+
const std::string hash = blurhash::encode(image.data(), width, height, 3, 3);
99+
QVERIFY(!hash.empty());
100+
101+
const auto decoded = blurhash::decode(hash, width, height);
102+
QCOMPARE(decoded.image.size(), width * height * 3);
103+
104+
long rs = 0, gs = 0, bs = 0;
105+
for (size_t i = 0; i < width * height; ++i) {
106+
rs += decoded.image[i * 3 + 0];
107+
gs += decoded.image[i * 3 + 1];
108+
bs += decoded.image[i * 3 + 2];
109+
}
110+
const long n = long(width * height);
111+
const long avgR = rs / n, avgG = gs / n, avgB = bs / n;
112+
// blurhash is lossy per-pixel, but the average (DC) stays close to the source color
113+
QVERIFY(avgR >= long(r) - 15 && avgR <= long(r) + 15);
114+
QVERIFY(avgG >= long(g) - 15 && avgG <= long(g) + 15);
115+
QVERIFY(avgB >= long(b) - 15 && avgB <= long(b) + 15);
116+
}
117+
118+
void TestBlurhash::decodeWithFourBytesPerPixel()
119+
{
120+
constexpr size_t width = 16, height = 16;
121+
std::vector<unsigned char> image = solidImage(width, height, 128, 64, 200);
122+
const std::string hash = blurhash::encode(image.data(), width, height, 3, 3);
123+
QVERIFY(!hash.empty());
124+
125+
const auto decoded = blurhash::decode(hash, width, height, 4);
126+
QCOMPARE(decoded.width, width);
127+
QCOMPARE(decoded.height, height);
128+
// buffer should be width * height * 4 (not 3)
129+
QCOMPARE(decoded.image.size(), width * height * 4);
130+
// first 3 bytes of each pixel should still be the color channels
131+
QVERIFY(decoded.image[0] != 255 || decoded.image[1] != 255 || decoded.image[2] != 255);
132+
}
133+
134+
void TestBlurhash::decodeTooShortHashReturnsEmpty()
135+
{
136+
// 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)
137+
const auto img = blurhash::decode(std::string_view("short"), 8, 8);
138+
QVERIFY(img.image.empty());
139+
}
140+
141+
void TestBlurhash::decodeWrongSizeForComponentsReturnsEmpty()
142+
{
143+
// 1 component char says 4x4 = 16 components -> needs 1+1+4+(16-1)*2 = 36 chars
144+
// but we'll give only 12 chars
145+
const auto img = blurhash::decode(std::string_view("LFE.}?a]a]a]a]a]a]a]"), 8, 8);
146+
// this hash has wrong length for its declared components -> should return empty
147+
QVERIFY(img.image.empty());
148+
}
149+
150+
QTEST_MAIN(TestBlurhash)
151+
#include "blurhashtest.moc"

0 commit comments

Comments
 (0)