From 3c8215fd07dfb4d907341db29484e4eb2e1eaa22 Mon Sep 17 00:00:00 2001 From: k1LoW Date: Thu, 14 Aug 2025 09:42:17 +0900 Subject: [PATCH 1/2] feat: add Type alias `ExitError` --- exec.go | 2 + exec_test.go | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/exec.go b/exec.go index 3965877..40cf49a 100644 --- a/exec.go +++ b/exec.go @@ -7,6 +7,8 @@ import ( "time" ) +type ExitError = exec.ExitError + // Exec represents an command executer. type Exec struct { Signal os.Signal diff --git a/exec_test.go b/exec_test.go index 6d0f6a2..25e007b 100644 --- a/exec_test.go +++ b/exec_test.go @@ -284,3 +284,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) + exitError, ok := err.(*ExitError) + if !ok { + t.Fatalf("Expected error to be *exec.ExitError, got %T", err) + } + + // Test that the error can also be type asserted to os/exec.ExitError + osExitError, ok := err.(*exec.ExitError) + if !ok { + 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") + } + + exitError, ok := err.(*ExitError) + if !ok { + 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.ProcessState.ExitCode() != 42 { + t.Errorf("Expected ProcessState.ExitCode() to return 42, got %d", exitError.ProcessState.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 + exitError, ok := err.(*ExitError) + if !ok { + 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()) + } + }) +} From c7be1675a05db4b8f9c019ea65808881ee330482 Mon Sep 17 00:00:00 2001 From: k1LoW Date: Thu, 14 Aug 2025 09:48:07 +0900 Subject: [PATCH 2/2] chore: fix lint warn --- exec_test.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/exec_test.go b/exec_test.go index 25e007b..39355c4 100644 --- a/exec_test.go +++ b/exec_test.go @@ -3,6 +3,7 @@ package exec import ( "bytes" "context" + "errors" "fmt" "os" "os/exec" @@ -311,14 +312,14 @@ func TestExitError(t *testing.T) { } // Test that the error can be type asserted to exec.ExitError (our alias) - exitError, ok := err.(*ExitError) - if !ok { + 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 - osExitError, ok := err.(*exec.ExitError) - if !ok { + var osExitError *exec.ExitError + if !errors.As(err, &osExitError) { t.Fatalf("Expected error to be *os/exec.ExitError, got %T", err) } @@ -351,8 +352,8 @@ func TestExitError(t *testing.T) { t.Fatal("Expected command to fail with exit code 42") } - exitError, ok := err.(*ExitError) - if !ok { + var exitError *ExitError + if !errors.As(err, &exitError) { t.Fatalf("Expected error to be *exec.ExitError, got %T", err) } @@ -373,8 +374,8 @@ func TestExitError(t *testing.T) { } // Test ProcessState.ExitCode() method - if exitError.ProcessState.ExitCode() != 42 { - t.Errorf("Expected ProcessState.ExitCode() to return 42, got %d", exitError.ProcessState.ExitCode()) + if exitError.ExitCode() != 42 { + t.Errorf("Expected ProcessState.ExitCode() to return 42, got %d", exitError.ExitCode()) } }) @@ -394,8 +395,8 @@ func TestExitError(t *testing.T) { } // Verify the alias works with CommandContext as well - exitError, ok := err.(*ExitError) - if !ok { + var exitError *ExitError + if !errors.As(err, &exitError) { t.Fatalf("Expected error to be *exec.ExitError, got %T", err) }