Skip to content

Commit

Permalink
Refactor shell_executable package and fix error handling in stage4.go
Browse files Browse the repository at this point in the history
The changes in the diff include refactoring the shell_executable package in the condition_reader/condition_reader.go file. The
ReadBytesUntil and ReadBytesUntilTimeout methods now wrap the reader error to handle program exit errors properly.

In the stage4.go file, the error handling for program exit in the testExit function has been updated to check for the
ErrProgramExited error instead of specific error types.

Additionally, there is a change in the fixtures/missing_command/wrong_output file, which shows that the program exited instead of
reaching EOF.

Commit message: "Refactor shell_executable package and fix error handling in stage4.go"
  • Loading branch information
rohitpaulk committed May 24, 2024
1 parent 4cd31f0 commit 16964c4
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 11 deletions.
2 changes: 1 addition & 1 deletion internal/condition_reader/condition_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ func (t *ConditionReader) ReadUntilTimeout(timeout time.Duration) ([]byte, error
}

return data, err
}
}
29 changes: 27 additions & 2 deletions internal/shell_executable/shell_executable.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package shell_executable

import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strings"
"syscall"
"time"

"github.com/codecrafters-io/shell-tester/internal/condition_reader"
Expand All @@ -18,6 +21,9 @@ import (
// ErrConditionNotMet is re-exported from condition_reader for convenience
var ErrConditionNotMet = condition_reader.ErrConditionNotMet

// ErrProgramExited is returned when the program exits
var ErrProgramExited = errors.New("Program exited")

type ShellExecutable struct {
executable *executable.Executable
logger *logger.Logger
Expand Down Expand Up @@ -70,11 +76,21 @@ func (b *ShellExecutable) LogOutput(output []byte) {
}

func (b *ShellExecutable) ReadBytesUntil(condition func([]byte) bool) ([]byte, error) {
return b.ptyReader.ReadUntilCondition(condition)
readBytes, err := b.ptyReader.ReadUntilCondition(condition)
if err != nil {
return readBytes, wrapReaderError(err)
}

return readBytes, nil
}

func (b *ShellExecutable) ReadBytesUntilTimeout(timeout time.Duration) ([]byte, error) {
return b.ptyReader.ReadUntilTimeout(timeout)
readBytes, err := b.ptyReader.ReadUntilTimeout(timeout)
if err != nil {
return readBytes, wrapReaderError(err)
}

return readBytes, nil
}

func (b *ShellExecutable) SendCommand(command string) error {
Expand Down Expand Up @@ -159,3 +175,12 @@ func StripANSI(data []byte) []byte {

return re.ReplaceAll(data, []byte(""))
}

func wrapReaderError(readerErr error) error {
// Linux returns syscall.EIO when the process is killed, MacOS returns io.EOF
if errors.Is(readerErr, io.EOF) || errors.Is(readerErr, syscall.EIO) {
return ErrProgramExited
}

return readerErr
}
9 changes: 2 additions & 7 deletions internal/stage4.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package internal

import (
"errors"
"fmt"
"io"
"regexp"
"strings"
"syscall"
"time"

"github.com/codecrafters-io/shell-tester/internal/shell_executable"
Expand Down Expand Up @@ -54,13 +51,11 @@ func testExit(stageHarness *test_case_harness.TestCaseHarness) error {
}

// We're expecting EOF since the program should've terminated
// HACK: We also allow syscall.EIO since that's what we get on Linux when the process is killed
// read /dev/ptmx: input/output error *fs.PathError
if !errors.Is(readErr, io.EOF) && !errors.Is(readErr, syscall.EIO) {
if readErr != shell_executable.ErrProgramExited {
if readErr == nil {
return fmt.Errorf("Expected program to exit with 0 exit code, program is still running.")
} else {
// TODO: Other than EOF, what other errors could we get? Are they user errors or internal errors?
// TODO: Other than ErrProgramExited, what other errors could we get? Are they user errors or internal errors?
return fmt.Errorf("Error reading output: %v", readErr)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ Debug = true
[your-program] $
[stage-2] > nonexistent
[your-program] Command: nonexistent
[stage-2] EOF
[stage-2] Program exited
[stage-2] Test failed

0 comments on commit 16964c4

Please sign in to comment.