diff --git a/plugin/logger.go b/plugin/logger.go index cc6ee35..a6c81a2 100644 --- a/plugin/logger.go +++ b/plugin/logger.go @@ -36,7 +36,11 @@ func loggingFlags(category string) []cli.Flag { // SetupConsoleLogger sets up the console logger. func SetupConsoleLogger(c *cli.Context) error { - level := c.String("log-level") + level := "info" + + if c != nil { + level = c.String("log-level") + } lvl, err := zerolog.ParseLevel(level) if err != nil { diff --git a/plugin/plugin.go b/plugin/plugin.go index 3a336db..c6819d1 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -96,13 +96,14 @@ func New(opt Options) *Plugin { _ = godotenv.Overload("/run/woodpecker/env") } + _ = SetupConsoleLogger(nil) + app := &cli.App{ Name: opt.Name, Usage: opt.Description, Version: opt.Version, Flags: append(opt.Flags, Flags()...), Before: SetupConsoleLogger, - After: SetupConsoleLogger, } if opt.HideWoodpeckerFlags { diff --git a/types/command.go b/types/command.go index 224ca4f..f955bbf 100644 --- a/types/command.go +++ b/types/command.go @@ -9,12 +9,22 @@ import ( "golang.org/x/sys/execabs" ) +// Cmd represents a command to be executed. It extends the execabs.Cmd struct +// and adds fields for controlling the command's execution, such as whether +// it should be executed in private mode (with output discarded) and whether +// its execution should be traced. type Cmd struct { *execabs.Cmd - Private bool - Trace *bool + Private bool + Trace *bool + TraceWriter io.Writer } +// Run executes the command and waits for it to complete. +// If the Trace field is nil, it is set to true. +// The Env, Stdout, and Stderr fields are set to their default values if they are nil. +// If the Private field is true, the Stdout field is set to io.Discard. +// If the Trace field is true, the command line is printed to Stdout. func (c *Cmd) Run() error { if c.Trace == nil { c.SetTrace(true) @@ -36,8 +46,12 @@ func (c *Cmd) Run() error { c.Stdout = io.Discard } + if c.TraceWriter == nil { + c.TraceWriter = os.Stdout + } + if *c.Trace { - fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(c.Args, " ")) + fmt.Fprintf(c.TraceWriter, "+ %s\n", strings.Join(c.Args, " ")) } if err := c.Start(); err != nil { @@ -47,6 +61,7 @@ func (c *Cmd) Run() error { return c.Wait() } +// SetTrace sets the Trace field of the Cmd to the provided boolean value. func (c *Cmd) SetTrace(trace bool) { c.Trace = &trace } diff --git a/types/command_test.go b/types/command_test.go new file mode 100644 index 0000000..df07da9 --- /dev/null +++ b/types/command_test.go @@ -0,0 +1,142 @@ +package types + +import ( + "bytes" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCmdRun(t *testing.T) { + tests := []struct { + name string + cmd *Cmd + wantErr bool + wantStdout string + wantStderr string + wantTrace string + }{ + { + name: "trace enabled", + cmd: &Cmd{ + Trace: boolPtr(true), + Cmd: &exec.Cmd{ + Path: "/usr/bin/echo", + Args: []string{"echo", "hello"}, + }, + }, + wantTrace: "+ echo hello\n", + wantStdout: "hello\n", + }, + { + name: "private output", + cmd: &Cmd{ + Private: true, + Cmd: &exec.Cmd{ + Path: "/usr/bin/echo", + Args: []string{"echo", "hello"}, + }, + }, + wantTrace: "+ echo hello\n", + }, + { + name: "custom env", + cmd: &Cmd{ + Cmd: &exec.Cmd{ + Path: "/bin/sh", + Args: []string{"sh", "-c", "echo $TEST"}, + Env: []string{"TEST=1"}, + }, + }, + wantTrace: "+ sh -c echo $TEST\n", + wantStdout: "1\n", + }, + { + name: "custom stdout", + cmd: &Cmd{ + Cmd: &exec.Cmd{ + Path: "/bin/sh", + Args: []string{"sh", "-c", "echo hello"}, + Stdout: new(bytes.Buffer), + }, + }, + wantTrace: "+ sh -c echo hello\n", + wantStdout: "hello\n", + }, + { + name: "custom stderr", + cmd: &Cmd{ + Cmd: &exec.Cmd{ + Path: "/bin/sh", + Args: []string{"sh", "-c", "echo error >&2"}, + Stderr: new(bytes.Buffer), + }, + }, + wantTrace: "+ sh -c echo error >&2\n", + wantStderr: "error\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + traceBuf := new(bytes.Buffer) + stdoutBuf := new(bytes.Buffer) + stderrBuf := new(bytes.Buffer) + tt.cmd.TraceWriter = traceBuf + tt.cmd.Stdout = stdoutBuf + tt.cmd.Stderr = stderrBuf + + err := tt.cmd.Run() + if tt.wantErr { + assert.Error(t, err) + + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wantTrace, traceBuf.String()) + assert.Equal(t, tt.wantStdout, stdoutBuf.String()) + assert.Equal(t, tt.wantStderr, stderrBuf.String()) + }) + } +} + +func TestCmdSetTrace(t *testing.T) { + tests := []struct { + name string + cmd *Cmd + trace bool + expected *bool + }{ + { + name: "set trace to true", + cmd: &Cmd{}, + trace: true, + expected: boolPtr(true), + }, + { + name: "set trace to false", + cmd: &Cmd{}, + trace: false, + expected: boolPtr(false), + }, + { + name: "overwrite existing trace value", + cmd: &Cmd{Trace: boolPtr(true)}, + trace: false, + expected: boolPtr(false), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.cmd.SetTrace(tt.trace) + assert.Equal(t, tt.expected, tt.cmd.Trace) + }) + } +} + +func boolPtr(b bool) *bool { + return &b +}