Skip to content

ci: add nightly contract tests with long-running tests enabled - #454

Merged
tanderson-ld merged 3 commits into
v7from
ta/SDK-3007/go-nightly-long-running
Sep 21, 2026
Merged

tanderson-ld merged 3 commits into
v7from
ta/SDK-3007/go-nightly-long-running

Conversation

@tanderson-ld

@tanderson-ld tanderson-ld commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Requirements

  • I have followed the repository's pull request submission guidelines
  • I have added test coverage for new or changed functionality — n/a, CI config only
  • I have validated my changes against all supported platform versions — n/a, single Go version by design

Related issues

Tracked in Jira. The issue key is omitted on purpose so that merging this does not auto-close the story before the follow-up fix PR.

Describe the solution you've provided

The test harness gates a set of RETRY-conformance and FDv2 tests behind -enable-long-running-tests, because they wait on real backoff timing and take about an hour. Per-PR CI does not pass that flag, so none of them have ever run for this SDK. This adds a nightly job that does — 11 test groups newly covered. It is the Go counterpart to java-core's nightly.

Warning

This job is expected to fail on exactly one test until a follow-up PR in this repo fixes it: streaming/fdv2/permanent fallback with recovery. On permanent synchronizer removal the SDK never resets its data source status, so it does not fall back to a healthy synchronizer before the init timeout. Pre-existing — it reproduces on 7.15.6, so not a RETRY regression. Deliberately not suppressed: a nightly that goes red on a real defect is the point. More than one failure means something else broke — check the uploaded JUnit results.

Choices worth noting:

  • Both harness majors float (v2, v3), matching common_ci.yml, so nightly and per-PR CI resolve the same harness.
  • Single Go version (latest, from the shared version-matrix workflow). RETRY timing is not Go-version-sensitive, and per-PR CI already runs the fast suite on all three.
  • Persistence tests off, so no persistent-stores step.
  • timeout-minutes: 120. java-core's equivalent job measures 59–65 min.
  • Two empty suppression files, so adding a suppression later is a one-line change. They must exist even when empty — the harness hard-errors on a missing -skip-from path.

Describe alternatives you've considered

  • Suppressing the known failure — rejected; it defeats the purpose. java-core's three suppressions from February were never revisited.
  • Splitting long-running into its own job — not worth duplicating build and service startup. Revisit if it proves flaky.
  • A concurrency group — unnecessary, since each run gets its own ephemeral VM, and it would queue the dispatch runs used to validate this.
  • Porting java-core's Slack notification — that webhook has never worked end to end. Fix it there first, then propagate one working solution.

Additional context

This cannot be validated before merge, because a new workflow file is not dispatchable until it is on the default branch. After merge:

gh workflow run nightly-contract-tests.yml --repo launchdarkly/go-server-sdk

Expect zero use -enable-long-running-tests to run skips, and exactly one failure.


Note

Overview
Adds a nightly GitHub Actions workflow (4:00 UTC cron plus manual dispatch) that runs the SDK contract harness the same way as per-PR CI—build/start the test service, then v2 and v3 harness runs—but turns on -enable-long-running-tests so RETRY/backoff and related scenarios that take ~an hour actually execute.

Compared to common_ci.yml contract tests, the job uses latest Go only (via the shared version matrix), disables persistence tests (no persistent-stores setup), passes -skip-from to empty suppression files under testservice/, writes JUnit under junit/, enables debug logging, and sets a 120-minute job timeout. It always uploads JUnit artifacts and test-service logs on completion.

The workflow is expected to stay red on one known FDv2 failure until a follow-up fix lands; more than one failure would indicate a broader regression.

Reviewed by Cursor Bugbot for commit 2aab427. Bugbot is set up for automated code reviews on this repo. Configure here.

Runs the full contract suite nightly with -enable-long-running-tests,
which per-PR CI omits because these tests wait on real RETRY backoff
timing and take roughly an hour. This is the Go counterpart to the
java-core nightly.

Both harness steps float their major version, matching common_ci.yml.
Persistence tests are off, so the persistent-stores step is omitted.
The Go version comes from the same shared sdk-go-version-matrix workflow
ci.yml uses, so the nightly cannot drift from per-PR CI.

Adds two intentionally-empty suppression files so that adding a
suppression is a one-line change rather than requiring the -skip-from
plumbing to be worked out first. They must exist even when empty: the
harness hard-errors on a missing -skip-from path.

The job is expected to fail on exactly one test until a follow-up PR
fixes the underlying bug:
  streaming/fdv2/permanent fallback with recovery

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally empty

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally empty

Drops the header comment block; the background it carried lives in the
pull request description instead, where it stays accurate.

Replaces the TEST_SERVICE_PORT job env var with the literal at both use
sites. In this repo the variable could not relocate the service anyway:
testservice/service.go hardcodes port 10000 and the Makefile passes
nothing, so it only told the harness where to connect. The literal also
matches the java-core nightly.
@tanderson-ld
tanderson-ld marked this pull request as ready for review September 18, 2026 13:58
@tanderson-ld
tanderson-ld requested a review from a team as a code owner September 18, 2026 13:58

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread .github/workflows/nightly-contract-tests.yml

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2aab427. Configure here.

Comment thread .github/workflows/nightly-contract-tests.yml
Comment thread .github/workflows/nightly-contract-tests.yml
@tanderson-ld
tanderson-ld merged commit b392b02 into v7 Sep 21, 2026
29 checks passed
@tanderson-ld
tanderson-ld deleted the ta/SDK-3007/go-nightly-long-running branch September 21, 2026 20:10
tanderson-ld added a commit that referenced this pull request Oct 12, 2026
**Requirements**

- [x] I have followed the repository's [pull request submission
guidelines](../blob/v7/CONTRIBUTING.md#submitting-pull-requests)
- [x] I have added test coverage for new or changed functionality
- [ ] I have validated my changes against all supported platform
versions — n/a, no platform-specific behavior

**Related issues**

SDK-3235. Also fixes the one known failure in the nightly contract tests
added in #454: `streaming/fdv2/permanent fallback with recovery`.

**Describe the solution you've provided**

The data system has two fallback timers, selected by reading the status:
a **10-second** one for startup (`cannotInitialize`) and a **1-minute**
one for steady state (`interruptedAtRuntime`).

A synchronizer that fails permanently during startup reports `Off`,
which the loop publishes as `Interrupted`. Nothing restores
`Initializing`, so the startup branch — which tested the status value —
was unreachable for the rest of startup, leaving only the 1-minute
timer. That only becomes visible when the *next* synchronizer connects
and then stays silent: it produces no result for the loop to act on, so
the loop depends entirely on the timer, and the client's start-wait
expires first. The healthy source is never contacted.

```diff
-cannotInitialize := status.State == interfaces.DataSourceStateInitializing &&
+cannotInitialize := !fdv2.dataApplied.Get() &&
     time.Since(status.StateSince) > 10*time.Second
```

Keying on whether data has ever been applied expresses the actual
predicate and cannot go stale as states are added. Runtime behavior is
unchanged: once data is applied, only the 1-minute timer applies.

Pre-existing, not a regression — it reproduces on 7.15.6, before the
RETRY work.

**Describe alternatives you've considered**

- **Resetting the status to `Initializing` on removal.** Rejected on
cross-SDK evidence: Java and .NET both report `INITIALIZING →
INTERRUPTED` and neither re-publishes `Initializing` after a source
fails. .NET makes that choice explicitly ("spoof interrupted"). This
keeps Go's externally visible status sequence identical to today's and
consistent with both siblings.
- **Adding `Interrupted` to the startup branch's state check.**
Rejected: `Interrupted && >10s` subsumes `Interrupted && >1min`, which
would make the runtime timer dead code and abandon a source after 10
seconds instead of a minute — and recovery back to it takes 5 minutes of
healthy operation.
- **Only refreshing `StateSince` on the transition.** Insufficient: the
1-minute timer would restart and fire at ~70s, still past a 60s
start-wait.

**Additional context**

The fallback and recovery conditions had **zero execution coverage**
before this (the closure bodies measured 0 executions, while the file
overall sat at 59.6%) — they are only evaluated on a 10-second tick with
2+ synchronizers configured, which nothing but the long-running contract
test reached. The new in-package tests cover both the predicate and the
multi-synchronizer loop path, and both fail when the fix is reverted.

Verified: all 4 `streaming/fdv2` fallback contract tests pass against
the patched SDK, including the recovery assertion that had never
executed for Go; all 28 root-package FDv2 end-to-end tests pass;
`golangci-lint` reports 0 issues.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> Fixes FDv2 multi-synchronizer **startup** so a synchronizer that
connects but never delivers data can still be abandoned within the
**10s** window, even after an earlier source fails permanently and the
data source status stays **Interrupted** instead of **Initializing**.
> 
> The startup branch of `fallbackCond` now keys off
**`!fdv2.dataApplied`** rather than `DataSourceStateInitializing`, so
the short timer stays active until the first payload is applied;
steady-state behavior after data has been applied is unchanged (still
**1 minute** on **Interrupted**).
> 
> Adds in-package tests that exercise the predicate (including
**Interrupted** before any data) and a multi-synchronizer loop that
waits out the real 10s fallback twice past a silent synchronizer.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
fc2eca8. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

2 participants