Interactive input and "go test"

853 views
Skip to first unread message

Amit Saha

unread,
Nov 5, 2021, 7:27:38 AM11/5/21
to golang-nuts
I have this test function:

package main

import (
        "bufio"
        "fmt"
        "os"
        "testing"
)

func TestInput(t *testing.T) {
        scanner := bufio.NewScanner(os.Stdin)
        msg := "Your name please? Press the Enter key when done"
        fmt.Fprintln(os.Stdout, msg)

        scanner.Scan()
        if err := scanner.Err(); err != nil {
                t.Fatal(err)
        }
        name := scanner.Text()
        if len(name) == 0 {
                t.Log("empty input")
        }
        t.Log(name)

}

When i run it via go test -v, this is what i get (TLDR; terminates without waiting for the interactive input):

% go test -v

=== RUN   TestInput

Your name please? Press the Enter key when done

    stdin_test.go:21: empty input

    stdin_test.go:23: 

--- PASS: TestInput (0.00s)

PASS

ok  test 0.370s


However, when i compile the test and then run the binary, it waits for me to enter the input:


% go test -c

% ./test.test 

Your name please? Press the Enter key when done


The latter behavior is more inline with what i was expecting in the first case as well. 

I thought may be it has something to do with the fact that go test is executing the binary (i think) after compiling, and started looking at: https://github.com/golang/go/blob/c7f2f51fed15b410dea5f608420858b401887d0a/src/cmd/go/internal/test/test.go , but can't see anything obvious.

Wondering if anyone of you folks have an answer?


Thanks,

Amit.



Amit Saha

unread,
Nov 5, 2021, 7:48:37 AM11/5/21
to golang-nuts
Did a bit more of experimentation, and it seems like, that’s just how exec.Command(“mycmd").Run() works.

I tried with exec.Command("wc").Run() and it returns immediately as well. (Running the Unix “word count” program).

I suppose I am surprised since in another language I am familiar with (Python), a command waiting for an interactive input when executed via os/exec would “hang”, for example:

% python3
Python 3.8.2 (default, Apr 8 2021, 23:19:18)
[Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import subprocess
>>> subprocess.check_output(["wc”])

This will hang

>
>
> Thanks,
>
> Amit.
>
>
>
>
>
> --
> You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/24pL7iQbx64/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/0f48fc8d-7e21-46a6-b736-90ea2f0bcbe6n%40googlegroups.com.

Axel Wagner

unread,
Nov 5, 2021, 8:09:31 AM11/5/21
to Amit Saha, golang-nuts
First, to point out the obvious: It is a bad idea to have a test read from stdin. You should pass a separate io.Reader and use that.

Next: exec.Cmd.{Stdin,Stdout,Stderr} are all set to os.DevNull by default, meaning you don't get any input or output. I assume

- `go test` does not modify Cmd.Stdin (leaving it at os.DevNull) of the sub-process it launches and redirects Cmd.{Stdout,Stderr} to buffers.
- Your test (if you use `exec.Command(…).Run()`) also does not modify any of them, so they stay at os.DevNull
- Other languages have them default to stdin/stdout/stderr of the parent process, so they try to read from stdin, which is a line-buffered terminal, so it blocks until you input a line.

Again, the solution here should be to not rely on os.Stdin at all, but instead test a function which uses a general io.Reader.

You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/25A4D3EA-F228-447C-A19E-8BE062840E20%40gmail.com.

Amit Saha

unread,
Nov 5, 2021, 8:25:48 AM11/5/21
to Axel Wagner, golang-nuts


On Fri, 5 Nov 2021, 11:08 pm Axel Wagner, <axel.wa...@googlemail.com> wrote:
First, to point out the obvious: It is a bad idea to have a test read from stdin. You should pass a separate io.Reader and use that.

Next: exec.Cmd.{Stdin,Stdout,Stderr} are all set to os.DevNull by default, meaning you don't get any input or output. I assume

- `go test` does not modify Cmd.Stdin (leaving it at os.DevNull) of the sub-process it launches and redirects Cmd.{Stdout,Stderr} to buffers.
- Your test (if you use `exec.Command(…).Run()`) also does not modify any of them, so they stay at os.DevNull
- Other languages have them default to stdin/stdout/stderr of the parent process, so they try to read from stdin, which is a line-buffered terminal, so it blocks until you input a line.

Again, the solution here should be to not rely on os.Stdin at all, but instead test a function which uses a general io.Reader.

Great thank you for clarifying that. 

I ran into this as I was writing a test for an interactive input function and I expected the test to hang but it didn't. So bit of an accidental find and glad I found this.

Amit Saha

unread,
Nov 5, 2021, 8:44:37 PM11/5/21
to golang-nuts
On Fri, Nov 5, 2021 at 11:24 PM Amit Saha <amits...@gmail.com> wrote:
>
>
>
> On Fri, 5 Nov 2021, 11:08 pm Axel Wagner, <axel.wa...@googlemail.com> wrote:
>>
>> First, to point out the obvious: It is a bad idea to have a test read from stdin. You should pass a separate io.Reader and use that.
>>
>> Next: exec.Cmd.{Stdin,Stdout,Stderr} are all set to os.DevNull by default, meaning you don't get any input or output. I assume
>>
>> - `go test` does not modify Cmd.Stdin (leaving it at os.DevNull) of the sub-process it launches and redirects Cmd.{Stdout,Stderr} to buffers.
>> - Your test (if you use `exec.Command(…).Run()`) also does not modify any of them, so they stay at os.DevNull
>> - Other languages have them default to stdin/stdout/stderr of the parent process, so they try to read from stdin, which is a line-buffered terminal, so it blocks until you input a line.
>>
>> Again, the solution here should be to not rely on os.Stdin at all, but instead test a function which uses a general io.Reader.
>
>
> Great thank you for clarifying that.
>
> I ran into this as I was writing a test for an interactive input function and I expected the test to hang but it didn't. So bit of an accidental find and glad I found this.

I posted this as a blog post: https://echorand.me/posts/go-test-stdin/
- in case someone else finds it useful.

Thanks again for your help, Axel.

Best Regards,
Amit.
Reply all
Reply to author
Forward
0 new messages