From 24a071888cc2f33f5d79db8f028964abf87a1be9 Mon Sep 17 00:00:00 2001 From: reginaldalfret Date: Mon, 5 Oct 2026 15:54:08 +0530 Subject: [PATCH] fix: return command output for ssh device and avoid printing null in sequence execution In SSHDevice.executeCommandString, return output.String() on successful command execution instead of (nil, nil), and return output.String() on exit error so captured command output is preserved. In SequenceMap.executeSequenceSteps, format string and Stringer step results directly to stdout and skip printing when the result is nil or empty, preventing 'null' from being emitted for each step. Fixes #28 Signed-off-by: reginaldalfret --- internal/configuration/sequence.go | 29 +++++++++++++++++++++---- internal/configuration/sequence_test.go | 22 +++++++++++++++++++ internal/device/ssh/sshdevice.go | 5 +++-- internal/device/ssh/sshdevice_test.go | 6 ++++- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/internal/configuration/sequence.go b/internal/configuration/sequence.go index 6e76064..7ae8937 100644 --- a/internal/configuration/sequence.go +++ b/internal/configuration/sequence.go @@ -11,6 +11,7 @@ import ( "fmt" "os" "regexp" + "strings" "time" "gopkg.in/yaml.v3" @@ -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 } diff --git a/internal/configuration/sequence_test.go b/internal/configuration/sequence_test.go index 24a7c00..e56a06e 100644 --- a/internal/configuration/sequence_test.go +++ b/internal/configuration/sequence_test.go @@ -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) + } +} + diff --git a/internal/device/ssh/sshdevice.go b/internal/device/ssh/sshdevice.go index ed899e3..42630e3 100644 --- a/internal/device/ssh/sshdevice.go +++ b/internal/device/ssh/sshdevice.go @@ -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) diff --git a/internal/device/ssh/sshdevice_test.go b/internal/device/ssh/sshdevice_test.go index 1df3ccb..28c9c01 100644 --- a/internal/device/ssh/sshdevice_test.go +++ b/internal/device/ssh/sshdevice_test.go @@ -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