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?
CodePudding user response:
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
CodePudding user response:
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).