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: 90.8% 69 / 0 / 76
Functions: 100.0% 11 / 0 / 11
Branches: 60.6% 132 / 0 / 218

presentation/ComposeDraftFiles.cpp
Line Branch Exec Source
1 #include "ComposeDraftFiles.h"
2
3 #include <QDir>
4 #include <QFile>
5 #include <QFileInfo>
6 #include <QJsonDocument>
7 #include <QSaveFile>
8 #include <QUuid>
9 #include <algorithm>
10
11 namespace ComposeDraftFiles {
12
13 namespace {
14 constexpr auto OwnerDirectory = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
15 332 QString draftPath(const QString &profile, const QString &session) {
16
3/3
✓ Branch 2 → 3 taken 332 times.
✓ Branch 3 → 4 taken 332 times.
✓ Branch 7 → 8 taken 332 times.
664 return QDir(sessionDirectory(profile, session)).filePath(QStringLiteral("draft.json"));
17 }
18 } // namespace
19
20 2522 QString root(const QString &profileDirectory) {
21
2/2
✓ Branch 2 → 3 taken 2522 times.
✓ Branch 6 → 7 taken 2522 times.
5044 return QDir(profileDirectory).filePath(QStringLiteral("compose-drafts"));
22 }
23
24 753 QString sessionDirectory(const QString &profileDirectory, const QString &sessionId) {
25
3/3
✓ Branch 2 → 3 taken 753 times.
✓ Branch 3 → 4 taken 753 times.
✓ Branch 4 → 5 taken 753 times.
753 return QDir(root(profileDirectory)).filePath(sessionId);
26 }
27
28 193 QString filesDirectory(const QString &profileDirectory, const QString &sessionId) {
29
3/3
✓ Branch 2 → 3 taken 193 times.
✓ Branch 3 → 4 taken 193 times.
✓ Branch 7 → 8 taken 193 times.
386 return QDir(sessionDirectory(profileDirectory, sessionId)).filePath(QStringLiteral("files"));
30 }
31
32 382 bool validSessionId(const QString &sessionId) {
33
2/3
✓ Branch 8 → 9 taken 382 times.
✓ Branch 9 → 10 taken 382 times.
✗ Branch 9 → 17 not taken.
764 return !QUuid::fromString(sessionId).isNull() && !sessionId.contains(QLatin1Char('/')) &&
34
4/7
✓ Branch 5 → 6 taken 382 times.
✗ Branch 5 → 17 not taken.
✓ Branch 12 → 13 taken 382 times.
✓ Branch 13 → 14 taken 382 times.
✗ Branch 13 → 17 not taken.
✓ Branch 15 → 16 taken 382 times.
✗ Branch 15 → 17 not taken.
764 !sessionId.contains(QLatin1Char('\\')) && sessionId.size() <= 40;
35 }
36
37 181 bool save(const QString &profileDirectory, const QString &sessionId, const QJsonObject &draft, QString *error) {
38
4/7
✓ Branch 3 → 4 taken 181 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 181 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 181 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 18 taken 181 times.
181 if (profileDirectory.isEmpty() || !validSessionId(sessionId)) {
39 ✗ if (error) *error = QStringLiteral("invalid draft session");
40 ✗ return false;
41 }
42 181 QJsonObject document = draft;
43
2/2
✓ Branch 19 → 20 taken 181 times.
✓ Branch 23 → 24 taken 181 times.
362 document.insert(QStringLiteral("version"), SchemaVersion);
44
2/2
✓ Branch 27 → 28 taken 181 times.
✓ Branch 31 → 32 taken 181 times.
362 document.insert(QStringLiteral("sessionId"), sessionId);
45
4/4
✓ Branch 35 → 36 taken 181 times.
✓ Branch 36 → 37 taken 181 times.
✓ Branch 37 → 38 taken 181 times.
✓ Branch 41 → 42 taken 181 times.
362 document.insert(QStringLiteral("savedAt"), QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs));
46
2/2
✓ Branch 47 → 48 taken 181 times.
✓ Branch 48 → 49 taken 181 times.
181 const QByteArray bytes = QJsonDocument(document).toJson(QJsonDocument::Compact);
47
1/2
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 61 taken 181 times.
181 if (bytes.size() > MaxDraftBytes) {
48 ✗ if (error) *error = QStringLiteral("too large");
49 ✗ return false;
50 }
51
1/1
✓ Branch 61 → 62 taken 181 times.
181 const QString directory = sessionDirectory(profileDirectory, sessionId);
52
4/4
✓ Branch 63 → 64 taken 181 times.
✓ Branch 64 → 65 taken 181 times.
✓ Branch 67 → 68 taken 3 times.
✓ Branch 67 → 77 taken 178 times.
181 if (!QDir().mkpath(directory)) {
53
1/2
✓ Branch 68 → 69 taken 3 times.
✗ Branch 68 → 76 not taken.
6 if (error) *error = QStringLiteral("cannot create the draft folder");
54 3 return false;
55 }
56
2/2
✓ Branch 77 → 78 taken 178 times.
✓ Branch 78 → 79 taken 178 times.
178 QFile::setPermissions(root(profileDirectory), OwnerDirectory);
57
1/1
✓ Branch 80 → 81 taken 178 times.
178 QFile::setPermissions(directory, OwnerDirectory);
58
2/2
✓ Branch 81 → 82 taken 178 times.
✓ Branch 82 → 83 taken 178 times.
178 QSaveFile file(draftPath(profileDirectory, sessionId));
59
4/6
✓ Branch 85 → 86 taken 178 times.
✓ Branch 88 → 89 taken 178 times.
✓ Branch 89 → 90 taken 178 times.
✗ Branch 89 → 95 not taken.
✓ Branch 92 → 93 taken 178 times.
✗ Branch 92 → 95 not taken.
534 if (!file.open(QIODevice::WriteOnly) || !file.setPermissions(QFile::ReadOwner | QFile::WriteOwner) ||
60
5/8
✓ Branch 86 → 87 taken 178 times.
✗ Branch 86 → 95 not taken.
✓ Branch 90 → 91 taken 178 times.
✓ Branch 93 → 94 taken 178 times.
✗ Branch 94 → 95 not taken.
✓ Branch 94 → 96 taken 178 times.
✗ Branch 97 → 98 not taken.
✓ Branch 97 → 113 taken 178 times.
534 file.write(bytes) != bytes.size() || !file.commit()) {
61 ✗ if (error) *error = file.errorString().isEmpty() ? QStringLiteral("disk full") : file.errorString();
62 ✗ return false;
63 }
64 178 return true;
65 181 }
66
67 143 std::optional<QJsonObject> load(const QString &profileDirectory, const QString &sessionId) {
68
2/3
✓ Branch 2 → 3 taken 143 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 143 times.
143 if (!validSessionId(sessionId)) return std::nullopt;
69
2/2
✓ Branch 5 → 6 taken 143 times.
✓ Branch 6 → 7 taken 143 times.
143 QFile file(draftPath(profileDirectory, sessionId));
70
7/8
✓ Branch 9 → 10 taken 143 times.
✓ Branch 10 → 11 taken 85 times.
✓ Branch 10 → 13 taken 58 times.
✓ Branch 11 → 12 taken 85 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 85 times.
✓ Branch 15 → 16 taken 58 times.
✓ Branch 15 → 17 taken 85 times.
143 if (!file.open(QIODevice::ReadOnly) || file.size() > MaxDraftBytes) return std::nullopt;
71 85 QJsonParseError parseError;
72
2/2
✓ Branch 17 → 18 taken 85 times.
✓ Branch 18 → 19 taken 85 times.
85 const QJsonDocument document = QJsonDocument::fromJson(file.read(MaxDraftBytes), &parseError);
73
4/7
✓ Branch 20 → 21 taken 85 times.
✗ Branch 20 → 23 not taken.
✓ Branch 21 → 22 taken 85 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 85 times.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 85 times.
85 if (parseError.error != QJsonParseError::NoError || !document.isObject()) return std::nullopt;
74
1/1
✓ Branch 27 → 28 taken 85 times.
85 const QJsonObject object = document.object();
75
6/16
✓ Branch 31 → 32 taken 85 times.
✓ Branch 32 → 33 taken 85 times.
✓ Branch 33 → 34 taken 85 times.
✗ Branch 33 → 41 not taken.
✓ Branch 53 → 54 taken 85 times.
✗ Branch 53 → 55 not taken.
✓ Branch 55 → 56 taken 85 times.
✗ Branch 55 → 57 not taken.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 59 taken 85 times.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 94 not taken.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
340 if (object.value(QStringLiteral("version")).toInt() != SchemaVersion ||
76
8/22
✓ Branch 37 → 38 taken 85 times.
✓ Branch 38 → 39 taken 85 times.
✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 85 times.
✓ Branch 43 → 44 taken 85 times.
✗ Branch 43 → 45 not taken.
✓ Branch 45 → 46 taken 85 times.
✗ Branch 45 → 47 not taken.
✓ Branch 47 → 48 taken 85 times.
✗ Branch 47 → 49 not taken.
✓ Branch 49 → 50 taken 85 times.
✗ Branch 49 → 51 not taken.
✓ Branch 51 → 52 taken 85 times.
✗ Branch 51 → 53 not taken.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 86 not taken.
170 object.value(QStringLiteral("sessionId")).toString() != sessionId)
77 ✗ return std::nullopt;
78 85 return object;
79 143 }
80
81 796 QStringList sessions(const QString &profileDirectory) {
82
2/2
✓ Branch 2 → 3 taken 796 times.
✓ Branch 3 → 4 taken 796 times.
796 const QDir directory(root(profileDirectory));
83 796 QList<QPair<QDateTime, QString>> found;
84
5/5
✓ Branch 7 → 8 taken 796 times.
✓ Branch 8 → 9 taken 796 times.
✓ Branch 9 → 10 taken 796 times.
✓ Branch 31 → 11 taken 6 times.
✓ Branch 31 → 32 taken 796 times.
802 for (const QString &name : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
85
2/3
✓ Branch 12 → 13 taken 6 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 6 times.
6 if (!validSessionId(name)) continue;
86
2/2
✓ Branch 15 → 16 taken 6 times.
✓ Branch 16 → 17 taken 6 times.
6 const QFileInfo info(draftPath(profileDirectory, name));
87
4/5
✓ Branch 18 → 19 taken 6 times.
✓ Branch 19 → 20 taken 6 times.
✗ Branch 19 → 26 not taken.
✓ Branch 20 → 21 taken 6 times.
✓ Branch 22 → 23 taken 6 times.
6 if (info.isFile()) found.append({info.lastModified(), name});
88 802 }
89
3/3
✓ Branch 33 → 34 taken 796 times.
✓ Branch 34 → 35 taken 796 times.
✓ Branch 35 → 36 taken 796 times.
797 std::sort(found.begin(), found.end(), [](const auto &a, const auto &b) { return a.first > b.first; });
90 796 QStringList out;
91
3/3
✓ Branch 40 → 41 taken 6 times.
✓ Branch 43 → 39 taken 6 times.
✓ Branch 43 → 44 taken 796 times.
802 for (const auto &entry : std::as_const(found)) out.append(entry.second);
92 796 return out;
93 796 }
94
95 47 bool remove(const QString &profileDirectory, const QString &sessionId) {
96
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 47 times.
47 if (!validSessionId(sessionId)) return false;
97
3/3
✓ Branch 5 → 6 taken 47 times.
✓ Branch 6 → 7 taken 47 times.
✓ Branch 7 → 8 taken 47 times.
47 return QDir(sessionDirectory(profileDirectory, sessionId)).removeRecursively();
98 }
99
100 794 int sweep(const QString &profileDirectory, const QSet<QString> &open, const QDateTime &before) {
101 794 int removed = 0;
102
2/2
✓ Branch 2 → 3 taken 794 times.
✓ Branch 3 → 4 taken 794 times.
794 const QDir directory(root(profileDirectory));
103
5/5
✓ Branch 7 → 8 taken 794 times.
✓ Branch 8 → 9 taken 794 times.
✓ Branch 9 → 10 taken 794 times.
✓ Branch 54 → 11 taken 5 times.
✓ Branch 54 → 55 taken 794 times.
799 for (const QString &name : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
104
4/7
✓ Branch 12 → 13 taken 5 times.
✓ Branch 13 → 14 taken 5 times.
✗ Branch 13 → 16 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 5 times.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 5 times.
5 if (!validSessionId(name) || open.contains(name)) continue;
105
2/2
✓ Branch 20 → 21 taken 5 times.
✓ Branch 21 → 22 taken 5 times.
5 const QFileInfo draft(draftPath(profileDirectory, name));
106
5/15
✓ Branch 23 → 24 taken 5 times.
✓ Branch 24 → 25 taken 5 times.
✗ Branch 24 → 26 not taken.
✓ Branch 25 → 29 taken 5 times.
✗ Branch 26 → 27 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 5 times.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 5 times.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
5 const QDateTime stamp = draft.isFile() ? draft.lastModified() : QFileInfo(directory.filePath(name)).lastModified();
107
5/18
✓ Branch 33 → 34 taken 5 times.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 41 taken 5 times.
✗ Branch 36 → 37 not taken.
✗ Branch 37 → 38 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 44 taken 5 times.
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 5 times.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 5 times.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
5 if (stamp < before && QDir(directory.filePath(name)).removeRecursively()) ++removed;
108 799 }
109 794 return removed;
110 794 }
111
112 } // namespace ComposeDraftFiles
113