[Go] [Stage #QP2] Builtin Command Autocompletion works Locally but fails in tests

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:
2025-03-11 10.42.39

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!

Note: This seems to be a related topic:
https://forum.codecrafters.io/t/stuck-on-stage-qp2/4294/13

However, I’m already doing the suggested solution.

Hey @dgiridharan, thanks for the detailed explanation!

The issue seems to be caused by creating a new bufio.NewReader for each character read inside the loop:

This resets the reader on every iteration, causing buffered input to be lost and preventing it from maintaining its state properly.

Try moving the creation of bufio.NewReader outside the loop to preserve the buffer. Let me know if that helps!

1 Like

Wow, thanks for the quick response @andy1li!

Move this line outside of the main loop in shell.Start() fixed the issue and now I’m passing the tests for this stage!

func (shell *Shell) Start() error {
	shell.running = true
	reader := bufio.NewReader(shell.readIn)
	for shell.running {
		// Display prompt
		if _, err := fmt.Fprint(shell.writerOut, "$ "); err != nil {
			return err
		}

		// Wait for user input
		input, err := shell.readlineWithCompletion(reader)
		if err != nil {
			return err
		}

		// Skip empty commands
		input = strings.Trim(input, "\n ")
		if input == "" {
			continue
		}

		// Parse the user input and execute the cmd
		p := parser.NewParser(input)
		cmd, err := p.Parse(shell.exeToPaths)
		if err != nil {
			fmt.Fprintf(shell.writerOut, "Error parsing input: %s - error: %s\n", input, err)
			continue
		}

		// Skip empty cmds
		if cmd == nil {
			continue
		}
		shell.execCmd(cmd)
	}

	return nil
}

However the previous stage tests still fail:

❯ codecrafters test --previous
Initiating test run...
⚡ This is a turbo test run. 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_mango_command
[your-program]                        
[tester::#CZ2] Output does not match expected value.
[tester::#CZ2] Expected: "invalid_mango_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

This is working locally:
2025-03-12 12.50.32

Any ideas?

Thank you again :slight_smile:

I’m currently looking at the test case repository to figure out how to test this locally and debug it.
Is this the test case for an invalid command?

I assume I should be able to reproduce this by running it locally, but it’s not straightforward.

I tried using fmt.Printf() statements, but they don’t seem to appear when I run the test using codecrafters test --previous. What am I missing? :thinking:

I already have debug set to true in codecrafters.yml

The issue occurs due to differences in how mac and linux handle the “Enter” key.

I added a log here:

For mac, it’s \r (13):

For linux, it’s \n (10):

Once your code handles both \r and \n, the issue should be resolved:

1 Like

Amazing, thanks @andy1li! I just finished all of the portions for the shell challenge :slight_smile:

OOC - when I tried to print out using fmt.Fprintf() or fmt.Println() etc…the logs include my print statements of codecrafters test --previous.

I’ve already set the debug: true in codecrafters.yml file. Is there anything else that I need to do?

1 Like

Setting debug: true only enables additional tester output. It shouldn’t affect your own print statements.

Let me know which specific print statements in your codebase aren’t working, and I can take a closer look.

@andy1li, I just tried a few print statements and the test is correctly displaying:
[your-program] Debug - message:…`

So, maybe I just missed it earlier :thinking: Unclear!

But I really want to thank you with all of the assistance and prompt responses! I really appreciate it :slight_smile:

2 Likes

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.