Hey @zbeers, it looks like the issue was caused by an extra \n
, since print
already appends a newline by default:
After each command, the tester will check if <command_name>: command not found
is printed, and whether a prompt is printed for the next command.
This is very confusing, the solution will result in command not found for anything input, so why not state that any command input will not be found, because we havent created any commands yet? I understand you are building up to it, but still the instructions are confusing.
Hi @SoundBoySelecta, thanks for sharing your feedback!
Could you confirm if you’re referring to #cz2 Handle invalid commands or #ff0 REPL?
Actually, we do mention “all commands [are] invalid” in both stages:
That said, I agree that the instructions should be clearer in setting the right expectations upfront.
Hi @andy1li ,
Surprisingly, I ran into some unexpected trouble with this part of the exercise. However, I finally figured out why!
When building a shell, there are multiple ways to capture input:
os.Stdin
/dev/tty
Most real-world shells (Bash, Zsh, Fish, etc.) always read from /dev/tty
, not stdin
, to ensure:
- The shell remains interactive even if stdin is redirected (e.g., when running in a pipeline).
- The shell can handle password prompts, interactive input, and terminal controls properly.
- The shell still works when running scripts while keeping user interaction possible.
So, I updated my code to read from /dev/tty
, thinking this was the correct thing to do. However, I think your tests send input via stdin
. This caused my implementaiton to fail despite working fine when manually running the shell locally.
Would it be possible to adjust the tests to accommodate /dev/tty
as well? That would be amazing!
I really appreciate the effort you guys have put into making such an awesome product!
Thanks again!
Durga
Hey @dgiridharan,
Thanks for the kind words!
In our tester, we internally use a pty
to handle the communication between the tester and the user’s shell.
You can refer to the code here for more details.
I tried hacking around to make reads
using go-tty
work, but came up empty handed.
However, I managed to create a handle to /dev/tty
and successfully read from it. This approach seems to work.
// New initializes a new Shell instance and returns in.
func New() (*Shell, error) {
var input io.Reader
ttyFile, err := os.Open("/dev/tty")
if err == nil {
input = ttyFile
} else {
// Fall back to stdin
input = os.Stdin
}
shell := Shell{
running: false,
readIn: input,
writerOut: os.Stdout,
}
return &shell, nil
}
Hi @ryan-gang,
Amazing! Thanks for the quick response and you’re absolutely right. I was able to get the tests passing using the following example:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
out := os.Stdout
in, err := os.Open("/dev/tty")
if err != nil {
_, _ = fmt.Fprintf(out, "Error: %s\n", err)
os.Exit(1)
}
defer in.Close()
reader := bufio.NewReader(in)
fmt.Fprint(out, "$ ")
input, err := reader.ReadString('\n')
if err != nil {
_, _ = fmt.Fprintf(out, "Error: %s\n", err)
os.Exit(1)
}
fmt.Fprintf(out, "%s: command not found\n", input[0:len(input)-1])
}
I was actually trying to use the go-tty
library instead. Here’s a quick example using it:
package main
import (
"fmt"
"os"
"github.com/mattn/go-tty"
)
func main() {
out := os.Stdout
tty, err := tty.Open()
if err != nil {
_, _ = fmt.Fprintf(out, "%s\n", err)
os.Exit(1)
}
defer tty.Close()
fmt.Fprint(out, "$ ")
input, err := tty.ReadString()
if err != nil {
_, _ = fmt.Fprintf(out, "%s\n", err)
os.Exit(1)
}
fmt.Fprintf(out, "%s: command not found\n", input)
}
When running this in the command line, it correctly prints proper output:
However, when running the test, I get this:
Initiating test run...
⏳ Turbo test runners busy. You are in queue.
Upgrade to skip the wait: https://codecrafters.io/turbo
Running tests. Logs should appear shortly...
[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
Debug = true
[tester::#OO8] Running tests for Stage #OO8 (Print a prompt)
[tester::#OO8] Running ./your_program.sh
[your-program] $
[tester::#OO8] ✓ Received prompt
[tester::#OO8] Test passed.
[tester::#CZ2] Running tests for Stage #CZ2 (Handle invalid commands)
[tester::#CZ2] Running ./your_program.sh
[your-program] $ invalid_pear_command
[tester::#CZ2] Output does not match expected value.
[tester::#CZ2] Expected: "invalid_pear_command: command not found"
[tester::#CZ2] Received: ""
[tester::#CZ2] Assertion failed.
[tester::#CZ2] Test failed
View our article on debugging test failures: https://codecrafters.io/debug
So it seems to be a library-specific issue. I was hoping to use go-tty
since it would simplify things quite a bit! It’s really unclear why this would is happening as the library does exactly what I did in my previous working example (but just abstracts everything).
Sorry for the trouble, and thanks again for the quick reply! I really appreciate it. You guys have built an incredible product, and I’m having a blast coding while learning a new language.
Cheers!