Skip to content

Commit 2caf4d1

Browse files
committed
test: run the subprocess suite in parallel
Closes #516. The package was never swept: the tests that had `t.Parallel()` got it one at a time as other work happened to touch them, and the rest were serial for no reason - pure functions over their arguments, with no shared fixture and, since #502, no swappable runner to contend for. Five stay serial and now carry the reason. One counts `runtime.NumGoroutine()`, which is process-wide and would put a neighbour's goroutines in its delta; one asserts a wall-clock ceiling, which is how a suite acquires a flake; two are the environment cases #515 documented; and one writes `color.NoColor` deliberately, to show nothing reads it any more.
1 parent 2c1c18a commit 2caf4d1

5 files changed

Lines changed: 68 additions & 2 deletions

File tree

subprocess/cancellation_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
// signal handled inside this package, so a caller holding a cancelled context
1515
// had no way to prevent the next command from running to completion.
1616
func TestCallExecCommandHonorsAlreadyCancelledContext(t *testing.T) {
17+
t.Parallel()
1718
ctx, cancel := context.WithCancel(context.Background())
1819
cancel()
1920

@@ -36,6 +37,7 @@ func TestCallExecCommandHonorsAlreadyCancelledContext(t *testing.T) {
3637
// as absence would make a plan claim drift and an apply then mutate a server
3738
// it never successfully read.
3839
func TestProbePropagatesCancellation(t *testing.T) {
40+
t.Parallel()
3941
ctx, cancel := context.WithCancel(context.Background())
4042
cancel()
4143

@@ -51,6 +53,9 @@ func TestProbePropagatesCancellation(t *testing.T) {
5153
// TestCallExecCommandDeadlineBoundsTheChild pins that a deadline actually
5254
// bounds a slow command, which is the property an embedding caller needs and
5355
// a signal handler cannot provide.
56+
//
57+
// Serial on purpose: the assertion is a wall-clock ceiling, and a timing
58+
// assertion under a loaded parallel run is how a suite acquires a flake.
5459
func TestCallExecCommandDeadlineBoundsTheChild(t *testing.T) {
5560
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
5661
defer cancel()
@@ -72,6 +77,12 @@ func TestCallExecCommandDeadlineBoundsTheChild(t *testing.T) {
7277
// signal.Notify without ever calling signal.Stop, and park a goroutine on that
7378
// channel that nothing ever woke - so a run leaked one goroutine, one channel
7479
// registration and one derived context per dokku command it issued.
80+
//
81+
// Serial on purpose, and the one test here that could not be made parallel
82+
// even in principle: runtime.NumGoroutine counts the whole process, so a test
83+
// running beside it would put its own goroutines in the delta. Go resumes
84+
// parallel tests only once every serial test has finished, so nothing else in
85+
// the package is running while this counts.
7586
func TestCallExecCommandDoesNotLeakGoroutines(t *testing.T) {
7687
ctx := context.Background()
7788
// Warm up so one-off runtime goroutines are not counted as growth.

subprocess/exec_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
)
1212

1313
func TestResolveCommandString(t *testing.T) {
14+
t.Parallel()
1415

1516
tests := []struct {
1617
name string
@@ -86,6 +87,7 @@ func TestResolveCommandStringWithoutATargetRunsLocally(t *testing.T) {
8687
}
8788

8889
func TestExecCommandResponseStdoutContents(t *testing.T) {
90+
t.Parallel()
8991
tests := []struct {
9092
name string
9193
stdout string
@@ -108,6 +110,7 @@ func TestExecCommandResponseStdoutContents(t *testing.T) {
108110
}
109111

110112
func TestExecCommandResponseStderrContents(t *testing.T) {
113+
t.Parallel()
111114
tests := []struct {
112115
name string
113116
stderr string
@@ -130,6 +133,7 @@ func TestExecCommandResponseStderrContents(t *testing.T) {
130133
}
131134

132135
func TestExecCommandResponseStdoutBytes(t *testing.T) {
136+
t.Parallel()
133137
resp := ExecCommandResponse{Stdout: " hello world \n"}
134138
got := resp.StdoutBytes()
135139
want := []byte("hello world")
@@ -144,6 +148,7 @@ func TestExecCommandResponseStdoutBytes(t *testing.T) {
144148
}
145149

146150
func TestExecCommandResponseStderrBytes(t *testing.T) {
151+
t.Parallel()
147152
resp := ExecCommandResponse{Stderr: " error msg \n"}
148153
got := resp.StderrBytes()
149154
want := []byte("error msg")
@@ -158,6 +163,7 @@ func TestExecCommandResponseStderrBytes(t *testing.T) {
158163
}
159164

160165
func TestCallExecCommandSuccess(t *testing.T) {
166+
t.Parallel()
161167
resp, err := CallExecCommand(context.Background(), ExecCommandInput{
162168
Command: "echo",
163169
Args: []string{"hello"},
@@ -174,6 +180,7 @@ func TestCallExecCommandSuccess(t *testing.T) {
174180
}
175181

176182
func TestCallExecCommandFailure(t *testing.T) {
183+
t.Parallel()
177184
resp, err := CallExecCommand(context.Background(), ExecCommandInput{
178185
Command: "false",
179186
})
@@ -195,6 +202,7 @@ func TestCallExecCommandFailure(t *testing.T) {
195202
}
196203

197204
func TestCallExecCommandNotFound(t *testing.T) {
205+
t.Parallel()
198206
_, err := CallExecCommand(context.Background(), ExecCommandInput{
199207
Command: "nonexistent-binary-docket-test-12345",
200208
})
@@ -235,6 +243,7 @@ func TestCallExecCommandInheritsProcessEnv(t *testing.T) {
235243
}
236244

237245
func TestCallExecCommandWithContext(t *testing.T) {
246+
t.Parallel()
238247
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
239248
defer cancel()
240249

@@ -284,6 +293,7 @@ func TestContextRunnerReceivesInputAndFallsBackToTheReal(t *testing.T) {
284293
}
285294

286295
func TestCallExecCommandResponseCommandIsMasked(t *testing.T) {
296+
t.Parallel()
287297
masker := NewMasker("topsecret123")
288298

289299
resp, err := CallExecCommand(ContextWithMasker(context.Background(), masker), ExecCommandInput{
@@ -335,6 +345,7 @@ func TestCallExecCommandTraceLogIsMasked(t *testing.T) {
335345
}
336346

337347
func TestCallExecCommandResponseCommandUnmaskedWhenNoSecrets(t *testing.T) {
348+
t.Parallel()
338349

339350
resp, err := CallExecCommand(context.Background(), ExecCommandInput{
340351
Command: "echo",

subprocess/mask_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
)
1010

1111
func TestMaskStringWithNothingRegistered(t *testing.T) {
12+
t.Parallel()
1213
m := NewMasker()
1314

1415
if got := m.String("hello world"); got != "hello world" {
@@ -17,6 +18,7 @@ func TestMaskStringWithNothingRegistered(t *testing.T) {
1718
}
1819

1920
func TestMaskStringReplacesAllOccurrences(t *testing.T) {
21+
t.Parallel()
2022
m := NewMasker("secret")
2123

2224
got := m.String("a secret and another secret")
@@ -27,6 +29,7 @@ func TestMaskStringReplacesAllOccurrences(t *testing.T) {
2729
}
2830

2931
func TestMaskStringEmptyEntriesSkipped(t *testing.T) {
32+
t.Parallel()
3033
m := NewMasker("", "tok")
3134

3235
got := m.String("xtoky")
@@ -40,6 +43,7 @@ func TestMaskStringEmptyEntriesSkipped(t *testing.T) {
4043
}
4144

4245
func TestMaskStringLongerBeforeShorter(t *testing.T) {
46+
t.Parallel()
4347
// "ab" is a substring of "abcdef"; the longer one must be masked first
4448
// so we don't see "***cdef" instead of a single "***".
4549
m := NewMasker("ab", "abcdef")
@@ -51,6 +55,7 @@ func TestMaskStringLongerBeforeShorter(t *testing.T) {
5155
}
5256

5357
func TestMaskerSetDeduplicates(t *testing.T) {
58+
t.Parallel()
5459
m := NewMasker("a", "a", "b")
5560

5661
values := m.Values()
@@ -84,6 +89,7 @@ func TestMaskersAreIndependent(t *testing.T) {
8489
}
8590

8691
func TestMaskerAddAppendsKeepingExisting(t *testing.T) {
92+
t.Parallel()
8793
m := NewMasker("first")
8894

8995
m.Add("second")
@@ -95,6 +101,7 @@ func TestMaskerAddAppendsKeepingExisting(t *testing.T) {
95101
}
96102

97103
func TestMaskerAddDeduplicatesAgainstExisting(t *testing.T) {
104+
t.Parallel()
98105
m := NewMasker("tok")
99106

100107
m.Add("tok", "", "tok")
@@ -111,6 +118,7 @@ func TestMaskerAddDeduplicatesAgainstExisting(t *testing.T) {
111118
}
112119

113120
func TestMaskerAddKeepsLengthDescOrder(t *testing.T) {
121+
t.Parallel()
114122
// "ab" registered first; adding the longer "abcdef" must still mask the
115123
// longer match first so a substring secret does not leak its remainder.
116124
m := NewMasker("ab")
@@ -123,6 +131,7 @@ func TestMaskerAddKeepsLengthDescOrder(t *testing.T) {
123131
}
124132

125133
func TestMaskerAddOnEmptyRegistry(t *testing.T) {
134+
t.Parallel()
126135
m := NewMasker()
127136

128137
m.Add("late")
@@ -133,6 +142,7 @@ func TestMaskerAddOnEmptyRegistry(t *testing.T) {
133142
}
134143

135144
func TestMaskerAddNoValuesIsNoop(t *testing.T) {
145+
t.Parallel()
136146
m := NewMasker("keep")
137147

138148
m.Add()
@@ -147,6 +157,7 @@ func TestMaskerAddNoValuesIsNoop(t *testing.T) {
147157
// docket builds text - the `(item=<value>)` loop suffix on a task name - and
148158
// masking is literal substring replacement, so both spellings must register.
149159
func TestMaskerSetRegistersTrimmedSpelling(t *testing.T) {
160+
t.Parallel()
150161
m := NewMasker(" padded ")
151162

152163
if got := m.String("deploy (item=padded)"); got != "deploy (item=***)" {
@@ -161,6 +172,7 @@ func TestMaskerSetRegistersTrimmedSpelling(t *testing.T) {
161172
// late-registration path, which is how a task-declared secret joins the
162173
// registry - after the recipe has parsed and already named its expansions.
163174
func TestMaskerAddRegistersTrimmedSpelling(t *testing.T) {
175+
t.Parallel()
164176
m := NewMasker()
165177

166178
m.Add("\tlate\n")
@@ -175,6 +187,7 @@ func TestMaskerAddRegistersTrimmedSpelling(t *testing.T) {
175187
// replaced first, so text holding the full value masks to a single `***`
176188
// rather than leaving the padding behind around an inner match.
177189
func TestSensitiveTrimmedSpellingSortsAfterLiteral(t *testing.T) {
190+
t.Parallel()
178191
m := NewMasker(" tok ")
179192

180193
values := m.Values()
@@ -193,6 +206,7 @@ func TestSensitiveTrimmedSpellingSortsAfterLiteral(t *testing.T) {
193206
// reintroducing the empty entry the masker drops: an all-whitespace
194207
// value trims to "", which would otherwise match every position in a string.
195208
func TestSensitiveWhitespaceOnlyValueIsDropped(t *testing.T) {
209+
t.Parallel()
196210
m := NewMasker(" ")
197211

198212
if got := m.Values(); len(got) != 1 || got[0] != " " {
@@ -206,6 +220,7 @@ func TestSensitiveWhitespaceOnlyValueIsDropped(t *testing.T) {
206220
// TestSensitiveUnpaddedValueRegistersOnce keeps the common case free of a
207221
// duplicate entry: a value that is already trimmed contributes one spelling.
208222
func TestSensitiveUnpaddedValueRegistersOnce(t *testing.T) {
223+
t.Parallel()
209224
m := NewMasker("plain")
210225

211226
if got := m.Values(); len(got) != 1 || got[0] != "plain" {
@@ -218,6 +233,7 @@ func TestSensitiveUnpaddedValueRegistersOnce(t *testing.T) {
218233
// not parse back, which escapes the double quote the value carries, so the
219234
// registered literal no longer matches inside the address it produced.
220235
func TestMaskerSetRegistersEscapedSpelling(t *testing.T) {
236+
t.Parallel()
221237
m := NewMasker(`quo"ted`)
222238

223239
if got := m.String(`dokku_stub[key="quo\"ted"]`); got != `dokku_stub[key="***"]` {
@@ -232,6 +248,7 @@ func TestMaskerSetRegistersEscapedSpelling(t *testing.T) {
232248
// late-registration path, which is how a task-declared secret joins the
233249
// registry - after the recipe has parsed and already named its tasks.
234250
func TestMaskerAddRegistersEscapedSpelling(t *testing.T) {
251+
t.Parallel()
235252
m := NewMasker()
236253

237254
m.Add(`la"te`)
@@ -246,6 +263,7 @@ func TestMaskerAddRegistersEscapedSpelling(t *testing.T) {
246263
// longer one, so it is replaced first; the reverse order would leave the
247264
// escaping backslash stranded next to a `***`.
248265
func TestSensitiveEscapedSpellingSortsBeforeLiteral(t *testing.T) {
266+
t.Parallel()
249267
m := NewMasker(`a"b`)
250268

251269
values := m.Values()
@@ -265,6 +283,7 @@ func TestSensitiveEscapedSpellingSortsBeforeLiteral(t *testing.T) {
265283
// own, so it reaches an address escaped only when the value also carries a
266284
// comma, a bracket, or a quote. Both spellings register either way.
267285
func TestSensitiveBackslashValueRegistersEscapedSpelling(t *testing.T) {
286+
t.Parallel()
268287
m := NewMasker(`a,b\c`)
269288

270289
if got := m.String(`dokku_stub[key="a,b\\c"]`); got != `dokku_stub[key="***"]` {
@@ -280,6 +299,7 @@ func TestSensitiveBackslashValueRegistersEscapedSpelling(t *testing.T) {
280299
// escaping inside those quotes, so the literal still matches there and the
281300
// registry stays at one entry.
282301
func TestSensitiveCommaValueRegistersOnce(t *testing.T) {
302+
t.Parallel()
283303
m := NewMasker("a,b")
284304

285305
if got := m.Values(); len(got) != 1 || got[0] != "a,b" {
@@ -294,6 +314,7 @@ func TestSensitiveCommaValueRegistersOnce(t *testing.T) {
294314
// derivations compose. A value that is both padded and escape-bearing is
295315
// printed four ways, and every one of them masks.
296316
func TestSensitivePaddedEscapedValueRegistersEverySpelling(t *testing.T) {
317+
t.Parallel()
297318
m := NewMasker(` p"q `)
298319

299320
values := m.Values()
@@ -312,6 +333,7 @@ func TestSensitivePaddedEscapedValueRegistersEverySpelling(t *testing.T) {
312333
// registering the escaped spelling must not widen masking to a value that
313334
// merely shares a prefix with a secret.
314335
func TestSensitiveEscapedSpellingLeavesLookalikesAlone(t *testing.T) {
336+
t.Parallel()
315337
m := NewMasker(`a"b`)
316338

317339
if got := m.String(`dokku_stub[key=keepzzz]`); got != `dokku_stub[key=keepzzz]` {
@@ -320,6 +342,7 @@ func TestSensitiveEscapedSpellingLeavesLookalikesAlone(t *testing.T) {
320342
}
321343

322344
func TestMaskStringConcurrent(t *testing.T) {
345+
t.Parallel()
323346
m := NewMasker("secret")
324347

325348
var wg sync.WaitGroup
@@ -343,6 +366,7 @@ func TestMaskStringConcurrent(t *testing.T) {
343366
}
344367

345368
func TestMaskerValuesReturnsCopy(t *testing.T) {
369+
t.Parallel()
346370
m := NewMasker("a")
347371

348372
values := m.Values()
@@ -358,6 +382,7 @@ func TestMaskerValuesReturnsCopy(t *testing.T) {
358382
// `loop:` can resolve to a scalar, a list, or a mapping, so a secret can sit
359383
// at any depth and on either side of a map entry.
360384
func TestMaskValue(t *testing.T) {
385+
t.Parallel()
361386
m := NewMasker("sekret")
362387

363388
cases := []struct {
@@ -416,6 +441,7 @@ func TestMaskValue(t *testing.T) {
416441
// when nothing is registered, so the listing renders identically for a recipe
417442
// that declares no secrets.
418443
func TestMaskValueWithNothingRegistered(t *testing.T) {
444+
t.Parallel()
419445
m := NewMasker()
420446

421447
in := map[string]interface{}{"user": "alice", "ports": []interface{}{80, "443"}}

0 commit comments

Comments
 (0)