Merged in feature/testingopts (pull request #129)
Testing Options and Clean Up * bitsandbobs * lint * healthchecks * imgintests * singlegenerate * testclean * network * networkclean * cmds
This commit is contained in:
+178
@@ -0,0 +1,178 @@
|
||||
// Package logger contains different methods to log.
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
gotesting "testing"
|
||||
"time"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
// Default is the default logger that is used for the Logf function, if no one is provided. It uses the
|
||||
// TerratestLogger to log messages. This can be overwritten to change the logging globally.
|
||||
Default = New(terratestLogger{})
|
||||
// Discard discards all logging.
|
||||
Discard = New(discardLogger{})
|
||||
// Terratest logs the given format and arguments, formatted using fmt.Sprintf, to stdout, along with a timestamp and
|
||||
// information about what test and file is doing the logging. Before Go 1.14, this is an alternative to t.Logf as it
|
||||
// logs to stdout immediately, rather than buffering all log output and only displaying it at the very end of the test.
|
||||
// This is useful because:
|
||||
//
|
||||
// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
|
||||
// right away, rather than at the very end of the test run.
|
||||
//
|
||||
// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
|
||||
// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
|
||||
// output available.
|
||||
//
|
||||
// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
|
||||
// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
|
||||
// this log method, you get log output continuously.
|
||||
//
|
||||
Terratest = New(terratestLogger{})
|
||||
// TestingT can be used to use Go's testing.T to log. If this is used, but no testing.T is provided, it will fallback
|
||||
// to Default.
|
||||
TestingT = New(testingT{})
|
||||
)
|
||||
|
||||
type TestLogger interface {
|
||||
Logf(t testing.TestingT, format string, args ...interface{})
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
l TestLogger
|
||||
}
|
||||
|
||||
func New(l TestLogger) *Logger {
|
||||
return &Logger{
|
||||
l,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
if tt, ok := t.(helper); ok {
|
||||
tt.Helper()
|
||||
}
|
||||
|
||||
// methods can be called on (typed) nil pointers. In this case, use the Default function to log. This enables the
|
||||
// caller to do `var l *Logger` and then use the logger already.
|
||||
if l == nil || l.l == nil {
|
||||
Default.Logf(t, format, args...)
|
||||
return
|
||||
}
|
||||
|
||||
l.l.Logf(t, format, args...)
|
||||
}
|
||||
|
||||
// helper is used to mark this library as a "helper", and thus not appearing in the line numbers. testing.T implements
|
||||
// this interface, for example.
|
||||
type helper interface {
|
||||
Helper()
|
||||
}
|
||||
|
||||
type discardLogger struct{}
|
||||
|
||||
func (_ discardLogger) Logf(_ testing.TestingT, format string, args ...interface{}) {}
|
||||
|
||||
type testingT struct{}
|
||||
|
||||
func (_ testingT) Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
// this should never fail
|
||||
tt, ok := t.(*gotesting.T)
|
||||
if !ok {
|
||||
// fallback
|
||||
DoLog(t, 2, os.Stdout, fmt.Sprintf(format, args...))
|
||||
return
|
||||
}
|
||||
|
||||
tt.Helper()
|
||||
tt.Logf(format, args...)
|
||||
return
|
||||
}
|
||||
|
||||
type terratestLogger struct{}
|
||||
|
||||
func (_ terratestLogger) Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
DoLog(t, 3, os.Stdout, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Deprecated: use Logger instead, as it provides more flexibility on logging.
|
||||
// Logf logs the given format and arguments, formatted using fmt.Sprintf, to stdout, along with a timestamp and information
|
||||
// about what test and file is doing the logging. Before Go 1.14, this is an alternative to t.Logf as it logs to stdout
|
||||
// immediately, rather than buffering all log output and only displaying it at the very end of the test. This is useful
|
||||
// because:
|
||||
//
|
||||
// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
|
||||
// right away, rather than at the very end of the test run.
|
||||
//
|
||||
// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
|
||||
// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
|
||||
// output available.
|
||||
//
|
||||
// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
|
||||
// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
|
||||
// this log method, you get log output continuously.
|
||||
//
|
||||
// Although t.Logf now supports streaming output since Go 1.14, this is kept for compatibility purposes.
|
||||
func Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
if tt, ok := t.(helper); ok {
|
||||
tt.Helper()
|
||||
}
|
||||
|
||||
mutexStdout.Lock()
|
||||
defer mutexStdout.Unlock()
|
||||
DoLog(t, 2, os.Stdout, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Log logs the given arguments to stdout, along with a timestamp and information about what test and file is doing the
|
||||
// logging. This is an alternative to t.Logf that logs to stdout immediately, rather than buffering all log output and
|
||||
// only displaying it at the very end of the test. See the Logf method for more info.
|
||||
func Log(t testing.TestingT, args ...interface{}) {
|
||||
if tt, ok := t.(helper); ok {
|
||||
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{}) {
|
||||
date := time.Now()
|
||||
prefix := fmt.Sprintf("%s %s %s:", t.Name(), date.Format(time.RFC3339), CallerPrefix(callDepth+1))
|
||||
allArgs := append([]interface{}{prefix}, args...)
|
||||
fmt.Fprintln(writer, allArgs...)
|
||||
}
|
||||
|
||||
// CallerPrefix returns the file and line number information about the methods that called this method, based on the current
|
||||
// goroutine's stack. The argument callDepth is the number of stack frames to ascend, with 0 identifying the method
|
||||
// that called CallerPrefix, 1 identifying the method that called that method, and so on.
|
||||
//
|
||||
// This code is adapted from testing.go, where it is in a private method called decorate.
|
||||
func CallerPrefix(callDepth int) string {
|
||||
_, file, line, ok := runtime.Caller(callDepth)
|
||||
if ok {
|
||||
// Truncate file name at last file name separator.
|
||||
if index := strings.LastIndex(file, "/"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
}
|
||||
} else {
|
||||
file = "???"
|
||||
line = 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
Reference in New Issue
Block a user