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: 100.0% 191 / 0 / 191
Functions: 100.0% 22 / 0 / 22
Branches: 80.5% 276 / 0 / 343

service/PredictorTrainingService.cpp
Line Branch Exec Source
1 #include "PredictorTrainingService.h"
2 #include "data/FolderPredictor.h"
3 #include "data/MailCache.h"
4 #include <QCoreApplication>
5 #include <QFutureWatcher>
6 #include <QPointer>
7 #include <QtConcurrent>
8 #include <QLoggingCategory>
9
10
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 3 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.
4 Q_LOGGING_CATEGORY(lcPredictorTraining, "mailjd.predictor.training")
11
12
1/1
✓ Branch 3 → 4 taken 42 times.
42 PredictorTrainingService::PredictorTrainingService(QObject *parent) : QObject(parent) {
13
1/1
✓ Branch 6 → 7 taken 42 times.
42 m_pool.setMaxThreadCount(1);
14
1/1
✓ Branch 7 → 8 taken 42 times.
42 m_pool.setExpiryTimeout(1000);
15
1/1
✓ Branch 9 → 10 taken 42 times.
42 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
16 43 this, [this] { stop(); });
17 42 }
18 64 PredictorTrainingService::~PredictorTrainingService() { stop(); }
19 736 PredictorTrainingService *PredictorTrainingService::shared() {
20
3/4
✓ Branch 2 → 3 taken 21 times.
✓ Branch 2 → 7 taken 715 times.
✓ Branch 4 → 5 taken 21 times.
✗ Branch 4 → 7 not taken.
736 static QPointer<PredictorTrainingService> service;
21
5/8
✓ Branch 8 → 9 taken 21 times.
✓ Branch 8 → 16 taken 715 times.
✓ Branch 11 → 12 taken 21 times.
✓ Branch 12 → 13 taken 21 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 21 times.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
736 if (!service) service = new PredictorTrainingService(QCoreApplication::instance());
22 736 return service;
23 }
24 46 void PredictorTrainingService::stop() {
25 46 m_stopping = true;
26
4/4
✓ Branch 2 → 3 taken 46 times.
✓ Branch 3 → 4 taken 46 times.
✓ Branch 10 → 5 taken 10 times.
✓ Branch 10 → 11 taken 46 times.
56 for (const auto &request : m_requests) request->cancelled.store(true);
27
3/4
✓ Branch 11 → 12 taken 46 times.
✓ Branch 12 → 13 taken 46 times.
✗ Branch 19 → 14 not taken.
✓ Branch 19 → 20 taken 46 times.
46 for (const auto &request : m_incremental) request->cancelled.store(true);
28
1/1
✓ Branch 21 → 22 taken 46 times.
46 m_pool.waitForDone();
29 46 }
30 1498 void PredictorTrainingService::cancel(QObject *owner) {
31
4/4
✓ Branch 2 → 3 taken 1498 times.
✓ Branch 3 → 4 taken 1498 times.
✓ Branch 19 → 5 taken 111 times.
✓ Branch 19 → 20 taken 1498 times.
1609 for (const auto &request : m_requests)
32
6/6
✓ Branch 8 → 9 taken 109 times.
✓ Branch 8 → 12 taken 2 times.
✓ Branch 11 → 12 taken 73 times.
✓ Branch 11 → 13 taken 36 times.
✓ Branch 14 → 15 taken 75 times.
✓ Branch 14 → 17 taken 36 times.
111 if (!request->owner || request->owner == owner) request->cancelled.store(true);
33
4/4
✓ Branch 20 → 21 taken 1498 times.
✓ Branch 21 → 22 taken 1498 times.
✓ Branch 37 → 23 taken 8 times.
✓ Branch 37 → 38 taken 1498 times.
1506 for (const auto &request : m_incremental)
34
3/6
✓ Branch 26 → 27 taken 8 times.
✗ Branch 26 → 30 not taken.
✓ Branch 29 → 30 taken 8 times.
✗ Branch 29 → 31 not taken.
✓ Branch 32 → 33 taken 8 times.
✗ Branch 32 → 35 not taken.
8 if (!request->owner || request->owner == owner) request->cancelled.store(true);
35 1498 startNextIncremental();
36 1498 }
37 752 void PredictorTrainingService::train(QObject *owner, QString cachePath,
38 QString predictorPath, QString account,
39 QStringList excludedFolders, Callback callback,
40 std::function<void(int documents)> stored) {
41
1/1
✓ Branch 2 → 3 taken 752 times.
752 cancel(owner);
42
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 751 times.
756 if (!owner) return;
43 751 qsizetype metadataBytes = 2 * (cachePath.size() + predictorPath.size() + account.size());
44
4/4
✓ Branch 8 → 9 taken 751 times.
✓ Branch 9 → 10 taken 751 times.
✓ Branch 15 → 11 taken 5253 times.
✓ Branch 15 → 16 taken 751 times.
6004 for (const auto &folder : excludedFolders) metadataBytes += 2 * folder.size();
45
11/11
✓ Branch 16 → 17 taken 750 times.
✓ Branch 16 → 22 taken 1 time.
✓ Branch 17 → 18 taken 750 times.
✓ Branch 18 → 19 taken 749 times.
✓ Branch 18 → 22 taken 1 time.
✓ Branch 19 → 20 taken 748 times.
✓ Branch 19 → 22 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 747 times.
✓ Branch 24 → 25 taken 4 times.
✓ Branch 24 → 60 taken 747 times.
751 if (m_stopping || liveRequests() >= 8 || metadataBytes > 32768 || excludedFolders.size() > 128) {
46
4/4
✓ Branch 25 → 26 taken 4 times.
✓ Branch 29 → 30 taken 4 times.
✓ Branch 52 → 27 taken 4 times.
✓ Branch 52 → 53 taken 4 times.
8 qCWarning(lcPredictorTraining).noquote()
47 8 << QStringLiteral("bootstrap rejected: live=%1 queued=%2 stopping=%3")
48
5/5
✓ Branch 35 → 36 taken 4 times.
✓ Branch 36 → 37 taken 4 times.
✓ Branch 40 → 41 taken 4 times.
✓ Branch 42 → 43 taken 4 times.
✓ Branch 43 → 44 taken 4 times.
16 .arg(liveRequests()).arg(m_requests.size() + m_incremental.size()).arg(m_stopping);
49 4 callback({false, 0, QStringLiteral("Predictor training queue is unavailable or exceeds its metadata budget")});
50 4 return;
51 }
52
1/1
✓ Branch 60 → 61 taken 747 times.
747 auto request = std::make_shared<Request>();
53
1/1
✓ Branch 62 → 63 taken 747 times.
747 request->owner = owner;
54 747 const std::weak_ptr<Request> weak = request;
55
1/1
✓ Branch 65 → 66 taken 747 times.
1494 request->destroyed = connect(owner, &QObject::destroyed, this, [weak] {
56
1/2
✓ Branch 4 → 5 taken 67 times.
✗ Branch 4 → 7 not taken.
67 if (const auto request = weak.lock()) request->cancelled.store(true);
57 747 });
58
1/1
✓ Branch 70 → 71 taken 747 times.
747 m_requests.append(request);
59
3/6
✓ Branch 71 → 72 taken 747 times.
✓ Branch 72 → 73 taken 747 times.
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 75 taken 747 times.
✗ Branch 145 → 146 not taken.
✗ Branch 145 → 147 not taken.
747 auto *watcher = new QFutureWatcher<PredictorTrainingResult>(this);
60 747 connect(watcher, &QFutureWatcher<PredictorTrainingResult>::finished, this,
61
1/3
✓ Branch 78 → 79 taken 747 times.
✗ Branch 150 → 151 not taken.
✗ Branch 150 → 152 not taken.
1494 [this, request, watcher, callback = std::move(callback)]() mutable {
62
1/1
✓ Branch 2 → 3 taken 745 times.
745 auto result = watcher->result();
63
1/1
✓ Branch 3 → 4 taken 745 times.
745 watcher->deleteLater();
64
1/1
✓ Branch 5 → 6 taken 745 times.
745 disconnect(request->destroyed);
65
1/1
✓ Branch 6 → 7 taken 745 times.
745 m_requests.removeOne(request);
66
9/9
✓ Branch 7 → 8 taken 737 times.
✓ Branch 7 → 15 taken 8 times.
✓ Branch 10 → 11 taken 672 times.
✓ Branch 10 → 15 taken 65 times.
✓ Branch 13 → 14 taken 669 times.
✓ Branch 13 → 15 taken 3 times.
✓ Branch 16 → 17 taken 669 times.
✓ Branch 16 → 22 taken 76 times.
✓ Branch 19 → 20 taken 669 times.
745 if (!m_stopping && request->owner && !request->cancelled.load()) callback(std::move(result));
67 745 });
68 // Delivered on this (the GUI) thread, before the result: queued first.
69 747 const bool wantsStored = static_cast<bool>(stored);
70
1/1
✓ Branch 82 → 83 taken 747 times.
747 const QPointer<PredictorTrainingService> self(this);
71 751 auto announce = [self, request, stored = std::move(stored)](int documents) {
72
2/8
✓ Branch 4 → 5 taken 4 times.
✓ Branch 6 → 7 taken 4 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
4 QMetaObject::invokeMethod(self, [self, request, stored, documents] {
73
7/10
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 13 not taken.
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 13 not taken.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 13 taken 1 time.
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 16 taken 1 time.
4 if (self && !self->m_stopping && request->owner && !request->cancelled.load()) stored(documents);
74 4 }, Qt::QueuedConnection);
75 751 };
76
1/1
✓ Branch 98 → 99 taken 747 times.
747 watcher->setFuture(QtConcurrent::run(&m_pool,
77
2/14
✓ Branch 96 → 97 taken 747 times.
✓ Branch 97 → 98 taken 747 times.
✗ Branch 159 → 160 not taken.
✗ Branch 159 → 161 not taken.
✗ Branch 162 → 163 not taken.
✗ Branch 162 → 164 not taken.
✗ Branch 165 → 166 not taken.
✗ Branch 165 → 167 not taken.
✗ Branch 168 → 169 not taken.
✗ Branch 168 → 170 not taken.
✗ Branch 171 → 172 not taken.
✗ Branch 171 → 173 not taken.
✗ Branch 174 → 175 not taken.
✗ Branch 174 → 176 not taken.
2988 [request, cachePath = std::move(cachePath), predictorPath = std::move(predictorPath),
78 1494 account = std::move(account), excludedFolders = std::move(excludedFolders), announce, wantsStored] {
79 747 PredictorTrainingResult result;
80
2/2
✓ Branch 4 → 5 taken 10 times.
✓ Branch 4 → 6 taken 737 times.
747 if (request->cancelled.load()) return result;
81
1/1
✓ Branch 6 → 7 taken 737 times.
737 MailCache cache;
82
3/3
✓ Branch 7 → 8 taken 737 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 13 taken 736 times.
737 if (!cache.openReadOnly(cachePath)) {
83
1/1
✓ Branch 9 → 10 taken 1 time.
1 result.error = cache.lastError();
84 1 return result;
85 }
86
1/1
✓ Branch 13 → 14 taken 736 times.
736 FolderPredictor predictor;
87
3/3
✓ Branch 14 → 15 taken 736 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 23 taken 735 times.
736 if (!predictor.open(predictorPath)) {
88 1 result.error = QStringLiteral("Cannot open predictor database");
89 1 return result;
90 }
91
2/2
✓ Branch 23 → 24 taken 732 times.
✓ Branch 23 → 27 taken 3 times.
735 if (wantsStored)
92
4/4
✓ Branch 24 → 25 taken 732 times.
✓ Branch 25 → 26 taken 4 times.
✓ Branch 25 → 27 taken 728 times.
✓ Branch 26 → 27 taken 4 times.
732 if (const int documents = predictor.totalDocuments(); documents > 0) announce(documents);
93 2205 result.completed = predictor.trainFromCache(&cache, account, excludedFolders,
94
1/1
✓ Branch 28 → 29 taken 735 times.
735 &result.error, &request->cancelled,
95 &result.trainedDocuments);
96
1/1
✓ Branch 29 → 30 taken 735 times.
735 result.documents = predictor.totalDocuments();
97 735 return result;
98 737 }));
99 755 }
100
101 777 qsizetype PredictorTrainingService::liveRequests() const {
102 777 qsizetype live = 0;
103
6/6
✓ Branch 7 → 8 taken 47 times.
✓ Branch 7 → 12 taken 2 times.
✓ Branch 10 → 11 taken 45 times.
✓ Branch 10 → 12 taken 2 times.
✓ Branch 15 → 4 taken 49 times.
✓ Branch 15 → 16 taken 777 times.
826 for (const auto &request : m_requests) live += request->owner && !request->cancelled.load();
104
5/6
✓ Branch 21 → 22 taken 36 times.
✗ Branch 21 → 26 not taken.
✓ Branch 24 → 25 taken 28 times.
✓ Branch 24 → 26 taken 8 times.
✓ Branch 29 → 18 taken 36 times.
✓ Branch 29 → 30 taken 777 times.
813 for (const auto &request : m_incremental) live += request->owner && !request->cancelled.load();
105 777 return live;
106 }
107
108 28 bool PredictorTrainingService::hasPendingHeaders(const QObject *owner) const {
109
2/2
✓ Branch 17 → 4 taken 5 times.
✓ Branch 17 → 18 taken 23 times.
28 for (const auto &request : m_incremental)
110
3/6
✓ Branch 7 → 8 taken 5 times.
✗ Branch 7 → 12 not taken.
✓ Branch 10 → 11 taken 5 times.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 5 times.
✗ Branch 13 → 15 not taken.
5 if (request->owner == owner && !request->cancelled.load()) return true;
111 23 return false;
112 }
113
114 38 void PredictorTrainingService::trainHeaders(
115 QObject *owner, QString cachePath, QString predictorPath, QString account,
116 qint64 folderId, QString folder, quint32 uidValidity,
117 const QList<qint64> &uids, Callback callback) {
118
2/2
✓ Branch 2 → 3 taken 6 times.
✓ Branch 2 → 4 taken 32 times.
46 if (!owner) return;
119 8 const auto reject = [&callback] {
120 8 callback({false, 0, QStringLiteral("Predictor training queue is unavailable or exceeds its identity budget")});
121
1/3
✓ Branch 5 → 6 taken 8 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
24 };
122 32 const qsizetype metadataBytes = 2 * (cachePath.size() + predictorPath.size() +
123 32 account.size() + folder.size());
124
6/6
✓ Branch 10 → 11 taken 30 times.
✓ Branch 10 → 17 taken 1 time.
✓ Branch 12 → 13 taken 29 times.
✓ Branch 12 → 17 taken 1 time.
✓ Branch 13 → 14 taken 28 times.
✓ Branch 13 → 17 taken 1 time.
31 if (m_stopping || uids.isEmpty() || uids.size() > 1024 ||
125
8/8
✓ Branch 8 → 9 taken 31 times.
✓ Branch 8 → 17 taken 1 time.
✓ Branch 15 → 16 taken 27 times.
✓ Branch 15 → 17 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 26 times.
✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 22 taken 26 times.
63 folderId <= 0 || folder.isEmpty() || metadataBytes > 32768) {
126
1/1
✓ Branch 20 → 21 taken 6 times.
6 reject(); return;
127 }
128 26 QSet<qint64> incoming;
129
2/2
✓ Branch 32 → 25 taken 3110 times.
✓ Branch 32 → 33 taken 25 times.
3135 for (qint64 uid : uids) {
130
3/3
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 29 taken 3109 times.
✓ Branch 27 → 28 taken 1 time.
3110 if (uid <= 0) { reject(); return; }
131
1/1
✓ Branch 29 → 30 taken 3109 times.
3109 incoming.insert(uid);
132 }
133 25 std::shared_ptr<IncrementalRequest> request;
134 25 qsizetype queued = 0;
135
4/4
✓ Branch 33 → 35 taken 25 times.
✓ Branch 35 → 36 taken 25 times.
✓ Branch 78 → 37 taken 30 times.
✓ Branch 78 → 79 taken 25 times.
55 for (const auto &candidate : m_incremental) {
136
3/6
✓ Branch 40 → 41 taken 30 times.
✗ Branch 40 → 44 not taken.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 30 times.
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 30 times.
30 if (!candidate->owner || candidate->cancelled.load()) continue;
137 30 queued += candidate->known.size();
138
2/4
✓ Branch 55 → 56 taken 30 times.
✗ Branch 55 → 73 not taken.
✓ Branch 58 → 59 taken 30 times.
✗ Branch 58 → 73 not taken.
90 if (!candidate->cancelled.load() && candidate->owner == owner &&
139
2/4
✓ Branch 61 → 62 taken 30 times.
✗ Branch 61 → 73 not taken.
✓ Branch 64 → 65 taken 30 times.
✗ Branch 64 → 73 not taken.
90 candidate->cachePath == cachePath && candidate->predictorPath == predictorPath &&
140
3/4
✓ Branch 66 → 67 taken 2 times.
✓ Branch 66 → 73 taken 28 times.
✓ Branch 69 → 70 taken 2 times.
✗ Branch 69 → 73 not taken.
62 candidate->account == account && candidate->folderId == folderId &&
141
4/6
✓ Branch 52 → 53 taken 30 times.
✗ Branch 52 → 73 not taken.
✓ Branch 71 → 72 taken 2 times.
✗ Branch 71 → 73 not taken.
✓ Branch 74 → 75 taken 2 times.
✓ Branch 74 → 76 taken 28 times.
62 candidate->folder == folder && candidate->uidValidity == uidValidity)
142 2 request = candidate;
143 }
144
3/3
✓ Branch 80 → 81 taken 2 times.
✓ Branch 80 → 83 taken 23 times.
✓ Branch 82 → 83 taken 2 times.
25 if (request) incoming.subtract(request->known);
145
4/4
✓ Branch 84 → 85 taken 24 times.
✓ Branch 84 → 89 taken 1 time.
✓ Branch 91 → 92 taken 1 time.
✓ Branch 91 → 94 taken 24 times.
49 if (queued + incoming.size() > 1024 ||
146
4/5
✓ Branch 86 → 87 taken 23 times.
✓ Branch 86 → 90 taken 1 time.
✓ Branch 87 → 88 taken 23 times.
✗ Branch 88 → 89 not taken.
✓ Branch 88 → 90 taken 23 times.
24 (!request && liveRequests() >= 8)) {
147
1/1
✓ Branch 92 → 93 taken 1 time.
1 reject(); return;
148 }
149
2/2
✓ Branch 95 → 96 taken 23 times.
✓ Branch 95 → 125 taken 1 time.
24 if (!request) {
150
1/1
✓ Branch 96 → 97 taken 23 times.
23 request = std::make_shared<IncrementalRequest>();
151
1/1
✓ Branch 100 → 101 taken 23 times.
23 request->owner = owner;
152 23 request->cachePath = std::move(cachePath);
153 23 request->predictorPath = std::move(predictorPath);
154 23 request->account = std::move(account);
155 23 request->folderId = folderId;
156 23 request->folder = std::move(folder);
157 23 request->uidValidity = uidValidity;
158 23 const std::weak_ptr<IncrementalRequest> weak = request;
159
1/1
✓ Branch 117 → 118 taken 23 times.
46 request->destroyed = connect(owner, &QObject::destroyed, this, [weak] {
160
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
1 if (const auto request = weak.lock()) request->cancelled.store(true);
161 23 });
162
1/1
✓ Branch 122 → 123 taken 23 times.
23 m_incremental.append(request);
163 23 }
164 24 request->callback = std::move(callback);
165
2/2
✓ Branch 141 → 130 taken 3108 times.
✓ Branch 141 → 142 taken 24 times.
3132 for (qint64 uid : uids) {
166
3/3
✓ Branch 131 → 132 taken 3108 times.
✓ Branch 132 → 133 taken 260 times.
✓ Branch 132 → 134 taken 2848 times.
3108 if (!incoming.remove(uid)) continue;
167
1/1
✓ Branch 135 → 136 taken 2848 times.
2848 request->pending.append(uid);
168
1/1
✓ Branch 137 → 138 taken 2848 times.
2848 request->known.insert(uid);
169 }
170
1/1
✓ Branch 142 → 143 taken 24 times.
24 startNextIncremental();
171 27 }
172
173 1556 void PredictorTrainingService::startNextIncremental() {
174
4/4
✓ Branch 2 → 3 taken 1555 times.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 5 taken 1545 times.
3066 if (m_stopping || m_incrementalActive) return;
175
5/6
✓ Branch 11 → 12 taken 42 times.
✓ Branch 11 → 21 taken 1510 times.
✓ Branch 15 → 16 taken 42 times.
✗ Branch 15 → 20 not taken.
✓ Branch 22 → 6 taken 7 times.
✓ Branch 22 → 23 taken 1545 times.
1636 while (!m_incremental.isEmpty() &&
176
4/4
✓ Branch 12 → 13 taken 42 times.
✓ Branch 16 → 17 taken 42 times.
✓ Branch 19 → 20 taken 7 times.
✓ Branch 19 → 21 taken 35 times.
84 (!m_incremental.first()->owner || m_incremental.first()->cancelled.load())) {
177
2/2
✓ Branch 6 → 7 taken 7 times.
✓ Branch 8 → 9 taken 7 times.
7 disconnect(m_incremental.first()->destroyed);
178 7 m_incremental.removeFirst();
179 }
180
2/2
✓ Branch 24 → 25 taken 1510 times.
✓ Branch 24 → 26 taken 35 times.
1545 if (m_incremental.isEmpty()) return;
181
1/1
✓ Branch 26 → 27 taken 35 times.
35 const auto request = m_incremental.first();
182
1/1
✓ Branch 29 → 30 taken 35 times.
35 const auto batch = request->pending.mid(0, 128);
183
1/1
✓ Branch 32 → 33 taken 35 times.
35 request->pending.remove(0, batch.size());
184 35 m_incrementalActive = true;
185
3/6
✓ Branch 33 → 34 taken 35 times.
✓ Branch 34 → 35 taken 35 times.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 35 times.
✗ Branch 53 → 54 not taken.
✗ Branch 53 → 55 not taken.
35 auto *watcher = new QFutureWatcher<PredictorTrainingResult>(this);
186 35 connect(watcher, &QFutureWatcher<PredictorTrainingResult>::finished, this,
187
1/5
✓ Branch 39 → 40 taken 35 times.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
70 [this, request, batch, watcher] {
188
1/1
✓ Branch 2 → 3 taken 35 times.
35 auto result = watcher->result();
189
1/1
✓ Branch 3 → 4 taken 35 times.
35 watcher->deleteLater();
190 35 m_incrementalActive = false;
191
1/1
✓ Branch 4 → 5 taken 35 times.
35 m_incremental.removeOne(request);
192
3/3
✓ Branch 9 → 10 taken 2837 times.
✓ Branch 12 → 7 taken 2837 times.
✓ Branch 12 → 13 taken 35 times.
2872 for (qint64 uid : batch) request->known.remove(uid);
193 35 request->trainedDocuments += result.trainedDocuments;
194
1/1
✓ Branch 14 → 15 taken 35 times.
35 QPointer<PredictorTrainingService> self(this);
195
7/8
✓ Branch 15 → 16 taken 35 times.
✗ Branch 15 → 23 not taken.
✓ Branch 18 → 19 taken 34 times.
✓ Branch 18 → 23 taken 1 time.
✓ Branch 21 → 22 taken 33 times.
✓ Branch 21 → 23 taken 1 time.
✓ Branch 24 → 25 taken 33 times.
✓ Branch 24 → 42 taken 2 times.
35 if (!m_stopping && request->owner && !request->cancelled.load()) {
196
6/6
✓ Branch 25 → 26 taken 29 times.
✓ Branch 25 → 30 taken 4 times.
✓ Branch 28 → 29 taken 19 times.
✓ Branch 28 → 30 taken 10 times.
✓ Branch 31 → 32 taken 19 times.
✓ Branch 31 → 33 taken 14 times.
33 if (result.completed && !request->pending.isEmpty()) {
197
1/1
✓ Branch 32 → 44 taken 19 times.
19 m_incremental.append(request); // Fairness between bounded mailbox batches.
198 } else {
199
1/1
✓ Branch 34 → 35 taken 14 times.
14 disconnect(request->destroyed);
200 14 result.trainedDocuments = request->trainedDocuments;
201
1/1
✓ Branch 39 → 40 taken 14 times.
14 request->callback(std::move(result));
202 }
203 } else {
204
1/1
✓ Branch 43 → 44 taken 2 times.
2 disconnect(request->destroyed);
205 }
206
3/3
✓ Branch 45 → 46 taken 34 times.
✓ Branch 45 → 47 taken 1 time.
✓ Branch 46 → 47 taken 34 times.
35 if (self) startNextIncremental();
207 35 });
208
2/4
✓ Branch 44 → 45 taken 35 times.
✓ Branch 45 → 46 taken 35 times.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
35 watcher->setFuture(QtConcurrent::run(&m_pool, [request, batch] {
209 35 PredictorTrainingResult result;
210
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 33 times.
35 if (request->cancelled.load()) return result;
211
1/1
✓ Branch 6 → 7 taken 33 times.
33 MailCache cache;
212
3/3
✓ Branch 8 → 9 taken 33 times.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 14 taken 32 times.
33 if (!cache.openReadOnly(request->cachePath)) {
213
1/1
✓ Branch 10 → 11 taken 1 time.
1 result.error = cache.lastError(); return result;
214 }
215 32 const auto page = cache.training().trainingHeaders(request->account, request->folderId,
216
1/1
✓ Branch 19 → 20 taken 32 times.
32 request->folder, request->uidValidity, batch);
217
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 24 taken 31 times.
32 if (!page.error.isEmpty()) { result.error = page.error; return result; }
218
1/1
✓ Branch 24 → 25 taken 31 times.
31 FolderPredictor predictor;
219
3/3
✓ Branch 26 → 27 taken 31 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 35 taken 30 times.
31 if (!predictor.open(request->predictorPath)) {
220 1 result.error = QStringLiteral("Cannot open predictor database"); return result;
221 }
222
1/1
✓ Branch 40 → 41 taken 30 times.
30 result.completed = predictor.trainCachedHeaders(request->account, request->folder,
223 30 request->uidValidity, page.headers,
224 58 [&] { return cache.training().trainingIdentityCurrent(request->account, request->folderId,
225 58 request->folder, request->uidValidity); },
226 30 &result.error, &request->cancelled, &result.trainedDocuments);
227
1/1
✓ Branch 42 → 43 taken 30 times.
30 result.documents = predictor.totalDocuments();
228 30 return result;
229 33 }));
230 35 }
231