Skip to content
Merged
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
2 changes: 2 additions & 0 deletions exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"time"
)

type ExitError = exec.ExitError

// Exec represents an command executer.
type Exec struct {
Signal os.Signal
Expand Down
121 changes: 121 additions & 0 deletions exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exec
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"os/exec"
Expand Down Expand Up @@ -284,3 +285,123 @@ func gentests(withSleepTest bool) []testcase {
}
return tests
}

func TestExitError(t *testing.T) {
t.Run("TypeIdentity", func(t *testing.T) {
// Test that exec.ExitError is identical to os/exec.ExitError
var execExitError *ExitError
var osExecExitError *exec.ExitError

// Type assertion should work both ways
_ = (*exec.ExitError)(execExitError)
_ = (*ExitError)(osExecExitError)
})

t.Run("ActualErrorCase", func(t *testing.T) {
// Create a command that will fail with non-zero exit code
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = Command("cmd", "/c", "exit 1")
} else {
cmd = Command("sh", "-c", "exit 1")
}

err := cmd.Run()
if err == nil {
t.Fatal("Expected command to fail with exit code 1")
}

// Test that the error can be type asserted to exec.ExitError (our alias)
var exitError *ExitError
if !errors.As(err, &exitError) {
t.Fatalf("Expected error to be *exec.ExitError, got %T", err)
}

// Test that the error can also be type asserted to os/exec.ExitError
var osExitError *exec.ExitError
if !errors.As(err, &osExitError) {
t.Fatalf("Expected error to be *os/exec.ExitError, got %T", err)
}

// Verify ExitCode method works correctly
if exitError.ExitCode() != 1 {
t.Errorf("Expected exit code 1, got %d", exitError.ExitCode())
}

if osExitError.ExitCode() != 1 {
t.Errorf("Expected exit code 1, got %d", osExitError.ExitCode())
}

// Verify they are the same underlying object
if exitError != osExitError {
t.Error("Expected both type assertions to return the same object")
}
})

t.Run("ErrorMessageAndMethods", func(t *testing.T) {
// Test with a different exit code
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = Command("cmd", "/c", "exit 42")
} else {
cmd = Command("sh", "-c", "exit 42")
}

err := cmd.Run()
if err == nil {
t.Fatal("Expected command to fail with exit code 42")
}

var exitError *ExitError
if !errors.As(err, &exitError) {
t.Fatalf("Expected error to be *exec.ExitError, got %T", err)
}

// Test ExitCode method
if exitError.ExitCode() != 42 {
t.Errorf("Expected exit code 42, got %d", exitError.ExitCode())
}

// Test Error method (inherited from os/exec.ExitError)
errorMsg := exitError.Error()
if errorMsg == "" {
t.Error("Expected non-empty error message")
}

// Test that ProcessState is accessible
if exitError.ProcessState == nil {
t.Error("Expected ProcessState to be non-nil")
}

// Test ProcessState.ExitCode() method
if exitError.ExitCode() != 42 {
t.Errorf("Expected ProcessState.ExitCode() to return 42, got %d", exitError.ExitCode())
}
})

t.Run("CommandContextWithError", func(t *testing.T) {
// Test with CommandContext function
ctx := context.Background()
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = CommandContext(ctx, "cmd", "/c", "exit 5")
} else {
cmd = CommandContext(ctx, "sh", "-c", "exit 5")
}

err := cmd.Run()
if err == nil {
t.Fatal("Expected command to fail with exit code 5")
}

// Verify the alias works with CommandContext as well
var exitError *ExitError
if !errors.As(err, &exitError) {
t.Fatalf("Expected error to be *exec.ExitError, got %T", err)
}

if exitError.ExitCode() != 5 {
t.Errorf("Expected exit code 5, got %d", exitError.ExitCode())
}
})
}
Loading