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

huangapple go评论110阅读模式
英文:

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 语句吗?

英文:

I'm running a Goroutine which, after some delay, logs a specific line to os.Stderr. I'd like to wait until that line is logged. So far, what I've tried is

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
		}
	}
}

However, if I run this, it just blocks:

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

Should this scanner not capture the standard error output and hit the break statement?

答案1

得分: 2

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

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

英文:

If you want to intercept output, you can use a pipe, like so:

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

答案2

得分: 0

似乎当io.Readeros.Stderr时,scanner.Scan()会无限期地阻塞,而如果我将其设置为自定义的读取器,比如*bytes.Buffer,它会在 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

(尽管如此,这并不是最高效的解决方案,因为它可能每次都会读取整个输出,但它应该能够完成工作)。

英文:

It seems that if the io.Reader is os.Stderr, scanner.Scan() blocks indefinitely, whereas if I set it to a custom reader like a *bytes.Buffer, it returns immediately before the Goroutine has a chance to write to it.

Since the context of this logic is a unit test, I worked around this by using 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)
}

This test passes in 1.1 seconds as expected:

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

(Granted, this is not the most efficient solution as it presumably reads the entire output each time, but it should do the job).

huangapple
  • 本文由 发表于 2022年4月17日 15:32:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/71900083.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定