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: 42.6% 26 / 0 / 61
Functions: 66.7% 2 / 0 / 3
Branches: 36.5% 35 / 0 / 96

presentation/QuickComposeForwardAttachments.cpp
Line Branch Exec Source
1 #include "QuickComposeForwardAttachments.h"
2
3 #include "data/AttachmentStore.h"
4 #include "data/MailCache.h"
5 #include "presentation/QuickComposeBackend.h"
6
7 #include <QFileInfo>
8 #include "service/Rfc2822Builder.h"
9
10 #include <QDir>
11 #include <QFile>
12 #include <QLoggingCategory>
13 #include <QTemporaryDir>
14
15 Q_DECLARE_LOGGING_CATEGORY(lcQuickBackend)
16
17 namespace QuickComposeForwardAttachments {
18
19 6 QStringList exportTo(const MailCache &reader, qint64 folderId, qint64 uid,
20 const QString &scratchDir) {
21 6 QStringList exported;
22
1/1
✓ Branch 3 → 4 taken 6 times.
6 const auto parts = reader.attachments().attachments(folderId, uid);
23
1/1
✓ Branch 4 → 5 taken 6 times.
6 const QDir dir(scratchDir);
24 6 qint64 total = 0;
25 6 QStringList skipped;
26
2/2
✓ Branch 105 → 7 taken 3 times.
✓ Branch 105 → 106 taken 6 times.
9 for (const Attachment &part : parts) {
27
3/6
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 11 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 3 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 16 taken 3 times.
6 if (part.size > Rfc2822Builder::MaxAttachmentBytes - total ||
28 3 exported.size() >= 16) {
29 ✗ skipped << part.filename;
30 ✗ continue;
31 }
32
1/1
✓ Branch 17 → 18 taken 3 times.
3 const QByteArray blob = reader.attachments().attachmentData(part.id);
33
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 22 taken 3 times.
3 if (blob.isEmpty()) {
34 ✗ skipped << part.filename;
35 ✗ continue;
36 }
37 QString safeName =
38
2/2
✓ Branch 25 → 26 taken 3 times.
✓ Branch 28 → 29 taken 3 times.
3 QString(part.filename).remove(QLatin1Char('/')).remove(QLatin1Char('\\'));
39
4/8
✓ Branch 32 → 33 taken 3 times.
✗ Branch 32 → 39 not taken.
✓ Branch 35 → 36 taken 3 times.
✗ Branch 35 → 39 not taken.
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 3 times.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 52 taken 3 times.
3 if (safeName.isEmpty() || safeName == QLatin1String(".") || safeName == QLatin1String(".."))
40 ✗ safeName = QStringLiteral("attachment-%1.bin").arg(part.id);
41 // S106-254: one subdirectory per attachment — two parts of one mail may
42 // share a file name ("a/b" and "ab" collide after the slash removal);
43 // the second used to overwrite the first, and the forward sent one
44 // file twice. The file keeps its name, which becomes the part's name.
45
2/2
✓ Branch 53 → 54 taken 3 times.
✓ Branch 54 → 55 taken 3 times.
3 const QString slot = dir.filePath(QString::number(exported.size()));
46
2/2
✓ Branch 56 → 57 taken 3 times.
✓ Branch 57 → 58 taken 3 times.
3 const QString file = QDir(slot).filePath(safeName);
47
1/1
✓ Branch 59 → 60 taken 3 times.
3 QFile out(file);
48
9/19
✓ Branch 61 → 62 taken 3 times.
✓ Branch 62 → 63 taken 3 times.
✓ Branch 63 → 64 taken 3 times.
✗ Branch 63 → 70 not taken.
✓ Branch 65 → 66 taken 3 times.
✓ Branch 66 → 67 taken 3 times.
✗ Branch 66 → 70 not taken.
✗ Branch 69 → 70 not taken.
✓ Branch 69 → 71 taken 3 times.
✓ Branch 72 → 73 taken 3 times.
✗ Branch 72 → 74 not taken.
✓ Branch 74 → 75 taken 3 times.
✗ Branch 74 → 76 not taken.
✗ Branch 76 → 77 not taken.
✓ Branch 76 → 79 taken 3 times.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 151 not taken.
✗ Branch 153 → 154 not taken.
✗ Branch 153 → 155 not taken.
6 if (!QDir().mkpath(slot) || !out.open(QIODevice::NewOnly | QIODevice::WriteOnly) ||
49
1/1
✓ Branch 67 → 68 taken 3 times.
3 out.write(blob) != blob.size()) {
50 ✗ skipped << part.filename;
51 ✗ continue;
52 }
53 3 total += blob.size();
54
1/1
✓ Branch 80 → 81 taken 3 times.
3 exported << file;
55 3 }
56
1/2
✗ Branch 107 → 108 not taken.
✓ Branch 107 → 123 taken 6 times.
6 if (!skipped.isEmpty())
57 ✗ qCInfo(lcQuickBackend) << "Forward skipped attachments beyond the budget:"
58 ✗ << skipped.join(QLatin1String(", "));
59 6 return exported;
60 6 }
61
62 5 std::shared_ptr<QTemporaryDir> makeScratch(const QString &profileDirectory) {
63 return std::make_shared<QTemporaryDir>(
64
3/3
✓ Branch 2 → 3 taken 5 times.
✓ Branch 6 → 7 taken 5 times.
✓ Branch 7 → 8 taken 5 times.
10 QDir(profileDirectory).filePath(QStringLiteral("forward-attachments/XXXXXX")));
65 }
66
67 ✗ void loadSendAttachments(Rfc2822Builder &builder, const QStringList &attachments,
68 QString &failedAttachment, bool &attachmentBudgetExceeded) {
69 ✗ qint64 attachmentTotal = 0;
70 ✗ for (const QString &path : attachments) {
71 ✗ const QFileInfo info(path);
72 ✗ const qint64 size = info.size();
73 ✗ if (!info.isFile() || !info.isReadable() || size < 0) {
74 ✗ failedAttachment = path;
75 ✗ break;
76 }
77 ✗ if (size > QuickComposeBackend::MaxComposeAttachmentsBytes - attachmentTotal) {
78 ✗ attachmentBudgetExceeded = true;
79 ✗ break;
80 }
81 ✗ attachmentTotal += size;
82 ✗ }
83 // The size pass rejects an oversized set before reading any file. The
84 // actual loaded-byte pass also enforces the cap if a file grows meanwhile.
85 ✗ if (failedAttachment.isEmpty() && !attachmentBudgetExceeded) {
86 ✗ attachmentTotal = 0;
87 ✗ for (const QString &path : attachments) {
88 ✗ const qint64 remaining = QuickComposeBackend::MaxComposeAttachmentsBytes - attachmentTotal;
89 ✗ qint64 loadedBytes = 0;
90 ✗ if (!builder.addAttachmentFromFile(
91 ✗ path, qMin(Rfc2822Builder::MaxAttachmentBytes, remaining),
92 &loadedBytes)) {
93 ✗ if (QFileInfo(path).size() > remaining)
94 ✗ attachmentBudgetExceeded = true;
95 else
96 ✗ failedAttachment = path;
97 ✗ break;
98 }
99 ✗ attachmentTotal += loadedBytes;
100 }
101 }
102 ✗ }
103
104 } // namespace QuickComposeForwardAttachments
105