Skip to content

Stabilize timing-sensitive tests - #1513

Merged
bmehta001 merged 9 commits into
microsoft:mainfrom
bmehta001:bmehta001-fix-macos-ci-flakes
Aug 3, 2026
Merged

Stabilize timing-sensitive tests#1513
bmehta001 merged 9 commits into
microsoft:mainfrom
bmehta001:bmehta001-fix-macos-ci-flakes

Conversation

@bmehta001

@bmehta001 bmehta001 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • use an injectable monotonic clock for kill-switch and Retry-After deadlines
  • add deterministic unit coverage for exact expiry boundaries
  • replace fixed sleeps in the temporary kill-switch functional test with bounded waits for observable drops and delivery
  • simulate expired SQLite reservation leases directly instead of depending on scheduler timing
  • rename the temporary kill-switch local logger to avoid MSVC /WX shadowing failures, and harden injected clocks by falling back from an empty Clock and invoking it outside the kill-switch mutex
  • replace the bad-network teardown test's external-endpoint loop with an injected HTTP client that waits for teardown cancellation and delivers exactly-once NetworkFailure callbacks deterministically

Motivation

The affected tests depended on wall-clock time and fixed sleeps. Under slower or
uneven macOS CI scheduling, they could observe state too early or wait longer than
necessary. These changes preserve the production and integration coverage while
making expiry behavior deterministic and every asynchronous wait bounded.

They also remove external network timing from the bad-network teardown coverage and
harden the injected-clock path for empty-clock callers and Windows CI.

Use a monotonic injectable clock for kill-switch deadlines, replace the sleep-heavy expiration functional test with deterministic unit coverage, and simulate expired SQLite leases directly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 77649dd9-3b60-4ca4-861d-8c5ac165d53c
@bmehta001
bmehta001 requested a review from a team as a code owner July 30, 2026 20:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces flakiness in timing-sensitive kill-switch and offline-storage tests by removing real sleeps and making expiry behavior deterministic via an injectable clock and direct lease manipulation.

Changes:

  • Introduce an injectable monotonic clock in KillSwitchManager and compute expiry times in milliseconds with overflow protection.
  • Replace the sleep-based SQLite reservation timeout test with a deterministic “force lease expiry” approach via SQL update.
  • Remove the long, sleep-heavy functional test for temporary kill-switch behavior and replace it with deterministic unit tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tests/unittests/OfflineStorageTests_SQLite.cpp Makes the reservation-timeout test deterministic by simulating expired leases directly in SQLite.
tests/unittests/KillSwitchManagerTests.cpp Adds unit tests that verify kill-switch expiry exactly at the deadline using an injected clock.
tests/functests/BasicFuncTests.cpp Removes a timing/sleep-heavy functional test that was sensitive to CI scheduling and wall-clock delays.
lib/offline/KillSwitchManager.hpp Adds injectable monotonic clock support and overflow-safe expiry computation for kill-switch deadlines.
Comments suppressed due to low confidence (2)

lib/offline/KillSwitchManager.hpp:124

  • KillSwitchManager now accepts an injectable Clock, but isTokenBlocked() calls m_clock() while holding m_lock. Since the injected clock can be arbitrary user code, this can deadlock (e.g., if the clock calls back into the manager) or unnecessarily extend the critical section. Evaluate the clock outside the mutex, then lock only for shared state access.
        bool isTokenBlocked(const std::string& tokenId)
        {
            std::lock_guard<std::mutex> guard(m_lock);
            const int64_t now = m_clock();

lib/offline/KillSwitchManager.hpp:115

  • addToken() calls expiryFromNow() while holding m_lock, which in turn calls the injectable m_clock(). To avoid executing arbitrary code under the mutex (deadlock / contention risk), compute the expiry before taking the lock and only lock around the map mutation.
        void addToken(const std::string& tokenId, int64_t timeInSeconds)
        {
            std::lock_guard<std::mutex> guard(m_lock);
            if (timeInSeconds > 0)
            {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/offline/KillSwitchManager.hpp
Comment thread lib/offline/KillSwitchManager.hpp
bmehta001 and others added 2 commits July 30, 2026 15:57
Rewrite killIsTemporary to observe active drops and eventual server delivery instead of sleeping for a fixed expiration window. Keep every wait bounded without adding test-only access to production internals.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 77649dd9-3b60-4ca4-861d-8c5ac165d53c
Rename the temporary kill-switch logger so MSVC /WX no longer promotes C4458 into C2220 in both Windows pipelines.

Files changed:
- lib/offline/KillSwitchManager.hpp: fall back from an empty Clock and invoke injected callbacks outside the mutex.
- tests/unittests/KillSwitchManagerTests.cpp: cover the empty-clock fallback.
- tests/functests/BasicFuncTests.cpp: avoid shadowing the fixture logger member.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

tests/functests/BasicFuncTests.cpp:566

  • waitForEvent holds mtx_requests while decoding/parsing every received request. That blocks the HTTP server callback from appending new requests, and it also re-decodes the same requests on every poll, which can make this bounded wait slower/flakier under load. Consider snapshotting only the new requests under the mutex, then decoding outside the lock.
    bool waitForEvent(const std::string& name, unsigned timeoutMs)
    {
        const auto deadline = PAL::getMonotonicTimeMs() + timeoutMs;
        while (PAL::getMonotonicTimeMs() < deadline)
        {

tests/functests/BasicFuncTests.cpp:1297

  • listener.waitForAtLeast(numHttpOK, 1, ...) is a prerequisite for the rest of the test (it ensures the kill-switch response was actually observed). Using EXPECT_TRUE allows the test to continue and fail later with less actionable errors; this should be an ASSERT_TRUE precondition.
    EXPECT_TRUE(listener.waitForAtLeast(listener.numHttpOK, 1, 10000));

bmehta001 and others added 2 commits July 30, 2026 19:40
Decode only newly arrived requests after releasing the HTTP callback mutex, avoiding repeated parsing and preventing the polling helper from delaying incoming requests. Treat kill-switch activation as a fatal prerequisite while preserving teardown on failure.

Files changed:
- tests/functests/BasicFuncTests.cpp: snapshot new requests outside the decode path and fail fast when activation is not observed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

bmehta001 and others added 3 commits July 31, 2026 12:50
Replace external endpoints with an injected HTTP client that holds requests until teardown cancellation, then reports NetworkFailure through the required exactly-once callback. This preserves the real cancellation and callback-drain path without simulator or network timing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 77649dd9-3b60-4ca4-861d-8c5ac165d53c
…ectly

WaitForRequest polled m_sent.load() directly while SentCount() was
already the named accessor for the same value. Using SentCount() keeps
the implementation consistent with the class's own public API and means
any future change to the accessor (e.g. different memory order) is
automatically picked up.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94
…age test

BasicFuncTests/killIsTemporary: flatten acceptedAfterKillExpires polling loop.
- Remove redundant pre-loop waitForEvent (nothing sent yet at that point,
  so it always returned false).
- Remove redundant post-loop grace-period block; absorb the 100 ms into
  expiryDeadline so the single loop covers both the poll and the grace.

OfflineStorageTests_SQLite/ReservedRecordsAreReleasedAfterTimeout:
- Reduce lease TTL from 60000 ms to 5000 ms. The value is the storage
  reservation duration, not a wall-clock wait (the test fast-forwards
  expiry via SQL). 5 s is clearer to readers and equally correct.

KillSwitchManager::expiryFromNow: add precondition comment documenting
that seconds > 0 is required and why all callers must guard it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7fe5faca-d77c-45c4-85d3-0d4a00d68a94
@bmehta001
bmehta001 merged commit 6a2e9ff into microsoft:main Aug 3, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants