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: 95.1% 78 / 0 / 82
Functions: 100.0% 4 / 0 / 4
Branches: 83.3% 105 / 0 / 126

data/MailPayloadReader.cpp
Line Branch Exec Source
1 #include "MailPayloadReader.h"
2 #include "MailCache.h"
3 #include <QCryptographicHash>
4 #include <memory>
5 #include <sqlite3.h>
6
7 81 MailPayloadReadResult MailPayloadReader::copy(const MailPayloadRequest &request,
8 QIODevice &destination,
9 const std::atomic_bool *cancelled) {
10 81 MailPayloadReadResult result;
11
4/4
✓ Branch 2 → 3 taken 115 times.
✓ Branch 2 → 6 taken 292 times.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 112 times.
407 const auto interrupted = [cancelled] { return cancelled && cancelled->load(std::memory_order_relaxed); };
12 39 const auto fail = [&result](const QString &error) {
13 39 result.error = error;
14 39 return result;
15 81 };
16
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 11 taken 80 times.
82 if (interrupted()) return fail(QStringLiteral("Payload read cancelled"));
17
2/2
✓ Branch 12 → 13 taken 20 times.
✓ Branch 12 → 31 taken 1 time.
21 if ((request.kind != MailPayloadRequest::Kind::Attachment && request.kind != MailPayloadRequest::Kind::RawSource) ||
18
6/6
✓ Branch 14 → 15 taken 77 times.
✓ Branch 14 → 31 taken 2 times.
✓ Branch 16 → 17 taken 76 times.
✓ Branch 16 → 31 taken 1 time.
✓ Branch 18 → 19 taken 75 times.
✓ Branch 18 → 31 taken 1 time.
155 !request.mailId.isValid() || request.databasePath.isEmpty() ||
19
5/5
✓ Branch 20 → 21 taken 75 times.
✓ Branch 21 → 22 taken 74 times.
✓ Branch 21 → 31 taken 1 time.
✓ Branch 23 → 24 taken 73 times.
✓ Branch 23 → 31 taken 1 time.
225 request.databasePath.size() > 4096 || request.databasePath.contains(QChar(0)) ||
20
3/3
✓ Branch 24 → 25 taken 73 times.
✓ Branch 25 → 26 taken 72 times.
✓ Branch 25 → 31 taken 1 time.
147 request.mailId.accountId.size() > 4096 || !destination.isWritable() ||
21
7/10
✓ Branch 11 → 12 taken 21 times.
✓ Branch 11 → 13 taken 59 times.
✓ Branch 26 → 27 taken 72 times.
✗ Branch 26 → 31 not taken.
✓ Branch 27 → 28 taken 72 times.
✗ Branch 27 → 31 not taken.
✓ Branch 28 → 29 taken 72 times.
✗ Branch 28 → 31 not taken.
✓ Branch 33 → 34 taken 9 times.
✓ Branch 33 → 41 taken 71 times.
232 request.skipBytes < 0 || request.maxBytes < -1 || request.maxBytes == 0 ||
22
4/4
✓ Branch 29 → 30 taken 52 times.
✓ Branch 29 → 32 taken 20 times.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 51 times.
72 (request.kind == MailPayloadRequest::Kind::Attachment && request.attachmentId <= 0))
23 9 return fail(QStringLiteral("Invalid payload request or destination"));
24
25 71 sqlite3 *rawDatabase = nullptr;
26
1/1
✓ Branch 41 → 42 taken 71 times.
71 const auto path = request.databasePath.toUtf8();
27
1/1
✓ Branch 43 → 44 taken 71 times.
71 const int opened = sqlite3_open_v2(path.constData(), &rawDatabase,
28 SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_NOFOLLOW, nullptr);
29 71 const std::unique_ptr<sqlite3, decltype(&sqlite3_close)> database(rawDatabase, sqlite3_close);
30 10 const auto databaseError = [&] {
31
1/1
✓ Branch 3 → 4 taken 10 times.
10 return rawDatabase ? QString::fromUtf8(sqlite3_errmsg(rawDatabase))
32
3/7
✓ Branch 2 → 3 taken 10 times.
✗ Branch 2 → 6 not taken.
✓ Branch 5 → 9 taken 10 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 10 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
30 : QStringLiteral("Cannot open payload database");
33 71 };
34
3/3
✓ Branch 45 → 46 taken 9 times.
✓ Branch 45 → 50 taken 62 times.
✓ Branch 46 → 47 taken 9 times.
71 if (opened != SQLITE_OK) return fail(databaseError());
35
1/1
✓ Branch 50 → 51 taken 62 times.
62 sqlite3_busy_timeout(rawDatabase, 100);
36
2/3
✓ Branch 51 → 52 taken 62 times.
✗ Branch 52 → 53 not taken.
✓ Branch 52 → 57 taken 62 times.
62 if (sqlite3_exec(rawDatabase, "PRAGMA cache_size=-2048; PRAGMA mmap_size=0; BEGIN", nullptr, nullptr, nullptr) != SQLITE_OK)
37 ✗ return fail(databaseError());
38 62 sqlite3_stmt *rawStatement = nullptr;
39
2/3
✓ Branch 57 → 58 taken 62 times.
✗ Branch 58 → 59 not taken.
✓ Branch 58 → 63 taken 62 times.
62 if (sqlite3_prepare_v2(rawDatabase, "PRAGMA user_version", -1, &rawStatement, nullptr) != SQLITE_OK)
40 ✗ return fail(databaseError());
41 62 std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)> statement(rawStatement, sqlite3_finalize);
42
7/8
✓ Branch 64 → 65 taken 62 times.
✓ Branch 65 → 66 taken 62 times.
✗ Branch 65 → 68 not taken.
✓ Branch 66 → 67 taken 62 times.
✓ Branch 67 → 68 taken 1 time.
✓ Branch 67 → 69 taken 61 times.
✓ Branch 70 → 71 taken 1 time.
✓ Branch 70 → 78 taken 61 times.
62 if (sqlite3_step(rawStatement) != SQLITE_ROW || sqlite3_column_int(rawStatement, 0) != MailCache::SchemaVersion)
43 1 return fail(QStringLiteral("Payload reader requires the current initialized cache schema"));
44 61 statement.reset();
45
46 61 const bool attachment = request.kind == MailPayloadRequest::Kind::Attachment;
47 61 const char *sql = attachment
48
2/2
✓ Branch 79 → 80 taken 41 times.
✓ Branch 79 → 81 taken 20 times.
61 ? "SELECT a.id,typeof(a.data) FROM folders f JOIN headers h ON h.folder_id=f.id "
49 "JOIN attachments a ON a.header_id=h.id WHERE f.account=? AND f.id=? AND h.uid=? AND f.uidvalidity=? AND a.id=?"
50 : "SELECT b.header_id,typeof(b.raw_body) FROM folders f JOIN headers h ON h.folder_id=f.id "
51 "JOIN bodies b ON b.header_id=h.id WHERE f.account=? AND f.id=? AND h.uid=? AND f.uidvalidity=?";
52 61 rawStatement = nullptr;
53
3/3
✓ Branch 82 → 83 taken 61 times.
✓ Branch 83 → 84 taken 1 time.
✓ Branch 83 → 88 taken 60 times.
61 if (sqlite3_prepare_v2(rawDatabase, sql, -1, &rawStatement, nullptr) != SQLITE_OK)
54
1/1
✓ Branch 84 → 85 taken 1 time.
1 return fail(databaseError());
55 60 statement.reset(rawStatement);
56
1/1
✓ Branch 89 → 90 taken 60 times.
60 const auto account = request.mailId.accountId.toUtf8();
57
1/1
✓ Branch 92 → 93 taken 60 times.
60 sqlite3_bind_text(rawStatement, 1, account.constData(), int(account.size()), SQLITE_TRANSIENT);
58
1/1
✓ Branch 93 → 94 taken 60 times.
60 sqlite3_bind_int64(rawStatement, 2, request.mailId.folderId);
59
1/1
✓ Branch 94 → 95 taken 60 times.
60 sqlite3_bind_int64(rawStatement, 3, request.mailId.uid);
60
1/1
✓ Branch 95 → 96 taken 60 times.
60 sqlite3_bind_int64(rawStatement, 4, request.uidValidity);
61
3/3
✓ Branch 96 → 97 taken 40 times.
✓ Branch 96 → 98 taken 20 times.
✓ Branch 97 → 98 taken 40 times.
60 if (attachment) sqlite3_bind_int64(rawStatement, 5, request.attachmentId);
62
1/1
✓ Branch 98 → 99 taken 60 times.
60 const int selected = sqlite3_step(rawStatement);
63
2/2
✓ Branch 99 → 100 taken 7 times.
✓ Branch 99 → 110 taken 53 times.
60 if (selected != SQLITE_ROW)
64
2/7
✓ Branch 100 → 101 taken 7 times.
✗ Branch 100 → 104 not taken.
✗ Branch 104 → 105 not taken.
✓ Branch 107 → 108 taken 7 times.
✗ Branch 107 → 109 not taken.
✗ Branch 214 → 215 not taken.
✗ Branch 214 → 216 not taken.
14 return fail(selected == SQLITE_DONE ? QStringLiteral("Payload is missing or its mailbox identity changed") : databaseError());
65
1/1
✓ Branch 110 → 111 taken 53 times.
53 const qint64 row = sqlite3_column_int64(rawStatement, 0);
66
2/2
✓ Branch 111 → 112 taken 53 times.
✓ Branch 112 → 113 taken 53 times.
53 const QByteArray type(reinterpret_cast<const char *>(sqlite3_column_text(rawStatement, 1)));
67 // typeof() inspects metadata. length(TEXT) would materialize a corrupt large
68 // value before the type guard can reject it; get the size from the BLOB handle.
69
2/2
✓ Branch 114 → 115 taken 6 times.
✓ Branch 114 → 122 taken 47 times.
59 if (type != "blob") return fail(QStringLiteral("Payload is not available as a BLOB"));
70 47 statement.reset();
71
72 47 sqlite3_blob *rawBlob = nullptr;
73
5/5
✓ Branch 123 → 124 taken 30 times.
✓ Branch 123 → 125 taken 17 times.
✓ Branch 126 → 127 taken 30 times.
✓ Branch 126 → 128 taken 17 times.
✓ Branch 129 → 130 taken 47 times.
47 if (sqlite3_blob_open(rawDatabase, "main", attachment ? "attachments" : "bodies",
74
1/2
✗ Branch 130 → 131 not taken.
✓ Branch 130 → 135 taken 47 times.
47 attachment ? "data" : "raw_body", row, 0, &rawBlob) != SQLITE_OK)
75 ✗ return fail(databaseError());
76 47 const std::unique_ptr<sqlite3_blob, decltype(&sqlite3_blob_close)> blob(rawBlob, sqlite3_blob_close);
77
1/1
✓ Branch 136 → 137 taken 47 times.
47 const int size = sqlite3_blob_bytes(rawBlob);
78 47 result.totalBytes = size;
79 // The window never materializes skipped bytes; the hash covers the window.
80 47 const qint64 start = qMin<qint64>(request.skipBytes, size);
81 47 const qint64 end = request.maxBytes < 0
82
2/2
✓ Branch 138 → 139 taken 32 times.
✓ Branch 138 → 140 taken 15 times.
62 ? qint64(size)
83 15 : qMin<qint64>(size, start + request.maxBytes);
84
1/1
✓ Branch 142 → 143 taken 47 times.
47 QCryptographicHash hash(QCryptographicHash::Sha256);
85
1/1
✓ Branch 144 → 145 taken 47 times.
47 QByteArray buffer(qMin<qint64>(end - start, ChunkBytes), Qt::Uninitialized);
86
2/2
✓ Branch 182 → 146 taken 283 times.
✓ Branch 182 → 183 taken 43 times.
326 for (qint64 offset = start; offset < end;) {
87
2/2
✓ Branch 147 → 148 taken 1 time.
✓ Branch 147 → 155 taken 282 times.
284 if (interrupted()) return fail(QStringLiteral("Payload read cancelled"));
88 282 const int bytes = int(qMin<qint64>(end - offset, ChunkBytes));
89
3/4
✓ Branch 156 → 157 taken 282 times.
✓ Branch 157 → 158 taken 282 times.
✗ Branch 158 → 159 not taken.
✓ Branch 158 → 163 taken 282 times.
282 if (sqlite3_blob_read(rawBlob, buffer.data(), bytes, int(offset)) != SQLITE_OK)
90 ✗ return fail(databaseError());
91
3/3
✓ Branch 164 → 165 taken 282 times.
✓ Branch 165 → 166 taken 3 times.
✓ Branch 165 → 178 taken 279 times.
282 if (destination.write(buffer.constData(), bytes) != bytes)
92
2/2
✓ Branch 170 → 171 taken 3 times.
✓ Branch 171 → 172 taken 3 times.
6 return fail(QStringLiteral("Short payload write: %1").arg(destination.errorString()));
93 279 hash.addData(QByteArrayView(buffer.constData(), bytes));
94 279 result.bytes += bytes;
95 279 offset += bytes;
96 }
97
2/2
✓ Branch 184 → 185 taken 1 time.
✓ Branch 184 → 192 taken 42 times.
44 if (interrupted()) return fail(QStringLiteral("Payload read cancelled"));
98
2/2
✓ Branch 192 → 193 taken 42 times.
✓ Branch 193 → 194 taken 42 times.
42 result.sha256 = hash.result().toHex();
99 42 result.completed = true;
100 42 return result;
101 81 }
102