Skip to content

update failure case #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 24, 2024
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: 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
7 changes: 7 additions & 0 deletions internal/stages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ func TestStages(t *testing.T) {
StdoutFixturePath: "./test_helpers/fixtures/navigation/pass",
NormalizeOutputFunc: normalizeTesterOutput,
},
"missing_command_fail": {
UntilStageSlug: "cz2",
CodePath: "./test_helpers/scenarios/missing_command/wrong_output",
ExpectedExitCode: 1,
StdoutFixturePath: "./test_helpers/fixtures/missing_command/wrong_output",
NormalizeOutputFunc: normalizeTesterOutput,
},
}

testerUtilsTesting.TestTesterOutput(t, testerDefinition, testCases)
Expand Down
3 changes: 2 additions & 1 deletion internal/test_cases/regex_test_case.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ func (t RegexTestCase) Run(shell *shell_executable.ShellExecutable, logger *logg
}

if err != nil {
if errors.Is(err, shell_executable.ErrConditionNotMet) {
if errors.Is(err, shell_executable.ErrConditionNotMet) || errors.Is(err, shell_executable.ErrProgramExited) {
logger.Errorf("Expected output to %s, got %q", t.ExpectedPatternExplanation, string(shell_executable.StripANSI(output)))
}

// TODO: Think about this, what other errors could be there other than ErrConditionNotMet and ErrProgramExited?
return err
}

Expand Down
24 changes: 0 additions & 24 deletions internal/test_helpers/failure/simple_shell.rb

This file was deleted.

4 changes: 0 additions & 4 deletions internal/test_helpers/failure/your_shell.sh

This file was deleted.

10 changes: 10 additions & 0 deletions internal/test_helpers/fixtures/missing_command/wrong_output
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Debug = true

[stage-2] Running tests for Stage #2: cz2
[stage-2] Running ./your_shell.sh
[your-program] $
[stage-2] > nonexistent
[your-program] Command: nonexistent
[stage-2] Expected output to contain "nonexistent: command not found", got "Command: nonexistent\r\n"
[stage-2] Program exited
[stage-2] Test failed
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import sys


def main():
sys.stdout.write("$ ")
sys.stdout.flush()

command = input()

# Wrong output
sys.stdout.write(f"Command: {command}\n")
sys.stdout.flush()


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
#
# DON'T EDIT THIS!
#
# CodeCrafters uses this file to test your code. Don't make any changes here!
#
# DON'T EDIT THIS!
exec python3 $(dirname "$0")/main.py "$@"
Loading