Skip to content

fix(track): skip telemetry under testing.Testing() to avoid term race#2137

Open
edwardrf wants to merge 1 commit into
mainfrom
edw/track-evt-race-fix
Open

fix(track): skip telemetry under testing.Testing() to avoid term race#2137
edwardrf wants to merge 1 commit into
mainfrom
edw/track-evt-race-fix

Conversation

@edwardrf
Copy link
Copy Markdown
Contributor

@edwardrf edwardrf commented Jun 1, 2026

What

Adds a testing.Testing() short-circuit at the top of track.Evt so telemetry is never fired during tests. Stops a data race between track.Evt's background goroutine and term.SetupTestTerm's mutation of term.DefaultTerm.

The race

```
WARNING: DATA RACE
Read at 0x... by goroutine 288:
pkg/term.Debug() ← grpcLogger.logRequest ← Track ← track.Evt.func1
Previous write at 0x... by goroutine 295:
pkg/term.SetupTestTerm() ← TestWorkspaceListVerboseTable
```

track.Evt spawns a goroutine that calls tracker.Track through the gRPC interceptor, which calls term.Debug — reading term.DefaultTerm. The goroutine isn't bound to the calling test's lifetime; it routinely outlives one test by the duration of an HTTP roundtrip.

term.SetupTestTerm writes term.DefaultTerm at the start of every test that uses it. When test N's tracking goroutine is still alive when test N+1 starts, the SetupTestTerm write races the orphan's term.Debug read.

Fix

```go
func Evt(name string, props ...Property) {
if testing.Testing() {
return
}
// ...
}
```

  • Stops the race at the source — no goroutine spawned in tests means nothing to race with.
  • testing.Testing() is the idiomatic Go-1.21+ way to detect test context from non-test code. Project is on Go 1.25.
  • Zero production risk — returns false outside go test binaries.

Why not the alternatives

Option Problem
FlushAllTracking in SetupTestTerm cleanup Import cycle: track already imports term.
Per-test t.Cleanup(track.FlushAllTracking) Manual; easy to forget on new tests.
Race-safe term.DefaultTerm Treats symptom not cause; future readers from orphan goroutines stay risky.
DEFANG_DISABLE_ANALYTICS=true in TestMain Per-package; doesn't help across packages.

Verification

```
$ cd src && go test -race -short -count=3 ./cmd/cli/command/
ok github.com/DefangLabs/defang/src/cmd/cli/command 12.675s
```

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Tests now execute without background tracking activity, improving test environment isolation and reliability.

`track.Evt` spawns a background goroutine that calls `tracker.Track`, which
goes through the gRPC interceptor (`grpcLogger.logRequest` → `term.Debug`).
That goroutine isn't synchronized with the calling test — it routinely
outlives a test by milliseconds-to-seconds because it's waiting on an HTTP
RPC roundtrip.

Meanwhile `term.SetupTestTerm` swaps `term.DefaultTerm` at the start of
each test and restores it via `t.Cleanup`. When test N's tracking
goroutine is still alive after test N's cleanup runs, test N+1's
SetupTestTerm assignment to `DefaultTerm` races the orphan goroutine's
`term.Debug` read of the same variable. The race detector caught this on
the workspace tests:

	WARNING: DATA RACE
	Read at 0x... by goroutine 288:
	  pkg/term.Debug() ← grpcLogger.logRequest ← Track ← track.Evt.func1
	Previous write at 0x... by goroutine 295:
	  pkg/term.SetupTestTerm() ← TestWorkspaceListVerboseTable

Disable tracking under `testing.Testing()` so the goroutine is never
spawned in tests. Production behavior is unchanged — `testing.Testing()`
returns false outside `go test` binaries. Available since Go 1.21 (project
is on 1.25). This was added specifically by the Go team for non-test
code to detect test context safely.

The alternatives all have downsides:
- Draining `FlushAllTracking` in SetupTestTerm cleanup creates an import
  cycle (`track` already imports `term`).
- Per-test `t.Cleanup(track.FlushAllTracking)` is manual and easy to forget.
- Making `term.DefaultTerm` atomic addresses the symptom, not the cause;
  future readers from the orphan goroutine stay risky.

Verified: `go test -race -short -count=3 ./cmd/cli/command/` passes 3×.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e8764ce-5935-42a6-9e0e-fc6986f40cd4

📥 Commits

Reviewing files that changed from the base of the PR and between e496a4a and 60f0025.

📒 Files selected for processing (1)
  • src/pkg/track/track.go

📝 Walkthrough

Walkthrough

This PR adds a test-context guard to the tracking system. The Evt function now returns immediately when running under Go's test harness, preventing the background tracking goroutine from starting and avoiding telemetry operations during tests.

Changes

Test-context tracking guard

Layer / File(s) Summary
Test-context guard in Evt
src/pkg/track/track.go
Import testing package and add an early-return guard to Evt that checks testing.Testing(). When in test context, the function returns without spawning the background tracking goroutine.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

In tests we skip, no goroutines spin,
Telemetry sleeps 'til production begins,
A guard so simple, a change so lean,
The cleanest tracking test ever seen! 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(track): skip telemetry under testing.Testing() to avoid term race' directly and clearly describes the main change: adding a guard to skip telemetry during tests to prevent a race condition.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch edw/track-evt-race-fix

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=warning msg="The linter 'gomodguard' is deprecated (since v2.12.0) due to: new major version. Replaced by gomodguard_v2."
level=warning msg="Suggested new configuration:\nlinters:\n enable:\n - gomodguard_v2\n"
level=warning msg="[linters_context] running gomodguard failed: unable to read module file go.mod: current working directory must have a go.mod file: if you are not using go modules it is suggested to disable this linter"
level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands and usage tips.

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