Skip to content

Commit f98de59

Browse files
authored
[subprocess] add helpers for starting processes (#760)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description - in subprocess, add a helper to start processes easily ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent d260a26 commit f98de59

File tree

3 files changed

+117
-1
lines changed

3 files changed

+117
-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: 95 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,80 @@ func TestExecute(t *testing.T) {
375396
}
376397
}
377398

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

0 commit comments

Comments
 (0)