Info
Challenge: Build your own shell
Language: Go
Stage: #QP2 - Builtin completions
Context/Issue
I’m currently working on the “Build Your Own Shell” challenge in Go. Locally, my autocompletion works flawlessly, but CodeCrafters’ tests keep failing.
Interestingly, after my latest changes, running codecrafters test --previous
started failing. However, everything still works fine in my local environment.
Logs
Output from codecrafters test --previous
[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] $ i
[tester::#CZ2] Output does not match expected value.
[tester::#CZ2] Expected: "$ invalid_banana_command"
[tester::#CZ2] Received: "$ i"
[tester::#CZ2] Assertion failed.
[tester::#CZ2] Test failed
Output from codecrafters test
[compile] Moved ./.codecrafters/run.sh → ./your_program.sh
[compile] Compilation successful.
Debug = true
[tester::#QP2] Running tests for Stage #QP2 (Autocompletion - Builtin completion)
[tester::#QP2] Running ./your_program.sh
[tester::#QP2] ✓ Received prompt ($ )
[tester::#QP2] Typed "ech"
[your-program] $ e
[tester::#QP2] Output does not match expected value.
[tester::#QP2] Expected: "$ ech"
[tester::#QP2] Received: "$ e"
[tester::#QP2] Assertion failed.
[tester::#QP2] Test failed
Local Env
Here’s a GIF of it working fine on my local machine:
Hypothesis
I suspect that setting the terminal to raw mode with:
term.MakeRaw
is causing issues in the test environment. I need raw mode to capture tab keystrokes for autocompletion, but it seems to interfere with CodeCrafters’ testing setup.
If I switch to:
bufio.NewReader
all tests pass, but then I lose the ability to capture \t
(tab) and other special characters.
Previously, I attempted using the go-tty
library (mattn/go-tty), and ran into the same issue—likely because it also puts the terminal in raw mode. This makes me think the test environment doesn’t handle raw mode properly.
Relevant code
My code is spread across multiple files, so it’s tricky to share everything, but here’s the most relevant snippet:
// readlineWithCompletion: Grabs keystrokes in raw mode to handle special keys.
func (shell *Shell) readlineWithCompletion() (string, error) {
fd, ok := isTerminal(shell.readIn)
tty := int(fd.Fd())
if !ok {
return bufio.NewReader(shell.readIn).ReadString('\n')
}
oldState, err := term.MakeRaw(tty)
if err != nil {
return bufio.NewReader(shell.readIn).ReadString('\n')
}
defer term.Restore(tty, oldState)
var input []rune
for {
keyPress, _, err := bufio.NewReader(shell.readIn).ReadRune()
if err != nil {
return "", err
}
switch key(keyPress) {
case keyEnter:
fmt.Fprint(shell.writerOut, "\r\n")
return string(input), nil
case keyTab:
currInput := string(input)
completed := shell.completionHandler.complete(currInput)
if completed != currInput {
input = []rune(completed)
fmt.Fprintf(shell.writerOut, "\r$ %s", completed)
}
case keyBackspace:
if len(input) > 0 {
input = input[:len(input)-1]
fmt.Fprint(shell.writerOut, "\b \b")
}
case keyCtrlC:
fmt.Fprintf(shell.writerOut, "^C\r\n")
return "", nil
default:
input = append(input, keyPress)
fmt.Fprintf(shell.writerOut, "%c", keyPress)
}
}
}
// complete: Simplistic prefix-based completion for built-in commands
func (c *completionHandler) complete(input string) string {
if len(input) == 0 {
return input
}
fields := strings.Fields(input)
prefix := fields[len(fields)-1]
var matches []string
for _, completion := range c.completions {
if strings.HasPrefix(completion, prefix) {
matches = append(matches, completion)
}
}
if len(matches) == 0 {
return input
}
return matches[0]
}
Please let me know if you need anything else from me.
Any help on this would be really appreciated!! Thanks in advance!