I’m stuck on Stage #vz4.
Here are my logs:
[compile] Compiling codecrafters-shell v0.1.0 (/app)
[compile] Finished `release` profile [optimized] target(s) in 1.78s
[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
Debug = true
[tester::#VZ4] Running tests for Stage #VZ4 (Redirection - Redirect stderr)
[tester::#VZ4] [setup] export PATH=/tmp/pear/pineapple/pear:$PATH
[tester::#VZ4] Running ./your_program.sh
[tester::#VZ4] [setup] echo -n "pear" > "/tmp/quz/pear"
[your-program] $ ls -1 nonexistent 2> /tmp/foo/baz.md
[your-program] $ cat /tmp/foo/baz.md
[your-program] ls: nonexistent: No such file or directory
[tester::#VZ4] ✓ Received redirected error message
[your-program] $ echo 'David file cannot be found' 2> /tmp/foo/foo.md
[tester::#VZ4] Failed to read file ("/tmp/foo/foo.md"): open /tmp/foo/foo.md: no such file or directory
[your-program] David file cannot be found
[your-program] $
[tester::#VZ4] Assertion failed.
[tester::#VZ4] Test failed
What makes me confused is the part below:
[your-program] $ echo 'David file cannot be found' 2> /tmp/foo/foo.md
[tester::#VZ4] Failed to read file ("/tmp/foo/foo.md"): open /tmp/foo/foo.md: no such file or directory
[your-program] David file cannot be found
Everything seems to be normal on the local machine (since I’ve already created /tmp/foo
and /tmp/bar
directories), Failed to read file
never occurs. But that’s not happening on the online test platform. Has anyone encountered any similar issue?
And here’s a snippet of my code:
#[derive(Debug, Clone, Default)]
pub struct Executor<'a> {
redirect_stdout: RedirectStdout,
redirect_stderr: RedirectStderr,
open_mode: OpenMode,
tokens: Vec<&'a str>,
}
impl Executor<'_> {
fn get_redirection_file(&self, path: &str) -> File {
let path = Path::new(path);
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.append(self.open_mode() == OpenMode::Append)
.open(path)
.unwrap()
}
}
impl Executor<'_> {
pub fn stdout(&self) -> Stdio {
match self.redirect_stdout() {
RedirectStdout::No => Stdio::inherit(),
RedirectStdout::Yes(path) => {
let file = self.get_redirection_file(path);
Stdio::from(file)
}
}
}
pub fn stderr(&self) -> Stdio {
match self.redirect_stderr() {
RedirectStderr::No => Stdio::inherit(),
RedirectStderr::Yes(path) => {
let file = self.get_redirection_file(path);
Stdio::from(file)
}
}
}
}
impl Executor<'_> {
pub fn print(&self, content: &str) {
match self.redirect_stdout() {
RedirectStdout::No => print!("{}", content),
RedirectStdout::Yes(path) => {
let mut file = self.get_redirection_file(path);
file.write_all(content.as_bytes()).unwrap();
}
}
}
#[inline]
pub fn println(&self, content: &str) {
self.print(&format!("{}\n", content));
}
pub fn eprint(&self, content: &str) {
match self.redirect_stderr() {
RedirectStderr::No => eprint!("{}", content),
RedirectStderr::Yes(path) => {
let mut file = self.get_redirection_file(path);
file.write_all(content.as_bytes()).unwrap();
}
}
}
#[inline]
pub fn eprintln(&self, content: &str) {
self.eprint(&format!("{}\n", content));
}
}
The parser
part has been verified to be well-implemented, and here’s the unit test:
#[test]
fn test_redirect_stdout_1() {
let input = "echo hello 1> world";
let mut tokenizer = super::Tokenizer::new(input.chars());
let tokens = tokenizer.tokenize();
assert_eq!(tokens, ["echo", "hello"]);
assert_eq!(
tokenizer.redirect_stdout,
super::RedirectStdout::Yes("world".to_string())
);
assert_eq!(tokenizer.redirect_stderr, super::RedirectStderr::No);
assert_eq!(tokenizer.open_mode, super::OpenMode::Truncate)
}
#[test]
fn test_redirect_stdout_2() {
let input = "echo hello > world";
let mut tokenizer = super::Tokenizer::new(input.chars());
let tokens = tokenizer.tokenize();
assert_eq!(tokens, ["echo", "hello"]);
assert_eq!(
tokenizer.redirect_stdout,
super::RedirectStdout::Yes("world".to_string())
);
assert_eq!(tokenizer.redirect_stderr, super::RedirectStderr::No);
assert_eq!(tokenizer.open_mode, super::OpenMode::Truncate)
}
#[test]
fn test_redirect_stderr() {
let input = "echo hello 2> world";
let mut tokenizer = super::Tokenizer::new(input.chars());
let tokens = tokenizer.tokenize();
assert_eq!(tokens, ["echo", "hello"]);
assert_eq!(tokenizer.redirect_stdout, super::RedirectStdout::No);
assert_eq!(
tokenizer.redirect_stderr,
super::RedirectStderr::Yes("world".to_string())
);
assert_eq!(tokenizer.open_mode, super::OpenMode::Truncate)
}
#[test]
fn test_redirect_append_stdout_1() {
let input = "echo hello 1>> world";
let mut tokenizer = super::Tokenizer::new(input.chars());
let tokens = tokenizer.tokenize();
assert_eq!(tokens, ["echo", "hello"]);
assert_eq!(
tokenizer.redirect_stdout,
super::RedirectStdout::Yes("world".to_string())
);
assert_eq!(tokenizer.redirect_stderr, super::RedirectStderr::No);
assert_eq!(tokenizer.open_mode, super::OpenMode::Append)
}
#[test]
fn test_redirect_append_stdout_2() {
let input = "echo hello >> world";
let mut tokenizer = super::Tokenizer::new(input.chars());
let tokens = tokenizer.tokenize();
assert_eq!(tokens, ["echo", "hello"]);
assert_eq!(
tokenizer.redirect_stdout,
super::RedirectStdout::Yes("world".to_string())
);
assert_eq!(tokenizer.redirect_stderr, super::RedirectStderr::No);
assert_eq!(tokenizer.open_mode, super::OpenMode::Append)
}