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: 51.6% 49 / 0 / 95
Functions: 50.0% 1 / 0 / 2
Branches: 37.4% 108 / 0 / 289

quick/quick_main.cpp
Line Branch Exec Source
1 #include "QuickMailHost.h"
2 #include "app/core/MailSessionCore.h"
3 #include "app/core/ProfileLease.h"
4 #include "app/core/ProfileActivation.h"
5 #include "app/core/ProcessTimeZone.h"
6 #include "app/MailtoUrl.h"
7 #include "presentation/AboutInfo.h"
8 #include "service/DesktopIntegration.h"
9 #include "util/LogConfigurator.h"
10 #include "version.h"
11 #include <QCommandLineParser>
12 #include <cstdio>
13 #include <QDir>
14 #include <QFileInfo>
15 #include <QSettings>
16 #include <QTranslator>
17 // QApplication (not QGuiApplication): the tray backends
18 // (QSystemTrayIcon, KStatusNotifierItem) live in the widgets stack; the
19 // frontend itself stays pure Qt Quick.
20 #include <QApplication>
21 #include <QtWebEngineQuick>
22
23 3 int main(int argc, char **argv) {
24 // --version answers before any GUI exists, so it also works without a
25 // display (scripts, bug reports over SSH). The option is still registered
26 // below so --help lists it.
27
2/2
✓ Branch 19 → 3 taken 8 times.
✓ Branch 19 → 20 taken 2 times.
10 for (int index = 1; index < argc; ++index) {
28
7/8
✓ Branch 3 → 4 taken 8 times.
✓ Branch 4 → 5 taken 7 times.
✓ Branch 4 → 7 taken 1 time.
✓ Branch 5 → 6 taken 7 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 7 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 18 taken 7 times.
8 if (qstrcmp(argv[index], "--version") == 0 || qstrcmp(argv[index], "-v") == 0) {
29
3/3
✓ Branch 10 → 11 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
1 fputs(qPrintable(AboutInfo::versionReport()), stdout);
30 1 return 0;
31 }
32 }
33 // Before any time conversion: local time without a per-call zone reload.
34
1/1
✓ Branch 20 → 21 taken 2 times.
2 ProcessTimeZone::pin();
35
1/1
✓ Branch 21 → 22 taken 2 times.
2 QtWebEngineQuick::initialize();
36
1/1
✓ Branch 22 → 23 taken 2 times.
2 QApplication app(argc, argv);
37
1/1
✓ Branch 23 → 24 taken 2 times.
2 Q_INIT_RESOURCE(app_resources);
38
2/2
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
2 app.setApplicationName("MailJD");
39
2/2
✓ Branch 27 → 28 taken 2 times.
✓ Branch 28 → 29 taken 2 times.
2 app.setOrganizationName("MailJD");
40
2/2
✓ Branch 30 → 31 taken 2 times.
✓ Branch 31 → 32 taken 2 times.
2 app.setDesktopFileName("mailjd");
41
1/1
✓ Branch 36 → 37 taken 2 times.
2 app.setApplicationVersion(QStringLiteral(MAILJD_VERSION_FULL));
42
1/1
✓ Branch 39 → 40 taken 2 times.
2 QCommandLineParser parser;
43
2/2
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
2 parser.setApplicationDescription("MailJD - a keyboard-driven mail client with calendar, tasks and contacts.");
44
1/1
✓ Branch 43 → 44 taken 2 times.
2 parser.addHelpOption();
45 // Own version option: Qt's addVersionOption prints the version only; bug
46 // reports also need the commit and the Qt runtime (AboutInfo).
47
5/7
✓ Branch 48 → 49 taken 2 times.
✓ Branch 51 → 52 taken 2 times.
✓ Branch 52 → 53 taken 2 times.
✓ Branch 59 → 60 taken 4 times.
✓ Branch 59 → 61 taken 2 times.
✗ Branch 509 → 510 not taken.
✗ Branch 509 → 511 not taken.
6 parser.addOption({{"v", "version"}, "Displays version, commit, Qt version and license."});
48
5/5
✓ Branch 61 → 62 taken 2 times.
✓ Branch 62 → 63 taken 2 times.
✓ Branch 63 → 64 taken 2 times.
✓ Branch 65 → 66 taken 2 times.
✓ Branch 66 → 67 taken 2 times.
2 parser.addOption({"profile", "Use this profile directory instead of the standard one.", "directory"});
49
4/4
✓ Branch 72 → 73 taken 2 times.
✓ Branch 73 → 74 taken 2 times.
✓ Branch 76 → 77 taken 2 times.
✓ Branch 77 → 78 taken 2 times.
2 parser.addOption({"offline", "Use local accounts and cache without keyring or mail connections."});
50
4/4
✓ Branch 83 → 84 taken 2 times.
✓ Branch 84 → 85 taken 2 times.
✓ Branch 87 → 88 taken 2 times.
✓ Branch 88 → 89 taken 2 times.
2 parser.addOption({"minimized", "Start minimized."});
51
4/4
✓ Branch 94 → 95 taken 2 times.
✓ Branch 95 → 96 taken 2 times.
✓ Branch 98 → 99 taken 2 times.
✓ Branch 99 → 100 taken 2 times.
2 parser.addOption({"compose", "Open a new message."});
52
4/4
✓ Branch 105 → 106 taken 2 times.
✓ Branch 106 → 107 taken 2 times.
✓ Branch 109 → 110 taken 2 times.
✓ Branch 110 → 111 taken 2 times.
2 parser.addOption({"activate", "Bring the application window to the foreground."});
53
4/4
✓ Branch 116 → 117 taken 2 times.
✓ Branch 117 → 118 taken 2 times.
✓ Branch 118 → 119 taken 2 times.
✓ Branch 119 → 120 taken 2 times.
2 parser.addPositionalArgument("mailto", "Optional mailto URL to open in the composer.", "[mailto:...]");
54
4/4
✓ Branch 123 → 124 taken 2 times.
✓ Branch 124 → 125 taken 2 times.
✓ Branch 127 → 128 taken 2 times.
✓ Branch 128 → 129 taken 2 times.
2 parser.addOption({"dark", "Start with the dark theme."});
55
4/4
✓ Branch 134 → 135 taken 2 times.
✓ Branch 135 → 136 taken 2 times.
✓ Branch 138 → 139 taken 2 times.
✓ Branch 139 → 140 taken 2 times.
2 parser.addOption({"wide", "Start with the wide layout."});
56
5/5
✓ Branch 145 → 146 taken 2 times.
✓ Branch 146 → 147 taken 2 times.
✓ Branch 147 → 148 taken 2 times.
✓ Branch 149 → 150 taken 2 times.
✓ Branch 150 → 151 taken 2 times.
2 parser.addOption({"language", "Interface language: auto, en or de (overrides saved preference).", "language"});
57 // P90.6: register log options before process() and apply the resolved
58 // filter rules right after, so no startup category logs at unfiltered
59 // verbosity. Default keeps mailjd.* at Error (see LogConfigurator).
60
1/1
✓ Branch 156 → 157 taken 2 times.
2 LogConfigurator::registerOptions(parser);
61
1/1
✓ Branch 157 → 158 taken 2 times.
2 parser.process(app);
62
1/1
✓ Branch 158 → 159 taken 2 times.
2 LogConfigurator::apply(parser);
63
1/1
✓ Branch 160 → 161 taken 2 times.
2 ProcessTimeZone::logPinResult();
64
1/1
✓ Branch 161 → 162 taken 2 times.
2 const auto positional = parser.positionalArguments();
65
1/1
✓ Branch 162 → 163 taken 2 times.
2 const QString mailto = positional.value(0);
66
4/6
✓ Branch 164 → 165 taken 2 times.
✗ Branch 164 → 172 not taken.
✓ Branch 166 → 167 taken 1 time.
✓ Branch 166 → 173 taken 1 time.
✓ Branch 168 → 169 taken 1 time.
✗ Branch 168 → 172 not taken.
4 if (positional.size() > 1 || (!positional.isEmpty() &&
67
5/9
✓ Branch 169 → 170 taken 1 time.
✗ Branch 171 → 172 not taken.
✓ Branch 171 → 173 taken 1 time.
✓ Branch 174 → 175 taken 1 time.
✓ Branch 174 → 176 taken 1 time.
✗ Branch 176 → 177 not taken.
✓ Branch 176 → 182 taken 2 times.
✗ Branch 650 → 651 not taken.
✗ Branch 650 → 652 not taken.
4 (mailto.size() > 65536 || !MailtoRequest::parse(mailto)))) {
68 ✗ qCritical() << "Expected at most one valid mailto URL (maximum 64 KiB).";
69 ✗ return 2;
70 }
71 // F4.8: the Quick build is the product now, so the standard profile is the
72 // default. An explicit --profile still overrides it (tests, side-by-side).
73
3/3
✓ Branch 182 → 183 taken 2 times.
✓ Branch 183 → 184 taken 2 times.
✓ Branch 184 → 185 taken 2 times.
2 QString profileDirectory = parser.value("profile").trimmed();
74
1/2
✗ Branch 188 → 189 not taken.
✓ Branch 188 → 193 taken 2 times.
2 if (profileDirectory.isEmpty())
75 ✗ profileDirectory = ProfileLease::defaultDirectory();
76 // Keep the lock alive until both the frontend and all session workers are gone.
77
1/1
✓ Branch 193 → 194 taken 2 times.
2 ProfileLease profile;
78
4/5
✓ Branch 194 → 195 taken 2 times.
✓ Branch 195 → 196 taken 2 times.
✓ Branch 196 → 197 taken 2 times.
✓ Branch 199 → 200 taken 2 times.
✗ Branch 199 → 238 not taken.
2 if (!profile.acquire(QDir(profileDirectory).absolutePath())) {
79 2 QString activationError;
80 // The forwarding process passes its xdg-activation capability along so
81 // the owner can claim compositor focus for the raised window.
82
2/2
✓ Branch 210 → 211 taken 2 times.
✓ Branch 211 → 212 taken 2 times.
2 if (ProfileActivation::forward(QDir(profileDirectory).absolutePath(), mailto,
83
3/3
✓ Branch 207 → 208 taken 2 times.
✓ Branch 208 → 209 taken 2 times.
✓ Branch 209 → 210 taken 2 times.
4 parser.isSet("compose"), &activationError, 5000,
84
1/1
✓ Branch 200 → 201 taken 2 times.
2 DesktopIntegration::currentActivationPlatformData()
85
4/4
✓ Branch 205 → 206 taken 2 times.
✓ Branch 206 → 207 taken 2 times.
✓ Branch 221 → 222 taken 1 time.
✓ Branch 221 → 223 taken 1 time.
6 .value(QStringLiteral("activation-token")).toString()))
86 1 return 0;
87
2/2
✓ Branch 224 → 225 taken 1 time.
✓ Branch 227 → 228 taken 1 time.
1 qCritical().noquote() << profile.error();
88
2/2
✓ Branch 231 → 232 taken 1 time.
✓ Branch 233 → 234 taken 1 time.
1 qCritical().noquote() << activationError;
89 1 return 2;
90 2 }
91 ✗ if (profile.directory() != QFileInfo(ProfileLease::defaultDirectory()).canonicalFilePath()) {
92 ✗ const QString settingsPath = QDir(profile.directory()).filePath("settings");
93 ✗ QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, settingsPath);
94 ✗ QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, settingsPath);
95 ✗ QSettings::setPath(QSettings::NativeFormat, QSettings::SystemScope, settingsPath);
96 ✗ QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, settingsPath);
97 ✗ }
98 ✗ QTranslator translator;
99 ✗ QString language = parser.isSet("language") ? parser.value("language")
100 ✗ : QSettings().value("i18n/language", "auto").toString();
101 ✗ if (language != "auto" && language != "en" && language != "de") {
102 ✗ qCritical() << "Unsupported interface language:" << language;
103 ✗ return 2;
104 }
105 ✗ if (language == "auto") language = QLocale::system().language() == QLocale::German ? "de" : "en";
106 ✗ else QLocale::setDefault(QLocale(language)); // Date/month names follow an explicit interface language.
107 ✗ if (language == "de") {
108 ✗ if (!translator.load(":/translations/mailjd_de.qm")) return 2;
109 ✗ app.installTranslator(&translator);
110 }
111 ✗ MailSessionCore session;
112 ✗ if (!session.openProfile(profile.directory())) {
113 ✗ qCritical().noquote() << session.profileError();
114 ✗ return 2;
115 }
116 ✗ QuickMailHost host(session, profile.directory());
117 ✗ if (!host.initialize(!parser.isSet("offline"), parser.isSet("dark") ? "dark" : "",
118 ✗ parser.isSet("wide") ? "wide" : "", QSettings("MailJD", "MailJD").fileName(),
119 ✗ parser.isSet("minimized"))) {
120 ✗ qCritical().noquote() << host.error();
121 ✗ return 3;
122 }
123 ✗ if ((!mailto.isEmpty() || parser.isSet("compose") || parser.isSet("activate")) &&
124 ✗ !host.activate(mailto, parser.isSet("compose"),
125 ✗ DesktopIntegration::currentActivationPlatformData()
126 ✗ .value(QStringLiteral("activation-token")).toString())) {
127 ✗ qCritical().noquote() << host.error();
128 ✗ return 3;
129 }
130 ✗ ProfileActivation activation;
131 ✗ if (!activation.listen(profile, [&host](const QString &url, bool compose,
132 const QString &activationToken) {
133 ✗ return host.activate(url, compose, activationToken);
134 }))
135 ✗ qWarning().noquote() << "Desktop activation is unavailable:" << activation.error();
136 ✗ const int result = app.exec();
137 ✗ if (!host.flush()) {
138 ✗ qCritical().noquote() << host.error();
139 ✗ return 3;
140 }
141 ✗ return result;
142 4 }
143