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
6 changes: 6 additions & 0 deletions src/exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ impl From<ExitCode> for i32 {
}
}

impl From<ExitCode> for std::process::ExitCode {
fn from(val: ExitCode) -> Self {
std::process::ExitCode::from(val.0)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ pub use shell::Shell;
/// let mut shell = Shell::builder().with_io(io);
/// let result = shell.run();
/// ```
pub fn run() -> anyhow::Result<()> {
pub fn run() -> anyhow::Result<exit::ExitCode> {
Shell::<crate::io::StandardIo>::builder().with_std_io().run()
}
5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
fn main() -> anyhow::Result<()> {
ferrish::run()
fn main() -> anyhow::Result<std::process::ExitCode> {
let code = ferrish::run()?;
Ok(code.into())
}
12 changes: 3 additions & 9 deletions src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl<IO: ShellIo> Shell<IO> {
self.io.borrow_mut()
}

pub fn run(&mut self) -> anyhow::Result<()> {
pub fn run(&mut self) -> anyhow::Result<ExitCode> {
loop {
{
let mut io = self.io.borrow_mut();
Expand All @@ -61,7 +61,7 @@ impl<IO: ShellIo> Shell<IO> {
let mut buffer = Vec::<u8>::new();
let bytes = self.io.borrow_mut().read_line(&mut buffer)?;
if bytes == 0 {
continue;
return Ok(ExitCode::SUCCESS);
}

let buffer = buffer.trim_ascii();
Expand All @@ -71,10 +71,7 @@ impl<IO: ShellIo> Shell<IO> {

let (command, args) = parser::parse(buffer);
match self.execute_command(command.clone(), args) {
Ok(Some(_exit_code)) => {
// TODO: set exit code in caller env
break;
}
Ok(Some(exit_code)) => return Ok(exit_code),
Ok(None) => {}
Err(e) => {
let fatal = e.is_fatal();
Expand All @@ -87,8 +84,6 @@ impl<IO: ShellIo> Shell<IO> {
}
}
}

Ok(())
}

pub fn run_script(&mut self, script: &[&str]) -> anyhow::Result<ExitCode> {
Expand All @@ -103,7 +98,6 @@ impl<IO: ShellIo> Shell<IO> {

let (command, args) = parser::parse(buffer);
if let Some(exit_code) = self.execute_command(command, args)? {
// TODO: set exit code in caller env instead of returning
return Ok(exit_code);
}
}
Expand Down
17 changes: 13 additions & 4 deletions tests/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,13 @@ impl ShellTest {
let output = String::from_utf8_lossy(shell.io().output()).to_string();
let error = String::from_utf8_lossy(shell.io().error()).to_string();

run_result.unwrap_or_else(|err| {
panic!("shell.run() failed: {err}\nstdout:\n{output}\nstderr:\n{error}");
});
let exit_code = run_result
.unwrap_or_else(|err| {
panic!("shell.run() failed: {err}\nstdout:\n{output}\nstderr:\n{error}");
})
.0;

TestResult { output, error, home_dir: self.home_dir }
TestResult { output, error, home_dir: self.home_dir, exit_code }
// self drops here — temp_dir is cleaned up
}
}
Expand All @@ -116,6 +118,8 @@ pub struct TestResult {
error: String,
#[allow(dead_code)]
home_dir: Option<PathBuf>,
#[allow(dead_code)]
exit_code: u8,
}

impl TestResult {
Expand All @@ -128,6 +132,11 @@ impl TestResult {
&self.error
}

#[allow(dead_code)]
pub fn exit_code(&self) -> u8 {
self.exit_code
}

#[allow(dead_code)]
pub fn output_contains(&self, s: &str) -> bool {
self.output.contains(s)
Expand Down
22 changes: 22 additions & 0 deletions tests/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,28 @@ fn test_repl_sequential_commands() {
result.assert_output_contains("hello");
}

// ============================================================================
// Exit Code Propagation Tests
// ============================================================================

#[test]
fn test_exit_zero_propagates() {
let result = ShellTest::new().script("exit 0").run();
assert_eq!(result.exit_code(), 0);
}

#[test]
fn test_exit_nonzero_propagates() {
let result = ShellTest::new().script("exit 1").run();
assert_eq!(result.exit_code(), 1);
}

#[test]
fn test_exit_arbitrary_code_propagates() {
let result = ShellTest::new().script("exit 42").run();
assert_eq!(result.exit_code(), 42);
}

// ============================================================================
// Error Handling and Recovery Tests
// ============================================================================
Expand Down
Loading