Skip to content

Commit

Permalink
Merge pull request #9 from hashmap-kz/feature/refactoring-v2
Browse files Browse the repository at this point in the history
feature/refactoring-v2
  • Loading branch information
hashmap-kz authored Jan 5, 2025
2 parents 3e7444f + 554b854 commit 08308ca
Show file tree
Hide file tree
Showing 4 changed files with 415 additions and 276 deletions.
55 changes: 32 additions & 23 deletions pkg/cmd/execs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"bytes"
"fmt"
"os/exec"
)

Expand All @@ -11,7 +12,6 @@ type ExecCmdInternalResult struct {
}

func ExecWithStdin(name string, stdinContent []byte, arg ...string) (ExecCmdInternalResult, error) {
// Define the command to execute
cmd := exec.Command(name, arg...)

// Buffers to capture stdout and stderr
Expand All @@ -22,44 +22,53 @@ func ExecWithStdin(name string, stdinContent []byte, arg ...string) (ExecCmdInte
// Create a pipe for stdin
stdin, err := cmd.StdinPipe()
if err != nil {
return ExecCmdInternalResult{
StdoutContent: stdoutBuf.String(),
StderrContent: getErrorDesc(err, stderrBuf),
}, err
return resultFromError(err, stderrBuf)
}

// Start the command
if err := cmd.Start(); err != nil {
return ExecCmdInternalResult{
StdoutContent: stdoutBuf.String(),
StderrContent: getErrorDesc(err, stderrBuf),
}, err
}
// Create a channel to capture errors from the goroutine
writeErrChan := make(chan error, 1)

// Write to stdin in a separate goroutine
go func() {
defer stdin.Close()
stdin.Write(stdinContent)
defer close(writeErrChan)
_, writeErr := stdin.Write(stdinContent)
if writeErr != nil {
writeErrChan <- writeErr
return
}
writeErrChan <- stdin.Close()
}()

// Start the command
if err := cmd.Start(); err != nil {
return resultFromError(err, stderrBuf)
}

// Wait for the command to finish
if err := cmd.Wait(); err != nil {
return ExecCmdInternalResult{
StdoutContent: stdoutBuf.String(),
StderrContent: getErrorDesc(err, stderrBuf),
}, err
return resultFromError(err, stderrBuf)
}

// Check if the write to stdin failed
if writeErr := <-writeErrChan; writeErr != nil {
return resultFromError(writeErr, stderrBuf)
}

return ExecCmdInternalResult{
StdoutContent: stdoutBuf.String(),
StderrContent: stderrBuf.String(),
}, err
}, nil
}

func getErrorDesc(err error, stderrBuf bytes.Buffer) string {
errorMessage := err.Error()
if stderrBuf.String() != "" {
errorMessage = stderrBuf.String()
if stderrBuf.Len() > 0 {
return stderrBuf.String()
}
return errorMessage
return err.Error()
}

func resultFromError(err error, stderrBuf bytes.Buffer) (ExecCmdInternalResult, error) {
return ExecCmdInternalResult{
StderrContent: getErrorDesc(err, stderrBuf),
}, fmt.Errorf("execution failed: %w", err)
}
111 changes: 105 additions & 6 deletions pkg/cmd/execs_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
package cmd

import (
"fmt"
"runtime"
"strings"
"testing"
)

// Cross-platform helper to get a command
func getCommand(command string, args []string) (string, []string) {
if runtime.GOOS == "windows" {
// Adjust commands for Windows
switch command {
case "cat":
// Use "more" to simulate "cat" on Windows for stdin
return "more", args
case "sh":
return "cmd", append([]string{"/C"}, args...)
case "ls":
return "cmd", append([]string{"/C", "dir"}, args...)
default:
return command, args
}
}
return command, args // Default for Linux/macOS
}

func TestExecWithStdin(t *testing.T) {
t.Run("Successful Execution", func(t *testing.T) {
// Command and input content
command := "cat" // `cat` reads from stdin and echoes to stdout
command, args := getCommand("cat", nil)
input := []byte("Hello, World!")

// Execute the command
result, err := ExecWithStdin(command, input)
result, err := ExecWithStdin(command, input, args...)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

// Validate stdout
if result.StdoutContent != string(input) {
if strings.TrimSpace(result.StdoutContent) != strings.TrimSpace(string(input)) {
t.Errorf("Expected stdout: %q, got: %q", string(input), result.StdoutContent)
}

Expand All @@ -29,23 +51,100 @@ func TestExecWithStdin(t *testing.T) {

t.Run("Command Error", func(t *testing.T) {
// Invalid command to simulate error
command := "nonexistent_command"
command, args := getCommand("nonexistent_command", nil)
input := []byte("This won't be used")

// Execute the command
result, err := ExecWithStdin(command, input)
result, err := ExecWithStdin(command, input, args...)

// Validate error
if err == nil {
t.Fatal("Expected error, got nil")
}

// Validate stdout and stderr are empty
// Validate stdout and stderr
if result.StdoutContent != "" {
t.Errorf("Expected empty stdout, got: %q", result.StdoutContent)
}
if result.StderrContent == "" {
t.Errorf("Expected stderr to contain error message, got empty")
}
})

t.Run("Command Produces Stderr", func(t *testing.T) {
// Command that writes to stderr
command, args := getCommand("sh", []string{"-c", "echo 'error message' 1>&2"})
if runtime.GOOS == "windows" {
command, args = getCommand("cmd", []string{"/C", "echo error message 1>&2"})
}
input := []byte("")

// Execute the command
result, err := ExecWithStdin(command, input, args...)

// Validate error (should not fail because the command exits with 0)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

// Validate stdout
if result.StdoutContent != "" {
t.Errorf("Expected empty stdout, got: %q", result.StdoutContent)
}

// Validate stderr
expectedStderr := "error message"
if strings.TrimSpace(result.StderrContent) != strings.TrimSpace(expectedStderr) {
t.Errorf("Expected stderr: %q, got: %q", expectedStderr, result.StderrContent)
}
})

t.Run("Command With Invalid Arguments", func(t *testing.T) {
// Command with invalid arguments
command, args := getCommand("ls", []string{"--invalid-option"})
input := []byte("")

// Execute the command
result, err := ExecWithStdin(command, input, args...)

// Validate error
if err == nil {
t.Fatal("Expected error, got nil")
}

// Validate stdout and stderr
if result.StdoutContent != "" {
t.Errorf("Expected empty stdout, got: %q", result.StdoutContent)
}
if result.StderrContent == "" {
t.Errorf("Expected stderr to contain error message, got empty")
}
})
}

func TestExecWithLargeInput(t *testing.T) {

// Generate a large input string
var largeInput strings.Builder
for i := 0; i < 1000000; i++ { // 1 million lines
largeInput.WriteString("Line " + fmt.Sprint(i) + "\n")
}

// Execute the command with the large input
result, err := ExecWithStdin("cat", []byte(largeInput.String()))

// Validate that the command executed successfully
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}

// Validate that the output matches the input
if result.StdoutContent != largeInput.String() {
t.Errorf("Output does not match input\n")
}

// Validate stderr is empty
if result.StderrContent != "" {
t.Errorf("Expected no stderr, got: %s", result.StderrContent)
}
}
Loading

0 comments on commit 08308ca

Please sign in to comment.