Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions internal/configuration/sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"os"
"regexp"
"strings"
"time"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -127,10 +128,30 @@ func (sm *SequenceMap) executeSequenceSteps(executor CommandExecutor, seqName st
return fmt.Errorf("sequence '%s' failed at step %d: %w", seqName, idx+1, err)
}
// TODO: provide option to suppress output
tui.SetOutputColor(tui.CBlue, os.Stdout)
enc := yaml.NewEncoder(os.Stdout)
enc.Encode(res)
tui.ResetOutputColor(os.Stdout)
if res != nil {
tui.SetOutputColor(tui.CBlue, os.Stdout)
switch v := res.(type) {
case string:
if len(strings.TrimSpace(v)) > 0 {
if !strings.HasSuffix(v, "\n") {
v += "\n"
}
fmt.Print(v)
}
case fmt.Stringer:
s := v.String()
if len(strings.TrimSpace(s)) > 0 {
if !strings.HasSuffix(s, "\n") {
s += "\n"
}
fmt.Print(s)
}
default:
enc := yaml.NewEncoder(os.Stdout)
enc.Encode(res)
}
tui.ResetOutputColor(os.Stdout)
}
}
return nil
}
Expand Down
22 changes: 22 additions & 0 deletions internal/configuration/sequence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,25 @@ func TestExecute_DefaultTimeout_UsedWhenUnset(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
}

// TestExecute_CommandOutputFormatting verifies that string command output is handled
// and that steps returning nil do not print "null".
func TestExecute_CommandOutputFormatting(t *testing.T) {
cmd1 := configuration.SequenceCmd{Cmd: configuration.T("cmd1")}
cmd2 := configuration.SequenceCmd{Cmd: configuration.T("cmd2")}
sm := configuration.SequenceMap{"seq": {cmd1, cmd2}}

exec := &mockExecutor{
executeFunc: func(callIdx int, _ context.Context, _ *configuration.SequenceCmd) (any, error) {
if callIdx == 0 {
return "expected command output\n", nil
}
return nil, nil
},
}

if err := sm.Execute(exec, "seq"); err != nil {
t.Fatalf("unexpected error executing sequence: %v", err)
}
}

5 changes: 3 additions & 2 deletions internal/device/ssh/sshdevice.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,11 @@ func (d *SSHDevice) executeCommandString(ctx context.Context, cmd string) (any,
case err := <-done:
var exitError *stdssh.ExitError
if errors.As(err, &exitError) {
return output, fmt.Errorf("exit code (%d)", exitError.ExitStatus())
} else {
return output.String(), fmt.Errorf("exit code (%d)", exitError.ExitStatus())
} else if err != nil {
return nil, err
}
return output.String(), nil

case <-ctx.Done():
_ = session.Signal(ssh.SIGKILL)
Expand Down
6 changes: 5 additions & 1 deletion internal/device/ssh/sshdevice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,17 @@ func TestSSHDevice_ExecuteCommand_OutputCaptured(t *testing.T) {
defer dev.Close()

cmd := mustSequenceCmd(t, "cmd: echo-test")
if _, err := dev.ExecuteCommand(context.Background(), cmd); err != nil {
res, err := dev.ExecuteCommand(context.Background(), cmd)
if err != nil {
t.Fatalf("unexpected error executing command: %v", err)
}

if !strings.Contains(logBuf.String(), want) {
t.Errorf("log writer: expected to contain %q, got %q", want, logBuf.String())
}
if outStr, ok := res.(string); !ok || !strings.Contains(outStr, want) {
t.Errorf("ExecuteCommand result: expected string containing %q, got %v", want, res)
}
}

// TestSSHDevice_ExecuteCommand_ParamsConcatenated verifies that the Cmd field
Expand Down