将文件用作Stdin(Golang)时,在第二提示符下获得EOF

时间:2018-08-06 06:57:28

标签: go integration-testing

我正在尝试对类似于this way的cli应用程序进行功能测试。

当命令在命令提示符下询问一些输入时,我将它们放在文件中并将其设置为os.Stdin。

cmd := exec.Command(path.Join(dir, binaryName), "myArg")
tmpfile := setStdin("TheMasterPassword\nSecondAnswer\n12121212\n")
cmd.Stdin = tmpfile
output, err := cmd.CombinedOutput()

setStdin仅创建一个tmpFile,在文件中写入字符串并返回*os.File

现在,我希望首先输入TheMasterPassword,并且它可以正常工作。但是对于第二个输入,总是得到Critical Error: EOF

我用于询问和获取用户输入的功能:

func Ask(question string, minLen int) string {
    reader := bufio.NewReader(os.Stdin)

    for {
        fmt.Printf("%s: ", question)

        response, err := reader.ReadString('\n')
        ExitIfError(err)

        if len(response) >= minLen {
            return strings.TrimSpace(response)
        } else {
            fmt.Printf("Provide at least %d character.\n", minLen)
        }
    }
}

能帮我找出问题所在吗? 非常感谢!

根据要求添加setStdin

func setStdin(userInput string) *os.File {
    tmpfile, err := ioutil.TempFile("", "test_stdin_")
    util.ExitIfError(err)

    _, err = tmpfile.Write([]byte(userInput))
    util.ExitIfError(err)
    _, err = tmpfile.Seek(0, 0)
    util.ExitIfError(err)

    return tmpfile
}

1 个答案:

答案 0 :(得分:2)

在您的应用中,只要您想要一个单个输入行,就好像您的呼叫Ask()

Ask()内,您创建了一个bufio.Reader以从os.Stdin读取。知道bufio.Reader(顾名思义)使用缓冲读取,这意味着它从其源读取的数据可能比其方法返回的数据更多(在这种情况下为Reader.ReadString())。这意味着,如果仅使用它来读取一行(或几行),然后扔掉阅读器,那么<​​em>您将丢弃未读取的缓冲数据。

因此,下次您再次呼叫Ask()并尝试从os.Stdin进行读取时,您将不会从上次中断的地方继续...

要解决此问题,请仅从bufio.Reader创建一个os.Stdin,并将其存储在全局变量中,例如,在Ask()内,始终使用此单个读取器。因此,在Ask()调用之间不会丢失缓冲和未读的数据。当然,从多个goroutine调用该解决方案无效,但从单个os.Stdin读取也不可行。

例如:

var reader = bufio.NewReader(os.Stdin)

func Ask(question string, minLen int) string {
    // use the global reader here...
}

还要注意,在您的情况下,使用bufio.Scanner会更容易。但是同样,bufio.Scanner可能还会从源中读取更多数据,因此,您在这里也必须使用共享的bufio.Scanner。还要注意,Reader.ReadString()返回一个包含定界符(在您的情况下,以\n结尾的行)的字符串,而您可能需要修剪,而Scanner.Text()(具有默认的行拆分功能)将在返回行之前先剥离该行。这也是您可以利用的一种简化。