Skip to content

UTS: fix flaky integration specs and make polling/timing contracts explicit - #517

Open
sacOO7 wants to merge 2 commits into
mainfrom
uts/integration-stability-fixes
Open

UTS: fix flaky integration specs and make polling/timing contracts explicit#517
sacOO7 wants to merge 2 commits into
mainfrom
uts/integration-stability-fixes

Conversation

@sacOO7

@sacOO7 sacOO7 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Related PR

Motivation

CI runs of the derived UTS integration tests in ably-cocoa surfaced three stability defects that live in the specs and docs, not in any SDK:

  1. RSP4b1 (rest presence, time-range history) is flaky by construction. The spec bracketed the queried window with client-side now_millis(), but the events are timestamped by the server. Runner clock skew larger than the window silently excludes the events. It fired twice on ably-cocoa CI (tvOS 2026-08-15, macOS 2026-08-19, both protocol variants each time).
  2. Teardown close-awaits were the tightest timeout in the suite. The shared AFTER EACH TEST cleanup template used a 10s AWAIT_STATE closed while every in-body await uses 15–30s — a stalled runner fails a passing test in teardown.
  3. auth_reauth's AUTH-frame assertion raced the proxy log. A single getLog() snapshot can observe 0 frames, because the SDK sends AUTH only after the auth callback returns and any token round-trip completes.

Separately, a cross-SDK audit showed why one SDK's translation drifted where others didn't: the poll_until contract (its value-returning form) was defined in the spec-author doc (writing-test-specs.md) but absent from the translator-facing doc (writing-derived-tests.md). Per the UTS principle that specs/docs must be explicit enough that no per-SDK translation guidance is needed, the contracts now live in the translator doc.

Spec changes

File Change
uts/rest/integration/presence.md (RSP4b1) Time-range boundaries derived from the events' server-assigned timestamps (±1000ms), mirroring the convention history.md already documents ("Client-side now() must not be used here")
7 files sharing the cleanup template (objects_faults, auth_reauth ×2, channel_faults, connection_open_failures, heartbeat, rest_faults, connection_lifecycle_test) Cleanup AWAIT_STATE closed 10s → 15s. In-body spec-timed awaits (e.g. channel_history_test.md's 10s connect-awaits) deliberately untouched
uts/realtime/integration/proxy/auth_reauth.md AUTH-frame assertion converted from a single log snapshot to a value-form poll_until (15s), matching how the log actually fills

Docs changes (translator-facing explicitness)

File Change
uts/docs/writing-derived-tests.md poll_until table row now states both forms and the settled-value rule: a value-assigned poll returns the settled result and later assertions run on it, never on a re-read (a refetch of an eventually-consistent read can under-return). Added: random_id() mapping row; "use the harness's default wait for steps the spec doesn't time"; spec-written WAIT carve-out (deliberate, translate as a real wait — the anti-sleep rule bans invented waits)
uts/docs/integration-testing.md Settled-value polling bullet under Avoiding Flaky Tests

Cross-SDK impact

  • ably-cocoa: ports aligned in the companion PR; every touched spec's port passes against the live sandbox/proxy.
  • ably-js: inherits on next re-sync — its presence.test.ts RSP4b1 still uses Date.now() (verbatim old-spec port; same latent flake, un-fired on ubuntu runners) and auth_reauth.test.ts still snapshots the log once.
  • ably-java: unaffected today (its ports poll only local state or already honour these timeouts); inherits the docs via its fetch-first rule when porting new tiers.

…cit in the derived-tests docs

Driven by CI evidence from ably-cocoa (the SDK whose runners surfaced these):
RSP4b1 fired twice on different lanes from runner-vs-server clock skew, and
teardown close-awaits timed out on stalled runners.

Spec fixes:
- rest/integration/presence.md (RSP4b1): derive the queried time range from the
  events' server-assigned timestamps instead of client now_millis() — mirrors
  the convention history.md already documents.
- Cleanup AWAIT_STATE closed normalized 10s -> 15s across the 7 files sharing
  the AFTER-EACH template (in-body spec-timed awaits deliberately untouched).
- realtime/integration/proxy/auth_reauth.md: the AUTH-frame assertion now polls
  the proxy log (value-form) instead of a single snapshot that can observe 0
  frames before the AUTH round-trip lands.

Derived-tests docs (so translations inherit the contracts without per-SDK
skill guidance):
- writing-derived-tests.md: poll_until's two forms + assert-on-settled-value
  rule (a refetch of an eventually-consistent read can under-return);
  random_id() mapping row; untimed-steps rule (use the harness default wait);
  spec-written WAIT carve-out (deliberate, translate as a real wait).
- integration-testing.md: settled-value polling bullet under Avoiding Flaky
  Tests.
- writing-derived-tests.md poll_until row: plain-English no-refetch wording
  (a refetch of an eventually-consistent read can return fewer items) plus
  the error semantics — an error raised inside the condition aborts the poll
  and fails the test; never swallow it in the predicate. poll_until_success
  row: full description (errors mean keep-polling, last error re-raised on
  timeout) and its exclusive raise-by-design use-cases.
- Infra-setup paragraph: port the reference definitions from
  writing-test-specs.md rather than reconstructing from prose — every clause
  is load-bearing (past harnesses drifted by refetching and by swallowing
  predicate errors).
- integration-testing.md anti-flake bullet: same plain-English rewording.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant