voicera/tester

View on GitHub
assert/assert.go

Summary

Maintainability
A
0 mins
Test Coverage
package assert

import (
    "fmt"
    "os"
    "reflect"
    "runtime"
    "strings"
    "sync"
    "testing"
    "time"

    "github.com/kr/pretty"
)

// TestContext provides methods to assert what the test actually got.
type TestContext interface {
    // ThatCalling adapts the specified call to an assertable one that's
    // expected to meet certain criteria.
    ThatCalling(func()) AssertableCall

    // ThatActual adapts the specified value to an assertable one that's
    // expected to meet certain criteria.
    ThatActual(value interface{}) AssertableValue

    // ThatActualError adapts the specified error to an assertable one that's
    // expected to meet certain criteria.
    ThatActualError(value error) AssertableError

    // ThatActualString adapts the specified string to an assertable one that's
    // expected to meet certain criteria.
    ThatActualString(value string) AssertableString

    // ThatActualTime adapts the specified time to an assertable one that's
    // expected to meet certain criteria.
    ThatActualTime(value *time.Time) AssertableTime

    // ThatType adapts the specified type to an assertable one that's
    // expected to meet certain criteria.
    ThatType(t reflect.Type) AssertableType
}

// testContext decorates and extends testing.TB that's passed to test functions
// to manage test state and support formatted test logs.
type testContext struct {
    testing.TB
    parameters []interface{}
    caller     func() (string, int) `test-hook:"verify-unexported"`
    fail       func()               `test-hook:"verify-unexported"`
}

const (
    testFileNameSuffix     = "_test.go"
    noCallerInfoLineNumber = -1
)

var printLock sync.Locker = &sync.Mutex{} // ensures that output is serialized

// For adapts from testing.TB to TestContext in order to allow
// the latter to assert on behalf of the former.
// The optional parameter(s) can be used to identify a specific test case
// in a data-driven test.
func For(t testing.TB, parameters ...interface{}) TestContext {
    return &testContext{t, parameters, caller, t.Fail}
}

func (testContext *testContext) ThatCalling(call func()) AssertableCall {
    return &assertableCall{testContext: testContext, call: call}
}

func (testContext *testContext) ThatActual(value interface{}) AssertableValue {
    return &assertableValue{testContext: testContext, value: value}
}

func (testContext *testContext) ThatActualError(value error) AssertableError {
    return &assertableError{testContext: testContext, value: value}
}

func (testContext *testContext) ThatActualString(value string) AssertableString {
    return &assertableString{testContext: testContext, value: value}
}

func (testContext *testContext) ThatActualTime(value *time.Time) AssertableTime {
    return &assertableTime{testContext: testContext, value: value}
}

func (testContext *testContext) ThatType(t reflect.Type) AssertableType {
    return &assertableType{testContext: testContext, Type: t}
}

// PrintDiff prints a pretty diff of the specified actual and expected values,
// in that order.
func PrintDiff(actual interface{}, expected interface{}) {
    printLock.Lock()
    defer printLock.Unlock()

    fmt.Println("Diff:")
    pretty.Fdiff(os.Stdout, actual, expected)
}

// PrettyPrint pretty-prints the specified actual and expected values,
// in that order.
func PrettyPrint(actual interface{}, expected interface{}) {
    printLock.Lock()
    defer printLock.Unlock()
    pretty.Printf("Pretty:\nActual: %s\nExpected: %s\n", actual, expected)
}

func (testContext *testContext) decoratedErrorf(format string, args ...interface{}) {
    file, line := testContext.caller()
    testContext.errorf(file, line, format, args...)
}

func (testContext *testContext) errorf(file string, line int, format string, args ...interface{}) {
    printLock.Lock()
    defer printLock.Unlock()

    if line != noCallerInfoLineNumber {
        fmt.Printf("%s:%d: ", file, line) // because t.Errorf prints out the wrong file and line info
    }

    if len(testContext.parameters) > 0 {
        fmt.Print(testContext.parameters, " ")
    }

    fmt.Printf(format, args...)
    testContext.fail()
}

func caller() (file string, line int) {
    skip := 1
    ok := true

    for ok {
        _, file, line, ok = runtime.Caller(skip)
        if strings.HasSuffix(file, "_test.go") {
            return
        }
        skip++
    }
    return "", noCallerInfoLineNumber
}