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: 100.0% 9 / 0 / 9
Functions: 100.0% 9 / 0 / 9
Branches: -% 0 / 0 / 0

service/ImapCommandTable.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QByteArray>
4 #include <QElapsedTimer>
5 #include <QList>
6 #include <QMap>
7 #include <QQueue>
8 #include <QString>
9
10 #include <functional>
11 #include <optional>
12
13 #include "data/Models.h"
14 #include "service/ImapService.h"
15
16 class QObject;
17 class QSslSocket;
18 class QTimer;
19
20 // FUNC-08 (Sprint 92 S92.2.x): everything one in-flight command needs at its
21 // tagged completion lives in the record of its tag, created when the command
22 // is written and destroyed when its tagged response is taken. Stateful
23 // commands stay serialized (no pipelining), so at most one record per
24 // family exists at a time; the layout no longer depends on that.
25 struct ImapPendingCommand {
26 QString type; // "SELECT", "STATUS", "MOVE", ...
27 std::optional<QElapsedTimer> timer; // T-210: started when written
28 // SELECT: the mailbox becomes selected only on the tagged OK (Bug 34).
29 QString selectFolder;
30 // STATUS: queried mailbox, query start and the last matching snapshot,
31 // published only at the tagged success boundary.
32 QString statusFolder;
33 qint64 statusStartedNs = 0;
34 std::optional<StatusResult> status;
35 // MOVE / COPY (move fallback) / STORE_DELETE / EXPUNGE_MOVE / COPY_ONLY
36 // (T-100): target, UIDs and every journal token riding the command (F4.3,
37 // one terminal outcome for all of them).
38 QString moveTarget;
39 QList<qint64> moveUids;
40 QList<qint64> mutationTokens;
41 // Move fallback: the [COPYUID …] evidence of the successful COPY, carried
42 // through STORE_DELETE and EXPUNGE_MOVE (S106-003).
43 QString copyUidEvidence;
44 // STORE: the journal token of the flag change (0 = untracked).
45 qint64 storeToken = 0;
46 // APPEND (T-176): the RFC 2822 bytes sent after the "+" continuation
47 // (cleared once the literal is completely queued), target folder,
48 // request id and journal token.
49 QByteArray appendData;
50 QString appendFolder;
51 quint64 appendRequestId = 0;
52 qint64 appendToken = 0;
53 // CREATE / DELETE / RENAME (T-281).
54 QString folderPath;
55 QString folderNewPath;
56 };
57
58 // Tags and in-flight commands of one IMAP connection (Sprint 92 S92.1.2,
59 // extracted from ImapService): tag generation, the pending-command table
60 // (tag → command type plus per-tag timing), the serialized and deferred
61 // command queues, the command inactivity timeout and absolute deadline,
62 // and the response budgets of the running command (bytes, literals, result
63 // items).
64 //
65 // Owner: ImapService (std::unique_ptr); lives on the connection's thread.
66 // Plain class: its two timers are children of the service, whose
67 // constructor connects them to the service's timeout slots at the former
68 // position. The push session (IDLE/NOTIFY state), mutation completion,
69 // the connection failure path and the E2E phase reporting stay outside and
70 // are reached through Deps; the selected mailbox is read by reference.
71 class ImapCommandTable {
72 public:
73 struct Deps {
74 std::function<bool()> isIdling;
75 std::function<bool()> isNotifying;
76 std::function<bool()> idleStartPending;
77 std::function<void()> startIdle;
78 std::function<void()> stopIdle;
79 std::function<void(const QString &error, ImapService::DisconnectReason reason)>
80 failConnection;
81 std::function<void(qint64 token, ImapService::MutationOutcome outcome,
82 const QString &error)>
83 completeMutation;
84 // MAILJD_E2E_TESTING only (the members exist in every build so all
85 // consumers agree on the object layout).
86 std::function<void(const QString &phase, quint64 requestToken)> reportE2eTransportPhase;
87 std::function<quint64(const ImapPendingCommand &command)> e2eRequestTokenFor;
88 };
89 ImapCommandTable(QObject *owner, QSslSocket *socket, const QString &selectedFolder,
90 Deps deps);
91
92 QString nextTag();
93 // Writes `<tag> command`; `record` carries the command's own state and is
94 // stored under the new tag before the bytes are written.
95 void sendCommand(const QString &type, const QString &command,
96 ImapPendingCommand record = {});
97 // T-720: like sendCommand() but exposes the generated tag (for probes).
98 QString sendTaggedCommand(const QString &type, const QString &command,
99 ImapPendingCommand record = {});
100 bool hasStatefulCommandInFlight() const;
101 bool hasTimeoutTrackedCommandInFlight() const;
102 bool queuedMailboxStillSelected(const QString &issuedFolder,
103 const QList<qint64> &mutationTokens,
104 const char *command);
105 void enqueueSerializedCommand(std::function<void()> command);
106 void runNextSerializedCommand();
107 // Execute a command after safely stopping IDLE (see ImapService).
108 void executeAfterIdle(std::function<void()> command);
109 // Runs the next deferred command unless IDLE is running (its tagged OK
110 // drains the queue then).
111 void runNextDeferredCommand();
112 void clearDeferredCommands();
113 void refreshCommandTimeout();
114 void refreshCommandDeadline();
115 void onCommandTimeout();
116 void onCommandDeadline();
117 void resetCommandAccumulators();
118 // Forgets every pending command, timer, queued command and budget
119 // (session lifecycle; the tag counter is reset by connectToServer only).
120 void clearCommands();
121 bool accountCommandResponseBytes(qint64 bytes);
122 bool accountCommandLiteral();
123 bool accountCommandResultItems(qint64 items = 1);
124
125 // State shared with the service's session lifecycle and response
126 // handlers (single owner: this table).
127 984 int &tagCounter() { return m_tagCounter; }
128 // In-flight records by tag (tag order).
129 1321 QMap<QString, ImapPendingCommand> &records() { return m_pending; }
130 bool hasCommandType(const QString &type) const;
131 // First in-flight record of a type (tag order), or nullptr.
132 ImapPendingCommand *recordOfType(const QString &type);
133 // Removes and returns the record of a tag (empty type when unknown).
134 ImapPendingCommand takeRecord(const QString &tag);
135 void insertRecord(const QString &tag, ImapPendingCommand record);
136 void eraseType(const QString &type);
137 450 QQueue<std::function<void()>> &deferredCommands() { return m_deferredCommands; }
138 149 QQueue<std::function<void()>> &serializedCommands() { return m_serializedCommands; }
139 5217 QTimer *commandTimeoutTimer() const { return m_commandTimeoutTimer; }
140 6616 QTimer *commandDeadlineTimer() const { return m_commandDeadlineTimer; }
141 2 qint64 &commandResponseBytes() { return m_commandResponseBytes; }
142 1 qint64 &commandLiteralCount() { return m_commandLiteralCount; }
143 1 qint64 &commandResultItems() { return m_commandResultItems; }
144
145 static constexpr int COMMAND_TIMEOUT_MS = 30000; // 30 s inactivity
146 static constexpr int COMMAND_DEADLINE_MS = 120000; // 2 min absolute
147
148 private:
149 QSslSocket *m_socket;
150 const QString &m_selectedFolder; // ImapService::m_selectedFolder
151 Deps m_deps;
152
153 QTimer *m_commandTimeoutTimer = nullptr;
154 QTimer *m_commandDeadlineTimer = nullptr;
155
156 int m_tagCounter = 0;
157
158 // In-flight commands: tag → record (type, timing, per-command state).
159 QMap<QString, ImapPendingCommand> m_pending;
160
161 // Deferred command queue: commands to execute after IDLE stops
162 QQueue<std::function<void()>> m_deferredCommands;
163 QQueue<std::function<void()>> m_serializedCommands;
164
165 // A command budget spans one serialized command or one intentionally
166 // pipelined command pair (SELECT+FETCH). It is reset only after the final
167 // non-IDLE tagged response, so slow-drip progress cannot evade it.
168 qint64 m_commandResponseBytes = 0;
169 qint64 m_commandLiteralCount = 0;
170 qint64 m_commandResultItems = 0;
171 };
172