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.6% 28 / 0 / 29
Functions: 100.0% 5 / 0 / 5
Branches: 93.1% 27 / 0 / 29

controller/ThreadBuilder.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QList>
4 #include <QString>
5 #include <memory>
6 #include <vector>
7 #include <functional>
8
9 #include "data/Models.h"
10
11 // A node in the mail thread tree.
12 // Children are owned via unique_ptr; root nodes are owned by the caller.
13 // T-79.B1/H7: the node owns its MailHeader by value. A pointer into the
14 // source QList shared its (implicitly shared) buffer with MailListModel —
15 // the first detach made the tree paint stale data and a later reallocation
16 // turned every node pointer into a use-after-free.
17 struct ThreadNode {
18 MailHeader header;
19 ThreadNode *parent = nullptr;
20 std::vector<std::unique_ptr<ThreadNode>> children;
21 int depth = 0;
22
23 // Presentation metadata populated by MailThreadModel after the tree is built.
24 int rowInParent = -1;
25 int cachedDescendants = 0;
26 int cachedUnreadDescendants = 0;
27
28 424651 ~ThreadNode() = default;
29
30 // Newest date in this subtree (for sorting root nodes).
31 225983 QDateTime newestDate() const {
32 225983 QDateTime newest = header.date;
33
2/2
✓ Branch 22 → 5 taken 187587 times.
✓ Branch 22 → 23 taken 225983 times.
413570 for (const auto &child : children) {
34
1/1
✓ Branch 7 → 8 taken 187587 times.
187587 QDateTime childDate = child->newestDate();
35
9/11
✓ Branch 8 → 9 taken 187587 times.
✓ Branch 9 → 10 taken 187587 times.
✗ Branch 9 → 16 not taken.
✓ Branch 10 → 11 taken 187587 times.
✓ Branch 11 → 12 taken 187587 times.
✗ Branch 11 → 15 not taken.
✓ Branch 12 → 13 taken 187587 times.
✓ Branch 14 → 15 taken 159572 times.
✓ Branch 14 → 16 taken 28015 times.
✓ Branch 17 → 18 taken 159572 times.
✓ Branch 17 → 19 taken 28015 times.
187587 if (childDate.isValid() && (!newest.isValid() || childDate > newest))
36 159572 newest = childDate;
37 187587 }
38 225983 return newest;
39 ✗ }
40
41 // Total count of descendants (not including self).
42 6101 int descendantCount() const {
43 6101 int count = 0;
44
2/2
✓ Branch 9 → 4 taken 6035 times.
✓ Branch 9 → 10 taken 6101 times.
12136 for (const auto &child : children) {
45
1/1
✓ Branch 6 → 7 taken 6035 times.
6035 count += 1 + child->descendantCount();
46 }
47 6101 return count;
48 }
49
50 // T-547: Check if any descendant has unseen mail.
51 2007 bool hasUnseenDescendant() const {
52
2/2
✓ Branch 15 → 4 taken 2005 times.
✓ Branch 15 → 16 taken 2003 times.
4008 for (const auto &child : children) {
53
2/2
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 9 taken 2002 times.
2005 if (!child->header.isSeen())
54 4 return true;
55
3/3
✓ Branch 10 → 11 taken 2002 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 2001 times.
2002 if (child->hasUnseenDescendant())
56 1 return true;
57 }
58 2003 return false;
59 }
60
61 // T-547: Count unseen descendants (not including self).
62 2011 int unseenDescendantCount() const {
63 2011 int count = 0;
64
2/2
✓ Branch 13 → 4 taken 2007 times.
✓ Branch 13 → 14 taken 2011 times.
4018 for (const auto &child : children) {
65
2/2
✓ Branch 7 → 8 taken 6 times.
✓ Branch 7 → 9 taken 2001 times.
2007 if (!child->header.isSeen())
66 6 ++count;
67
1/1
✓ Branch 10 → 11 taken 2007 times.
2007 count += child->unseenDescendantCount();
68 }
69 2011 return count;
70 }
71 };
72
73 // Builds a deterministic references-first thread tree from a flat list of
74 // MailHeaders. Duplicate/missing IDs, cycles, depth, and conservative subject
75 // fallback have stable policies independent of input order.
76 class ThreadBuilder {
77 public:
78 struct FallbackCandidate {
79 QString subject;
80 QString participant;
81 qint64 date = 0;
82 bool reply = false;
83 };
84 // Maximum number of parent edges accepted for one message. Links beyond
85 // this depth are detached into another root so every message stays visible.
86 static constexpr int MaximumDepth = 64;
87 static constexpr qint64 FallbackWindowSeconds = 30LL * 24 * 60 * 60;
88
89 // Build thread tree from flat header list.
90 // The returned unique_ptrs own the root-level ThreadNodes (and their
91 // children). Each node holds its own copy of the MailHeader, so 'headers'
92 // may be modified or destroyed after this call.
93 static std::vector<std::unique_ptr<ThreadNode>>
94 buildThreads(const QList<MailHeader> &headers,
95 const std::function<bool()> &cancelled = {});
96
97 // Exact Message-ID/References dependency keys for component discovery.
98 // Subject fallback uses participant/date candidate rows separately.
99 static QStringList dependencyKeys(const MailHeader &header);
100
101 // Candidate rows use the same normalization, address parser and prefix
102 // classification as buildThreads. A root/reply pair can only participate
103 // in subject fallback when subject and participant match and its dates are
104 // ordered within 30 days. This may include roots with resolved references;
105 // the builder remains the final authority on whether a link is made.
106 static QList<FallbackCandidate> fallbackCandidates(const MailHeader &header);
107 };
108