Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testutil: NewLogBuffer - buffer logs until a test fails #7948

Merged
merged 1 commit into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzRe
a := &TestACLAgent{Name: name, HCL: hcl, resolveAuthzFn: resolveAuthz, resolveIdentFn: resolveIdent}
dataDir := `data_dir = "acl-agent"`

logOutput := testutil.TestWriter(t)
logOutput := testutil.NewLogBuffer(t)
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: a.Name,
Level: hclog.Debug,
Expand Down
2 changes: 1 addition & 1 deletion agent/ae/ae_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ func (m *mock) SyncChanges() error {
func testSyncer(t *testing.T) *StateSyncer {
logger := hclog.New(&hclog.LoggerOptions{
Level: 0,
Output: testutil.TestWriter(t),
Output: testutil.NewLogBuffer(t),
})

l := NewStateSyncer(nil, time.Second, nil, logger)
Expand Down
4 changes: 2 additions & 2 deletions agent/checks/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"os"
Expand All @@ -14,7 +15,6 @@ import (
"time"

"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/go-hclog"

"github.com/armon/circbuf"
Expand Down Expand Up @@ -613,7 +613,7 @@ func (c *CheckDocker) Start() {
}

if c.Logger == nil {
c.Logger = testutil.NewDiscardLogger()
c.Logger = hclog.New(&hclog.LoggerOptions{Output: ioutil.Discard})
}

if c.Shell == "" {
Expand Down
2 changes: 1 addition & 1 deletion agent/consul/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ func newTestACLResolver(t *testing.T, delegate *ACLResolverTestDelegate, cb func
config.ACLsEnabled = delegate.enabled
rconf := &ACLResolverConfig{
Config: config,
Logger: testutil.LoggerWithName(t, t.Name()),
Logger: testutil.Logger(t),
CacheConfig: &structs.ACLCachesConfig{
Identities: 4,
Policies: 4,
Expand Down
2 changes: 1 addition & 1 deletion agent/consul/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func testClientConfig(t *testing.T) (string, *Config) {
config.SerfLANConfig.MemberlistConfig.ProbeTimeout = 200 * time.Millisecond
config.SerfLANConfig.MemberlistConfig.ProbeInterval = time.Second
config.SerfLANConfig.MemberlistConfig.GossipInterval = 100 * time.Millisecond
config.LogOutput = testutil.TestWriter(t)
config.LogOutput = testutil.NewLogBuffer(t)

return dir, config
}
Expand Down
2 changes: 1 addition & 1 deletion agent/consul/leader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1284,7 +1284,7 @@ func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) {
}()

dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.LogOutput = io.MultiWriter(pw, testutil.TestWriter(t))
c.LogOutput = io.MultiWriter(pw, testutil.NewLogBuffer(t))
c.Build = "1.6.0"
c.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceSplitterConfigEntry{
Expand Down
2 changes: 1 addition & 1 deletion agent/consul/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func testServerConfig(t *testing.T) (string, *Config) {
config.Bootstrap = true
config.Datacenter = "dc1"
config.DataDir = dir
config.LogOutput = testutil.TestWriter(t)
config.LogOutput = testutil.NewLogBuffer(t)

// bind the rpc server to a random port. config.RPCAdvertise will be
// set to the listen address unless it was set in the configuration.
Expand Down
3 changes: 2 additions & 1 deletion agent/testagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"time"

metrics "github.com/armon/go-metrics"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-hclog"
uuid "github.com/hashicorp/go-uuid"
Expand Down Expand Up @@ -186,7 +187,7 @@ func (a *TestAgent) Start(t *testing.T) (err error) {

logOutput := a.LogOutput
if logOutput == nil {
logOutput = os.Stderr
logOutput = testutil.NewLogBuffer(t)
}

logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
Expand Down
2 changes: 1 addition & 1 deletion api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func makeClientWithConfig(
retry.RunWith(retry.ThreeTimes(), t, func(r *retry.R) {
server, err = testutil.NewTestServerConfigT(t, cb2)
if err != nil {
r.Fatal(err)
r.Fatalf("Failed to start server: %v", err.Error())
}
})
if server.Config.Bootstrap {
Expand Down
2 changes: 1 addition & 1 deletion sdk/testutil/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

func TestFoo_bar(t *testing.T) {
// Create a test Consul server
srv1, err := testutil.NewTestServerT(t)
srv1, err := testutil.NewTestServerConfigT(t, nil)
if err != nil {
t.Fatal(err)
}
Expand Down
59 changes: 13 additions & 46 deletions sdk/testutil/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ type TestServerConfig struct {
Connect map[string]interface{} `json:"connect,omitempty"`
EnableDebug bool `json:"enable_debug,omitempty"`
ReadyTimeout time.Duration `json:"-"`
Stdout, Stderr io.Writer `json:"-"`
Stdout io.Writer `json:"-"`
Stderr io.Writer `json:"-"`
Args []string `json:"-"`
ReturnPorts func() `json:"-"`
}
Expand Down Expand Up @@ -132,13 +133,14 @@ type ServerConfigCallback func(c *TestServerConfig)

// defaultServerConfig returns a new TestServerConfig struct
// with all of the listen ports incremented by one.
func defaultServerConfig() *TestServerConfig {
func defaultServerConfig(t CleanupT) *TestServerConfig {
nodeID, err := uuid.GenerateUUID()
if err != nil {
panic(err)
}

ports := freeport.MustTake(6)
logBuffer := NewLogBuffer(t)

return &TestServerConfig{
NodeName: "node-" + nodeID,
Expand Down Expand Up @@ -171,6 +173,8 @@ func defaultServerConfig() *TestServerConfig {
ReturnPorts: func() {
freeport.Return(ports)
},
Stdout: logBuffer,
Stderr: logBuffer,
}
}

Expand Down Expand Up @@ -211,34 +215,11 @@ type TestServer struct {
tmpdir string
}

// Deprecated: Use NewTestServerT instead.
func NewTestServer() (*TestServer, error) {
return NewTestServerConfigT(nil, nil)
}

// NewTestServerT is an easy helper method to create a new Consul
// test server with the most basic configuration.
func NewTestServerT(t *testing.T) (*TestServer, error) {
if t == nil {
return nil, errors.New("testutil: a non-nil *testing.T is required")
}
return NewTestServerConfigT(t, nil)
}

func NewTestServerConfig(cb ServerConfigCallback) (*TestServer, error) {
return NewTestServerConfigT(nil, cb)
}

// NewTestServerConfig creates a new TestServer, and makes a call to an optional
// callback function to modify the configuration. If there is an error
// configuring or starting the server, the server will NOT be running when the
// function returns (thus you do not need to stop it).
func NewTestServerConfigT(t testing.TB, cb ServerConfigCallback) (*TestServer, error) {
return newTestServerConfigT(t, cb)
}

// newTestServerConfigT is the internal helper for NewTestServerConfigT.
func newTestServerConfigT(t testing.TB, cb ServerConfigCallback) (*TestServer, error) {
path, err := exec.LookPath("consul")
if err != nil || path == "" {
return nil, fmt.Errorf("consul not found on $PATH - download and install " +
Expand All @@ -255,11 +236,7 @@ func newTestServerConfigT(t testing.TB, cb ServerConfigCallback) (*TestServer, e
return nil, errors.Wrap(err, "failed to create tempdir")
}

cfg := defaultServerConfig()
testWriter := TestWriter(t)
cfg.Stdout = testWriter
cfg.Stderr = testWriter

cfg := defaultServerConfig(t)
cfg.DataDir = filepath.Join(tmpdir, "data")
if cb != nil {
cb(cfg)
Expand All @@ -272,32 +249,20 @@ func newTestServerConfigT(t testing.TB, cb ServerConfigCallback) (*TestServer, e
return nil, errors.Wrap(err, "failed marshaling json")
}

if t != nil {
// if you really want this output ensure to pass a valid t
t.Logf("CONFIG JSON: %s", string(b))
}
t.Logf("CONFIG JSON: %s", string(b))
configFile := filepath.Join(tmpdir, "config.json")
if err := ioutil.WriteFile(configFile, b, 0644); err != nil {
cfg.ReturnPorts()
os.RemoveAll(tmpdir)
return nil, errors.Wrap(err, "failed writing config content")
}

stdout := testWriter
if cfg.Stdout != nil {
stdout = cfg.Stdout
}
stderr := testWriter
if cfg.Stderr != nil {
stderr = cfg.Stderr
}

// Start the server
args := []string{"agent", "-config-file", configFile}
args = append(args, cfg.Args...)
cmd := exec.Command("consul", args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Stdout = cfg.Stdout
cmd.Stderr = cfg.Stderr
if err := cmd.Start(); err != nil {
cfg.ReturnPorts()
os.RemoveAll(tmpdir)
Expand Down Expand Up @@ -331,7 +296,9 @@ func newTestServerConfigT(t testing.TB, cb ServerConfigCallback) (*TestServer, e

// Wait for the server to be ready
if err := server.waitForAPI(); err != nil {
server.Stop()
if err := server.Stop(); err != nil {
t.Logf("server stop failed with: %v", err)
}
return nil, err
}

Expand Down
91 changes: 32 additions & 59 deletions sdk/testutil/testlog.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
package testutil

import (
"fmt"
"bytes"
"io"
"io/ioutil"
"log"
"os"
"strings"
"sync"
"testing"

"github.com/hashicorp/go-hclog"
)

var sendTestLogsToStdout bool

func init() {
sendTestLogsToStdout = os.Getenv("NOLOGBUFFER") == "1"
}

// Deprecated: use Logger(t)
func TestLogger(t testing.TB) *log.Logger {
return log.New(&testWriter{t}, t.Name()+": ", log.LstdFlags)
}

func NewDiscardLogger() hclog.Logger {
return hclog.New(&hclog.LoggerOptions{
Level: 0,
Output: ioutil.Discard,
})
}

func Logger(t testing.TB) hclog.InterceptLogger {
return LoggerWithOutput(t, &testWriter{t})
return LoggerWithOutput(t, NewLogBuffer(t))
}

func LoggerWithOutput(t testing.TB, output io.Writer) hclog.InterceptLogger {
Expand All @@ -42,48 +22,41 @@ func LoggerWithOutput(t testing.TB, output io.Writer) hclog.InterceptLogger {
})
}

// Deprecated: use LoggerWithName(t)
func TestLoggerWithName(t testing.TB, name string) *log.Logger {
return log.New(&testWriter{t}, "test["+name+"]: ", log.LstdFlags)
}
var sendTestLogsToStdout = os.Getenv("NOLOGBUFFER") == "1"

func LoggerWithName(t testing.TB, name string) hclog.InterceptLogger {
return hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: "test[" + name + "]",
Level: hclog.Debug,
Output: &testWriter{t},
// NewLogBuffer returns an io.Writer which buffers all writes. When the test
// ends, t.Failed is checked. If the test has failed all log output is printed
// to stdout.
//
// Set the env var NOLOGBUFFER=1 to disable buffering, resulting in all log
// output being written immediately to stdout.
func NewLogBuffer(t CleanupT) io.Writer {
if sendTestLogsToStdout {
return os.Stdout
}
buf := &logBuffer{buf: new(bytes.Buffer)}
t.Cleanup(func() {
if t.Failed() {
buf.Lock()
defer buf.Unlock()
buf.buf.WriteTo(os.Stdout)
}
})
return buf
}

func TestWriter(t testing.TB) io.Writer {
return &testWriter{t}
type CleanupT interface {
Cleanup(f func())
Failed() bool
}

type testWriter struct {
t testing.TB
type logBuffer struct {
buf *bytes.Buffer
sync.Mutex
}

func (tw *testWriter) Write(p []byte) (n int, err error) {
if tw.t != nil {
tw.t.Helper()
}
if sendTestLogsToStdout || tw.t == nil {
fmt.Fprint(os.Stdout, strings.TrimSpace(string(p))+"\n")
} else {
defer func() {
if r := recover(); r != nil {
if sr, ok := r.(string); ok {
if strings.HasPrefix(sr, "Log in goroutine after ") {
// These sorts of panics are undesirable, but requires
// total control over goroutine lifetimes to correct.
fmt.Fprint(os.Stdout, "SUPPRESSED PANIC: "+sr+"\n")
return
}
}
panic(r)
}
}()
tw.t.Log(strings.TrimSpace(string(p)))
}
return len(p), nil
func (lb *logBuffer) Write(p []byte) (n int, err error) {
lb.Lock()
defer lb.Unlock()
return lb.buf.Write(p)
}