File: assert.go

package info (click to toggle)
golang-github-issue9-assert 0.0~git20170908.0.ceac1aa-5.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 120 kB
  • sloc: makefile: 2
file content (263 lines) | stat: -rw-r--r-- 9,423 bytes parent folder | download | duplicates (3)
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})
}