如何等到特定行打印到 os.Stderr?

How to wait until a specific line is printed to os.Stderr?

我正在 运行 设置一个 Goroutine,在延迟一段时间后,它会将特定行记录到 os.Stderr。我想等到记录该行。到目前为止,我尝试过的是

package main

import (
    "bufio"
    "log"
    "os"
    "strings"
    "time"
)

func main() {
    go func() {
        time.Sleep(time.Second)
        log.Println("Hello, world!")
    }()

    scanner := bufio.NewScanner(os.Stderr)
    for scanner.Scan() {
        if strings.Contains(scanner.Text(), "Hello, world!") {
            break
        }
    }
}

但是,如果我 运行 这个,它只会阻塞:

> go run main.go
2022/04/17 00:31:43 Hello, world!

这个扫描器不应该捕获标准错误输出并命中 break 语句吗?

似乎如果 io.Readeros.Stderrscanner.Scan() 会无限期地阻塞,而如果我将它设置为自定义 reader 就像 *bytes.Buffer ,它 returns 在 Goroutine 有机会写入它之前。

由于这个逻辑的上下文是一个单元测试,我通过使用 assert.Eventually:

解决了这个问题
func TestScan(t *testing.T) {
    buf := bytes.NewBuffer([]byte{})
    log.SetOutput(buf)

    go func() {
        time.Sleep(time.Second)
        log.Println("Hello, world!")
    }()

    assert.Eventually(t, func() bool {
        scanner := bufio.NewScanner(buf)
        for scanner.Scan() {
            if strings.Contains(scanner.Text(), "Hello, world!") {
                return true
            }
        }
        return false
    }, 10*time.Second, 100*time.Millisecond)
}

此测试按预期在 1.1 秒内通过:

> go test ./... -v
=== RUN   TestScan
--- PASS: TestScan (1.10s)
PASS

(当然,这不是最有效的解决方案,因为它可能每次都读取整个输出,但它应该完成这项工作)。

如果你想拦截输出,你可以使用管道,像这样:

r, w, _ := os.Pipe()

log.SetOutput(w)

go func() {
    time.Sleep(time.Second)
    log.Println("Hello, world!")
}()

scanner := bufio.NewScanner(r)

https://go.dev/play/p/HdEs5tbDYDE