Skip to content

Commit 5f0a137

Browse files
authored
test: improve test parallelism, deterministic sync, and DI refactoring (#234)
* test: add t.Parallel() to safe test packages * chore: go fmt * refactor: eliminate shared mutable state in changelogparser via DI * refactor: eliminate shared mutable state in versionvalidator via DI * test: add depsync operations test coverage * test: replace time.Sleep with deterministic synchronization * test: remove t.Parallel() from tui package to fix data race SetTheme()/resetTheme() mutate package-level var currentTheme, causing race conditions when subtests run in parallel under -race. * test: fix resolve "text file busy" in parallel exit-code recovery test Each parallel subtest now uses its own t.TempDir() instead of sharing a single script path that races between write and exec. * refactor: eliminate shared mutable state in extensionmgr via DI Add ManifestLoader field to ExtensionHookRunner struct, replacing direct calls to the extensions.LoadExtensionManifestFn global. RunHooks() and LoadExtensionsForHook() now use the injected loader, enabling safe t.Parallel() under -race.
1 parent fdf1604 commit 5f0a137

136 files changed

Lines changed: 2500 additions & 121 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/apperrors/errors_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
)
88

99
func TestVersionFileNotFoundError(t *testing.T) {
10+
t.Parallel()
1011
err := &VersionFileNotFoundError{Path: "/path/to/.version"}
1112

1213
if err.Error() != "version file not found at /path/to/.version" {
@@ -21,6 +22,7 @@ func TestVersionFileNotFoundError(t *testing.T) {
2122
}
2223

2324
func TestInvalidVersionError(t *testing.T) {
25+
t.Parallel()
2426
tests := []struct {
2527
version string
2628
reason string
@@ -39,6 +41,7 @@ func TestInvalidVersionError(t *testing.T) {
3941
}
4042

4143
func TestInvalidBumpTypeError(t *testing.T) {
44+
t.Parallel()
4245
err := &InvalidBumpTypeError{BumpType: "huge"}
4346
expected := "invalid bump type: huge (expected: patch, minor, or major)"
4447

@@ -48,6 +51,7 @@ func TestInvalidBumpTypeError(t *testing.T) {
4851
}
4952

5053
func TestConfigError(t *testing.T) {
54+
t.Parallel()
5155
inner := errors.New("file not found")
5256
err := &ConfigError{Operation: "load", Err: inner}
5357

@@ -65,6 +69,7 @@ func TestConfigError(t *testing.T) {
6569
}
6670

6771
func TestCommandError(t *testing.T) {
72+
t.Parallel()
6873
inner := fmt.Errorf("exit status 1")
6974

7075
tests := []struct {
@@ -89,6 +94,7 @@ func TestCommandError(t *testing.T) {
8994
}
9095

9196
func TestPathValidationError(t *testing.T) {
97+
t.Parallel()
9298
err := &PathValidationError{Path: "../secret", Reason: "path traversal detected"}
9399
expected := `invalid path "../secret": path traversal detected`
94100

@@ -98,6 +104,7 @@ func TestPathValidationError(t *testing.T) {
98104
}
99105

100106
func TestHookError(t *testing.T) {
107+
t.Parallel()
101108
inner := errors.New("make: *** No rule to make target")
102109
err := &HookError{HookName: "pre-build", Err: inner}
103110

@@ -115,6 +122,7 @@ func TestHookError(t *testing.T) {
115122
}
116123

117124
func TestGitError(t *testing.T) {
125+
t.Parallel()
118126
inner := errors.New("fatal: not a git repository")
119127
err := &GitError{Operation: "status", Err: inner}
120128

@@ -141,6 +149,7 @@ func TestGitError(t *testing.T) {
141149
}
142150

143151
func TestFileError(t *testing.T) {
152+
t.Parallel()
144153
inner := errors.New("permission denied")
145154
err := &FileError{Op: "read", Path: "/etc/shadow", Err: inner}
146155

@@ -162,9 +171,11 @@ func TestFileError(t *testing.T) {
162171
}
163172

164173
func TestExtensionError(t *testing.T) {
174+
t.Parallel()
165175
inner := errors.New("script failed")
166176

167177
t.Run("with name", func(t *testing.T) {
178+
t.Parallel()
168179
err := &ExtensionError{Name: "my-ext", Op: "execute", Err: inner}
169180

170181
expected := `extension "my-ext" execute: script failed`
@@ -182,6 +193,7 @@ func TestExtensionError(t *testing.T) {
182193
})
183194

184195
t.Run("without name", func(t *testing.T) {
196+
t.Parallel()
185197
err := &ExtensionError{Name: "", Op: "load", Err: inner}
186198

187199
expected := "extension load: script failed"
@@ -192,14 +204,17 @@ func TestExtensionError(t *testing.T) {
192204
}
193205

194206
func TestWrapGit(t *testing.T) {
207+
t.Parallel()
195208
t.Run("nil error returns nil", func(t *testing.T) {
209+
t.Parallel()
196210
result := WrapGit("clone", nil)
197211
if result != nil {
198212
t.Errorf("expected nil, got %v", result)
199213
}
200214
})
201215

202216
t.Run("non-nil error wraps correctly", func(t *testing.T) {
217+
t.Parallel()
203218
inner := errors.New("remote not found")
204219
result := WrapGit("fetch", inner)
205220

@@ -219,14 +234,17 @@ func TestWrapGit(t *testing.T) {
219234
}
220235

221236
func TestWrapFile(t *testing.T) {
237+
t.Parallel()
222238
t.Run("nil error returns nil", func(t *testing.T) {
239+
t.Parallel()
223240
result := WrapFile("write", "/tmp/test", nil)
224241
if result != nil {
225242
t.Errorf("expected nil, got %v", result)
226243
}
227244
})
228245

229246
t.Run("non-nil error wraps correctly", func(t *testing.T) {
247+
t.Parallel()
230248
inner := errors.New("disk full")
231249
result := WrapFile("write", "/tmp/test", inner)
232250

@@ -250,14 +268,17 @@ func TestWrapFile(t *testing.T) {
250268
}
251269

252270
func TestWrapExtension(t *testing.T) {
271+
t.Parallel()
253272
t.Run("nil error returns nil", func(t *testing.T) {
273+
t.Parallel()
254274
result := WrapExtension("my-ext", "run", nil)
255275
if result != nil {
256276
t.Errorf("expected nil, got %v", result)
257277
}
258278
})
259279

260280
t.Run("non-nil error wraps correctly", func(t *testing.T) {
281+
t.Parallel()
261282
inner := errors.New("timeout")
262283
result := WrapExtension("my-ext", "run", inner)
263284

@@ -281,6 +302,7 @@ func TestWrapExtension(t *testing.T) {
281302
}
282303

283304
func TestSentinelErrors(t *testing.T) {
305+
t.Parallel()
284306
tests := []struct {
285307
err error
286308
expected string
@@ -297,6 +319,7 @@ func TestSentinelErrors(t *testing.T) {
297319

298320
for _, tt := range tests {
299321
t.Run(tt.expected, func(t *testing.T) {
322+
t.Parallel()
300323
if tt.err.Error() != tt.expected {
301324
t.Errorf("expected %q, got %q", tt.expected, tt.err.Error())
302325
}

internal/cliflags/flags_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
)
99

1010
func TestMultiModuleFlags(t *testing.T) {
11+
t.Parallel()
1112
flags := MultiModuleFlags()
1213

1314
if len(flags) == 0 {
@@ -44,6 +45,7 @@ func TestMultiModuleFlags(t *testing.T) {
4445
}
4546

4647
func TestMultiModuleFlags_FlagTypes(t *testing.T) {
48+
t.Parallel()
4749
flags := MultiModuleFlags()
4850

4951
// Verify some specific flag configurations
@@ -86,6 +88,7 @@ func TestMultiModuleFlags_FlagTypes(t *testing.T) {
8688
}
8789

8890
func TestMultiModuleFlags_Aliases(t *testing.T) {
91+
t.Parallel()
8992
flags := MultiModuleFlags()
9093

9194
aliasMap := make(map[string][]string)

internal/clix/clix_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
)
1616

1717
func TestGetOrInitVersionFile(t *testing.T) {
18+
t.Parallel()
1819
t.Run("strict=true and file exists", func(t *testing.T) {
20+
t.Parallel()
1921
tmpDir := t.TempDir()
2022
tmpFile := testutils.WriteTempVersionFile(t, tmpDir, "0.1.0")
2123

@@ -29,6 +31,7 @@ func TestGetOrInitVersionFile(t *testing.T) {
2931
})
3032

3133
t.Run("strict=true and file missing", func(t *testing.T) {
34+
t.Parallel()
3235
missingPath := filepath.Join(t.TempDir(), "missing.version")
3336

3437
created, err := GetOrInitVersionFile(missingPath, true)
@@ -41,6 +44,7 @@ func TestGetOrInitVersionFile(t *testing.T) {
4144
})
4245

4346
t.Run("strict=false and initialization succeeds", func(t *testing.T) {
47+
t.Parallel()
4448
tmpDir := t.TempDir()
4549
targetPath := filepath.Join(tmpDir, ".version")
4650

@@ -60,7 +64,10 @@ func TestGetOrInitVersionFile(t *testing.T) {
6064
}
6165

6266
func TestGetOrInitVersionFile_InitError(t *testing.T) {
67+
t.Parallel(
6368
// Use MockFileSystem with write error to simulate initialization failure
69+
)
70+
6471
mockFS := core.NewMockFileSystem()
6572
mockFS.WriteErr = errors.New("mock init failure")
6673

@@ -83,7 +90,9 @@ func TestGetOrInitVersionFile_InitError(t *testing.T) {
8390
}
8491

8592
func TestFromCommand(t *testing.T) {
93+
t.Parallel()
8694
t.Run("path exists, strict false", func(t *testing.T) {
95+
t.Parallel()
8796
tmpDir := t.TempDir()
8897
tmpFile := testutils.WriteTempVersionFile(t, tmpDir, "0.1.0")
8998

@@ -100,6 +109,7 @@ func TestFromCommand(t *testing.T) {
100109
})
101110

102111
t.Run("path init success, strict false", func(t *testing.T) {
112+
t.Parallel()
103113
tmpDir := t.TempDir()
104114
targetPath := filepath.Join(tmpDir, ".version")
105115

internal/clix/execution_mode_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
)
88

99
func TestExecutionMode_String(t *testing.T) {
10+
t.Parallel()
1011
tests := []struct {
1112
name string
1213
mode ExecutionMode
@@ -31,6 +32,7 @@ func TestExecutionMode_String(t *testing.T) {
3132

3233
for _, tt := range tests {
3334
t.Run(tt.name, func(t *testing.T) {
35+
t.Parallel()
3436
got := tt.mode.String()
3537
if got != tt.want {
3638
t.Errorf("ExecutionMode.String() = %v, want %v", got, tt.want)
@@ -40,6 +42,7 @@ func TestExecutionMode_String(t *testing.T) {
4042
}
4143

4244
func TestExecutionContext_IsSingleModule(t *testing.T) {
45+
t.Parallel()
4346
tests := []struct {
4447
name string
4548
ctx *ExecutionContext
@@ -63,6 +66,7 @@ func TestExecutionContext_IsSingleModule(t *testing.T) {
6366

6467
for _, tt := range tests {
6568
t.Run(tt.name, func(t *testing.T) {
69+
t.Parallel()
6670
got := tt.ctx.IsSingleModule()
6771
if got != tt.want {
6872
t.Errorf("ExecutionContext.IsSingleModule() = %v, want %v", got, tt.want)
@@ -72,6 +76,7 @@ func TestExecutionContext_IsSingleModule(t *testing.T) {
7276
}
7377

7478
func TestExecutionContext_IsMultiModule(t *testing.T) {
79+
t.Parallel()
7580
tests := []struct {
7681
name string
7782
ctx *ExecutionContext
@@ -95,6 +100,7 @@ func TestExecutionContext_IsMultiModule(t *testing.T) {
95100

96101
for _, tt := range tests {
97102
t.Run(tt.name, func(t *testing.T) {
103+
t.Parallel()
98104
got := tt.ctx.IsMultiModule()
99105
if got != tt.want {
100106
t.Errorf("ExecutionContext.IsMultiModule() = %v, want %v", got, tt.want)
@@ -104,6 +110,7 @@ func TestExecutionContext_IsMultiModule(t *testing.T) {
104110
}
105111

106112
func TestExecutionContext_EmptyModules(t *testing.T) {
113+
t.Parallel()
107114
execCtx := &ExecutionContext{
108115
Mode: MultiModuleMode,
109116
Modules: []*workspace.Module{},
@@ -119,7 +126,10 @@ func TestExecutionContext_EmptyModules(t *testing.T) {
119126
}
120127

121128
func TestExecutionContext_SingleModuleWithModules(t *testing.T) {
129+
t.Parallel(
122130
// This is an edge case - single module mode should not have Modules set
131+
)
132+
123133
execCtx := &ExecutionContext{
124134
Mode: SingleModuleMode,
125135
Path: "/test/.version",
@@ -139,7 +149,10 @@ func TestExecutionContext_SingleModuleWithModules(t *testing.T) {
139149
}
140150

141151
func TestExecutionContext_MultiModuleWithPath(t *testing.T) {
152+
t.Parallel(
142153
// This is an edge case - multi-module mode should not have Path set
154+
)
155+
143156
execCtx := &ExecutionContext{
144157
Mode: MultiModuleMode,
145158
Path: "/test/.version",
@@ -162,6 +175,7 @@ func TestExecutionContext_MultiModuleWithPath(t *testing.T) {
162175
}
163176

164177
func TestWithDefaultAll(t *testing.T) {
178+
t.Parallel()
165179
opts := &executionOptions{}
166180

167181
// Apply the option

0 commit comments

Comments
 (0)