Skip to content

Commit eef1142

Browse files
authored
[subprocess] Add Output to return process output (#329)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description - process stdOutput and stdErr are merged and returned by `Output()` ### 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 bfcc3c7 commit eef1142

File tree

11 files changed

+221
-57
lines changed

11 files changed

+221
-57
lines changed

changes/20231010192716.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[logs]` Added a `PlainStringLogger` to store only logged messages without prefixes or flags

changes/20231010195032.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[logs]` Added a `CombinedLoggers` to log into multiple loggers

changes/20231010200309.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[subprocess]` Added `Output` to store the output of a process into a string

utils/logs/multiple_logger.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ func (c *MultipleLogger) AppendLogger(l ...logr.Logger) error {
9999
}
100100

101101
func (c *MultipleLogger) Append(l ...Loggers) error {
102+
c.mu.Lock()
103+
defer c.mu.Unlock()
104+
c.loggers = append(c.loggers, l...)
105+
return nil
106+
}
107+
108+
type MultipleLoggerWithLoggerSource struct {
109+
MultipleLogger
110+
}
111+
112+
func (c *MultipleLoggerWithLoggerSource) Append(l ...Loggers) error {
102113
c.mu.Lock()
103114
defer c.mu.Unlock()
104115
c.loggers = append(c.loggers, l...)
@@ -107,24 +118,35 @@ func (c *MultipleLogger) Append(l ...Loggers) error {
107118

108119
// NewMultipleLoggers returns a logger which abstracts and internally manages a list of loggers.
109120
// if no default loggers are provided, the logger will be set to print to the standard output.
110-
func NewMultipleLoggers(loggerSource string, defaultLoggers ...Loggers) (l IMultipleLoggers, err error) {
121+
func NewMultipleLoggers(loggerSource string, loggersList ...Loggers) (l IMultipleLoggers, err error) {
111122
if loggerSource == "" {
112123
err = commonerrors.ErrNoLoggerSource
113124
return
114125
}
115-
list := defaultLoggers
126+
list := loggersList
116127
if len(list) == 0 {
117128
std, err := NewStdLogger(loggerSource)
118129
if err != nil {
119130
return nil, err
120131
}
121132
list = []Loggers{std}
122133
}
123-
l = &MultipleLogger{}
134+
l = &MultipleLoggerWithLoggerSource{}
124135
err = l.Append(list...)
125136
if err != nil {
126137
return
127138
}
128139
err = l.SetLoggerSource(loggerSource)
129140
return
130141
}
142+
143+
// NewCombinedLoggers returns a logger which logs to a list of logger. If list is empty, it will error.
144+
func NewCombinedLoggers(loggersList ...Loggers) (l IMultipleLoggers, err error) {
145+
if len(loggersList) == 0 {
146+
err = commonerrors.ErrNoLogger
147+
return
148+
}
149+
l = &MultipleLogger{}
150+
err = l.Append(loggersList...)
151+
return
152+
}

utils/logs/multiple_logger_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
"github.com/stretchr/testify/require"
1212

13+
"github.com/ARM-software/golang-utils/utils/commonerrors"
14+
"github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
1315
"github.com/ARM-software/golang-utils/utils/filesystem"
16+
"github.com/ARM-software/golang-utils/utils/logs/logstest"
1417
)
1518

1619
func TestMultipleLogger(t *testing.T) {
@@ -19,6 +22,18 @@ func TestMultipleLogger(t *testing.T) {
1922
testLog(t, loggers)
2023
}
2124

25+
func TestCombinedLogger(t *testing.T) {
26+
_, err := NewCombinedLoggers()
27+
errortest.RequireError(t, err, commonerrors.ErrNoLogger)
28+
testLogger, err := NewLogrLogger(logstest.NewTestLogger(t), "Test")
29+
require.NoError(t, err)
30+
nl, err := NewNoopLogger("Test2")
31+
require.NoError(t, err)
32+
loggers, err := NewCombinedLoggers(testLogger, nl)
33+
require.NoError(t, err)
34+
testLog(t, loggers)
35+
}
36+
2237
func TestMultipleLoggers(t *testing.T) {
2338
// With default logger
2439
loggers, err := NewMultipleLoggers("Test Multiple")

utils/logs/string_logger.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ func (l *StringLoggers) Close() (err error) {
6262
}
6363

6464
// NewStringLogger creates a logger to a string builder.
65+
// All messages (whether they are output or error) are merged together.
6566
func NewStringLogger(loggerSource string) (loggers *StringLoggers, err error) {
6667
loggers = &StringLoggers{
6768
LogWriter: StringWriter{},
@@ -73,6 +74,19 @@ func NewStringLogger(loggerSource string) (loggers *StringLoggers, err error) {
7374
return
7475
}
7576

77+
// NewPlainStringLogger creates a logger to a string builder with no extra flag, prefix or tag, just the logged text.
78+
// All messages (whether they are output or error) are merged together.
79+
func NewPlainStringLogger() (loggers *StringLoggers, err error) {
80+
loggers = &StringLoggers{
81+
LogWriter: StringWriter{},
82+
}
83+
loggers.GenericLoggers = GenericLoggers{
84+
Output: log.New(&loggers.LogWriter, "", 0),
85+
Error: log.New(&loggers.LogWriter, "", 0),
86+
}
87+
return
88+
}
89+
7690
// CreateStringLogger creates a logger to a string builder.
7791
//
7892
// Deprecated: Use NewStringLogger instead

utils/logs/string_logger_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,19 @@ func TestStringLogger(t *testing.T) {
1818
loggers.LogError("Test err")
1919
loggers.Log("Test1")
2020
contents := loggers.GetLogContent()
21-
require.NotZero(t, contents)
21+
require.NotEmpty(t, contents)
22+
require.True(t, strings.Contains(contents, "Test err"))
23+
require.True(t, strings.Contains(contents, "Test1"))
24+
}
25+
26+
func TestPlainStringLogger(t *testing.T) {
27+
loggers, err := NewPlainStringLogger()
28+
require.NoError(t, err)
29+
testLog(t, loggers)
30+
loggers.LogError("Test err")
31+
loggers.Log("Test1")
32+
contents := loggers.GetLogContent()
33+
require.NotEmpty(t, contents)
2234
require.True(t, strings.Contains(contents, "Test err"))
2335
require.True(t, strings.Contains(contents, "Test1"))
2436
}

utils/subprocess/command_wrapper_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121

2222
func TestCmdRun(t *testing.T) {
2323
currentDir, err := os.Getwd()
24-
require.Nil(t, err)
24+
require.NoError(t, err)
2525

2626
tests := []struct {
2727
name string
@@ -52,7 +52,7 @@ func TestCmdRun(t *testing.T) {
5252
defer goleak.VerifyNone(t)
5353
var cmd *command
5454
loggers, err := logs.NewLogrLogger(logstest.NewTestLogger(t), "test")
55-
require.Nil(t, err)
55+
require.NoError(t, err)
5656
if platform.IsWindows() {
5757
cmd = newCommand(loggers, test.cmdWindows, test.argWindows...)
5858
} else {
@@ -62,20 +62,20 @@ func TestCmdRun(t *testing.T) {
6262
defer cancel()
6363
wrapper := cmd.GetCmd(ctx)
6464
err = wrapper.Run()
65-
require.Nil(t, err)
65+
require.NoError(t, err)
6666
err = wrapper.Run()
67-
require.NotNil(t, err)
67+
require.Error(t, err)
6868
cmd.Reset()
6969
wrapper = cmd.GetCmd(ctx)
7070
err = wrapper.Run()
71-
require.Nil(t, err)
71+
require.NoError(t, err)
7272
})
7373
}
7474
}
7575

7676
func TestCmdStartStop(t *testing.T) {
7777
currentDir, err := os.Getwd()
78-
require.Nil(t, err)
78+
require.NoError(t, err)
7979

8080
tests := []struct {
8181
name string
@@ -106,7 +106,7 @@ func TestCmdStartStop(t *testing.T) {
106106
defer goleak.VerifyNone(t)
107107
var cmd *command
108108
loggers, err := logs.NewLogrLogger(logstest.NewTestLogger(t), "test")
109-
require.Nil(t, err)
109+
require.NoError(t, err)
110110

111111
if platform.IsWindows() {
112112
cmd = newCommand(loggers, test.cmdWindows, test.argWindows...)
@@ -117,14 +117,14 @@ func TestCmdStartStop(t *testing.T) {
117117
defer cancel()
118118
wrapper := cmd.GetCmd(ctx)
119119
err = wrapper.Start()
120-
require.Nil(t, err)
120+
require.NoError(t, err)
121121
pid, err := wrapper.Pid()
122-
require.Nil(t, err)
122+
require.NoError(t, err)
123123
assert.NotZero(t, pid)
124124
err = wrapper.Start()
125-
require.NotNil(t, err)
125+
require.Error(t, err)
126126
err = wrapper.Stop()
127-
require.Nil(t, err)
127+
require.NoError(t, err)
128128
})
129129
}
130130
}

utils/subprocess/executor.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5-
// Package subprocess allows you to spawn new processes, retrieve their output/error pipes, and obtain their return codes.
5+
6+
// Package subprocess allows you to spawn new processes, log their output/error and obtain their return codes.
67
package subprocess
78

89
import (
@@ -32,6 +33,12 @@ func New(ctx context.Context, loggers logs.Loggers, messageOnStart string, messa
3233
return
3334
}
3435

36+
func newPlainSubProcess(ctx context.Context, loggers logs.Loggers, cmd string, args ...string) (p *Subprocess, err error) {
37+
p = new(Subprocess)
38+
err = p.setup(ctx, loggers, false, "", "", "", cmd, args...)
39+
return
40+
}
41+
3542
// Execute executes a command (i.e. spawns a subprocess)
3643
func Execute(ctx context.Context, loggers logs.Loggers, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) (err error) {
3744
p, err := New(ctx, loggers, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
@@ -41,8 +48,37 @@ func Execute(ctx context.Context, loggers logs.Loggers, messageOnStart string, m
4148
return p.Execute()
4249
}
4350

51+
// Output executes a command and returns its output (stdOutput and stdErr are merged) as string.
52+
func Output(ctx context.Context, loggers logs.Loggers, cmd string, args ...string) (output string, err error) {
53+
if loggers == nil {
54+
err = commonerrors.ErrNoLogger
55+
return
56+
}
57+
58+
stringLogger, err := logs.NewPlainStringLogger()
59+
if err != nil {
60+
return
61+
}
62+
mLoggers, err := logs.NewCombinedLoggers(loggers, stringLogger)
63+
if err != nil {
64+
return
65+
}
66+
p, err := newPlainSubProcess(ctx, mLoggers, cmd, args...)
67+
if err != nil {
68+
return
69+
}
70+
err = p.Execute()
71+
output = stringLogger.GetLogContent()
72+
return
73+
}
74+
4475
// Setup sets up a sub-process i.e. defines the command cmd and the messages on start, success and failure.
4576
func (s *Subprocess) Setup(ctx context.Context, loggers logs.Loggers, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) (err error) {
77+
return s.setup(ctx, loggers, true, messageOnStart, messageOnSuccess, messageOnFailure, cmd, args...)
78+
}
79+
80+
// Setup sets up a sub-process i.e. defines the command cmd and the messages on start, success and failure.
81+
func (s *Subprocess) setup(ctx context.Context, loggers logs.Loggers, withAdditionalMessages bool, messageOnStart string, messageOnSuccess, messageOnFailure string, cmd string, args ...string) (err error) {
4682
if s.IsOn() {
4783
err = s.Stop()
4884
if err != nil {
@@ -54,7 +90,7 @@ func (s *Subprocess) Setup(ctx context.Context, loggers logs.Loggers, messageOnS
5490
s.isRunning.Store(false)
5591
s.processMonitoring = newSubprocessMonitoring(ctx)
5692
s.command = newCommand(loggers, cmd, args...)
57-
s.messsaging = newSubprocessMessaging(loggers, messageOnSuccess, messageOnFailure, messageOnStart, s.command.GetPath())
93+
s.messsaging = newSubprocessMessaging(loggers, withAdditionalMessages, messageOnSuccess, messageOnFailure, messageOnStart, s.command.GetPath())
5894
s.reset()
5995
return s.check()
6096
}

0 commit comments

Comments
 (0)