Skip to content

Commit

Permalink
Merge pull request gruntwork-io#1467 from gruntwork-io/thread-safe-log
Browse files Browse the repository at this point in the history
Make `Log` and `Logf` threadsafe
  • Loading branch information
james03160927 authored Nov 14, 2024
2 parents 40c8075 + 9943f43 commit 5abf9f0
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 0 deletions.
7 changes: 7 additions & 0 deletions modules/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"runtime"
"strings"
"sync"
gotesting "testing"
"time"

Expand Down Expand Up @@ -125,6 +126,8 @@ func Logf(t testing.TestingT, format string, args ...interface{}) {
tt.Helper()
}

mutexStdout.Lock()
defer mutexStdout.Unlock()
DoLog(t, 2, os.Stdout, fmt.Sprintf(format, args...))
}

Expand All @@ -136,9 +139,13 @@ func Log(t testing.TestingT, args ...interface{}) {
tt.Helper()
}

mutexStdout.Lock()
defer mutexStdout.Unlock()
DoLog(t, 2, os.Stdout, args...)
}

var mutexStdout sync.Mutex

// DoLog logs the given arguments to the given writer, along with a timestamp and information about what test and file is
// doing the logging.
func DoLog(t testing.TestingT, callDepth int, writer io.Writer, args ...interface{}) {
Expand Down
54 changes: 54 additions & 0 deletions modules/logger/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package logger
import (
"bytes"
"fmt"
"io"
"os"
"strings"
"testing"

tftesting "github.com/gruntwork-io/terratest/modules/testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDoLog(t *testing.T) {
Expand Down Expand Up @@ -52,3 +55,54 @@ func TestCustomLogger(t *testing.T) {
assert.Equal(t, "log output 2", c.logs[1])
assert.Equal(t, "subtest log", c.logs[2])
}

// TestLockedLog make sure that Log and Logf which use stdout are thread-safe
func TestLockedLog(t *testing.T) {
// should not call t.Parallel() since we are modifying os.Stdout
stdout := os.Stdout
t.Cleanup(func() {
os.Stdout = stdout
})

data := []struct {
name string
fn func(*testing.T, string)
}{
{
name: "Log",
fn: func(t *testing.T, s string) {
Log(t, s)
}},
{
name: "Logf",
fn: func(t *testing.T, s string) {
Logf(t, "%s", s)
}},
}

for _, d := range data {
mutexStdout.Lock()
str := "Logging something" + t.Name()

r, w, _ := os.Pipe()
os.Stdout = w
ch := make(chan struct{})
go func() {
d.fn(t, str)
w.Close()
close(ch)
}()

select {
case <-ch:
t.Error("Log should be locked")
default:
}

mutexStdout.Unlock()
b, err := io.ReadAll(r)
require.NoError(t, err, "log should be unlocked")
assert.Contains(t, string(b), str, "should contains logged string")
}

}

0 comments on commit 5abf9f0

Please sign in to comment.