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: 95.4% 83 / 0 / 87
Functions: 100.0% 8 / 0 / 8
Branches: 79.6% 144 / 0 / 181

data/CollectedAddressStore.cpp
Line Branch Exec Source
1 #include "CollectedAddressStore.h"
2
3 #include "ContactIndex.h"
4 #include "ContactSqlValue.h"
5
6 #include <QLoggingCategory>
7 #include <QSet>
8 #include <QSqlError>
9 #include <QSqlQuery>
10
11
4/7
✓ Branch 2 → 3 taken 11 times.
✓ Branch 2 → 8 taken 112 times.
✓ Branch 4 → 5 taken 11 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 11 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
123 Q_LOGGING_CATEGORY(lcContactCollect, "mailjd.contacts.collect")
12
13 120 bool CollectedAddressStore::recordSent(const QList<Recipient> &recipients, bool collect, const QDateTime &now) {
14
6/6
✓ Branch 2 → 3 taken 120 times.
✓ Branch 3 → 4 taken 111 times.
✓ Branch 3 → 5 taken 9 times.
✓ Branch 5 → 6 taken 9 times.
✓ Branch 6 → 7 taken 120 times.
✓ Branch 7 → 8 taken 120 times.
120 const QString stamp = (now.isValid() ? now : QDateTime::currentDateTimeUtc()).toUTC().toString(Qt::ISODate);
15 120 QSet<QString> seen;
16 120 int counted = 0;
17 120 int collected = 0;
18 // S106-169: without the transaction nothing is written (the writes used
19 // to run one by one in autocommit and the call still reported failure).
20
3/3
✓ Branch 11 → 12 taken 120 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 29 taken 119 times.
120 if (!m_db.transaction()) {
21
8/8
✓ Branch 13 → 14 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 19 → 20 taken 1 time.
✓ Branch 20 → 21 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 27 → 15 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
2 qCWarning(lcContactCollect) << "Recording sent recipients: transaction:" << m_db.lastError().text();
22 1 return false;
23 }
24 119 bool ok = true;
25
2/2
✓ Branch 167 → 31 taken 362 times.
✓ Branch 167 → 168 taken 119 times.
481 for (const Recipient &recipient : recipients) {
26
1/1
✓ Branch 32 → 33 taken 362 times.
362 const QString key = ContactIndex::addressKey(recipient.address);
27
7/9
✓ Branch 34 → 35 taken 362 times.
✗ Branch 34 → 41 not taken.
✓ Branch 37 → 38 taken 362 times.
✓ Branch 38 → 39 taken 362 times.
✗ Branch 38 → 41 not taken.
✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 42 taken 361 times.
✓ Branch 43 → 44 taken 1 time.
✓ Branch 43 → 45 taken 361 times.
362 if (key.isEmpty() || !key.contains(QLatin1Char('@')) || seen.contains(key))
28 1 continue;
29
1/1
✓ Branch 45 → 46 taken 361 times.
361 seen.insert(key);
30 361 bool known = false;
31 {
32
1/1
✓ Branch 46 → 47 taken 361 times.
361 QSqlQuery card(m_db);
33
1/1
✓ Branch 50 → 51 taken 361 times.
361 card.prepare(QStringLiteral("SELECT 1 FROM card_addresses WHERE address_key = ? LIMIT 1"));
34
2/2
✓ Branch 55 → 56 taken 361 times.
✓ Branch 56 → 57 taken 361 times.
361 card.addBindValue(sqlValue(key));
35
6/6
✓ Branch 59 → 60 taken 361 times.
✓ Branch 60 → 61 taken 360 times.
✓ Branch 60 → 64 taken 1 time.
✓ Branch 61 → 62 taken 360 times.
✓ Branch 62 → 63 taken 200 times.
✓ Branch 62 → 64 taken 160 times.
361 known = card.exec() && card.next();
36 361 }
37
4/4
✓ Branch 66 → 67 taken 161 times.
✓ Branch 66 → 69 taken 200 times.
✓ Branch 67 → 68 taken 2 times.
✓ Branch 67 → 69 taken 159 times.
361 if (!known && !collect)
38 2 continue;
39
1/1
✓ Branch 69 → 70 taken 359 times.
359 QSqlQuery usage(m_db);
40
1/1
✓ Branch 73 → 74 taken 359 times.
359 usage.prepare(QStringLiteral("INSERT INTO address_usage (address_key, use_count, last_used) VALUES (?, 1, ?) "
41 "ON CONFLICT(address_key) DO UPDATE SET use_count = use_count + 1, last_used = ?"));
42
2/2
✓ Branch 78 → 79 taken 359 times.
✓ Branch 79 → 80 taken 359 times.
359 usage.addBindValue(sqlValue(key));
43
2/2
✓ Branch 84 → 85 taken 359 times.
✓ Branch 85 → 86 taken 359 times.
359 usage.addBindValue(sqlValue(stamp));
44
2/2
✓ Branch 90 → 91 taken 359 times.
✓ Branch 91 → 92 taken 359 times.
359 usage.addBindValue(sqlValue(stamp));
45
4/5
✓ Branch 94 → 95 taken 359 times.
✓ Branch 95 → 96 taken 358 times.
✓ Branch 95 → 98 taken 1 time.
✓ Branch 96 → 97 taken 358 times.
✗ Branch 96 → 98 not taken.
359 ok = usage.exec() && ok;
46 359 ++counted;
47
2/2
✓ Branch 99 → 100 taken 200 times.
✓ Branch 99 → 101 taken 159 times.
359 if (known)
48 200 continue;
49
1/1
✓ Branch 101 → 102 taken 159 times.
159 QSqlQuery insert(m_db);
50
1/1
✓ Branch 105 → 106 taken 159 times.
159 insert.prepare(QStringLiteral(
51 "INSERT INTO collected_addresses (address_key, address, display_name, first_used, last_used) "
52 "VALUES (?, ?, ?, ?, ?) ON CONFLICT(address_key) DO UPDATE SET last_used = excluded.last_used, "
53 "display_name = CASE WHEN excluded.display_name <> '' THEN excluded.display_name ELSE display_name END"));
54
2/2
✓ Branch 110 → 111 taken 159 times.
✓ Branch 111 → 112 taken 159 times.
159 insert.addBindValue(sqlValue(key));
55
3/3
✓ Branch 115 → 116 taken 159 times.
✓ Branch 117 → 118 taken 159 times.
✓ Branch 118 → 119 taken 159 times.
159 insert.addBindValue(sqlValue(recipient.address.trimmed()));
56
6/7
✓ Branch 123 → 124 taken 159 times.
✓ Branch 124 → 125 taken 159 times.
✗ Branch 126 → 127 not taken.
✓ Branch 126 → 128 taken 159 times.
✓ Branch 128 → 129 taken 159 times.
✓ Branch 130 → 131 taken 159 times.
✓ Branch 131 → 132 taken 159 times.
159 insert.addBindValue(sqlValue(recipient.name.trimmed() == recipient.address.trimmed() ? QString() : recipient.name.trimmed()));
57
2/2
✓ Branch 139 → 140 taken 159 times.
✓ Branch 140 → 141 taken 159 times.
159 insert.addBindValue(sqlValue(stamp));
58
2/2
✓ Branch 145 → 146 taken 159 times.
✓ Branch 146 → 147 taken 159 times.
159 insert.addBindValue(sqlValue(stamp));
59
4/5
✓ Branch 149 → 150 taken 159 times.
✓ Branch 150 → 151 taken 158 times.
✓ Branch 150 → 153 taken 1 time.
✓ Branch 151 → 152 taken 158 times.
✗ Branch 151 → 153 not taken.
159 ok = insert.exec() && ok;
60 159 ++collected;
61 562 }
62
2/2
✓ Branch 168 → 169 taken 1 time.
✓ Branch 168 → 181 taken 118 times.
119 if (!ok) {
63
1/1
✓ Branch 169 → 170 taken 1 time.
1 m_db.rollback();
64
5/5
✓ Branch 170 → 171 taken 1 time.
✓ Branch 174 → 175 taken 1 time.
✓ Branch 175 → 176 taken 1 time.
✓ Branch 179 → 172 taken 1 time.
✓ Branch 179 → 180 taken 1 time.
2 qCWarning(lcContactCollect) << "Recording sent recipients failed";
65 1 return false;
66 }
67
1/1
✓ Branch 181 → 182 taken 118 times.
118 ok = m_db.commit();
68 // Counts only: addresses are contact data (privacy rule 14).
69
9/9
✓ Branch 182 → 183 taken 118 times.
✓ Branch 186 → 187 taken 118 times.
✓ Branch 187 → 188 taken 118 times.
✓ Branch 188 → 189 taken 118 times.
✓ Branch 189 → 190 taken 118 times.
✓ Branch 190 → 191 taken 118 times.
✓ Branch 191 → 192 taken 118 times.
✓ Branch 195 → 184 taken 118 times.
✓ Branch 195 → 196 taken 118 times.
236 qCInfo(lcContactCollect) << "Recorded" << counted << "recipients," << collected << "collected";
70 118 return ok;
71 120 }
72
73 // Both deletes or neither (S106-169).
74 5 bool CollectedAddressStore::inTransaction(const std::function<bool()> &write) {
75
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
5 if (!m_db.transaction())
76 ✗ return false;
77
3/6
✓ Branch 6 → 7 taken 5 times.
✗ Branch 6 → 10 not taken.
✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 10 not taken.
✓ Branch 11 → 12 taken 5 times.
✗ Branch 11 → 13 not taken.
5 if (write() && m_db.commit())
78 5 return true;
79 ✗ m_db.rollback();
80 ✗ return false;
81 }
82
83 3 bool CollectedAddressStore::forgetAll() {
84
1/1
✓ Branch 3 → 4 taken 3 times.
3 const bool ok = inTransaction([this] {
85
1/1
✓ Branch 2 → 3 taken 3 times.
3 QSqlQuery collected(m_db);
86
1/1
✓ Branch 3 → 4 taken 3 times.
3 QSqlQuery usage(m_db);
87
3/9
✓ Branch 7 → 8 taken 3 times.
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 15 not taken.
✓ Branch 22 → 23 taken 3 times.
✗ Branch 22 → 24 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
12 return collected.exec(QStringLiteral("DELETE FROM collected_addresses")) &&
88
4/11
✓ Branch 12 → 13 taken 3 times.
✓ Branch 13 → 14 taken 3 times.
✗ Branch 13 → 15 not taken.
✓ Branch 18 → 19 taken 3 times.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 3 times.
✗ Branch 20 → 22 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
6 usage.exec(QStringLiteral("DELETE FROM address_usage WHERE address_key NOT IN "
89
1/2
✓ Branch 16 → 17 taken 3 times.
✗ Branch 16 → 18 not taken.
6 "(SELECT address_key FROM card_addresses)"));
90 3 });
91
6/6
✓ Branch 5 → 6 taken 3 times.
✓ Branch 9 → 10 taken 3 times.
✓ Branch 10 → 11 taken 3 times.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 15 → 7 taken 3 times.
✓ Branch 15 → 16 taken 3 times.
6 qCInfo(lcContactCollect) << "Forgot collected addresses:" << ok;
92 3 return ok;
93 }
94
95 2 bool CollectedAddressStore::forget(const QString &address) {
96
1/1
✓ Branch 2 → 3 taken 2 times.
2 const QString key = ContactIndex::addressKey(address);
97
1/1
✓ Branch 4 → 5 taken 2 times.
4 return inTransaction([this, &key] {
98
1/1
✓ Branch 2 → 3 taken 2 times.
2 QSqlQuery collected(m_db);
99
1/1
✓ Branch 6 → 7 taken 2 times.
2 collected.prepare(QStringLiteral("DELETE FROM collected_addresses WHERE address_key = ?"));
100
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
2 collected.addBindValue(sqlValue(key));
101
1/1
✓ Branch 15 → 16 taken 2 times.
2 QSqlQuery usage(m_db);
102
1/1
✓ Branch 19 → 20 taken 2 times.
2 usage.prepare(QStringLiteral("DELETE FROM address_usage WHERE address_key = ? AND address_key NOT IN "
103 "(SELECT address_key FROM card_addresses)"));
104
2/2
✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
2 usage.addBindValue(sqlValue(key));
105
4/6
✓ Branch 28 → 29 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✗ Branch 29 → 33 not taken.
✓ Branch 30 → 31 taken 2 times.
✓ Branch 31 → 32 taken 2 times.
✗ Branch 31 → 33 not taken.
4 return collected.exec() && usage.exec();
106 6 });
107 2 }
108
109 5 int CollectedAddressStore::collectedCount() const {
110
1/1
✓ Branch 2 → 3 taken 5 times.
5 QSqlQuery q(m_db);
111
2/8
✓ Branch 14 → 15 taken 5 times.
✗ Branch 14 → 16 not taken.
✓ Branch 16 → 17 taken 5 times.
✗ Branch 16 → 22 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
10 if (q.exec(QStringLiteral("SELECT COUNT(*) FROM collected_addresses WHERE address_key NOT IN "
112
3/5
✓ Branch 6 → 7 taken 5 times.
✓ Branch 7 → 8 taken 5 times.
✗ Branch 7 → 11 not taken.
✓ Branch 12 → 13 taken 5 times.
✗ Branch 12 → 14 not taken.
15 "(SELECT address_key FROM card_addresses)")) &&
113
2/3
✓ Branch 8 → 9 taken 5 times.
✓ Branch 9 → 10 taken 5 times.
✗ Branch 9 → 11 not taken.
5 q.next())
114
2/2
✓ Branch 17 → 18 taken 5 times.
✓ Branch 18 → 19 taken 5 times.
5 return q.value(0).toInt();
115 ✗ return 0;
116 5 }
117