1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
|
// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
package assert
import (
"fmt"
"os"
"path"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
)
// 定位错误信息的触发函数。输出格式为:TestXxx(xxx_test.go:17)。
func getCallerInfo() string {
for i := 0; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
basename := path.Base(file)
// 定位以 _test.go 结尾的文件。
// 8 == len("_test.go")
l := len(basename)
if l < 8 || (basename[l-8:l] != "_test.go") {
continue
}
// 定位函数名为 Test 开头的行。
// 为什么要定位到 TestXxx 函数,是因为考虑以下情况:
// func isOK(val interface{}, t *testing.T) {
// // do somthing
// assert.True(t, val) // (1
// }
//
// func TestOK(t *testing.T) {
// isOK("123", t) // (2
// isOK(123, t) // (3
// }
// 以上这段代码,定位到 (2、(3 的位置比总是定位到 (1 的位置更直观!
funcName := runtime.FuncForPC(pc).Name()
index := strings.LastIndex(funcName, ".Test")
if -1 == index {
continue
}
funcName = funcName[index+1:]
if strings.IndexByte(funcName, '.') > -1 { // Go1.5 之后的匿名函数
continue
}
return funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
}
return "<无法获取调用者信息>"
}
// 格式化错误提示信息。
//
// msg1 中的所有参数将依次被传递给 fmt.Sprintf() 函数,
// 所以 msg1[0] 必须可以转换成 string(如:string, []byte, []rune, fmt.Stringer)
//
// msg2 参数格式与 msg1 完全相同,在 msg1 为空的情况下,会使用 msg2 的内容,
// 否则 msg2 不会启作用。
func formatMessage(msg1 []interface{}, msg2 []interface{}) string {
if len(msg1) == 0 {
msg1 = msg2
}
if len(msg1) == 0 {
return "<未提供任何错误信息>"
}
format := ""
switch v := msg1[0].(type) {
case []byte:
format = string(v)
case []rune:
format = string(v)
case string:
format = v
case fmt.Stringer:
format = v.String()
default:
return "<无法正确转换错误提示信息>"
}
return fmt.Sprintf(format, msg1[1:]...)
}
// 当 expr 条件不成立时,输出错误信息。
//
// expr 返回结果值为bool类型的表达式;
// msg1,msg2 输出的错误信息,之所以提供两组信息,是方便在用户没有提供的情况下,
// 可以使用系统内部提供的信息,优先使用 msg1 中的信息,若不存在,则使用 msg2 的内容。
func assert(t testing.TB, expr bool, msg1 []interface{}, msg2 []interface{}) {
if !expr {
t.Error(formatMessage(msg1, msg2) + "@" + getCallerInfo())
}
}
// True 断言表达式 expr 为 true,否则输出错误信息。
//
// args 对应 fmt.Printf() 函数中的参数,其中 args[0] 对应第一个参数 format,依次类推,
// 具体可参数 formatMessage() 函数的介绍。其它断言函数的 args 参数,功能与此相同。
func True(t testing.TB, expr bool, args ...interface{}) {
assert(t, expr, args, []interface{}{"True失败,实际值为[%T:%[1]v]", expr})
}
// False 断言表达式 expr 为 false,否则输出错误信息
func False(t testing.TB, expr bool, args ...interface{}) {
assert(t, !expr, args, []interface{}{"False失败,实际值为[%T:%[1]v]", expr})
}
// Nil 断言表达式 expr 为 nil,否则输出错误信息
func Nil(t testing.TB, expr interface{}, args ...interface{}) {
assert(t, IsNil(expr), args, []interface{}{"Nil失败,实际值为[%T:%[1]v]", expr})
}
// NotNil 断言表达式 expr 为非 nil 值,否则输出错误信息
func NotNil(t testing.TB, expr interface{}, args ...interface{}) {
assert(t, !IsNil(expr), args, []interface{}{"NotNil失败,实际值为[%T:%[1]v]", expr})
}
// Equal 断言 v1 与 v2 两个值相等,否则输出错误信息
func Equal(t testing.TB, v1, v2 interface{}, args ...interface{}) {
assert(t, IsEqual(v1, v2), args, []interface{}{"Equal失败,实际值为v1=[%T:%[1]v];v2=[%T:%[2]v]", v1, v2})
}
// NotEqual 断言 v1 与 v2 两个值不相等,否则输出错误信息
func NotEqual(t testing.TB, v1, v2 interface{}, args ...interface{}) {
assert(t, !IsEqual(v1, v2), args, []interface{}{"NotEqual失败,实际值为v1=[%T:%[1]v];v2=[%T:%[2]v]", v1, v2})
}
// Empty 断言 expr 的值为空(nil,"",0,false),否则输出错误信息
func Empty(t testing.TB, expr interface{}, args ...interface{}) {
assert(t, IsEmpty(expr), args, []interface{}{"Empty失败,实际值为[%T:%[1]v]", expr})
}
// NotEmpty 断言 expr 的值为非空(除 nil,"",0,false之外),否则输出错误信息
func NotEmpty(t testing.TB, expr interface{}, args ...interface{}) {
assert(t, !IsEmpty(expr), args, []interface{}{"NotEmpty失败,实际值为[%T:%[1]v]", expr})
}
// Error 断言有错误发生,否则输出错误信息。
// 传递未初始化的 error 值(var err error = nil),将断言失败
func Error(t testing.TB, expr interface{}, args ...interface{}) {
if IsNil(expr) { // 空值,必定没有错误
assert(t, false, args, []interface{}{"Error失败,未初始化的类型[%T]", expr})
return
}
_, ok := expr.(error)
assert(t, ok, args, []interface{}{"Error失败,实际类型为[%T]", expr})
}
// ErrorString 断言有错误发生,且错误信息中包含指定的字符串 str。
// 传递未初始化的 error 值(var err error = nil),将断言失败
func ErrorString(t testing.TB, expr interface{}, str string, args ...interface{}) {
if IsNil(expr) { // 空值,必定没有错误
assert(t, false, args, []interface{}{"ErrorString失败,未初始化的类型[%T]", expr})
return
}
if err, ok := expr.(error); ok {
index := strings.Index(err.Error(), str)
assert(t, index >= 0, args, []interface{}{"Error失败,实际类型为[%T]", expr})
}
}
// ErrorType 断言有错误发生,且错误的类型与 typ 的类型相同。
// 传递未初始化的 error 值(var err error = nil),将断言失败
func ErrorType(t testing.TB, expr interface{}, typ error, args ...interface{}) {
if IsNil(expr) { // 空值,必定没有错误
assert(t, false, args, []interface{}{"ErrorType失败,未初始化的类型[%T]", expr})
return
}
if _, ok := expr.(error); !ok {
assert(t, false, args, []interface{}{"ErrorType失败,实际类型为[%T],且无法转换成error接口", expr})
return
}
t1 := reflect.TypeOf(expr)
t2 := reflect.TypeOf(typ)
assert(t, t1 == t2, args, []interface{}{"ErrorType失败,v1[%v]为一个错误类型,但与v2[%v]的类型不相同", t1, t2})
}
// NotError 断言没有错误发生,否则输出错误信息
func NotError(t testing.TB, expr interface{}, args ...interface{}) {
if IsNil(expr) { // 空值必定没有错误
assert(t, true, args, []interface{}{"NotError失败,实际类型为[%T]", expr})
return
}
err, ok := expr.(error)
assert(t, !ok, args, []interface{}{"NotError失败,错误信息为[%v]", err})
}
// FileExists 断言文件存在,否则输出错误信息
func FileExists(t testing.TB, path string, args ...interface{}) {
_, err := os.Stat(path)
if err != nil && !os.IsExist(err) {
assert(t, false, args, []interface{}{"FileExists发生以下错误:%v", err.Error()})
}
}
// FileNotExists 断言文件不存在,否则输出错误信息
func FileNotExists(t testing.TB, path string, args ...interface{}) {
_, err := os.Stat(path)
assert(t, os.IsNotExist(err), args, []interface{}{"FileExists发生以下错误:%v", err.Error()})
}
// Panic 断言函数会发生 panic,否则输出错误信息。
func Panic(t testing.TB, fn func(), args ...interface{}) {
has, _ := HasPanic(fn)
assert(t, has, args, []interface{}{"并未发生panic"})
}
// PanicString 断言函数会发生 panic,且 panic 信息中包含指定的字符串内容,否则输出错误信息。
func PanicString(t testing.TB, fn func(), str string, args ...interface{}) {
if has, msg := HasPanic(fn); has {
index := strings.Index(fmt.Sprint(msg), str)
assert(t, index >= 0, args, []interface{}{"并未发生panic"})
}
}
// PanicType 断言函数会发生 panic,且 panic 返回的类型与 typ 的类型相同。
func PanicType(t testing.TB, fn func(), typ interface{}, args ...interface{}) {
has, msg := HasPanic(fn)
if !has {
return
}
t1 := reflect.TypeOf(msg)
t2 := reflect.TypeOf(typ)
assert(t, t1 == t2, args, []interface{}{"PanicType失败,v1[%v]的类型与v2[%v]的类型不相同", t1, t2})
}
// NotPanic 断言函数会发生 panic,否则输出错误信息。
func NotPanic(t testing.TB, fn func(), args ...interface{}) {
has, msg := HasPanic(fn)
assert(t, !has, args, []interface{}{"发生了panic,其信息为[%v]", msg})
}
// Contains 断言 container 包含 item 的或是包含 item 中的所有项
// 具体函数说明可参考 IsContains()
func Contains(t testing.TB, container, item interface{}, args ...interface{}) {
assert(t, IsContains(container, item), args,
[]interface{}{"container:[%v]并未包含item[%v]", container, item})
}
// NotContains 断言 container 不包含 item 的或是不包含 item 中的所有项
func NotContains(t testing.TB, container, item interface{}, args ...interface{}) {
assert(t, !IsContains(container, item), args,
[]interface{}{"container:[%v]包含item[%v]", container, item})
}
|