Skip to content

Commit

Permalink
Refactor code to make it simpler to use
Browse files Browse the repository at this point in the history
Signed-off-by: Ramon Rüttimann <me@ramonr.ch>
  • Loading branch information
tommyknows committed May 15, 2020
1 parent f12854e commit 4f4eba4
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 148 deletions.
8 changes: 4 additions & 4 deletions modules/docker/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type BuildOptions struct {
// solely focus on the most important ones.
OtherOptions []string

// Set one or more loggers that should be used. See the logger package for more info.
Log *logger.Loggers
// Set a logger that should be used. See the logger package for more info.
Logger *logger.Logger
}

// Build runs the 'docker build' command at the given path with the given options and fails the test if there are any
Expand All @@ -32,7 +32,7 @@ func Build(t testing.TestingT, path string, options *BuildOptions) {

// BuildE runs the 'docker build' command at the given path with the given options and returns any errors.
func BuildE(t testing.TestingT, path string, options *BuildOptions) error {
options.Log.Logf(t, "Running 'docker build' in %s", path)
options.Logger.Logf(t, "Running 'docker build' in %s", path)

args, err := formatDockerBuildArgs(path, options)
if err != nil {
Expand All @@ -42,7 +42,7 @@ func BuildE(t testing.TestingT, path string, options *BuildOptions) error {
cmd := shell.Command{
Command: "docker",
Args: args,
Log: options.Log,
Logger: options.Logger,
}

_, buildErr := shell.RunCommandAndGetOutputE(t, cmd)
Expand Down
6 changes: 3 additions & 3 deletions modules/docker/docker_compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
type Options struct {
WorkingDir string
EnvVars map[string]string
// Set one or more loggers that should be used. See the logger package for more info.
Log *logger.Loggers
// Set a logger that should be used. See the logger package for more info.
Logger *logger.Logger
}

// RunDockerCompose runs docker-compose with the given arguments and options and return stdout/stderr.
Expand All @@ -32,7 +32,7 @@ func RunDockerComposeE(t testing.TestingT, options *Options, args ...string) (st
Args: append([]string{"--project-name", t.Name()}, args...),
WorkingDir: options.WorkingDir,
Env: options.EnvVars,
Log: options.Log,
Logger: options.Logger,
}

return shell.RunCommandAndGetOutputE(t, cmd)
Expand Down
2 changes: 1 addition & 1 deletion modules/docker/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func InspectE(t *testing.T, id string) (*ContainerInspect, error) {
Command: "docker",
Args: []string{"container", "inspect", id},
// inspect is a short-running command, don't print the output.
Log: logger.With(logger.Discard),
Logger: logger.Discard,
}

out, err := shell.RunCommandAndGetStdOutE(t, cmd)
Expand Down
12 changes: 6 additions & 6 deletions modules/docker/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ type RunOptions struct {
// solely focus on the most important ones.
OtherOptions []string

// Set one or more loggers that should be used. See the logger package for more info.
Log *logger.Loggers
// Set a logger that should be used. See the logger package for more info.
Logger *logger.Logger
}

// Run runs the 'docker run' command on the given image with the given options and return stdout/stderr. This method
Expand All @@ -62,7 +62,7 @@ func Run(t testing.TestingT, image string, options *RunOptions) string {

// RunE runs the 'docker run' command on the given image with the given options and return stdout/stderr, or any error.
func RunE(t testing.TestingT, image string, options *RunOptions) (string, error) {
options.Log.Logf(t, "Running 'docker run' on image '%s'", image)
options.Logger.Logf(t, "Running 'docker run' on image '%s'", image)

args, err := formatDockerRunArgs(image, options)
if err != nil {
Expand All @@ -72,7 +72,7 @@ func RunE(t testing.TestingT, image string, options *RunOptions) (string, error)
cmd := shell.Command{
Command: "docker",
Args: args,
Log: options.Log,
Logger: options.Logger,
}

return shell.RunCommandAndGetOutputE(t, cmd)
Expand All @@ -89,7 +89,7 @@ func RunAndGetID(t testing.TestingT, image string, options *RunOptions) string {
// RunAndGetIDE runs the 'docker run' command on the given image with the given options and returns the container ID
// that is returned in stdout, or any error.
func RunAndGetIDE(t testing.TestingT, image string, options *RunOptions) (string, error) {
options.Log.Logf(t, "Running 'docker run' on image '%s', returning stdout", image)
options.Logger.Logf(t, "Running 'docker run' on image '%s', returning stdout", image)

args, err := formatDockerRunArgs(image, options)
if err != nil {
Expand All @@ -99,7 +99,7 @@ func RunAndGetIDE(t testing.TestingT, image string, options *RunOptions) (string
cmd := shell.Command{
Command: "docker",
Args: args,
Log: options.Log,
Logger: options.Logger,
}

return shell.RunCommandAndGetStdOutE(t, cmd)
Expand Down
8 changes: 4 additions & 4 deletions modules/docker/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ type StopOptions struct {
// Seconds to wait for stop before killing the container (default 10)
Time int

// Set one or more loggers that should be used. See the logger package for more info.
Log *logger.Loggers
// Set a logger that should be used. See the logger package for more info.
Logger *logger.Logger
}

// Stop runs the 'docker stop' command for the given containers and return the stdout/stderr. This method fails
Expand All @@ -28,7 +28,7 @@ func Stop(t testing.TestingT, containers []string, options *StopOptions) string

// StopE runs the 'docker stop' command for the given containers and returns any errors.
func StopE(t testing.TestingT, containers []string, options *StopOptions) (string, error) {
options.Log.Logf(t, "Running 'docker stop' on containers '%s'", containers)
options.Logger.Logf(t, "Running 'docker stop' on containers '%s'", containers)

args, err := formatDockerStopArgs(containers, options)
if err != nil {
Expand All @@ -38,7 +38,7 @@ func StopE(t testing.TestingT, containers []string, options *StopOptions) (strin
cmd := shell.Command{
Command: "docker",
Args: args,
Log: options.Log,
Logger: options.Logger,
}

return shell.RunCommandAndGetOutputE(t, cmd)
Expand Down
2 changes: 1 addition & 1 deletion modules/helm/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func RunHelmCommandAndGetOutputE(t testing.TestingT, options *Options, cmd strin
Args: args,
WorkingDir: ".",
Env: options.EnvVars,
Log: options.Log,
Logger: options.Logger,
}
return shell.RunCommandAndGetOutputE(t, helmCmd)
}
2 changes: 1 addition & 1 deletion modules/helm/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ type Options struct {
HomePath string // The path to the helm home to use when calling out to helm. Empty string means use default ($HOME/.helm).
EnvVars map[string]string // Environment variables to set when running helm
Version string // Version of chart
Log *logger.Loggers // Set one or more loggers that should be used. See the logger package for more info.
Logger *logger.Logger // Set a non-default logger that should be used. See the logger package for more info.
}
160 changes: 82 additions & 78 deletions modules/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,60 +6,83 @@ import (
"io"
"os"
"runtime"
"strconv"
"strings"
gotesting "testing"
"time"

"github.com/gruntwork-io/terratest/modules/testing"
)

// defaultLogf will be used if the caller uses the function
// Logf, where on the first call to that function, a sane
// default logging function will be set. We keep that in a
// global variable so that we don't need to define it on
// every call to Logf.
var defaultLogf LogFunc
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 Loggers []LogFunc
type LogFunc func(t testing.TestingT, format string, args ...interface{})
type TestLogger interface {
Logf(t testing.TestingT, format string, args ...interface{})
}

func With(l ...LogFunc) *Loggers {
lo := Loggers(l)
return &lo
type Logger struct {
l TestLogger
}

// Logf logs to all given loggers. If no loggers are given (or it is nil), it will
// use the default logger. This allows for the following usecases:
// var l *Loggers
// l.Logf(...)
// l = With(TestingT)
// l.Logf(...)
func (l *Loggers) Logf(t testing.TestingT, format string, args ...interface{}) {
if tt, ok := t.(*gotesting.T); ok {
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()
}

// if l is not initialised or no loggers
// are supplied, use the default logging.
if l == nil || len(*l) == 0 {
logDefaultLogf(t, format, args...)
// 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
}

for _, logf := range *l {
logf(t, format, args...)
}
l.l.Logf(t, format, args...)
}

// Discard discards all logging.
func Discard(_ testing.TestingT, format string, args ...interface{}) {}
// 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()
}

// TestingT can be used to explicitly use Go's testing.T to log.
// It is also used as the default if Go version >= 1.14 (if detected
// correctly). If this is used, but no testing.T is provided, it will
// fallback to Logger.
func TestingT(t testing.TestingT, format string, args ...interface{}) {
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 {
Expand All @@ -73,64 +96,45 @@ func TestingT(t testing.TestingT, format string, args ...interface{}) {
return
}

// Logger is the conventional logging utility that terratest uses.
// Default up until Go 1.14.
func Logger(t testing.TestingT, format string, args ...interface{}) {
type terratestLogger struct{}

func (_ terratestLogger) Logf(t testing.TestingT, format string, args ...interface{}) {
DoLog(t, 3, os.Stdout, fmt.Sprintf(format, args...))
}

// Logf logs the given format and arguments with the default logging utility. If the Go
// version can be determined and is 1.14 or above, t.Logf will be used (Go 1.14 introduced
// streaming log output). Else, a default logger will be used that adds a timestamp and
// information about what test and file is doing the logging.
// Compared to the Go's builtin testing.T.Logf, this will always print the output instead
// of buffering it and only display it at the very end of the test.
// 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.
//
// To have more control over logging, use With(...LogFunc) to get a custom logger.
// Builtin alternatives are Discard, TestingT and Logger.
// 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.(*gotesting.T); ok {
if tt, ok := t.(helper); ok {
tt.Helper()
}

logDefaultLogf(t, format, args...)
}

func logDefaultLogf(t testing.TestingT, format string, args ...interface{}) {
if defaultLogf == nil {
// if a gotesting.T is given and the go version is 1.14, use
// gotesting.T.Logf
if tt, ok := t.(*gotesting.T); ok && hasStreamingLogf(runtime.Version()) {
tt.Helper()
// we should not assign tt.Logf directly as testing.T may change
// during the execution of the test (consider subtests, for example).
defaultLogf = TestingT
} else {
defaultLogf = Logger
defaultLogf(t, "streaming logf not supported, falling back to legacy logger")
}
}

defaultLogf(t, format, args...)
}

// hasStreamingLogf returns true if the go runtime version
// is >= Go 1.14, where streaming Logf output has been
// introduced (https://github.com/golang/go/issues/24929)
func hasStreamingLogf(goVersion string) bool {
noMajor := strings.TrimPrefix(goVersion, "go1.")
ver, err := strconv.ParseFloat(noMajor, 32)
if err != nil {
return false
}

return ver >= 14
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()
}

DoLog(t, 2, os.Stdout, args...)
}

Expand Down
Loading

0 comments on commit 4f4eba4

Please sign in to comment.