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

Time:04-17

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).

  •  Tags:  
  • go
  • Related