diff --git a/internal/logged_shell_asserter/logged_shell_asserter.go b/internal/logged_shell_asserter/logged_shell_asserter.go index 49a50d01..d6c2865c 100644 --- a/internal/logged_shell_asserter/logged_shell_asserter.go +++ b/internal/logged_shell_asserter/logged_shell_asserter.go @@ -116,19 +116,20 @@ func (a *LoggedShellAsserter) onAssertionSuccess(startRowIndex int, processedRow } func (a *LoggedShellAsserter) logAssertionError(err assertions.AssertionError) { - a.logRows(a.lastLoggedRowIndex+1, err.ErrorRowIndex+1) + a.logRows(a.lastLoggedRowIndex+1, err.ErrorRowIndex) a.Shell.GetLogger().Errorf("%s", err.Message) - a.logRows(err.ErrorRowIndex+1, len(a.Shell.GetScreenState())) + a.logRows(err.ErrorRowIndex+1, len(a.Shell.GetScreenState())-1) } func (a *LoggedShellAsserter) LogRemainingOutput() { startRowIndex := a.lastLoggedRowIndex + 1 - endRowIndex := len(a.Shell.GetScreenState()) + endRowIndex := len(a.Shell.GetScreenState()) - 1 a.logRows(startRowIndex, endRowIndex) + a.lastLoggedRowIndex = endRowIndex } func (a *LoggedShellAsserter) logRows(startRowIndex int, endRowIndex int) { - for i := startRowIndex; i < endRowIndex; i++ { + for i := startRowIndex; i <= endRowIndex; i++ { rawRow := a.Shell.GetScreenState()[i] cleanedRow := virtual_terminal.BuildCleanedRow(rawRow) if len(cleanedRow) > 0 { diff --git a/internal/stage4.go b/internal/stage4.go index 419447c0..ed09fc13 100644 --- a/internal/stage4.go +++ b/internal/stage4.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/codecrafters-io/shell-tester/internal/condition_reader" "github.com/codecrafters-io/shell-tester/internal/logged_shell_asserter" "github.com/codecrafters-io/shell-tester/internal/shell_executable" "github.com/codecrafters-io/shell-tester/internal/test_cases" @@ -43,10 +44,15 @@ func testExit(stageHarness *test_case_harness.TestCaseHarness) error { readErr := shell.ReadUntilConditionOrTimeout(utils.AsBool(assertFn), logged_shell_asserter.SUBSEQUENT_READ_TIMEOUT) output := virtual_terminal.BuildCleanedRow(shell.GetScreenState()[asserter.GetLastLoggedRowIndex()+1]) + asserter.LogRemainingOutput() + // We're expecting EOF since the program should've terminated if !errors.Is(readErr, shell_executable.ErrProgramExited) { + if readErr == nil { return fmt.Errorf("Expected program to exit with 0 exit code, program is still running.") + } else if errors.Is(readErr, condition_reader.ErrConditionNotMet) { + return fmt.Errorf("Expected program to exit with 0 exit code, program is still running.") } else { // 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) diff --git a/internal/stages_test.go b/internal/stages_test.go index 8344a015..4b1db31f 100644 --- a/internal/stages_test.go +++ b/internal/stages_test.go @@ -34,6 +34,13 @@ func TestStages(t *testing.T) { StdoutFixturePath: "./test_helpers/fixtures/escape_codes", NormalizeOutputFunc: normalizeTesterOutput, }, + "exit_error_fail": { + UntilStageSlug: "pn5", + CodePath: "./test_helpers/scenarios/exit_error", + ExpectedExitCode: 1, + StdoutFixturePath: "./test_helpers/fixtures/exit_error", + NormalizeOutputFunc: normalizeTesterOutput, + }, "base_pass_bash": { UntilStageSlug: "ip1", CodePath: "./test_helpers/bash", diff --git a/internal/test_helpers/fixtures/bash/base/pass b/internal/test_helpers/fixtures/bash/base/pass index c561da47..ae84b34b 100644 --- a/internal/test_helpers/fixtures/bash/base/pass +++ b/internal/test_helpers/fixtures/bash/base/pass @@ -21,13 +21,13 @@ Debug = true [stage-7] [setup] - my_exe [stage-7] Running ./your_shell.sh [your-program] $ type cat -[your-program] cat is /bin/cat +[your-program] cat is /usr/bin/cat [stage-7] ✓ Received expected response [your-program] $ type cp -[your-program] cp is /bin/cp +[your-program] cp is /usr/bin/cp [stage-7] ✓ Received expected response [your-program] $ type mkdir -[your-program] mkdir is /bin/mkdir +[your-program] mkdir is /usr/bin/mkdir [stage-7] ✓ Received expected response [your-program] $ type my_exe [your-program] my_exe is /tmp/foo/my_exe @@ -78,9 +78,9 @@ Debug = true [your-program] bash: invalid_strawberry_command: command not found [stage-4] ✓ Received command not found message [your-program] $ exit 0 +[your-program] exit [stage-4] ✓ Program exited successfully [stage-4] ✓ No output after exit command -[your-program] exit [stage-4] Test passed. [stage-3] Running tests for Stage #3: ff0 diff --git a/internal/test_helpers/fixtures/bash/navigation/pass b/internal/test_helpers/fixtures/bash/navigation/pass index 3d118272..6884bb71 100644 --- a/internal/test_helpers/fixtures/bash/navigation/pass +++ b/internal/test_helpers/fixtures/bash/navigation/pass @@ -74,13 +74,13 @@ Debug = true [stage-7] [setup] - my_exe [stage-7] Running ./your_shell.sh [your-program] $ type cat -[your-program] cat is /bin/cat +[your-program] cat is /usr/bin/cat [stage-7] ✓ Received expected response [your-program] $ type cp -[your-program] cp is /bin/cp +[your-program] cp is /usr/bin/cp [stage-7] ✓ Received expected response [your-program] $ type mkdir -[your-program] mkdir is /bin/mkdir +[your-program] mkdir is /usr/bin/mkdir [stage-7] ✓ Received expected response [your-program] $ type my_exe [your-program] my_exe is /tmp/quz/my_exe @@ -134,9 +134,9 @@ Debug = true [your-program] bash: invalid_mango_command: command not found [stage-4] ✓ Received command not found message [your-program] $ exit 0 +[your-program] exit [stage-4] ✓ Program exited successfully [stage-4] ✓ No output after exit command -[your-program] exit [stage-4] Test passed. [stage-3] Running tests for Stage #3: ff0 diff --git a/internal/test_helpers/fixtures/exit_error b/internal/test_helpers/fixtures/exit_error new file mode 100644 index 00000000..009fec10 --- /dev/null +++ b/internal/test_helpers/fixtures/exit_error @@ -0,0 +1,12 @@ +Debug = true + +[stage-4] Running tests for Stage #4: pn5 +[stage-4] Running ./your_shell.sh +[your-program] $ invalid_apple_command +[your-program] invalid_apple_command: command not found +[stage-4] ✓ Received command not found message +[your-program] $ exit 0 +[your-program] exit: command not found +[your-program] $ +[stage-4] Expected program to exit with 0 exit code, program is still running. +[stage-4] Test failed diff --git a/internal/test_helpers/scenarios/exit_error/codecrafters.yml b/internal/test_helpers/scenarios/exit_error/codecrafters.yml new file mode 100644 index 00000000..72f77132 --- /dev/null +++ b/internal/test_helpers/scenarios/exit_error/codecrafters.yml @@ -0,0 +1,5 @@ +# Set this to true if you want debug logs. +# +# These can be VERY verbose, so we suggest turning them off +# unless you really need them. +debug: true \ No newline at end of file diff --git a/internal/test_helpers/scenarios/exit_error/main.py b/internal/test_helpers/scenarios/exit_error/main.py new file mode 100644 index 00000000..419755e0 --- /dev/null +++ b/internal/test_helpers/scenarios/exit_error/main.py @@ -0,0 +1,22 @@ +import sys + + +def main(): + while True: + sys.stdout.write("$ ") + sys.stdout.flush() + + command = input().strip() + parts = command.split(" ") + cmd = parts[0] + args = parts[1:] + + if cmd == "exitt": + sys.exit(0) + else: + sys.stdout.write(f"{cmd}: command not found\n") + sys.stdout.flush() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/internal/test_helpers/scenarios/exit_error/your_shell.sh b/internal/test_helpers/scenarios/exit_error/your_shell.sh new file mode 100755 index 00000000..24d39bdf --- /dev/null +++ b/internal/test_helpers/scenarios/exit_error/your_shell.sh @@ -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 "$@" \ No newline at end of file