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% 10 / 0 / 10
Functions: 100.0% 10 / 0 / 10
Branches: 33.3% 1 / 0 / 3

app/core/DavSyncScheduler.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QHash>
4 #include <QObject>
5 #include <QSet>
6 #include <QStringList>
7 #include <QTimer>
8 #include <functional>
9
10 #include "CalendarAccountSync.h"
11
12 // Sprint 96 S96.3.1/S96.3.4: the single owner of all automatic calendar
13 // read triggers (SPRINT-96.md §4.2/§4.3). Poll timer, push notices,
14 // recovery tickets and manual requests only mark accounts/collections
15 // dirty; the scheduler starts at most one batch per account and never
16 // overlapping loops.
17 //
18 // Invariants:
19 // - A started batch takes over the dirty marks present at its start; marks
20 // arriving while it runs stay dirty and cause exactly one follow-up batch
21 // (catch-up: a push during REPORT/commit is never lost).
22 // - Push notices are coalesced: 500 ms after the latest notice, at most
23 // 2 s after the first; a push-only batch starts at most every 2 s per
24 // account. Manual requests skip debounce and spacing.
25 // - At most two accounts run in parallel; a slow account blocks no other.
26 // - Offline/paused: nothing starts, marks are kept.
27 // - Retry-After and credential/TLS locks hold back automatic batches of
28 // that account; a manual request clears the lock.
29 // - Safety polling with up to 10 % earlier-only jitter keeps running with
30 // healthy push. After a push failure an account polls every 60 s for at
31 // most 15 min (fallback window), then the normal interval applies.
32 class DavSyncScheduler final : public QObject {
33 Q_OBJECT
34 public:
35 struct Timing {
36 int pushDebounceMs = 500;
37 int pushMaxDelayMs = 2000;
38 int minBatchSpacingMs = 2000;
39 int maxParallelAccounts = 2;
40 int fallbackPollMs = 60000;
41 int fallbackWindowMs = 15 * 60000;
42 };
43 using Executor = std::function<bool(const CalendarSyncRequest &request)>;
44 using Clock = std::function<qint64()>; // monotonic milliseconds
45
46 explicit DavSyncScheduler(Executor executor, QObject *parent = nullptr);
47
48 41 void setTiming(const Timing &timing) { m_timing = timing; }
49 void setClock(Clock clock) { m_clock = std::move(clock); }
50 12 void setJitter(std::function<double()> jitter) { m_jitter = std::move(jitter); }
51 957 void setNetworkGate(std::function<bool()> gate) { m_networkGate = std::move(gate); }
52 // Accounts with selected calendars (polls and "all" requests).
53 957 void setAccountsProvider(std::function<QStringList()> accounts) { m_accounts = std::move(accounts); }
54 // Called once per poll/manual round before its batches (outbox flush,
55 // ICS subscriptions).
56 957 void setRoundHook(std::function<void(unsigned reasons)> hook) { m_roundHook = std::move(hook); }
57
58 // Safety poll interval; 0 stops automatic polling.
59 void setPollIntervalMs(qint64 intervalMs);
60 2 qint64 nextPollAt() const { return m_nextPollAt; }
61 // Milliseconds until the next safety poll; -1 when polling is off.
62
1/3
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 2 times.
✗ Branch 3 → 4 not taken.
2 qint64 msUntilNextPoll() const { return m_nextPollAt > 0 ? qMax<qint64>(0, m_nextPollAt - now()) : -1; }
63
64 // Full run of every account (discovery + CTag), e.g. poll or "Sync now".
65 void requestAll(unsigned reasons);
66 // Full run of one account.
67 void requestAccount(const QString &accountId, unsigned reasons);
68 // Targeted sync of collections (push, recovery); overrides the CTag.
69 void requestCollections(const QString &accountId, const QStringList &paths, unsigned reasons);
70
71 void batchFinished(const CalendarSyncResult &result);
72 void enterFallback(const QString &accountId);
73 void leaveFallback(const QString &accountId);
74 bool inFallback(const QString &accountId) const;
75 void holdUntil(const QString &accountId, qint64 epochMs); // Retry-After
76 void lockAccount(const QString &accountId); // credentials/TLS
77 2 bool locked(const QString &accountId) const { return m_locked.contains(accountId); }
78 // Re-evaluates after the network gate opened again.
79 void kick();
80 void stop();
81
82 7 bool busy(const QString &accountId) const { return m_busy.contains(accountId); }
83 4 bool hasPending(const QString &accountId) const { return m_pending.contains(accountId); }
84 int runningCount() const { return int(m_busy.size()); }
85
86 signals:
87 void batchStarted(const CalendarSyncRequest &request);
88
89 private:
90 struct Pending {
91 bool full = false;
92 unsigned reasons = 0;
93 QStringList paths;
94 qint64 dueAt = 0;
95 qint64 firstPushAt = 0;
96 };
97 void mark(const QString &accountId, bool full, const QStringList &paths, unsigned reasons);
98 void schedule();
99 void evaluate();
100 void onPollTimer();
101 qint64 now() const;
102 qint64 earliestStart(const QString &accountId, const Pending &pending) const;
103
104 Executor m_executor;
105 Clock m_clock;
106 std::function<double()> m_jitter;
107 std::function<bool()> m_networkGate;
108 std::function<QStringList()> m_accounts;
109 std::function<void(unsigned)> m_roundHook;
110 Timing m_timing;
111 QHash<QString, Pending> m_pending;
112 QSet<QString> m_busy;
113 QHash<QString, qint64> m_lastStart;
114 QHash<QString, qint64> m_holdUntil;
115 QHash<QString, qint64> m_fallbackUntil;
116 QHash<QString, qint64> m_nextFallbackPoll;
117 QSet<QString> m_locked;
118 QTimer m_wake;
119 QTimer m_pollTimer;
120 qint64 m_pollIntervalMs = 0;
121 qint64 m_nextPollAt = 0;
122 bool m_stopped = false;
123 };
124