• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cdprice02 / ferrish / #4

29 Mar 2026 06:36PM UTC coverage: 54.264% (+11.0%) from 43.243%
#4

push

cdprice02
Add comprehensive test infrastructure and fix clippy warnings

- Add lightweight ShellTest harness for integration tests
- Add environment isolation tests (env_handling.rs)
- Add additional integration tests for error handling and builtins
- Refactor existing tests to use new harness
- Remove legacy tests/common/mod.rs
- Add unit tests to shell.rs module
- Fix all clippy warnings (needless borrows, len_zero, dead_code)
- Add Default impl for ShellTest
- All 73 tests passing (46 unit + 27 integration)
- Clippy clean with -D warnings

140 of 258 relevant lines covered (54.26%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.43
/src/executor.rs
1
use crate::{
2
    Arg, Command,
3
    arg::Args,
4
    command::builtin::{BuiltInCommand, BuiltInName},
5
    env,
6
    error::{ShellError, ShellResult},
7
    exit::ExitCode,
8
    fs,
9
    io::ShellIo,
10
};
11

12
pub fn execute(
1✔
13
    command: Command,
14
    args: Args,
15
    io: &mut impl ShellIo,
16
) -> ShellResult<Option<ExitCode>> {
17
    match command {
1✔
18
        Command::BuiltIn(builtin) => execute_builtin(builtin, args, io),
2✔
19
        Command::Executable(executable) => execute_executable(executable, args, io),
×
20
        Command::Unrecognized(_) => Err(ShellError::CommandNotFound),
×
21
    }
22
}
23

24
fn execute_builtin(
1✔
25
    builtin: BuiltInCommand,
26
    args: Args,
27
    io: &mut impl ShellIo,
28
) -> ShellResult<Option<ExitCode>> {
29
    let execute = match builtin.name() {
2✔
30
        BuiltInName::Exit => {
31
            // TODO: parse exit code argument
32
            return Ok(Some(ExitCode::SUCCESS));
1✔
33
        }
34
        BuiltInName::Cd => execute_cd,
×
35
        BuiltInName::Echo => execute_echo,
1✔
36
        BuiltInName::Type => execute_type,
×
37
        BuiltInName::Pwd => execute_pwd,
×
38
    };
39
    execute(args, io)?;
1✔
40

41
    Ok(None)
1✔
42
}
43

44
fn execute_cd(args: Args, _io: &mut impl ShellIo) -> ShellResult<()> {
×
45
    let default_target = Arg::from(b"~".as_slice());
×
46
    let target = args.first().unwrap_or(&default_target);
×
47
    let new_dir = fs::resolve_path(&target.into())?;
×
48

49
    if !new_dir.exists() {
×
50
        return Err(ShellError::FileNotFound {
×
51
            arg: target.clone(),
×
52
        });
53
    } else if !new_dir.is_dir() {
×
54
        return Err(ShellError::NotADirectory {
×
55
            arg: target.clone(),
×
56
        });
57
    }
58

59
    env::set_current_dir(&new_dir)?;
×
60

61
    Ok(())
×
62
}
63

64
fn execute_echo(args: Args, io: &mut impl ShellIo) -> ShellResult<()> {
1✔
65
    writeln!(
2✔
66
        io.out_writer(),
1✔
67
        "{}",
68
        args.iter()
2✔
69
            .map(|a| a.to_string())
3✔
70
            .collect::<Vec<_>>()
1✔
71
            .join(" ")
1✔
72
    )?;
73

74
    Ok(())
1✔
75
}
76

77
fn execute_type(args: Args, io: &mut impl ShellIo) -> ShellResult<()> {
2✔
78
    if args.is_empty() {
4✔
79
        return Err(ShellError::MissingOperand);
×
80
    }
81

82
    // TODO: get type without fully parsing the arg
83
    match Command::from(args.first().expect("at least one arg")) {
4✔
84
        Command::BuiltIn(builtin) => writeln!(io.out_writer(), "{} is a shell builtin", builtin)?,
4✔
85
        Command::Executable(executable) => writeln!(
×
86
            io.out_writer(),
×
87
            "{} is {}",
88
            executable,
89
            executable.file_path().display()
×
90
        )?,
91
        Command::Unrecognized(_) => return Err(ShellError::CommandNotFound),
1✔
92
    }
93

94
    Ok(())
2✔
95
}
96

97
fn execute_pwd(_args: Args, io: &mut impl ShellIo) -> ShellResult<()> {
1✔
98
    writeln!(io.out_writer(), "{}", env::current_dir()?.display())?;
2✔
99

100
    Ok(())
1✔
101
}
102

103
// TODO: handle custom I/O (at the moment, this inherits from the parent process which will break tests)
104
fn execute_executable(
×
105
    executable: crate::command::executable::ExecutableCommand,
106
    args: Args,
107
    _io: &mut impl ShellIo,
108
) -> ShellResult<Option<ExitCode>> {
109
    let args = args.iter().map(|a| a.to_string()).collect::<Vec<_>>();
×
110
    let mut child = std::process::Command::new(executable.file_path())
×
111
        .args(args)
×
112
        .spawn()
113
        .map_err(ShellError::SpawnFailed)?;
×
114
    let status = child.wait().map_err(ShellError::WaitFailed)?;
×
115

116
    if !status.success() {
×
117
        return Err(ShellError::NonZeroExit(status));
×
118
    }
119

120
    Ok(None)
×
121
}
122

123
#[cfg(test)]
124
mod tests {
125
    use super::*;
126
    use crate::io::MockIo;
127

128
    #[test]
129
    fn test_execute_builtin_exit() {
130
        let mut io = MockIo::empty();
131
        let result = execute_builtin(BuiltInCommand::new(BuiltInName::Exit), vec![], &mut io);
132
        assert!(result.is_ok());
133
        let exit_code = result.unwrap();
134
        assert_eq!(exit_code, Some(ExitCode::SUCCESS));
135
    }
136

137
    #[test]
138
    fn test_execute_type_builtin() {
139
        let args = vec![Arg::from("echo")];
140
        let mut io = MockIo::empty();
141
        execute_type(args, &mut io).unwrap();
142
        let output = io.output();
143
        assert_eq!(output, b"echo is a shell builtin\n");
144
    }
145

146
    #[test]
147
    fn test_execute_type_unrecognized() {
148
        let args = vec![Arg::from("nonexistentcommand")];
149
        let mut io = MockIo::empty();
150
        let result = execute_type(args, &mut io);
151
        assert!(result.is_err());
152
        let err = result.err().unwrap();
153
        assert_eq!(
154
            err,
155
            ShellError::CommandNotFound,
156
            "Expected CommandNotFound error"
157
        );
158
    }
159

160
    #[test]
161
    fn test_execute_type_exit_builtin() {
162
        let args = vec![Arg::from("exit")];
163
        let mut io = MockIo::empty();
164
        execute_type(args, &mut io).unwrap();
165
        let output = io.output();
166
        assert_eq!(output, b"exit is a shell builtin\n");
167
    }
168

169
    #[test]
170
    fn test_execute_builtin_pwd() {
171
        let args: Vec<Arg> = vec![];
172
        let mut io = MockIo::empty();
173
        let result = execute_pwd(args, &mut io);
174
        assert!(result.is_ok());
175
        let output = io.output();
176
        assert!(!output.is_empty());
177
    }
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc