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: 79.2% 99 / 0 / 125
Functions: 78.6% 11 / 0 / 14
Branches: 66.3% 124 / 0 / 187

app/core/MailSessionCore.cpp
Line Branch Exec Source
1 #include "MailSessionCore.h"
2 #include "AccountSession.h"
3 #include "BackgroundAccountSession.h"
4 #include "SettingsSyncSession.h"
5 #include "CalendarSyncCoordinator.h"
6 #include "CalendarPushSource.h"
7 #include "ContactPushSource.h"
8 #include "DavPushCoordinator.h"
9 #include "DavPushSecrets.h"
10 #include "data/DavCredentials.h"
11 #include "service/NetworkLifecycleCoordinator.h"
12 #include "ContactSyncCoordinator.h"
13 #include "FolderPredictionSession.h"
14 #include "app/FtsIndexCoordinator.h"
15 #include "controller/MailController.h"
16 #include "controller/UndoManager.h"
17 #include "data/CalendarStore.h"
18 #include "data/MailCache.h"
19 #include "service/ConnectionHealthMonitor.h"
20 #include "service/ImapService.h"
21 #include "service/SmtpService.h"
22 #include "ui/MailListModel.h"
23 #include "ui/MailThreadModel.h"
24 #include "performance/PerformanceTrace.h"
25 #include "performance/StartupPerformanceTracker.h"
26 #include "performance/BackgroundPerformanceTracker.h"
27 #include <QDir>
28 #include <QFileInfo>
29 #include <QStandardPaths>
30
31 854 MailSessionCore::MailSessionCore(QObject *parent)
32 : QObject(parent),
33 ✗ m_imap(std::make_unique<ImapService>(this)),
34
1/1
✓ Branch 4 → 5 taken 854 times.
854 m_cache(std::make_unique<MailCache>(this)),
35
1/1
✓ Branch 5 → 6 taken 854 times.
854 m_headers(std::make_unique<MailListModel>(this)),
36
1/1
✓ Branch 6 → 7 taken 854 times.
854 m_threads(std::make_unique<MailThreadModel>(this)),
37
1/1
✓ Branch 7 → 8 taken 854 times.
854 m_undo(std::make_unique<UndoManager>(this)),
38
1/1
✓ Branch 8 → 9 taken 854 times.
854 m_health(std::make_unique<ConnectionHealthMonitor>(true, this)),
39 854 m_controller(std::make_unique<MailController>(
40
1/1
✓ Branch 12 → 13 taken 854 times.
854 m_imap.get(), m_cache.get(), m_headers.get(), nullptr, this)),
41
1/1
✓ Branch 13 → 14 taken 854 times.
854 m_accounts(std::make_unique<AccountSession>(*this, this)),
42
2/2
✓ Branch 3 → 4 taken 854 times.
✓ Branch 15 → 16 taken 854 times.
1708 m_predictions(std::make_unique<FolderPredictionSession>(*this, this)) {
43 854 m_controller->setThreadModel(m_threads.get());
44
1/1
✓ Branch 30 → 31 taken 854 times.
854 m_controller->setUndoManager(m_undo.get());
45
1/1
✓ Branch 33 → 34 taken 854 times.
854 m_health->attach(m_imap.get());
46 // F-50 (SPRINT-104, D-11): the non-selected accounts stay online with
47 // their own staggered connections — badges and arrival signals only;
48 // the selected account's own primary connection takes over on switch.
49 {
50 854 BackgroundAccountSession::Deps deps;
51 854 deps.cache = m_cache.get();
52 1751 deps.configurations = [this] { return m_accounts->configurations(); };
53
2/2
✓ Branch 5 → 6 taken 890 times.
✓ Branch 5 → 10 taken 15 times.
905 deps.selectedAccountId = [this] { return m_accounts->primary() ? m_accounts->primary()->id
54 1759 : QString(); };
55 1751 deps.networkEnabled = [this] { return m_accounts->networkEnabled(); };
56
1/1
✓ Branch 40 → 41 taken 854 times.
854 m_backgroundAccounts = std::make_unique<BackgroundAccountSession>(std::move(deps), this);
57 // S106-185: the watches follow the session's network gate. Before, the
58 // background session's own gate was never opened in production (only
59 // tests called setNetworkEnabled), so no account was ever watched.
60
1/1
✓ Branch 45 → 46 taken 854 times.
854 connect(m_accounts.get(), &AccountSession::accountsLoaded, m_backgroundAccounts.get(), [this] {
61 866 m_backgroundAccounts->onAccountsLoaded();
62 866 m_backgroundAccounts->setNetworkEnabled(m_accounts->networkEnabled());
63 866 });
64
1/1
✓ Branch 49 → 50 taken 854 times.
854 connect(m_accounts.get(), &AccountSession::configurationChanged,
65 854 m_backgroundAccounts.get(), &BackgroundAccountSession::onConfigurationChanged);
66 // configurationRemoved() is a method: connecting it failed at run time
67 // ("signal not found"), so a removed account kept its watch.
68
1/1
✓ Branch 53 → 54 taken 854 times.
854 connect(m_accounts.get(), &AccountSession::accountRemoved,
69 854 m_backgroundAccounts.get(), &BackgroundAccountSession::onConfigurationRemoved);
70 854 }
71 854 }
72
73 1068 MailSessionCore::~MailSessionCore() {
74 // A real quit stops push reception; valid registrations stay for the
75 // next start (SPRINT-96.md §3.3).
76
2/2
✓ Branch 3 → 4 taken 820 times.
✓ Branch 3 → 6 taken 34 times.
854 if (m_davPush) m_davPush->shutdown();
77 854 m_davPush.reset();
78
2/2
✓ Branch 8 → 9 taken 820 times.
✓ Branch 8 → 11 taken 34 times.
854 if (m_fts) m_fts->shutdown();
79 854 m_health->setActive(false);
80 854 m_health->detach();
81 1068 }
82
83 824 bool MailSessionCore::openProfile(const QString &directory) {
84 824 m_profileError.clear();
85
9/12
✓ Branch 3 → 4 taken 824 times.
✓ Branch 5 → 6 taken 823 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 6 → 7 taken 823 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 822 times.
✓ Branch 10 → 11 taken 824 times.
✗ Branch 10 → 12 not taken.
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 20 taken 822 times.
✗ Branch 274 → 275 not taken.
✗ Branch 274 → 276 not taken.
824 if (directory.trimmed().isEmpty() || !QDir::isAbsolutePath(directory)) {
86 2 m_profileError = QStringLiteral("Cannot open the explicit mail profile directory.");
87 2 return false;
88 }
89
2/2
✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 44 taken 820 times.
822 if (!m_profileDirectory.isEmpty()) {
90
10/17
✓ Branch 22 → 23 taken 2 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 30 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 28 → 29 taken 1 time.
✗ Branch 28 → 30 not taken.
✓ Branch 31 → 32 taken 2 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 2 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 1 time.
✓ Branch 35 → 37 taken 1 time.
✗ Branch 278 → 279 not taken.
✗ Branch 278 → 280 not taken.
✗ Branch 282 → 283 not taken.
✗ Branch 282 → 284 not taken.
2 if (QFileInfo(directory).canonicalFilePath() == m_profileDirectory && m_cache->isOpen()) return true;
91 1 m_profileError = QStringLiteral("A mail session cannot switch its database profile.");
92 1 return false;
93 }
94
3/4
✓ Branch 45 → 46 taken 820 times.
✓ Branch 46 → 47 taken 820 times.
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 57 taken 820 times.
820 if (!QDir().mkpath(directory)) {
95 ✗ m_profileError = QStringLiteral("Cannot create the explicit mail profile directory.");
96 ✗ return false;
97 }
98
2/2
✓ Branch 57 → 58 taken 820 times.
✓ Branch 58 → 59 taken 820 times.
820 const QString canonical = QFileInfo(directory).canonicalFilePath();
99
3/3
✓ Branch 60 → 61 taken 820 times.
✓ Branch 61 → 62 taken 820 times.
✓ Branch 62 → 63 taken 820 times.
820 const QString cachePath = QDir(canonical).filePath("mail_cache.db");
100
5/13
✓ Branch 66 → 67 taken 820 times.
✗ Branch 66 → 74 not taken.
✓ Branch 68 → 69 taken 820 times.
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 75 taken 820 times.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
✗ Branch 76 → 77 not taken.
✓ Branch 76 → 78 taken 820 times.
✗ Branch 78 → 79 not taken.
✓ Branch 78 → 86 taken 820 times.
✗ Branch 301 → 302 not taken.
✗ Branch 301 → 303 not taken.
820 if (canonical.isEmpty() || (m_cache->isOpen() && m_cache->databasePath() != cachePath)) {
101 ✗ m_profileError = QStringLiteral("The mail session already has a different cache connection.");
102 ✗ return false;
103 }
104
1/1
✓ Branch 86 → 87 taken 820 times.
820 const auto started = MailJD::Performance::PerformanceTrace::monotonicNowNs();
105
1/1
✓ Branch 88 → 89 taken 820 times.
1640 m_fts = std::make_unique<FtsIndexCoordinator>([cachePath] {
106 ✗ MailCache threadCache;
107 ✗ if (!threadCache.open(cachePath)) return false;
108 ✗ return threadCache.search().rebuildSearchIndex();
109 1640 }, this);
110
1/1
✓ Branch 92 → 93 taken 820 times.
820 m_smtp = std::make_unique<SmtpService>(this);
111
1/1
✓ Branch 96 → 97 taken 820 times.
820 connect(m_fts.get(), &FtsIndexCoordinator::stateChanged, this,
112 820 [this](FtsIndexCoordinator::State state) {
113
1/4
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 820 times.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
820 if (state != FtsIndexCoordinator::State::Queued || m_ftsRebuildStartNs != 0) return;
114 ✗ m_ftsRebuildStartNs = MailJD::Performance::PerformanceTrace::monotonicNowNs();
115 ✗ auto &tracker = MailJD::Performance::BackgroundPerformanceTracker::instance();
116 ✗ tracker.beginBackground({QStringLiteral("fts_rebuild"), QStringLiteral("fts_incremental"),
117 ✗ QStringLiteral("search_during_fts"), QStringLiteral("typing_during_fts")}, m_ftsRebuildStartNs);
118 ✗ tracker.backgroundPhaseStarted(QStringLiteral("fts_index"));
119 ✗ });
120
1/1
✓ Branch 99 → 100 taken 820 times.
820 connect(m_fts.get(), &FtsIndexCoordinator::rebuildFinished, this, [this](bool success, quint64) {
121 ✗ MailJD::Performance::StartupPerformanceTracker::instance().recordPhase(
122 ✗ QStringLiteral("background_fts_rebuild"), m_ftsRebuildStartNs,
123 MailJD::Performance::PerformanceTrace::monotonicNowNs());
124 ✗ auto &tracker = MailJD::Performance::BackgroundPerformanceTracker::instance();
125 ✗ tracker.backgroundPhaseFinished(QStringLiteral("fts_index"));
126 ✗ tracker.backgroundFinished(success);
127 ✗ m_ftsRebuildStartNs = 0;
128 ✗ });
129
4/6
✓ Branch 102 → 103 taken 820 times.
✓ Branch 103 → 104 taken 820 times.
✗ Branch 103 → 107 not taken.
✓ Branch 105 → 106 taken 820 times.
✓ Branch 106 → 107 taken 820 times.
✗ Branch 106 → 108 not taken.
820 const bool opened = m_cache->isOpen() || m_cache->open(cachePath);
130
3/3
✓ Branch 109 → 110 taken 820 times.
✓ Branch 110 → 111 taken 820 times.
✓ Branch 114 → 115 taken 820 times.
1640 MailJD::Performance::StartupPerformanceTracker::instance().recordPhase(
131 1640 QStringLiteral("mail_database_open_migrations"), started,
132 MailJD::Performance::PerformanceTrace::monotonicNowNs());
133
1/2
✗ Branch 117 → 118 not taken.
✓ Branch 117 → 123 taken 820 times.
820 if (!opened) {
134 ✗ m_profileError = m_cache->lastError();
135 ✗ return false;
136 }
137 // D2: session-owned calendar store and background CalDAV sync. The store
138 // path matches the Widgets frontend, so both see the same data (WAL keeps
139 // the connections safe). The coordinator's timer writes; the Quick
140 // projections reload on its storeChanged() signal.
141 {
142 const QString calendarDb =
143
2/2
✓ Branch 123 → 124 taken 820 times.
✓ Branch 124 → 125 taken 820 times.
1640 QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation))
144
1/1
✓ Branch 128 → 129 taken 820 times.
820 .filePath(QStringLiteral("mailjd/cache/calendar.db"));
145
4/4
✓ Branch 134 → 135 taken 820 times.
✓ Branch 135 → 136 taken 820 times.
✓ Branch 136 → 137 taken 820 times.
✓ Branch 137 → 138 taken 820 times.
820 QDir().mkpath(QFileInfo(calendarDb).absolutePath());
146
1/1
✓ Branch 142 → 143 taken 820 times.
820 m_calendarStore = std::make_unique<CalendarStore>(this);
147
2/3
✓ Branch 146 → 147 taken 820 times.
✓ Branch 147 → 148 taken 820 times.
✗ Branch 147 → 187 not taken.
820 if (m_calendarStore->open(calendarDb)) {
148
1/1
✓ Branch 148 → 149 taken 820 times.
820 m_calendarSync = std::make_unique<CalendarSyncCoordinator>(calendarDb, this);
149
2/4
✓ Branch 3 → 4 taken 119 times.
✗ Branch 3 → 8 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 119 times.
939 m_calendarSync->setNetworkGate([this] { return m_accounts && m_accounts->networkEnabled(); });
150
2/3
✓ Branch 156 → 157 taken 820 times.
✗ Branch 156 → 159 not taken.
✓ Branch 158 → 159 taken 820 times.
820 if (m_accounts) m_calendarSync->startBackgroundSync();
151 820 DavPushCoordinator::Deps push;
152
2/2
✓ Branch 161 → 162 taken 820 times.
✓ Branch 163 → 164 taken 820 times.
820 push.sources.push_back(std::make_unique<CalendarPushSource>(m_calendarSync.get()));
153
1/1
✓ Branch 166 → 167 taken 820 times.
820 push.secrets = std::make_unique<KeyringDavPushSecretStore>();
154
3/4
✓ Branch 3 → 4 taken 1742 times.
✗ Branch 3 → 8 not taken.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 1741 times.
2562 push.networkAllowed = [this] { return m_accounts && m_accounts->networkEnabled(); };
155 820 push.resolveCredentials = [](QObject *owner, const QString &id, const QString &url, const QString &user,
156 DavCredentials::PasswordCallback callback) {
157 ✗ DavCredentials::readPasswordAsync(owner, id, url, user, std::move(callback));
158 820 };
159
1/1
✓ Branch 171 → 172 taken 820 times.
820 push.lifecycle = NetworkLifecycleCoordinator::instance();
160
3/3
✓ Branch 173 → 174 taken 820 times.
✓ Branch 177 → 178 taken 820 times.
✓ Branch 178 → 179 taken 820 times.
2460 m_davPush = std::make_unique<DavPushCoordinator>(QDir(canonical).filePath(QStringLiteral("dav-push.sqlite")),
161 1640 std::move(push), this);
162 820 } else {
163 ✗ m_calendarStore.reset();
164 ✗ m_calendarSync.reset();
165 }
166 820 }
167 // Q09: same runtime ownership for CardDAV contact sync into the shared
168 // contacts database (the Quick contact projection reloads on
169 // contactsChanged()). The shared DAV interval drives both coordinators.
170 {
171 const QString contactsDb =
172
2/2
✓ Branch 190 → 191 taken 820 times.
✓ Branch 191 → 192 taken 820 times.
1640 QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation))
173
1/1
✓ Branch 195 → 196 taken 820 times.
820 .filePath(QStringLiteral("mailjd/contacts.db"));
174
4/4
✓ Branch 201 → 202 taken 820 times.
✓ Branch 202 → 203 taken 820 times.
✓ Branch 203 → 204 taken 820 times.
✓ Branch 204 → 205 taken 820 times.
820 QDir().mkpath(QFileInfo(contactsDb).absolutePath());
175
1/1
✓ Branch 209 → 210 taken 820 times.
820 m_contactSync = std::make_unique<ContactSyncCoordinator>(contactsDb, this);
176
2/3
✓ Branch 213 → 214 taken 820 times.
✗ Branch 214 → 215 not taken.
✓ Branch 214 → 216 taken 820 times.
820 if (!m_contactSync->open()) m_contactSync.reset();
177 else {
178
3/4
✓ Branch 3 → 4 taken 144 times.
✗ Branch 3 → 8 not taken.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 142 times.
964 m_contactSync->setNetworkGate([this] { return m_accounts && m_accounts->networkEnabled(); });
179
2/3
✓ Branch 221 → 222 taken 820 times.
✗ Branch 221 → 224 not taken.
✓ Branch 223 → 224 taken 820 times.
820 if (m_accounts) m_contactSync->startBackgroundSync();
180 }
181 820 }
182 // Push starts once every collection source exists; address books join
183 // as a second source (S96.6.2).
184
5/8
✓ Branch 226 → 227 taken 820 times.
✗ Branch 226 → 230 not taken.
✓ Branch 228 → 229 taken 820 times.
✗ Branch 228 → 230 not taken.
✓ Branch 231 → 232 taken 820 times.
✗ Branch 231 → 240 not taken.
✓ Branch 234 → 235 taken 820 times.
✓ Branch 236 → 237 taken 820 times.
820 if (m_davPush && m_contactSync) m_davPush->addSource(std::make_unique<ContactPushSource>(m_contactSync.get()));
185
6/10
✓ Branch 241 → 242 taken 820 times.
✗ Branch 241 → 248 not taken.
✓ Branch 243 → 244 taken 820 times.
✗ Branch 243 → 248 not taken.
✓ Branch 245 → 246 taken 820 times.
✓ Branch 246 → 247 taken 820 times.
✗ Branch 246 → 248 not taken.
✓ Branch 249 → 250 taken 820 times.
✗ Branch 249 → 252 not taken.
✓ Branch 251 → 252 taken 820 times.
820 if (m_davPush && m_accounts && m_davPush->isOpen()) m_davPush->start();
186 820 m_profileDirectory = canonical;
187
1/1
✓ Branch 259 → 260 taken 820 times.
1640 m_settingsSync = std::make_unique<SettingsSyncSession>(*m_accounts, canonical,
188
1/2
✓ Branch 254 → 255 taken 820 times.
✗ Branch 254 → 257 not taken.
2460 m_calendarStore ? m_calendarStore->databasePath() : QString{}, this);
189
2/4
✓ Branch 265 → 266 taken 820 times.
✗ Branch 266 → 267 not taken.
✓ Branch 266 → 269 taken 820 times.
✗ Branch 268 → 269 not taken.
820 if (m_cache->search().searchIndexNeedsRebuild()) m_fts->requestRebuild();
190 820 return true;
191 820 }
192