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: 96.3% 105 / 0 / 109
Functions: 100.0% 22 / 0 / 22
Branches: 69.5% 121 / 0 / 174

service/SearchWorkerLane.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QCoreApplication>
4 #include <QFutureWatcher>
5 #include <QPointer>
6 #include <QThreadPool>
7 #include <QtConcurrent>
8 #include <atomic>
9 #include <functional>
10 #include <memory>
11 #include <optional>
12
13 // One background lane of the search reader (Sprint 86, generalized by
14 // Sprint 101 S101.1.3): a single worker thread, at most one pending request
15 // per owner (a new request replaces the owner's previous one and interrupts
16 // it), a bounded queue, and delivery on the GUI thread only while the owner
17 // lives and the request was not cancelled. The search reader runs pages and
18 // summaries on two lanes so a result page never waits for a summary.
19 template <typename Request, typename Result>
20 class SearchWorkerLane {
21 public:
22 using Callback = std::function<void(Result)>;
23 using Executor = std::function<Result(const Request &, const std::atomic_bool &)>;
24
25 68 SearchWorkerLane(QObject *context, Executor executor, int maxPending)
26
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::SearchWorkerLane(QObject*, std::function<MailSearchPage (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 4 → 5 taken 34 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::SearchWorkerLane(QObject*, std::function<MailSearchSummary (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 4 → 5 taken 34 times.
68 : m_context(context), m_executor(std::move(executor)), m_maxPending(maxPending) {
27
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::SearchWorkerLane(QObject*, std::function<MailSearchPage (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 7 → 8 taken 34 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::SearchWorkerLane(QObject*, std::function<MailSearchSummary (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 7 → 8 taken 34 times.
68 m_pool.setMaxThreadCount(1);
28
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::SearchWorkerLane(QObject*, std::function<MailSearchPage (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 8 → 9 taken 34 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::SearchWorkerLane(QObject*, std::function<MailSearchSummary (MailSearchRequest const&, std::atomic<bool> const&)>, int):
✓ Branch 8 → 9 taken 34 times.
68 m_pool.setExpiryTimeout(1000);
29 68 }
30 68 ~SearchWorkerLane() { stop(); }
31 SearchWorkerLane(const SearchWorkerLane &) = delete;
32 SearchWorkerLane &operator=(const SearchWorkerLane &) = delete;
33
34 // False when the queue is full or the lane stopped (the caller answers Busy).
35 657 bool enqueue(QObject *owner, Request request, Callback callback) {
36
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 2 → 3 taken 380 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 2 → 3 taken 277 times.
657 cancel(owner);
37
10/16
SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 3 → 4 taken 380 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 380 times.
✗ Branch 4 → 7 not taken.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 378 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 11 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 3 → 4 taken 277 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 277 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 277 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 277 times.
657 if (!owner || m_stopping || m_pending.size() >= m_maxPending) return false;
38
6/22
SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 11 → 12 taken 378 times.
✓ Branch 16 → 17 taken 378 times.
✓ Branch 17 → 18 taken 378 times.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 11 → 12 taken 277 times.
✓ Branch 16 → 17 taken 277 times.
✓ Branch 17 → 18 taken 277 times.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
655 Item item{owner, std::move(request), std::move(callback), std::make_shared<std::atomic_bool>(false), {}};
39
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 19 → 20 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 19 → 20 taken 277 times.
655 item.ownerDestroyed = QObject::connect(owner, &QObject::destroyed, m_context,
40 1338 [cancelled = item.cancelled] { cancelled->store(true); });
41
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 24 → 25 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 24 → 25 taken 277 times.
655 m_pending.append(std::move(item));
42
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchPage)>):
✓ Branch 25 → 26 taken 378 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::enqueue(QObject*, MailSearchRequest, std::function<void (MailSearchSummary)>):
✓ Branch 25 → 26 taken 277 times.
655 startNext();
43 655 return true;
44 655 }
45
46 5309 void cancel(QObject *owner) {
47
12/12
SearchWorkerLane<MailSearchRequest, MailSearchPage>::cancel(QObject*):
✓ Branch 3 → 4 taken 289 times.
✓ Branch 3 → 8 taken 3726 times.
✓ Branch 6 → 7 taken 146 times.
✓ Branch 6 → 8 taken 143 times.
✓ Branch 9 → 10 taken 146 times.
✓ Branch 9 → 13 taken 3869 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::cancel(QObject*):
✓ Branch 3 → 4 taken 262 times.
✓ Branch 3 → 8 taken 1032 times.
✓ Branch 6 → 7 taken 156 times.
✓ Branch 6 → 8 taken 106 times.
✓ Branch 9 → 10 taken 156 times.
✓ Branch 9 → 13 taken 1138 times.
5309 if (m_active && m_active->owner == owner) m_active->cancelled->store(true);
48 6088 m_pending.removeIf([owner](const Item &item) {
49
5/6
✓ Branch 3 → 4 taken 779 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 712 times.
✓ Branch 5 → 7 taken 67 times.
✓ Branch 8 → 9 taken 712 times.
✓ Branch 8 → 10 taken 67 times.
779 if (item.owner && item.owner != owner) return false;
50 67 QObject::disconnect(item.ownerDestroyed);
51 67 return true;
52 });
53 5309 }
54
55 138 void stop() {
56 138 m_stopping = true;
57
6/10
SearchWorkerLane<MailSearchRequest, MailSearchPage>::stop():
✓ Branch 2 → 3 taken 69 times.
✓ Branch 3 → 4 taken 69 times.
✗ Branch 6 → 7 not taken.
✗ Branch 9 → 5 not taken.
✓ Branch 9 → 10 taken 69 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::stop():
✓ Branch 2 → 3 taken 69 times.
✓ Branch 3 → 4 taken 69 times.
✗ Branch 6 → 7 not taken.
✗ Branch 9 → 5 not taken.
✓ Branch 9 → 10 taken 69 times.
276 for (const auto &item : m_pending) QObject::disconnect(item.ownerDestroyed);
58 138 m_pending.clear();
59
4/4
SearchWorkerLane<MailSearchRequest, MailSearchPage>::stop():
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 18 taken 67 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::stop():
✓ Branch 12 → 13 taken 4 times.
✓ Branch 12 → 18 taken 65 times.
138 if (m_active) {
60 6 QObject::disconnect(m_active->ownerDestroyed);
61 6 m_active->cancelled->store(true);
62 }
63
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::stop():
✓ Branch 19 → 20 taken 69 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::stop():
✓ Branch 19 → 20 taken 69 times.
138 m_pool.waitForDone();
64 138 }
65
66 bool stopping() const { return m_stopping; }
67 4 int queueDepth() const { return int(m_pending.size()); }
68
2/2
✓ Branch 3 → 4 taken 14 times.
✓ Branch 3 → 5 taken 23 times.
37 int activeReads() const { return m_active ? 1 : 0; }
69
70 private:
71 struct Item {
72 QPointer<QObject> owner;
73 Request request;
74 Callback callback;
75 std::shared_ptr<std::atomic_bool> cancelled;
76 QMetaObject::Connection ownerDestroyed;
77 };
78
79 1240 void startNext() {
80
10/12
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 2 → 3 taken 702 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 105 times.
✓ Branch 4 → 6 taken 597 times.
✓ Branch 7 → 8 taken 105 times.
✓ Branch 7 → 9 taken 597 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 2 → 3 taken 538 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 70 times.
✓ Branch 4 → 6 taken 468 times.
✓ Branch 7 → 8 taken 70 times.
✓ Branch 7 → 9 taken 468 times.
1717 if (m_stopping || m_active) return;
81
10/14
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 14 → 15 taken 325 times.
✓ Branch 14 → 19 taken 272 times.
✓ Branch 15 → 16 taken 325 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 325 times.
✗ Branch 20 → 10 not taken.
✓ Branch 20 → 21 taken 597 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 14 → 15 taken 263 times.
✓ Branch 14 → 19 taken 205 times.
✓ Branch 15 → 16 taken 263 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 263 times.
✗ Branch 20 → 10 not taken.
✓ Branch 20 → 21 taken 468 times.
1065 while (!m_pending.isEmpty() && !m_pending.first().owner) {
82 ✗ QObject::disconnect(m_pending.first().ownerDestroyed);
83 ✗ m_pending.removeFirst();
84 }
85
4/4
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 22 → 23 taken 272 times.
✓ Branch 22 → 24 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 22 → 23 taken 205 times.
✓ Branch 22 → 24 taken 263 times.
1065 if (m_pending.isEmpty()) return;
86
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 24 → 25 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 24 → 25 taken 263 times.
588 m_active = m_pending.takeFirst();
87
6/12
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 27 → 28 taken 325 times.
✓ Branch 28 → 29 taken 325 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 325 times.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 27 → 28 taken 263 times.
✓ Branch 28 → 29 taken 263 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 263 times.
✗ Branch 52 → 53 not taken.
✗ Branch 52 → 54 not taken.
588 auto *watcher = new QFutureWatcher<Result>(m_context);
88
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 31 → 32 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 31 → 32 taken 263 times.
1173 QObject::connect(watcher, &QFutureWatcher<Result>::finished, m_context, [this, watcher] {
89 585 auto item = std::move(*m_active);
90 585 m_active.reset();
91
1/1
✓ Branch 6 → 7 taken 585 times.
585 QObject::disconnect(item.ownerDestroyed);
92
1/1
✓ Branch 7 → 8 taken 585 times.
585 auto result = watcher->result();
93
1/1
✓ Branch 8 → 9 taken 585 times.
585 watcher->deleteLater();
94
1/1
✓ Branch 9 → 10 taken 585 times.
585 QPointer<QObject> guard(m_context);
95
8/9
✓ Branch 10 → 11 taken 585 times.
✗ Branch 10 → 17 not taken.
✓ Branch 12 → 13 taken 559 times.
✓ Branch 12 → 17 taken 26 times.
✓ Branch 15 → 16 taken 489 times.
✓ Branch 15 → 17 taken 70 times.
✓ Branch 18 → 19 taken 489 times.
✓ Branch 18 → 24 taken 96 times.
✓ Branch 21 → 22 taken 489 times.
585 if (!m_stopping && item.owner && !item.cancelled->load()) item.callback(std::move(result));
96
2/3
✓ Branch 25 → 26 taken 585 times.
✗ Branch 25 → 27 not taken.
✓ Branch 26 → 27 taken 585 times.
585 if (guard) startNext();
97 585 });
98
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 34 → 35 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 34 → 35 taken 263 times.
588 const auto request = m_active->request;
99 588 const auto cancelled = m_active->cancelled;
100
2/2
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 37 → 38 taken 325 times.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 37 → 38 taken 263 times.
588 const auto executor = m_executor;
101
8/16
SearchWorkerLane<MailSearchRequest, MailSearchPage>::startNext():
✓ Branch 38 → 39 taken 325 times.
✓ Branch 40 → 41 taken 325 times.
✓ Branch 41 → 42 taken 325 times.
✓ Branch 42 → 43 taken 325 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
SearchWorkerLane<MailSearchRequest, MailSearchSummary>::startNext():
✓ Branch 38 → 39 taken 263 times.
✓ Branch 40 → 41 taken 263 times.
✓ Branch 41 → 42 taken 263 times.
✓ Branch 42 → 43 taken 263 times.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 63 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
1176 watcher->setFuture(QtConcurrent::run(&m_pool, [request, cancelled, executor] {
102 588 return executor(request, *cancelled);
103 }));
104 588 }
105
106 QObject *m_context;
107 Executor m_executor;
108 int m_maxPending;
109 QThreadPool m_pool;
110 QList<Item> m_pending;
111 std::optional<Item> m_active;
112 bool m_stopping = false;
113 };
114