Skip to content

Commit 3a20cee

Browse files
committed
[subprocess] add helpers for starting processes
1 parent d260a26 commit 3a20cee

File tree

3 files changed

+114
-1
lines changed

3 files changed

+114
-1
lines changed

changes/20251209165547.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: [subprocess] add helpers for starting processes

utils/subprocess/executor.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,21 @@ func ExecuteWithEnvironment(ctx context.Context, loggers logs.Loggers, additiona
6666
return ExecuteWithEnvironmentWithIO(ctx, loggers, nil, additionalEnvVars, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
6767
}
6868

69+
// StartWithEnvironment starts a command (i.e. spawns a subprocess). It allows to specify the environment the subprocess should use. Each entry is of the form "key=value".
70+
func StartWithEnvironment(ctx context.Context, loggers logs.Loggers, additionalEnvVars []string, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) (*Subprocess, error) {
71+
return StartWithEnvironmentWithIO(ctx, loggers, nil, additionalEnvVars, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
72+
}
73+
6974
// Execute executes a command (i.e. spawns a subprocess).
7075
func Execute(ctx context.Context, loggers logs.Loggers, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) error {
7176
return ExecuteWithEnvironment(ctx, loggers, nil, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
7277
}
7378

79+
// Start starts a command (i.e. spawns a subprocess).
80+
func Start(ctx context.Context, loggers logs.Loggers, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) (*Subprocess, error) {
81+
return StartWithEnvironment(ctx, loggers, nil, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
82+
}
83+
7484
// ExecuteAs executes a command (i.e. spawns a subprocess) as a different user.
7585
func ExecuteAs(ctx context.Context, loggers logs.Loggers, messageOnStart string, messageOnSuccess, messageOnFailure string, as *commandUtils.CommandAsDifferentUser, cmd string, args ...string) error {
7686
return ExecuteAsWithEnvironment(ctx, loggers, nil, messageOnStart, messageOnSuccess, messageOnFailure, as, cmd, args...)
@@ -86,7 +96,7 @@ func ExecuteWithSudo(ctx context.Context, loggers logs.Loggers, messageOnStart s
8696
return ExecuteAs(ctx, loggers, messageOnStart, messageOnSuccess, messageOnFailure, commandUtils.Sudo(), cmd, args...)
8797
}
8898

89-
// ExecuteWithEnvironment executes a command (i.e. spawns a subprocess) with overridden stdin/stdout/stderr. It allows to specify the environment the subprocess should use. Each entry is of the form "key=value".
99+
// ExecuteWithEnvironmentWithIO executes a command (i.e. spawns a subprocess) with overridden stdin/stdout/stderr. It allows to specify the environment the subprocess should use. Each entry is of the form "key=value".
90100
func ExecuteWithEnvironmentWithIO(ctx context.Context, loggers logs.Loggers, io ICommandIO, additionalEnvVars []string, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) (err error) {
91101
p, err := NewWithEnvironmentWithIO(ctx, loggers, io, additionalEnvVars, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
92102
if err != nil {
@@ -95,6 +105,16 @@ func ExecuteWithEnvironmentWithIO(ctx context.Context, loggers logs.Loggers, io
95105
return p.Execute()
96106
}
97107

108+
// StartWithEnvironmentWithIO starts a command (i.e. spawns a subprocess) with overridden stdin/stdout/stderr. It allows to specify the environment the subprocess should use. Each entry is of the form "key=value".
109+
func StartWithEnvironmentWithIO(ctx context.Context, loggers logs.Loggers, io ICommandIO, additionalEnvVars []string, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) (p *Subprocess, err error) {
110+
p, err = NewWithEnvironmentWithIO(ctx, loggers, io, additionalEnvVars, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
111+
if err != nil {
112+
return
113+
}
114+
err = p.Start()
115+
return
116+
}
117+
98118
// ExecuteWithIO executes a command (i.e. spawns a subprocess) with overridden stdin/stdout/stderr.
99119
func ExecuteWithIO(ctx context.Context, loggers logs.Loggers, io ICommandIO, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) error {
100120
return ExecuteWithEnvironmentWithIO(ctx, loggers, io, nil, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)

utils/subprocess/executor_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func (t *testIO) Register(context.Context) (io.Reader, io.Writer, io.Writer) {
4848
}
4949

5050
type execFunc func(ctx context.Context, l logs.Loggers, cmd string, args ...string) error
51+
type startFunc func(ctx context.Context, l logs.Loggers, cmd string, args ...string) (*Subprocess, error)
5152

5253
func newDefaultExecutor(t *testing.T) execFunc {
5354
t.Helper()
@@ -68,6 +69,26 @@ func newCustomIOExecutor(t *testing.T, customIO *testIO) execFunc {
6869
}
6970
}
7071

72+
func newDefaultStarter(t *testing.T) startFunc {
73+
t.Helper()
74+
return func(ctx context.Context, l logs.Loggers, cmd string, args ...string) (*Subprocess, error) {
75+
return Start(ctx, l, "", "", "", cmd, args...)
76+
}
77+
}
78+
79+
func newCustomIOStarter(t *testing.T, customIO *testIO) startFunc {
80+
t.Helper()
81+
return func(ctx context.Context, l logs.Loggers, cmd string, args ...string) (p *Subprocess, err error) {
82+
p = &Subprocess{}
83+
err = p.SetupWithEnvironmentWithCustomIO(ctx, l, customIO, nil, "", "", "", cmd, args...)
84+
if err != nil {
85+
return
86+
}
87+
err = p.Start()
88+
return
89+
}
90+
}
91+
7192
func TestExecuteEmptyLines(t *testing.T) {
7293
t.Skip("would need to be reinstated when fixed")
7394
defer goleak.VerifyNone(t)
@@ -375,6 +396,77 @@ func TestExecute(t *testing.T) {
375396
}
376397
}
377398

399+
func TestStart(t *testing.T) {
400+
currentDir, err := os.Getwd()
401+
require.NoError(t, err)
402+
403+
tests := []struct {
404+
name string
405+
cmdWindows string
406+
argWindows []string
407+
cmdOther string
408+
argOther []string
409+
expectIO bool
410+
}{
411+
{
412+
name: "ShortProcess",
413+
cmdWindows: "cmd",
414+
argWindows: []string{"/c", "dir", currentDir},
415+
cmdOther: "ls",
416+
argOther: []string{"-l", currentDir},
417+
expectIO: true,
418+
},
419+
{
420+
name: "LongProcess",
421+
cmdWindows: "cmd",
422+
argWindows: []string{"/c", fmt.Sprintf("ping -n 2 -w %v localhost > nul", time.Second.Milliseconds())}, // See https://stackoverflow.com/a/79268314/45375
423+
cmdOther: "sleep",
424+
argOther: []string{"1"},
425+
expectIO: false,
426+
},
427+
}
428+
429+
for _, test := range tests {
430+
t.Run(test.name, func(t *testing.T) {
431+
// defer goleak.VerifyNone(t)
432+
433+
customIO := newTestIO()
434+
executors := []struct {
435+
name string
436+
start startFunc
437+
io *testIO
438+
}{
439+
{"normal", newDefaultStarter(t), nil},
440+
{"with IO", newCustomIOStarter(t, customIO), customIO},
441+
}
442+
443+
for _, executor := range executors {
444+
t.Run(executor.name, func(t *testing.T) {
445+
var loggers logs.Loggers = &logs.GenericLoggers{}
446+
err := loggers.Check()
447+
assert.Error(t, err)
448+
449+
_, err = executor.start(context.Background(), loggers, "ls")
450+
assert.Error(t, err)
451+
452+
loggers, err = logs.NewLogrLogger(logstest.NewTestLogger(t), "test")
453+
require.NoError(t, err)
454+
455+
var p *Subprocess
456+
if platform.IsWindows() {
457+
p, err = executor.start(context.Background(), loggers, test.cmdWindows, test.argWindows...)
458+
} else {
459+
p, err = executor.start(context.Background(), loggers, test.cmdOther, test.argOther...)
460+
}
461+
require.NoError(t, err)
462+
require.NotNil(t, p)
463+
require.NoError(t, p.Wait(context.Background()))
464+
})
465+
}
466+
})
467+
}
468+
}
469+
378470
func TestExecuteWithCustomIO_Stderr(t *testing.T) {
379471
if platform.IsWindows() {
380472
t.Skip("Uses bash and redirection so can't run on Windows")

0 commit comments

Comments
 (0)