Skip to content

fix: data races in tests#440

Open
alnr wants to merge 2 commits into
masterfrom
alnr/fix-race
Open

fix: data races in tests#440
alnr wants to merge 2 commits into
masterfrom
alnr/fix-race

Conversation

@alnr

@alnr alnr commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Calling cmdA.AddCommand(cmdB) modifies both cmdA and cmdB and hence is not goroutine safe.

Since we extensively test most commands and subcommands, we cannot have global var ... = &cobra.Command{...} declarations.

This diff is a lot more palatable when ignoring whitespace.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a Docker build target to produce a ready-to-use container image.
  • Tests

    • CI now runs Go tests with race detection enabled.
    • Added concurrent command-execution tests to improve stability.
  • Chores

    • Large internal refactor of CLI command initialization for improved maintainability.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: efdcbbc8-1164-44ab-a242-24b8090aabc0

📥 Commits

Reviewing files that changed from the base of the PR and between 88bcaba and 6ac119a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (19)
  • cmd/dev/ci/main.go
  • cmd/dev/ci/monorepo/changes.go
  • cmd/dev/ci/monorepo/changes_test.go
  • cmd/dev/ci/monorepo/components.go
  • cmd/dev/ci/monorepo/depgraph.go
  • cmd/dev/ci/monorepo/depgraph_test.go
  • cmd/dev/ci/monorepo/main.go
  • cmd/dev/ci/monorepo/run.go
  • cmd/dev/ci/monorepo/run_test.go
  • cmd/dev/ci/monorepo/test/circular/componentA/monorepo.yml
  • cmd/dev/ci/monorepo/test/circular/componentB/monorepo.yml
  • cmd/dev/ci/monorepo/test/invalidConfigFile/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/backoffice/docker/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/backoffice/helm/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/backoffice/src/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/console/helm/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/console/src/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/stack/monorepo.yml
  • go.mod
💤 Files with no reviewable changes (19)
  • cmd/dev/ci/monorepo/test/working/console/helm/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/stack/monorepo.yml
  • cmd/dev/ci/monorepo/run_test.go
  • cmd/dev/ci/monorepo/test/circular/componentB/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/console/src/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/backoffice/docker/monorepo.yml
  • cmd/dev/ci/monorepo/changes_test.go
  • cmd/dev/ci/main.go
  • cmd/dev/ci/monorepo/depgraph.go
  • cmd/dev/ci/monorepo/test/working/backoffice/src/monorepo.yml
  • cmd/dev/ci/monorepo/depgraph_test.go
  • cmd/dev/ci/monorepo/test/invalidConfigFile/monorepo.yml
  • cmd/dev/ci/monorepo/test/circular/componentA/monorepo.yml
  • cmd/dev/ci/monorepo/test/working/backoffice/helm/monorepo.yml
  • cmd/dev/ci/monorepo/changes.go
  • cmd/dev/ci/monorepo/main.go
  • cmd/dev/ci/monorepo/run.go
  • cmd/dev/ci/monorepo/components.go
  • go.mod

📝 Walkthrough

Walkthrough

This PR refactors all Cobra command definitions from package-level variables with init()-based registration to factory functions, eliminating global state races during concurrent command instantiation. Monorepo subcommands use scratch variables populated per-invocation to safely bind flags. Testing and CI now include race-condition detection.

Changes

Cobra command factory refactoring for concurrent safety

Layer / File(s) Summary
Root command factory foundation
cmd/version.go, cmd/dev.go, cmd/nodev.go, cmd/root.go, cmd/root_race_test.go
NewVersionCmd() and newDevCommands() factories replace package variables. Root command wires subcommands via factory calls. New concurrent test spawns goroutines creating/executing fresh root commands to detect races.
Monorepo subcommand with concurrency safeguards
cmd/dev/ci/monorepo/main.go, cmd/dev/ci/monorepo/run.go, cmd/dev/ci/monorepo/changes.go, cmd/dev/ci/monorepo/components.go
Package-level scratch variables are populated from per-command local flags at invocation time. NewCommand()/newXxxCmd() factories build fresh command instances so flags are bound safely per run.
Dev command hierarchy
cmd/dev/main.go, cmd/dev/headers/main.go, cmd/dev/headers/copyright.go, cmd/dev/headers/cp.go, cmd/dev/markdown/main.go, cmd/dev/markdown/render.go, cmd/dev/schema/main.go, cmd/dev/schema/render_version.go, cmd/dev/swagger/main.go, cmd/dev/swagger/sanitize.go
Each module now exposes a factory that constructs its root command and registers subcommands; command-local flags and handlers were moved into those factories.
CI utility commands
cmd/dev/ci/main.go, cmd/dev/ci/deps/main.go, cmd/dev/ci/deps/url.go, cmd/dev/ci/github/main.go, cmd/dev/ci/github/env.go, cmd/dev/ci/orbs/main.go, cmd/dev/ci/orbs/bump.go
CI root NewCommand() registers deps, github, and orbs subcommands. Deps/url, github/env, and orbs/bump command definitions are now produced by factory functions rather than package globals.
Release, openapi and newsletter chains
cmd/dev/openapi/main.go, cmd/dev/openapi/migrate.go, cmd/dev/release/main.go, cmd/dev/release/compile.go, cmd/dev/release/publish.go, cmd/dev/release/notify/main.go, cmd/dev/release/notify/draft.go, cmd/dev/release/notify/send.go, cmd/dev/newsletter/main.go, cmd/dev/newsletter/draft.go, cmd/dev/newsletter/send.go
Release, notify, publish, compile, openapi migrate, and newsletter commands moved to constructor-style factories; flags and execution logic are encapsulated in returned commands.
CI and build infrastructure
.github/workflows/ci.yaml, Makefile, go.mod
CI test job runs go test -race ./.... Makefile replaces post-release with a docker target that builds oryd/ory:latest-sqlite. go.mod removes an unused require.

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: data races in tests' accurately summarizes the main change: refactoring Cobra commands to use factory functions instead of global variables to prevent goroutine-unsafe command mutations during concurrent testing.
Description check ✅ Passed The pull request description clearly explains the problem (AddCommand modifies both commands making them unsafe for goroutines), the solution (replace global variables with factory functions), and provides context about testing. It adequately covers the motivation for these extensive changes without being unnecessarily verbose.
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 alnr/fix-race

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/dev/ci/monorepo/run.go (1)

94-96: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use mode parameter instead of package-level runMode in error message.

The error message references the package-level scratch variable runMode instead of the function parameter mode. This is inconsistent with the rest of the function and could produce misleading error messages in tests that call runWrapper directly.

Proposed fix
 	default:
-		return fmt.Errorf("unknown runMode: %s", runMode)
+		return fmt.Errorf("unknown runMode: %s", mode)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/dev/ci/monorepo/run.go` around lines 94 - 96, The default case in
runWrapper currently uses the package-level variable runMode in the error
string; update the error to reference the function parameter mode instead.
Locate the switch/default inside runWrapper and change the fmt.Errorf("unknown
runMode: %s", runMode) call to fmt.Errorf("unknown runMode: %s", mode) so the
error message reflects the actual argument passed to runWrapper rather than the
package-level scratch variable.
🧹 Nitpick comments (1)
cmd/dev/ci/monorepo/components.go (1)

17-19: ⚡ Quick win

Remove the componentMode package-level scratch variable and switch on localComponentMode directly.

componentMode is only used inside newComponentsCmd()’s Run closure in cmd/dev/ci/monorepo/components.go, so the package-level mutable state is unnecessary. Eliminating it avoids any residual shared-state risk while keeping the behavior the same.

♻️ Proposed change
-// Package-level scratch variable for the `components` subcommand, populated
-// from a per-command local in Run so concurrent construction does not race.
-var componentMode string
-
 func newComponentsCmd() *cobra.Command {
 	var localComponentMode string
 	c := &cobra.Command{
 		Use:   "components",
 		Short: "List components based on mode.",
 		Long:  `List components based on mode by reading dependency configs and displaying the dependency graph.`,
 		Run: func(cmd *cobra.Command, args []string) {
-			componentMode = localComponentMode
-
 			var graph ComponentGraph
 			_, _ = graph.getComponentGraph(rootDirectory)
 
-			switch componentMode {
+			switch localComponentMode {
 			case "affected":
 				affectedComponents := getAffectedComponents(&graph)
 				displayComponents(affectedComponents)
 			case "all":
 				allComponents := graph.components
 				displayComponents(allComponents)
 			case "changed":
 				changedComponents := getChangedComponents(&graph)
 				displayComponents(changedComponents)
 			case "involved":
 				involvedComponents := getInvolvedComponents(&graph)
 				displayComponents(involvedComponents)
 			default:
-				log.Fatalf("Unknown ListMode '%s'", componentMode)
+				log.Fatalf("Unknown ListMode '%s'", localComponentMode)
 			}
 		},
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/dev/ci/monorepo/components.go` around lines 17 - 19, Remove the
package-level variable componentMode and use the local variable
localComponentMode directly inside the Run closure of newComponentsCmd; update
any switch/case or conditional that references componentMode to instead
reference localComponentMode (inside newComponentsCmd and its Run), and delete
the now-unused componentMode declaration to eliminate unnecessary package-level
mutable state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/dev/ci/github/env.go`:
- Around line 54-59: The guard incorrectly treats an unset/empty
SWAGGER_SPEC_IGNORE_PKGS as non-empty because strings.Split("", ",") yields
[""], causing export of "-x " entries; update the logic around reading
SWAGGER_SPEC_IGNORE_PKGS (the os.Getenv call and ignorePkgs handling) to first
check if the env var is non-empty (or filter out empty strings after
strings.Split) before building the "-x " list and calling fmt.Printf, ensuring
ignorePkgs contains only non-empty package names before joining and printing.

In `@cmd/dev/ci/monorepo/main.go`:
- Line 18: Remove the unused scratch variable "branch" and its flag binding from
the monorepo command or wire it into downstream logic; specifically, delete the
"branch string" declaration and the corresponding "--branch" PersistentFlag
setup in main.go (the PersistentPreRun populates it) OR propagate the variable
into the existing flow (e.g., pass branch into the functions used by
run.go/changes.go or use it inside isPR()/change-detection logic) so the value
is consumed rather than unused.

---

Outside diff comments:
In `@cmd/dev/ci/monorepo/run.go`:
- Around line 94-96: The default case in runWrapper currently uses the
package-level variable runMode in the error string; update the error to
reference the function parameter mode instead. Locate the switch/default inside
runWrapper and change the fmt.Errorf("unknown runMode: %s", runMode) call to
fmt.Errorf("unknown runMode: %s", mode) so the error message reflects the actual
argument passed to runWrapper rather than the package-level scratch variable.

---

Nitpick comments:
In `@cmd/dev/ci/monorepo/components.go`:
- Around line 17-19: Remove the package-level variable componentMode and use the
local variable localComponentMode directly inside the Run closure of
newComponentsCmd; update any switch/case or conditional that references
componentMode to instead reference localComponentMode (inside newComponentsCmd
and its Run), and delete the now-unused componentMode declaration to eliminate
unnecessary package-level mutable state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a9f04a2a-9a62-468a-86d5-2ec0fa88be67

📥 Commits

Reviewing files that changed from the base of the PR and between 3ef403b and 88bcaba.

📒 Files selected for processing (39)
  • .github/workflows/ci.yaml
  • Makefile
  • cmd/dev.go
  • cmd/dev/ci/deps/main.go
  • cmd/dev/ci/deps/url.go
  • cmd/dev/ci/github/env.go
  • cmd/dev/ci/github/main.go
  • cmd/dev/ci/main.go
  • cmd/dev/ci/monorepo/changes.go
  • cmd/dev/ci/monorepo/components.go
  • cmd/dev/ci/monorepo/main.go
  • cmd/dev/ci/monorepo/run.go
  • cmd/dev/ci/orbs/bump.go
  • cmd/dev/ci/orbs/main.go
  • cmd/dev/headers/copyright.go
  • cmd/dev/headers/cp.go
  • cmd/dev/headers/main.go
  • cmd/dev/main.go
  • cmd/dev/markdown/main.go
  • cmd/dev/markdown/render.go
  • cmd/dev/newsletter/draft.go
  • cmd/dev/newsletter/main.go
  • cmd/dev/newsletter/send.go
  • cmd/dev/openapi/main.go
  • cmd/dev/openapi/migrate.go
  • cmd/dev/release/compile.go
  • cmd/dev/release/main.go
  • cmd/dev/release/notify/draft.go
  • cmd/dev/release/notify/main.go
  • cmd/dev/release/notify/send.go
  • cmd/dev/release/publish.go
  • cmd/dev/schema/main.go
  • cmd/dev/schema/render_version.go
  • cmd/dev/swagger/main.go
  • cmd/dev/swagger/sanitize.go
  • cmd/nodev.go
  • cmd/root.go
  • cmd/root_race_test.go
  • cmd/version.go
💤 Files with no reviewable changes (1)
  • Makefile

Comment thread cmd/dev/ci/github/env.go
Comment on lines +54 to +59
if ignorePkgs := strings.Split(os.Getenv("SWAGGER_SPEC_IGNORE_PKGS"), ","); len(ignorePkgs) > 0 {
for k, p := range ignorePkgs {
ignorePkgs[k] = "-x " + p
}
fmt.Printf(`export SWAGGER_SPEC_IGNORE_PKGS='%s'`, strings.Join(ignorePkgs, " "))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard is always true when SWAGGER_SPEC_IGNORE_PKGS is unset.

strings.Split("", ",") returns [""] (length 1), so len(ignorePkgs) > 0 never short-circuits. When the env var is empty/unset, this emits export SWAGGER_SPEC_IGNORE_PKGS='-x ', which is not the intended no-op. This is pre-existing logic carried over by the refactor, but worth fixing while it's being touched.

🐛 Proposed fix
-			if ignorePkgs := strings.Split(os.Getenv("SWAGGER_SPEC_IGNORE_PKGS"), ","); len(ignorePkgs) > 0 {
+			if raw := os.Getenv("SWAGGER_SPEC_IGNORE_PKGS"); raw != "" {
+				ignorePkgs := strings.Split(raw, ",")
 				for k, p := range ignorePkgs {
 					ignorePkgs[k] = "-x " + p
 				}
 				fmt.Printf(`export SWAGGER_SPEC_IGNORE_PKGS='%s'`, strings.Join(ignorePkgs, " "))
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ignorePkgs := strings.Split(os.Getenv("SWAGGER_SPEC_IGNORE_PKGS"), ","); len(ignorePkgs) > 0 {
for k, p := range ignorePkgs {
ignorePkgs[k] = "-x " + p
}
fmt.Printf(`export SWAGGER_SPEC_IGNORE_PKGS='%s'`, strings.Join(ignorePkgs, " "))
}
if raw := os.Getenv("SWAGGER_SPEC_IGNORE_PKGS"); raw != "" {
ignorePkgs := strings.Split(raw, ",")
for k, p := range ignorePkgs {
ignorePkgs[k] = "-x " + p
}
fmt.Printf(`export SWAGGER_SPEC_IGNORE_PKGS='%s'`, strings.Join(ignorePkgs, " "))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/dev/ci/github/env.go` around lines 54 - 59, The guard incorrectly treats
an unset/empty SWAGGER_SPEC_IGNORE_PKGS as non-empty because strings.Split("",
",") yields [""], causing export of "-x " entries; update the logic around
reading SWAGGER_SPEC_IGNORE_PKGS (the os.Getenv call and ignorePkgs handling) to
first check if the env var is non-empty (or filter out empty strings after
strings.Split) before building the "-x " list and calling fmt.Printf, ensuring
ignorePkgs contains only non-empty package names before joining and printing.

Comment thread cmd/dev/ci/monorepo/main.go Outdated
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.

1 participant