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: 86.7% 98 / 0 / 113
Functions: 77.8% 14 / 0 / 18
Branches: 64.7% 90 / 0 / 139

presentation/ServerDraftCleanup.cpp
Line Branch Exec Source
1 #include "ServerDraftCleanup.h"
2
3 #include "service/ImapService.h"
4
5 #include <QLoggingCategory>
6
7
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 2 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
3 Q_LOGGING_CATEGORY(lcDraftCleanup, "mailjd.quick.draftcleanup")
8
9 namespace {
10
3/3
✓ Branch 2 → 3 taken 14 times.
✓ Branch 5 → 6 taken 14 times.
✓ Branch 6 → 7 taken 14 times.
14 QString key(const QString &folder, qint64 uid) { return folder + QLatin1Char('\n') + QString::number(uid); }
11 } // namespace
12
13 800 ServerDraftCleanup::ServerDraftCleanup(std::function<ImapService *()> imap,
14 800 std::function<QString()> currentFolder, QObject *parent)
15
1/1
✓ Branch 11 → 12 taken 800 times.
800 : QObject(parent), m_imap(std::move(imap)), m_currentFolder(std::move(currentFolder)) {
16
1/1
✓ Branch 12 → 13 taken 800 times.
800 m_watchdog.setSingleShot(true);
17
1/1
✓ Branch 13 → 14 taken 800 times.
800 m_watchdog.setInterval(WatchdogMs);
18
1/1
✓ Branch 14 → 15 taken 800 times.
800 connect(&m_watchdog, &QTimer::timeout, this, [this] {
19 ✗ qCWarning(lcDraftCleanup) << "Superseded server draft kept: the server did not answer the cleanup";
20 ✗ finish();
21 ✗ });
22 800 }
23
24 8 void ServerDraftCleanup::remember(const QString &folder, qint64 uid, quint32 uidValidity) {
25
2/4
✓ Branch 2 → 3 taken 8 times.
✗ Branch 2 → 8 not taken.
✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 8 not taken.
8 if (uid > 0 && uidValidity > 0)
26
2/2
✓ Branch 4 → 5 taken 8 times.
✓ Branch 5 → 6 taken 8 times.
8 m_uidValidity.insert(key(folder, uid), uidValidity);
27 8 }
28
29 6 void ServerDraftCleanup::remove(const QString &drafts, qint64 uid) {
30 6 auto *imap = m_imap();
31
4/8
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 6 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 6 times.
6 if (!imap || uid <= 0 || drafts.isEmpty()) return;
32
2/2
✓ Branch 11 → 12 taken 6 times.
✓ Branch 12 → 13 taken 6 times.
6 const quint32 uidValidity = m_uidValidity.take(key(drafts, uid));
33
1/2
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 26 taken 6 times.
6 if (uidValidity == 0) {
34 ✗ qCInfo(lcDraftCleanup) << "Superseded server draft kept: its mailbox identity is unknown (no APPENDUID)";
35 ✗ return;
36 }
37 // F-04: queue while another cleanup runs (dropped before); the next one
38 // starts from finish().
39 6 m_pending.append({drafts, uid, uidValidity});
40
1/2
✓ Branch 29 → 30 taken 6 times.
✗ Branch 29 → 31 not taken.
6 if (!m_active) startNext();
41 6 }
42
43 11 void ServerDraftCleanup::startNext() {
44
2/2
✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 5 taken 6 times.
12 if (m_pending.isEmpty()) return;
45
1/1
✓ Branch 5 → 6 taken 6 times.
6 const Pending next = m_pending.takeFirst();
46
1/1
✓ Branch 6 → 7 taken 6 times.
6 auto *imap = m_imap();
47
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 6 times.
6 if (!imap) {
48 ✗ m_pending.clear();
49 ✗ return;
50 }
51 // F-04: every authenticated state may run the cleanup. With a folder
52 // open the connection is Selected or Idling; a disconnected connection
53 // drops the queue (the drafts stay; the next save's cleanup retries).
54 6 const auto state = imap->state();
55
4/4
✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 16 taken 4 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 16 taken 1 time.
6 if (state != ImapService::State::Authenticated &&
56
1/2
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 16 not taken.
1 state != ImapService::State::Selected &&
57 state != ImapService::State::Idling) {
58
1/1
✓ Branch 14 → 15 taken 1 time.
1 m_pending.clear();
59 1 return;
60 }
61 5 m_active = true;
62
1/1
✓ Branch 16 → 17 taken 5 times.
5 m_watchdog.start();
63
1/1
✓ Branch 17 → 18 taken 5 times.
5 m_restore = m_currentFolder();
64 5 m_reselect = m_restore != next.drafts;
65
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 5 times.
5 if (!m_reselect) {
66 ✗ storeAndExpunge(next.drafts, next.uid, next.uidValidity);
67 ✗ return;
68 }
69
1/1
✓ Branch 25 → 26 taken 5 times.
10 m_steps << connect(imap, &ImapService::folderSelected, this,
70 5 [this, next](const QString &folder, int, quint32, quint64) {
71
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
5 if (folder != next.drafts) return;
72 5 disconnectSteps();
73 5 storeAndExpunge(next.drafts, next.uid, next.uidValidity);
74
1/1
✓ Branch 26 → 27 taken 5 times.
10 });
75 // A failing SELECT of the Drafts folder (deleted elsewhere, refused)
76 // must not leave the cleanup active forever. S106-347: only that SELECT —
77 // another command's error is not the Drafts folder failing.
78
1/1
✓ Branch 30 → 31 taken 5 times.
10 m_steps << connect(imap, &ImapService::folderSelectFailed, this, [this, next](const QString &folder) {
79 ✗ if (folder != next.drafts) return;
80 ✗ qCWarning(lcDraftCleanup) << "Superseded server draft kept: selecting the Drafts folder failed";
81 ✗ finish();
82
1/1
✓ Branch 31 → 32 taken 5 times.
10 });
83 5 const QString drafts = next.drafts;
84
1/2
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 42 taken 5 times.
5 if (state == ImapService::State::Idling)
85 ✗ imap->executeAfterIdle([imap, drafts] { imap->selectFolder(drafts); });
86 else
87
1/1
✓ Branch 42 → 43 taken 5 times.
5 imap->selectFolder(drafts);
88 6 }
89
90 5 void ServerDraftCleanup::storeAndExpunge(const QString &drafts, qint64 uid, quint32 uidValidity) {
91 5 auto *imap = m_imap();
92
5/6
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 4 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 21 taken 4 times.
5 if (!imap || imap->selectedUidValidity() != uidValidity) {
93
5/5
✓ Branch 9 → 10 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 18 → 11 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
2 qCWarning(lcDraftCleanup) << "Superseded server draft kept: the Drafts UIDVALIDITY changed";
94 1 finish();
95 1 return;
96 }
97 // S106-345/346: the STORE carries its own token (far beyond the journal's
98 // row ids); its outcome — not any STORE's completion — decides the step.
99 4 const qint64 token = (qint64(1) << 62) + ++m_storeTokens;
100 4 m_steps << connect(imap, &ImapService::mutationCompleted, this,
101
1/3
✓ Branch 22 → 23 taken 4 times.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
8 [this, imap, drafts, uid, uidValidity, token](qint64 completed, ImapService::MutationOutcome outcome,
102 const QString &error, const QString &) {
103
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 4 times.
4 if (completed != token) return;
104 4 disconnectSteps();
105
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 19 taken 3 times.
4 if (outcome != ImapService::MutationOutcome::Confirmed) {
106
6/6
✓ Branch 6 → 7 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 16 → 8 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
2 qCWarning(lcDraftCleanup) << "Superseded server draft kept: marking it deleted failed:" << error;
107 1 finish();
108 1 return;
109 }
110
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 33 taken 2 times.
3 if (!imap->hasUidExpungeCapability()) {
111
5/5
✓ Branch 21 → 22 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 30 → 23 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
2 qCInfo(lcDraftCleanup) << "Superseded server draft marked deleted; the server has no UID EXPUNGE";
112 1 finish();
113 1 return;
114 }
115
2/2
✓ Branch 33 → 34 taken 2 times.
✓ Branch 34 → 35 taken 2 times.
4 m_steps << connect(imap, &ImapService::expungeComplete, this, [this] { finish(); });
116 2 m_steps << connect(imap, &ImapService::expungeFailed, this, [this](const QString &error) {
117 ✗ qCWarning(lcDraftCleanup) << "Superseded server draft not expunged:" << error;
118 ✗ finish();
119
2/2
✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
2 });
120
2/2
✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
2 imap->expungeUids({uid}, drafts, uidValidity);
121
1/1
✓ Branch 23 → 24 taken 4 times.
8 });
122
1/1
✓ Branch 29 → 30 taken 4 times.
8 imap->storeFlag(uid, QStringLiteral("\\Deleted"), true, token);
123 }
124
125 5 void ServerDraftCleanup::finish() {
126 5 m_watchdog.stop();
127 5 disconnectSteps();
128 5 auto *imap = m_imap();
129
6/8
✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 5 times.
✗ Branch 6 → 10 not taken.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 4 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 20 taken 4 times.
5 if (m_reselect && imap && !m_restore.isEmpty()) {
130 1 const QString restore = m_restore;
131
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
2 imap->executeAfterIdle([imap, restore] { imap->selectFolder(restore); });
132 1 }
133 5 m_restore.clear();
134 5 m_reselect = false;
135 5 m_active = false;
136 5 startNext(); // F-04: the queued cleanup of the next revision
137 5 }
138
139 1 void ServerDraftCleanup::reset() {
140 1 m_watchdog.stop();
141 1 disconnectSteps();
142 1 m_pending.clear();
143 1 m_restore.clear();
144 1 m_reselect = false;
145 1 m_active = false;
146 1 }
147
148 15 void ServerDraftCleanup::disconnectSteps() {
149
3/3
✓ Branch 6 → 7 taken 18 times.
✓ Branch 9 → 5 taken 18 times.
✓ Branch 9 → 10 taken 15 times.
33 for (const auto &step : std::as_const(m_steps)) QObject::disconnect(step);
150 15 m_steps.clear();
151 15 }
152