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: 97.8% 88 / 0 / 90
Functions: 100.0% 10 / 0 / 10
Branches: 71.8% 102 / 0 / 142

presentation/ComposePasteJob.cpp
Line Branch Exec Source
1 #include "ComposePasteJob.h"
2
3 #include "ComposeLog.h"
4 #include "RichTextController.h"
5
6 #include <QDateTime>
7 #include <QElapsedTimer>
8 #include <QTextBlock>
9 #include <QTextDocument>
10 #include <QTextFrame>
11 #include <QTextList>
12 #include <QTimer>
13 #include <QtConcurrent>
14
15 namespace {
16
17 // A part may end before `block` when the part is long enough and the cut
18 // splits neither a list nor a table (tables are frames: the root frame's
19 // iterator steps over them whole, so `block` is never inside one).
20 133 bool cutsBefore(const QTextBlock &block) {
21
1/1
✓ Branch 2 → 3 taken 133 times.
133 const QTextBlock previous = block.previous();
22
2/3
✓ Branch 3 → 4 taken 133 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 133 times.
133 if (!previous.isValid()) return false;
23
1/1
✓ Branch 6 → 7 taken 133 times.
133 QTextList *list = block.textList();
24
4/5
✓ Branch 7 → 8 taken 38 times.
✓ Branch 7 → 10 taken 95 times.
✓ Branch 8 → 9 taken 38 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 38 times.
133 return !list || list != previous.textList();
25 }
26
27 } // namespace
28
29 4 ComposePasteJob::Prepared ComposePasteJob::prepare(const QByteArray &html, const ComposeHtmlImport::Options &options,
30 const std::atomic_bool *cancelled, int partCharacters) {
31
1/1
✓ Branch 2 → 3 taken 4 times.
4 Prepared prepared;
32
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 4 → 5 taken 4 times.
4 prepared.imported = ComposeHtmlImport::import(html, options, cancelled);
33
7/8
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 11 taken 1 time.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 12 taken 1 time.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 3 times.
4 if (!prepared.imported.ok() || (cancelled && cancelled->load())) return prepared;
34
1/1
✓ Branch 15 → 16 taken 3 times.
3 QTextDocument document;
35
2/2
✓ Branch 16 → 17 taken 3 times.
✓ Branch 17 → 18 taken 3 times.
3 QTextCursor(&document).insertFragment(prepared.imported.fragment);
36
1/1
✓ Branch 19 → 20 taken 3 times.
3 QList<int> cuts{0};
37
5/5
✓ Branch 20 → 21 taken 3 times.
✓ Branch 21 → 22 taken 3 times.
✓ Branch 39 → 40 taken 3894 times.
✓ Branch 41 → 23 taken 3894 times.
✓ Branch 41 → 42 taken 3 times.
3897 for (QTextFrame::iterator it = document.rootFrame()->begin(); !it.atEnd(); ++it) {
38
2/2
✓ Branch 24 → 25 taken 37 times.
✓ Branch 24 → 26 taken 3857 times.
3894 if (it.currentFrame()) continue;
39
1/1
✓ Branch 26 → 27 taken 3857 times.
3857 const QTextBlock block = it.currentBlock();
40
11/11
✓ Branch 27 → 28 taken 3857 times.
✓ Branch 28 → 29 taken 3857 times.
✓ Branch 29 → 30 taken 133 times.
✓ Branch 29 → 33 taken 3724 times.
✓ Branch 30 → 31 taken 133 times.
✓ Branch 31 → 32 taken 95 times.
✓ Branch 31 → 33 taken 38 times.
✓ Branch 34 → 35 taken 95 times.
✓ Branch 34 → 37 taken 3762 times.
✓ Branch 35 → 36 taken 95 times.
✓ Branch 36 → 37 taken 95 times.
3857 if (block.position() - cuts.last() >= partCharacters && cutsBefore(block)) cuts.append(block.position());
41 }
42 // The last part runs to the end without the document's final separator;
43 // every other part ends at the start of the next part's first block, so
44 // it carries the paragraph break between them.
45
2/2
✓ Branch 42 → 43 taken 3 times.
✓ Branch 43 → 44 taken 3 times.
3 cuts.append(document.characterCount() - 1);
46
2/2
✓ Branch 56 → 45 taken 98 times.
✓ Branch 56 → 57 taken 3 times.
101 for (int i = 0; i + 1 < cuts.size(); ++i) {
47
1/1
✓ Branch 45 → 46 taken 98 times.
98 QTextCursor range(&document);
48
1/1
✓ Branch 47 → 48 taken 98 times.
98 range.setPosition(cuts.at(i));
49
1/1
✓ Branch 49 → 50 taken 98 times.
98 range.setPosition(cuts.at(i + 1), QTextCursor::KeepAnchor);
50
2/2
✓ Branch 50 → 51 taken 98 times.
✓ Branch 51 → 52 taken 98 times.
98 prepared.parts.append(QTextDocumentFragment(range));
51 98 }
52
2/2
✓ Branch 57 → 58 taken 3 times.
✓ Branch 58 → 59 taken 3 times.
3 prepared.imported.fragment = {};
53 3 return prepared;
54 3 }
55
56 3 ComposePasteJob::ComposePasteJob(RichTextController &editor, QObject *parent)
57
3/3
✓ Branch 3 → 4 taken 3 times.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 6 → 7 taken 3 times.
3 : QObject(parent), m_editor(editor), m_cancelled(std::make_shared<std::atomic_bool>(false)) {
58
1/1
✓ Branch 7 → 8 taken 3 times.
3 connect(&m_watcher, &QFutureWatcher<Prepared>::finished, this, &ComposePasteJob::onPrepared);
59 3 }
60
61 6 ComposePasteJob::~ComposePasteJob() {
62 // The worker may still run; it sees the flag and its result is dropped.
63 3 m_cancelled->store(true);
64 6 }
65
66 3 void ComposePasteJob::start(const QByteArray &html, const ComposeHtmlImport::Options &options) {
67
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
3 if (m_running) return;
68 3 m_running = true;
69 3 m_prepared = false;
70 3 m_inserted = 0;
71 3 m_longestPartMs = 0;
72 3 m_parts.clear();
73 3 m_startedMs = QDateTime::currentMSecsSinceEpoch();
74
1/1
✓ Branch 6 → 7 taken 3 times.
3 m_at = m_editor.caretCursor();
75
2/6
✓ Branch 9 → 10 taken 3 times.
✗ Branch 13 → 14 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 20 → 11 not taken.
✓ Branch 20 → 21 taken 3 times.
3 qCInfo(lcCompose) << "paste import started bytes" << html.size();
76
1/1
✓ Branch 25 → 26 taken 3 times.
3 m_watcher.setFuture(QtConcurrent::run(
77
2/6
✓ Branch 22 → 23 taken 3 times.
✓ Branch 24 → 25 taken 3 times.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
9 [html, options, cancelled = m_cancelled] { return prepare(html, options, cancelled.get()); }));
78 }
79
80 3 void ComposePasteJob::onPrepared() {
81
2/4
✓ Branch 2 → 3 taken 3 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
4 if (!m_running || m_prepared) return;
82 3 m_prepared = true;
83
1/1
✓ Branch 5 → 6 taken 3 times.
3 const Prepared prepared = m_watcher.result();
84
5/6
✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 10 taken 1 time.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 2 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 25 taken 2 times.
3 if (!prepared.imported.ok() || prepared.parts.isEmpty()) {
85
2/5
✓ Branch 13 → 14 taken 1 time.
✗ Branch 17 → 18 not taken.
✗ Branch 18 → 19 not taken.
✗ Branch 22 → 15 not taken.
✓ Branch 22 → 23 taken 1 time.
1 qCInfo(lcCompose) << "paste import refused the HTML; the text goes in instead";
86 1 m_running = false;
87
1/1
✓ Branch 23 → 24 taken 1 time.
1 emit failed();
88 1 return;
89 }
90 2 m_parts = prepared.parts;
91
2/7
✓ Branch 26 → 27 taken 2 times.
✗ Branch 30 → 31 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 40 → 28 not taken.
✓ Branch 40 → 41 taken 2 times.
2 qCInfo(lcCompose) << "paste imported parts" << m_parts.size() << "ms"
92 ✗ << QDateTime::currentMSecsSinceEpoch() - m_startedMs;
93
1/1
✓ Branch 41 → 42 taken 2 times.
2 emit imported(prepared.imported);
94
1/1
✓ Branch 42 → 43 taken 2 times.
2 insertNext();
95 3 }
96
97 8 void ComposePasteJob::insertNext() {
98
3/6
✓ Branch 2 → 3 taken 8 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 8 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 8 times.
10 if (!m_running || m_parts.isEmpty()) return;
99 // Parts until the turn's budget is used, then a frame in between.
100 8 QElapsedTimer turn;
101 8 turn.start();
102
1/2
✓ Branch 30 → 11 taken 34 times.
✗ Branch 30 → 31 not taken.
34 while (!m_parts.isEmpty()) {
103 34 QElapsedTimer timer;
104 34 timer.start();
105
2/2
✓ Branch 12 → 13 taken 34 times.
✓ Branch 13 → 14 taken 34 times.
34 const int caret = m_editor.insertFragmentPart(m_at, m_parts.takeFirst(), m_inserted == 0);
106 34 ++m_inserted;
107 34 m_longestPartMs = std::max(m_longestPartMs, double(timer.nsecsElapsed()) / 1e6);
108
5/6
✓ Branch 18 → 19 taken 32 times.
✓ Branch 18 → 20 taken 2 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 32 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 25 taken 32 times.
34 if (m_parts.isEmpty() || caret < 0) {
109
1/1
✓ Branch 23 → 24 taken 2 times.
2 finish(caret);
110 2 return;
111 }
112
2/2
✓ Branch 26 → 27 taken 6 times.
✓ Branch 26 → 28 taken 26 times.
32 if (turn.elapsed() >= TurnBudgetMs) break;
113 }
114
1/1
✓ Branch 31 → 32 taken 6 times.
6 QTimer::singleShot(0, this, &ComposePasteJob::insertNext);
115 }
116
117 1 void ComposePasteJob::finishNow() {
118
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
1 if (!m_running) return;
119
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
1 if (!m_prepared) {
120 1 m_watcher.waitForFinished();
121 1 onPrepared(); // the queued finished() then finds it prepared
122 }
123
5/6
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 13 taken 1 time.
✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 8 taken 2 times.
✓ Branch 14 → 15 taken 1 time.
3 while (m_running && !m_parts.isEmpty()) insertNext();
124 }
125
126 2 void ComposePasteJob::finish(int caret) {
127 2 m_running = false;
128 2 m_parts.clear();
129
2/9
✓ Branch 3 → 4 taken 2 times.
✗ Branch 7 → 8 not taken.
✗ Branch 8 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 12 → 13 not taken.
✗ Branch 18 → 5 not taken.
✓ Branch 18 → 19 taken 2 times.
2 qCInfo(lcCompose) << "paste done parts" << m_inserted << "longest part ms" << m_longestPartMs << "total ms"
130 ✗ << QDateTime::currentMSecsSinceEpoch() - m_startedMs;
131 2 emit finished(caret);
132 2 }
133