blob: fbaef7276d7c0c88c7fb5c384ac80079cb6f7cc0 [file] [log] [blame]
/*
Gocheck - A rich testing framework for Go
Copyright (c) 2010, Gustavo Niemeyer <gustavo@niemeyer.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package gocheck
import (
"reflect"
"strings"
"regexp"
"fmt"
"os"
)
// -----------------------------------------------------------------------
// Basic succeeding/failing logic.
// Return true if the currently running test has already failed.
func (c *C) Failed() bool {
return c.status == failedSt
}
// Mark the currently running test as failed. Something ought to have been
// previously logged so that the developer knows what went wrong. The higher
// level helper functions will fail the test and do the logging properly.
func (c *C) Fail() {
c.status = failedSt
}
// Mark the currently running test as failed, and stop running the test.
// Something ought to have been previously logged so that the developer
// knows what went wrong. The higher level helper functions will fail the
// test and do the logging properly.
func (c *C) FailNow() {
c.Fail()
c.stopNow()
}
// Mark the currently running test as succeeded, undoing any previous
// failures.
func (c *C) Succeed() {
c.status = succeededSt
}
// Mark the currently running test as succeeded, undoing any previous
// failures, and stop running the test.
func (c *C) SucceedNow() {
c.Succeed()
c.stopNow()
}
// Expect the currently running test to fail, for the given reason. If the
// test does not fail, an error will be reported to raise the attention to
// this fact. The reason string is just a summary of why the given test is
// supposed to fail. This method is useful to temporarily disable tests
// which cover well known problems until a better time to fix the problem
// is found, without forgetting about the fact that a failure still exists.
func (c *C) ExpectFailure(reason string) {
c.expectedFailure = &reason
}
// -----------------------------------------------------------------------
// Basic logging.
// Return the current test error output.
func (c *C) GetTestLog() string {
return c.logv
}
// Log some information into the test error output. The provided arguments
// will be assembled together into a string using fmt.Sprint().
func (c *C) Log(args ...interface{}) {
c.log(args...)
}
// Log some information into the test error output. The provided arguments
// will be assembled together into a string using fmt.Sprintf().
func (c *C) Logf(format string, args ...interface{}) {
c.logf(format, args...)
}
// Log an error into the test error output, and mark the test as failed.
// The provided arguments will be assembled together into a string using
// fmt.Sprint().
func (c *C) Error(args ...interface{}) {
c.logCaller(1, fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.Fail()
}
// Log an error into the test error output, and mark the test as failed.
// The provided arguments will be assembled together into a string using
// fmt.Sprintf().
func (c *C) Errorf(format string, args ...interface{}) {
c.logCaller(1, fmt.Sprintf("Error: " + format, args...))
c.Fail()
}
// Log an error into the test error output, mark the test as failed, and
// stop the test execution. The provided arguments will be assembled
// together into a string using fmt.Sprint().
func (c *C) Fatal(args ...interface{}) {
c.logCaller(1, fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.FailNow()
}
// Log an error into the test error output, mark the test as failed, and
// stop the test execution. The provided arguments will be assembled
// together into a string using fmt.Sprintf().
func (c *C) Fatalf(format string, args ...interface{}) {
c.logCaller(1, fmt.Sprint("Error: ", fmt.Sprintf(format, args...)))
c.FailNow()
}
// -----------------------------------------------------------------------
// Equality testing.
// Verify if the first value is equal to the second value. In case
// they're not equal, an error will be logged, the test will be marked as
// failed, and the test execution will continue. The extra arguments are
// optional and, if provided, will be assembled together with fmt.Sprint()
// and printed next to the reported problem in case of errors. The returned
// value will be false in case the verification fails.
func (c *C) CheckEqual(obtained interface{}, expected interface{},
issue ...interface{}) bool {
summary := "CheckEqual(obtained, expected):"
return c.internalCheckEqual(obtained, expected, true, summary, issue...)
}
// Verify if the first value is not equal to the second value. In case
// they are equal, an error will be logged, the test will be marked as
// failed, and the test execution will continue. The extra arguments are
// optional and, if provided, will be assembled together with fmt.Sprint()
// and printed next to the reported problem in case of errors. The returned
// value will be false in case the verification fails.
func (c *C) CheckNotEqual(obtained interface{}, expected interface{},
issue ...interface{}) bool {
summary := "CheckNotEqual(obtained, unexpected):"
return c.internalCheckEqual(obtained, expected, false, summary, issue...)
}
// Ensure that the first value is equal to the second value. In case
// they're not equal, an error will be logged, the test will be marked as
// failed, and the test execution will stop. The extra arguments are
// optional and, if provided, will be assembled together with fmt.Sprint()
// and printed next to the reported problem in case of errors.
func (c *C) AssertEqual(obtained interface{}, expected interface{},
issue ...interface{}) {
summary := "AssertEqual(obtained, expected):"
if !c.internalCheckEqual(obtained, expected, true, summary, issue...) {
c.stopNow()
}
}
// Ensure that the first value is not equal to the second value. In case
// they are equal, an error will be logged, the test will be marked as
// failed, and the test execution will stop. The extra arguments are
// optional and, if provided, will be assembled together with fmt.Sprint()
// and printed next to the reported problem in case of errors.
func (c *C) AssertNotEqual(obtained interface{}, expected interface{},
issue ...interface{}) {
summary := "AssertNotEqual(obtained, unexpected):"
if !c.internalCheckEqual(obtained, expected, false, summary, issue...) {
c.stopNow()
}
}
var usedDeprecatedChecks = false
func (c *C) internalCheckEqual(a interface{}, b interface{}, equal bool,
summary string, issue ...interface{}) bool {
usedDeprecatedChecks = true
typeA := reflect.Typeof(a)
typeB := reflect.Typeof(b)
if (typeA == typeB && checkEqual(a, b)) != equal {
c.logCaller(2, summary)
if equal {
c.logValue("Obtained", a)
c.logValue("Expected", b)
} else {
c.logValue("Both", a)
}
if len(issue) != 0 {
c.logString(fmt.Sprint(issue...))
}
c.logNewLine()
c.Fail()
return false
}
return true
}
// This will use a fast path to check for equality of normal types,
// and then fallback to reflect.DeepEqual if things go wrong.
func checkEqual(a interface{}, b interface{}) (result bool) {
defer func() {
if recover() != nil {
result = reflect.DeepEqual(a, b)
}
}()
return (a == b)
}
// -----------------------------------------------------------------------
// String matching testing.
// Verify if the value provided matches with the given regular expression.
// The value must be either a string, or a value which provides the String()
// method. In case it doesn't match, an error will be logged, the test will
// be marked as failed, and the test execution will continue. The extra
// arguments are optional and, if provided, will be assembled together with
// fmt.Sprint() and printed next to the reported problem in case of errors.
func (c *C) CheckMatch(value interface{}, expression string,
issue ...interface{}) bool {
summary := "CheckMatch(value, expression):"
return c.internalCheckMatch(value, expression, true, summary, issue...)
}
// Ensure that the value provided matches with the given regular expression.
// The value must be either a string, or a value which provides the String()
// method. In case it doesn't match, an error will be logged, the test will
// be marked as failed, and the test execution will stop. The extra
// arguments are optional and, if provided, will be assembled together with
// fmt.Sprint() and printed next to the reported problem in case of errors.
func (c *C) AssertMatch(value interface{}, expression string,
issue ...interface{}) {
summary := "AssertMatch(value, expression):"
if !c.internalCheckMatch(value, expression, true, summary, issue...) {
c.stopNow()
}
}
func (c *C) internalCheckMatch(value interface{}, expression string,
equal bool, summary string,
issue ...interface{}) bool {
usedDeprecatedChecks = true
valueStr, valueIsStr := value.(string)
if !valueIsStr {
if valueWithStr, valueHasStr := value.(hasString); valueHasStr {
valueStr, valueIsStr = valueWithStr.String(), true
}
}
var err os.Error
var matches bool
if valueIsStr {
matches, err = regexp.MatchString("^" + expression + "$", valueStr)
}
if !matches || err != nil {
c.logCaller(2, summary)
var msg string
if !matches {
c.logValue("Value", value)
msg = fmt.Sprintf("Expected to match expression: %#v", expression)
} else {
msg = fmt.Sprintf("Can't compile match expression: %#v", expression)
}
c.logString(msg)
if len(issue) != 0 {
c.logString(fmt.Sprint(issue...))
}
c.logNewLine()
c.Fail()
return false
}
return true
}
// -----------------------------------------------------------------------
// Generic checks and assertions based on checkers.
// Verify if the first value matches with the expected value. What
// matching means is defined by the provided checker. In case they do not
// match, an error will be logged, the test will be marked as failed, and
// the test execution will continue. Some checkers may not need the expected
// argument (e.g. IsNil). In either case, any extra arguments provided to
// the function will be logged next to the reported problem when the
// matching fails. This is a handy way to provide problem-specific hints.
func (c *C) Check(obtained interface{}, checker Checker,
args ...interface{}) bool {
return c.internalCheck("Check", obtained, checker, args...)
}
// Ensure that the first value matches with the expected value. What
// matching means is defined by the provided checker. In case they do not
// match, an error will be logged, the test will be marked as failed, and
// the test execution will stop. Some checkers may not need the expected
// argument (e.g. IsNil). In either case, any extra arguments provided to
// the function will be logged next to the reported problem when the
// matching fails. This is a handy way to provide problem-specific hints.
func (c *C) Assert(obtained interface{}, checker Checker,
args ...interface{}) {
if !c.internalCheck("Assert", obtained, checker, args...) {
c.stopNow()
}
}
func (c *C) internalCheck(funcName string,
obtained interface{}, checker Checker,
args ...interface{}) bool {
if checker == nil {
c.logCaller(2, fmt.Sprintf("%s(obtained, nil!?, ...):", funcName))
c.logString("Oops.. you've provided a nil checker!")
goto fail
}
// If the last argument is a bug info, extract it out.
var bug BugInfo
if len(args) > 0 {
if gotBug, hasBug := args[len(args)-1].(BugInfo); hasBug {
bug = gotBug
args = args[:len(args)-1]
}
}
// Ensure we got the needed number of arguments in expected. Note that
// this logic is a bit more complex than it ought to be, mainly because
// it's leaving the door open to multiple expected values.
var expectedWanted int
var expected interface{}
if checker.NeedsExpectedValue() {
expectedWanted = 1
}
if len(args) == expectedWanted {
if expectedWanted > 0 {
expected = args[0]
}
} else {
obtainedName, expectedName := checker.VarNames()
c.logCaller(2, fmt.Sprintf("%s(%s, %s, >%s<):", funcName, obtainedName,
checker.Name(), expectedName))
c.logString(fmt.Sprintf("Wrong number of %s args for %s: " +
"want %d, got %d", expectedName, checker.Name(),
expectedWanted, len(args)))
goto fail
}
// Do the actual check.
result, error := checker.Check(obtained, expected)
if !result || error != "" {
obtainedName, expectedName := checker.VarNames()
var summary string
if expectedWanted > 0 {
summary = fmt.Sprintf("%s(%s, %s, %s):", funcName, obtainedName,
checker.Name(), expectedName)
} else {
summary = fmt.Sprintf("%s(%s, %s):", funcName, obtainedName,
checker.Name())
}
c.logCaller(2, summary)
c.logValue(strings.Title(obtainedName), obtained)
if expectedWanted > 0 {
c.logValue(strings.Title(expectedName), expected)
}
if error != "" {
c.logString(error)
} else if bug != nil {
c.logString(bug.GetBugInfo())
}
goto fail
}
return true
fail:
c.logNewLine()
c.Fail()
return false
}