MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 94.5% 104 / 0 / 110
Functions: 100.0% 4 / 0 / 4
Branches: 86.5% 147 / 0 / 170

data/PredictorTrainingReader.cpp
Line Branch Exec Source
1 #include "data/PredictorTrainingReader.h"
2 #include "data/MailCacheSql.h"
3 #include "data/Models.h"
4
5 #include <QDateTime>
6 #include <QSqlError>
7 #include <QSqlQuery>
8 #include <QTimeZone>
9 #include <QVariant>
10
11 377 PredictorTrainingReader::TrainingSnapshot PredictorTrainingReader::trainingSnapshot(
12 const QString &account, const QString &folder) const {
13 377 TrainingSnapshot result;
14
3/3
✓ Branch 2 → 3 taken 377 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 376 times.
377 if (!m_db.isOpen()) {
15 1 result.error = QStringLiteral("Training cache is not open"); return result;
16 }
17
1/1
✓ Branch 11 → 12 taken 376 times.
376 QSqlQuery query(m_db);
18
1/1
✓ Branch 15 → 16 taken 376 times.
376 query.prepare(QStringLiteral(
19 "SELECT f.id,f.uidvalidity,coalesce(r.revision,'') FROM folders f "
20 "LEFT JOIN training_revisions r ON r.folder_id=f.id WHERE f.account=? AND f.path=?"));
21
2/2
✓ Branch 20 → 21 taken 376 times.
✓ Branch 24 → 25 taken 376 times.
376 query.addBindValue(account); query.addBindValue(folder);
22
7/8
✓ Branch 26 → 27 taken 376 times.
✓ Branch 27 → 28 taken 376 times.
✗ Branch 27 → 30 not taken.
✓ Branch 28 → 29 taken 376 times.
✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 31 taken 375 times.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 32 → 49 taken 375 times.
376 if (!query.exec() || !query.next()) {
23
3/8
✓ Branch 33 → 34 taken 1 time.
✓ Branch 34 → 35 taken 1 time.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 38 taken 1 time.
✗ Branch 36 → 37 not taken.
✗ Branch 37 → 41 not taken.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
4 result.error = query.lastError().isValid() ? query.lastError().text()
24
2/6
✓ Branch 43 → 44 taken 1 time.
✗ Branch 43 → 45 not taken.
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 1 time.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
2 : QStringLiteral("Predictor training mailbox no longer exists");
25 1 return result;
26 }
27
2/2
✓ Branch 49 → 50 taken 375 times.
✓ Branch 50 → 51 taken 375 times.
375 result.folderId = query.value(0).toLongLong();
28
2/2
✓ Branch 52 → 53 taken 375 times.
✓ Branch 53 → 54 taken 375 times.
375 result.uidValidity = query.value(1).toUInt();
29
2/2
✓ Branch 55 → 56 taken 375 times.
✓ Branch 56 → 57 taken 375 times.
375 result.revision = query.value(2).toString();
30 375 return result;
31 376 }
32
33 572 PredictorTrainingReader::TrainingPage PredictorTrainingReader::trainingPage(const QString &account,
34 const QString &folder,
35 qint64 afterUid) const {
36 572 TrainingPage result;
37
3/3
✓ Branch 2 → 3 taken 572 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 571 times.
572 if (!m_db.isOpen()) {
38 1 result.error = QStringLiteral("Training cache is not open");
39 1 return result;
40 }
41
1/1
✓ Branch 11 → 12 taken 571 times.
571 QSqlQuery query(m_db);
42
1/1
✓ Branch 15 → 16 taken 571 times.
571 query.prepare(QStringLiteral(
43 "SELECT h.uid, "
44 "CASE WHEN length(CAST(h.from_addr AS BLOB))<=8192 THEN h.from_addr END, "
45 "CASE WHEN length(CAST(h.subject AS BLOB))<=8192 THEN h.subject END, "
46 "CASE WHEN length(CAST(h.to_addr AS BLOB))<=8192 THEN h.to_addr END, "
47 "coalesce(length(CAST(h.from_addr AS BLOB)),0)>8192 OR "
48 "coalesce(length(CAST(h.subject AS BLOB)),0)>8192 OR "
49 "coalesce(length(CAST(h.to_addr AS BLOB)),0)>8192, f.uidvalidity, f.id "
50 "FROM headers h JOIN folders f ON f.id=h.folder_id "
51 "WHERE f.account=? AND f.path=? AND h.uid>? ORDER BY h.uid LIMIT 128"));
52
1/1
✓ Branch 20 → 21 taken 571 times.
571 query.addBindValue(account);
53
1/1
✓ Branch 24 → 25 taken 571 times.
571 query.addBindValue(folder);
54
1/1
✓ Branch 28 → 29 taken 571 times.
571 query.addBindValue(afterUid);
55
3/3
✓ Branch 30 → 31 taken 571 times.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 38 taken 569 times.
571 if (!query.exec()) {
56
2/2
✓ Branch 32 → 33 taken 2 times.
✓ Branch 33 → 34 taken 2 times.
2 result.error = query.lastError().text();
57 2 return result;
58 }
59
3/3
✓ Branch 80 → 81 taken 5685 times.
✓ Branch 81 → 39 taken 5118 times.
✓ Branch 81 → 82 taken 567 times.
5685 while (query.next()) {
60
4/4
✓ Branch 39 → 40 taken 5118 times.
✓ Branch 40 → 41 taken 5118 times.
✓ Branch 42 → 43 taken 2 times.
✓ Branch 42 → 51 taken 5116 times.
5118 if (query.value(4).toBool()) {
61
1/1
✓ Branch 43 → 44 taken 2 times.
2 result.headers.clear();
62 2 result.error = QStringLiteral("Predictor header exceeds the 8 KiB field limit");
63 2 return result;
64 }
65
2/2
✓ Branch 51 → 52 taken 5116 times.
✓ Branch 52 → 53 taken 5116 times.
5116 result.uidValidity = query.value(5).toUInt();
66
2/2
✓ Branch 54 → 55 taken 5116 times.
✓ Branch 55 → 56 taken 5116 times.
5116 result.folderId = query.value(6).toLongLong();
67 5116 MailHeader header;
68
2/2
✓ Branch 58 → 59 taken 5116 times.
✓ Branch 59 → 60 taken 5116 times.
5116 header.uid = query.value(0).toLongLong();
69
2/2
✓ Branch 61 → 62 taken 5116 times.
✓ Branch 62 → 63 taken 5116 times.
5116 header.from = query.value(1).toString();
70
2/2
✓ Branch 66 → 67 taken 5116 times.
✓ Branch 67 → 68 taken 5116 times.
5116 header.subject = query.value(2).toString();
71
2/2
✓ Branch 71 → 72 taken 5116 times.
✓ Branch 72 → 73 taken 5116 times.
5116 header.to = query.value(3).toString();
72
1/1
✓ Branch 77 → 78 taken 5116 times.
5116 result.headers.append(std::move(header));
73 5116 }
74
3/4
✓ Branch 82 → 83 taken 567 times.
✓ Branch 83 → 84 taken 567 times.
✗ Branch 85 → 86 not taken.
✓ Branch 85 → 93 taken 567 times.
567 if (query.lastError().isValid()) {
75 ✗ result.headers.clear();
76 ✗ result.error = query.lastError().text();
77 }
78 567 return result;
79 571 }
80
81 481 PredictorTrainingReader::TrainingPage PredictorTrainingReader::trainingHeaders(
82 const QString &account, qint64 folderId, const QString &folder,
83 quint32 uidValidity, const QList<qint64> &uids) const {
84 481 TrainingPage result;
85 481 result.uidValidity = uidValidity;
86 481 result.folderId = folderId;
87
8/9
✓ Branch 2 → 3 taken 481 times.
✓ Branch 3 → 4 taken 481 times.
✗ Branch 3 → 8 not taken.
✓ Branch 5 → 6 taken 480 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 479 times.
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 18 taken 479 times.
481 if (!m_db.isOpen() || uids.isEmpty() || uids.size() > 128) {
88 2 result.error = QStringLiteral("Training cache is closed or the UID batch is invalid");
89 2 return result;
90 }
91 479 QStringList placeholders;
92
2/2
✓ Branch 37 → 20 taken 3644 times.
✓ Branch 37 → 38 taken 478 times.
4122 for (qint64 uid : uids) {
93
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 29 taken 3643 times.
3644 if (uid <= 0) {
94 1 result.error = QStringLiteral("Invalid predictor training UID");
95 1 return result;
96 }
97
1/1
✓ Branch 32 → 33 taken 3643 times.
3643 placeholders.append(QStringLiteral("?"));
98 }
99
1/1
✓ Branch 38 → 39 taken 478 times.
478 QSqlQuery query(m_db);
100
1/1
✓ Branch 47 → 48 taken 478 times.
1434 query.prepare(QStringLiteral(
101 "SELECT h.uid, "
102 "CASE WHEN length(CAST(h.from_addr AS BLOB))<=8192 THEN h.from_addr END, "
103 "CASE WHEN length(CAST(h.subject AS BLOB))<=8192 THEN h.subject END, "
104 "CASE WHEN length(CAST(h.to_addr AS BLOB))<=8192 THEN h.to_addr END, "
105 "coalesce(length(CAST(h.from_addr AS BLOB)),0)>8192 OR "
106 "coalesce(length(CAST(h.subject AS BLOB)),0)>8192 OR "
107 "coalesce(length(CAST(h.to_addr AS BLOB)),0)>8192, h.is_spam, "
108 // Sprint 105 S105.3.1: the evidence fields, bounded like the rest.
109 "substr(h.cc_addr,1,8192), substr(h.list_id,1,1024), substr(h.message_id,1,1024), "
110 "substr(h.in_reply_to,1,8192), substr(h.ref_ids,1,8192), h.date "
111 "FROM folders f LEFT JOIN headers h ON h.folder_id=f.id AND h.uid IN (%1) "
112 "WHERE f.id=? AND f.account=? AND f.path=? AND f.uidvalidity=? ORDER BY h.uid")
113
2/2
✓ Branch 45 → 46 taken 478 times.
✓ Branch 46 → 47 taken 478 times.
956 .arg(placeholders.join(QLatin1Char(','))));
114
3/3
✓ Branch 57 → 58 taken 3643 times.
✓ Branch 61 → 54 taken 3643 times.
✓ Branch 61 → 62 taken 478 times.
4121 for (qint64 uid : uids) query.addBindValue(uid);
115
1/1
✓ Branch 64 → 65 taken 478 times.
478 query.addBindValue(folderId);
116
1/1
✓ Branch 68 → 69 taken 478 times.
478 query.addBindValue(account);
117
1/1
✓ Branch 72 → 73 taken 478 times.
478 query.addBindValue(folder);
118
1/1
✓ Branch 76 → 77 taken 478 times.
478 query.addBindValue(uidValidity);
119
2/3
✓ Branch 78 → 79 taken 478 times.
✗ Branch 79 → 80 not taken.
✓ Branch 79 → 86 taken 478 times.
478 if (!query.exec()) {
120 ✗ result.error = query.lastError().text();
121 ✗ return result;
122 }
123 478 bool identityMatched = false;
124
3/3
✓ Branch 173 → 174 taken 3978 times.
✓ Branch 174 → 87 taken 3502 times.
✓ Branch 174 → 175 taken 476 times.
3978 while (query.next()) {
125 3502 identityMatched = true;
126
4/4
✓ Branch 87 → 88 taken 3502 times.
✓ Branch 88 → 89 taken 3502 times.
✓ Branch 90 → 91 taken 5 times.
✓ Branch 90 → 92 taken 3497 times.
3502 if (query.value(0).isNull()) continue; // Matching mailbox; requested messages were removed.
127
4/4
✓ Branch 92 → 93 taken 3497 times.
✓ Branch 93 → 94 taken 3497 times.
✓ Branch 95 → 96 taken 2 times.
✓ Branch 95 → 104 taken 3495 times.
3497 if (query.value(4).toBool()) {
128
1/1
✓ Branch 96 → 97 taken 2 times.
2 result.headers.clear();
129 2 result.error = QStringLiteral("Predictor header exceeds the 8 KiB field limit");
130 2 return result;
131 }
132 3495 MailHeader header;
133
2/2
✓ Branch 105 → 106 taken 3495 times.
✓ Branch 106 → 107 taken 3495 times.
3495 header.uid = query.value(0).toLongLong();
134
2/2
✓ Branch 108 → 109 taken 3495 times.
✓ Branch 109 → 110 taken 3495 times.
3495 header.from = query.value(1).toString();
135
2/2
✓ Branch 113 → 114 taken 3495 times.
✓ Branch 114 → 115 taken 3495 times.
3495 header.subject = query.value(2).toString();
136
2/2
✓ Branch 118 → 119 taken 3495 times.
✓ Branch 119 → 120 taken 3495 times.
3495 header.to = query.value(3).toString();
137 3495 header.folderId = folderId;
138
2/2
✓ Branch 123 → 124 taken 3495 times.
✓ Branch 124 → 125 taken 3495 times.
3495 header.isSpam = query.value(5).toBool();
139
2/2
✓ Branch 126 → 127 taken 3495 times.
✓ Branch 127 → 128 taken 3495 times.
3495 header.cc = query.value(6).toString();
140
2/2
✓ Branch 131 → 132 taken 3495 times.
✓ Branch 132 → 133 taken 3495 times.
3495 header.listId = query.value(7).toString();
141
2/2
✓ Branch 136 → 137 taken 3495 times.
✓ Branch 137 → 138 taken 3495 times.
3495 header.messageId = query.value(8).toString();
142
3/3
✓ Branch 141 → 142 taken 3495 times.
✓ Branch 142 → 143 taken 3495 times.
✓ Branch 143 → 144 taken 3495 times.
3495 header.inReplyToIds = MailCacheSql::decodeThreadIds(query.value(9).toString());
143
3/3
✓ Branch 148 → 149 taken 3495 times.
✓ Branch 149 → 150 taken 3495 times.
✓ Branch 150 → 151 taken 3495 times.
3495 header.references = MailCacheSql::decodeThreadIds(query.value(10).toString());
144
4/4
✓ Branch 155 → 156 taken 3495 times.
✓ Branch 156 → 157 taken 3495 times.
✓ Branch 158 → 159 taken 636 times.
✓ Branch 158 → 168 taken 2859 times.
3495 if (!query.value(11).isNull())
145
4/4
✓ Branch 159 → 160 taken 636 times.
✓ Branch 160 → 161 taken 636 times.
✓ Branch 161 → 162 taken 636 times.
✓ Branch 162 → 163 taken 636 times.
636 header.date = QDateTime::fromSecsSinceEpoch(query.value(11).toLongLong(), QTimeZone::utc());
146
1/1
✓ Branch 169 → 170 taken 3495 times.
3495 result.headers.append(std::move(header));
147 3495 }
148
3/4
✓ Branch 175 → 176 taken 476 times.
✓ Branch 176 → 177 taken 476 times.
✗ Branch 178 → 179 not taken.
✓ Branch 178 → 186 taken 476 times.
476 if (query.lastError().isValid()) {
149 ✗ result.headers.clear();
150 ✗ result.error = query.lastError().text();
151
2/2
✓ Branch 186 → 187 taken 13 times.
✓ Branch 186 → 194 taken 463 times.
476 } else if (!identityMatched) {
152 13 result.error = QStringLiteral("Predictor training mailbox identity changed");
153 }
154 476 return result;
155 479 }
156
157 985 bool PredictorTrainingReader::trainingIdentityCurrent(const QString &account, qint64 folderId,
158 const QString &folder, quint32 uidValidity) const {
159
1/1
✓ Branch 2 → 3 taken 985 times.
985 QSqlQuery query(m_db);
160
1/1
✓ Branch 6 → 7 taken 985 times.
985 query.prepare(QStringLiteral(
161 "SELECT 1 FROM folders WHERE id=? AND account=? AND path=? AND uidvalidity=?"));
162
2/2
✓ Branch 11 → 12 taken 985 times.
✓ Branch 15 → 16 taken 985 times.
985 query.addBindValue(folderId); query.addBindValue(account);
163
2/2
✓ Branch 19 → 20 taken 985 times.
✓ Branch 23 → 24 taken 985 times.
985 query.addBindValue(folder); query.addBindValue(uidValidity);
164
5/6
✓ Branch 25 → 26 taken 985 times.
✓ Branch 26 → 27 taken 985 times.
✗ Branch 26 → 30 not taken.
✓ Branch 27 → 28 taken 985 times.
✓ Branch 28 → 29 taken 984 times.
✓ Branch 28 → 30 taken 1 time.
1970 return query.exec() && query.next();
165 985 }
166