New Upstream Release - golang-github-issue9-assert
Recent merge proposals
Ready changes
Summary
Merged new upstream version: 3.0.4 (was: 0.0~git20170908.0.ceac1aa).
Resulting package
Built on 2023-01-03T18:55 (took 3m20s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-issue9-assert-dev
Lintian Result
Diff
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..d8720ad
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+
+# html
+[*.{htm,html,js,css}]
+indent_style = space
+indent_size = 4
+
+# 配置文件
+[*.{yml,yaml,json}]
+indent_style = space
+indent_size = 2
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 72ded60..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: go
-go:
- - tip
- - 1.9
- - 1.8
- - 1.7
- - 1.6
- - 1.5
- - 1.4
- - 1.3
diff --git a/README.md b/README.md
index 1e3828f..2c71d49 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,75 @@
-assert [![Build Status](https://travis-ci.org/issue9/assert.svg?branch=master)](https://travis-ci.org/issue9/assert)
+assert
======
+[![Go](https://github.com/issue9/assert/workflows/Go/badge.svg)](https://github.com/issue9/assert/actions?query=workflow%3AGo)
+[![codecov](https://codecov.io/gh/issue9/assert/branch/master/graph/badge.svg)](https://codecov.io/gh/issue9/assert)
+[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://opensource.org/licenses/MIT)
+[![PkgGoDev](https://pkg.go.dev/badge/github.com/issue9/assert)](https://pkg.go.dev/github.com/issue9/assert/v3)
+[![Go version](https://img.shields.io/github/go-mod/go-version/issue9/assert)](https://golang.org)
+
assert 包是对 testing 的一个简单扩展,提供的一系列的断言函数,
方便在测试函数中使用:
+
```go
-func TestA(t testing.T) {
+func TestA(t *testing.T) {
v := true
- assert.True(v)
-
- a := assert.New(t)
+ a := assert.New(t, false)
a.True(v)
}
-// 也可以对testing.B使用
+// 也可以对 testing.B 使用
func Benchmark1(b *testing.B) {
- a := assert.New(b)
+ a := assert.New(b, false)
v := false
a.True(v)
for(i:=0; i<b.N; i++) {
// do something
}
}
-```
-### 安装
+// 对 API 请求做测试,可以引用 assert/rest
+func TestHTTP( t *testing.T) {
+ a := assert.New(t, false)
+
+ srv := rest.NewServer(a, h, nil)
+ a.NotNil(srv)
+ defer srv.Close()
-```shell
-go get github.com/issue9/assert
+ srv.NewRequest(http.MethodGet, "/body").
+ Header("content-type", "application/json").
+ Query("page", "5").
+ JSONBody(&bodyTest{ID: 5}).
+ Do().
+ Status(http.StatusCreated).
+ Header("content-type", "application/json;charset=utf-8").
+ JSONBody(&bodyTest{ID: 6})
+}
```
+也可以直接对原始数据进行测试。
-### 文档
+```go
+// 请求数据
+req :=`POST /users HTTP/1.1
+Host: example.com
+Content-type: application/json
+
+{"username": "admin", "password":"123"}
+
+`
-[![Go Walker](https://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/issue9/assert)
-[![GoDoc](https://godoc.org/github.com/issue9/assert?status.svg)](https://godoc.org/github.com/issue9/assert)
+// 期望的返回数据
+resp :=`HTTP/1.1 201
+Location: https://example.com/users/1
+`
+func TestRaw(t *testing.T) {
+ a := assert.New(t, false)
+ rest.RawHTTP(a, nil,req, resp)
+}
+```
-### 版权
+版权
+----
本项目采用 [MIT](https://opensource.org/licenses/MIT) 开源授权许可证,完整的授权说明可在 [LICENSE](LICENSE) 文件中找到。
diff --git a/assert.go b/assert.go
index b56ba43..83dcd97 100644
--- a/assert.go
+++ b/assert.go
@@ -1,263 +1,101 @@
-// 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.
+// SPDX-License-Identifier: MIT
+// Package assert 是对 testing 包的一些简单包装
+//
+// func TestAssert(t *testing.T) {
+// var v interface{} = 5
+//
+// a := assert.New(t, false)
+// a.True(v==5, "v的值[%v]不等于5", v).
+// Equal(5, v, "v的值[%v]不等于5", v).
+// Nil(v).
+// TB().Log("success")
+// }
+//
+// // 也可以对 testing.B 使用
+// func Benchmark1(b *testing.B) {
+// a := assert.New(b)
+// a.True(false)
+// for(i:=0; i<b.N; i++) {
+// // do something
+// }
+// }
package assert
import (
"fmt"
- "os"
- "path"
- "reflect"
- "runtime"
- "strconv"
+ "sort"
"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
- }
+var failureSprint FailureSprintFunc = DefaultFailureSprint
- return funcName + "(" + basename + ":" + strconv.Itoa(line) + ")"
- }
-
- return "<无法获取调用者信息>"
+// Failure 在断言出错时输出的错误信息
+type Failure struct {
+ Action string // 操作名称,比如 Equal,NotEqual 等方法名称。
+ Values map[string]interface{} // 断言出错是返回的一些额外参数
+ User string // 断言出错时用户反馈的额外信息
}
-// 格式化错误提示信息。
-//
-// msg1 中的所有参数将依次被传递给 fmt.Sprintf() 函数,
-// 所以 msg1[0] 必须可以转换成 string(如:string, []byte, []rune, fmt.Stringer)
+// FailureSprintFunc 将 Failure 转换成文本的函数
//
-// msg2 参数格式与 msg1 完全相同,在 msg1 为空的情况下,会使用 msg2 的内容,
-// 否则 msg2 不会启作用。
-func formatMessage(msg1 []interface{}, msg2 []interface{}) string {
- if len(msg1) == 0 {
- msg1 = msg2
- }
+// NOTE: 可以使用此方法实现对错误信息的本地化。
+type FailureSprintFunc func(*Failure) string
- 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,否则输出错误信息。
+// SetFailureSprintFunc 设置一个全局的转换方法
//
-// 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
+// [New] 方法在默认情况下继承由此方法设置的值。
+func SetFailureSprintFunc(f FailureSprintFunc) { failureSprint = f }
+
+// DefaultFailureSprint 默认的 [FailureSprintFunc] 实现
+func DefaultFailureSprint(f *Failure) string {
+ s := strings.Builder{}
+ s.WriteString(f.Action)
+ s.WriteString(" 断言失败!")
+
+ if len(f.Values) > 0 {
+ keys := make([]string, 0, len(f.Values))
+ for k := range f.Values {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ s.WriteString("反馈以下参数:\n")
+ for _, k := range keys {
+ s.WriteString(k)
+ s.WriteByte('=')
+ s.WriteString(fmt.Sprint(f.Values[k]))
+ s.WriteByte('\n')
+ }
}
- 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()})
+ if f.User != "" {
+ s.WriteString("用户反馈信息:")
+ s.WriteString(f.User)
}
-}
-// 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()})
+ return s.String()
}
-// 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"})
+// NewFailure 声明 Failure 对象
+//
+// user 表示用户提交的反馈,。其第一个元素如果是 string,那么将调用 fmt.Sprintf(user[0], user[1:]...)
+// 对数据进行格式化,否则采用 fmt.Sprint(user...) 格式化数据;
+// kv 表示当前错误返回的数据;
+func NewFailure(action string, user []interface{}, kv map[string]interface{}) *Failure {
+ var u string
+ if len(user) > 0 {
+ switch v := user[0].(type) {
+ case string:
+ u = fmt.Sprintf(v, user[1:]...)
+ default:
+ u = fmt.Sprint(user...)
+ }
}
-}
-// PanicType 断言函数会发生 panic,且 panic 返回的类型与 typ 的类型相同。
-func PanicType(t testing.TB, fn func(), typ interface{}, args ...interface{}) {
- has, msg := HasPanic(fn)
- if !has {
- return
+ return &Failure{
+ Action: action,
+ User: u,
+ Values: kv,
}
-
- 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})
}
diff --git a/assert_test.go b/assert_test.go
index a3c27cb..f12c428 100644
--- a/assert_test.go
+++ b/assert_test.go
@@ -1,233 +1,42 @@
-// 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.
+// SPDX-License-Identifier: MIT
package assert
-import (
- "errors"
- "testing"
-)
+import "testing"
-func TestGetCallerInfo(t *testing.T) {
- str := getCallerInfo()
- // NOTE:注意这里涉及到调用函数的行号信息
- if str != "TestGetCallerInfo(assert_test.go:13)" {
- t.Errorf("getCallerInfo返回的信息不正确,其返回值为:%v", str)
+func TestDefaultFailureSprint(t *testing.T) {
+ f := NewFailure("A", nil, nil)
+ if f.Action != "A" || f.User != "" || len(f.Values) != 0 {
+ t.Error("err1")
}
-
- // 嵌套调用,第二个参数为当前的行号
- testGetCallerInfo(t, "20")
- testGetCallerInfo(t, "21")
-
- // 闭合函数,line为调用所在的行号。
- f := func(line string) {
- str := getCallerInfo()
- if str != "TestGetCallerInfo(assert_test.go:"+line+")" {
- t.Errorf("getCallerInfo返回的信息不正确,其返回值为:%v", str)
- }
+ if s := DefaultFailureSprint(f); s != "A 断言失败!" {
+ t.Error("err2")
}
- f("30") // 参数为当前等号
- f("31")
-}
-// 参数line,为调用此函数所在的行号。
-func testGetCallerInfo(t *testing.T, line string) {
- str := getCallerInfo()
- if str != "TestGetCallerInfo(assert_test.go:"+line+")" {
- t.Errorf("getCallerInfo返回的信息不正确,其返回值为:%v", str)
+ // 带 user
+ f = NewFailure("AB", []interface{}{1, 2}, nil)
+ if f.Action != "AB" || f.User != "1 2" || len(f.Values) != 0 {
+ t.Error("err3")
}
-
-}
-
-func TestFormatMsg(t *testing.T) {
- msg1 := []interface{}{}
- msg2 := []interface{}{[]rune("msg:%v"), 2}
- msg3 := []interface{}{"msg:%v", 3}
- msg4 := []interface{}{123, 456}
-
- str := formatMessage(msg1, msg2)
- if str != "msg:2" {
- t.Errorf("formatMessage(msg1,msg2)返回信息错误:[%v]", str)
+ if s := DefaultFailureSprint(f); s != "AB 断言失败!用户反馈信息:1 2" {
+ t.Error("err4", s)
}
- str = formatMessage(nil, msg2)
- if str != "msg:2" {
- t.Errorf("formatMessage(msg1,msg2)返回信息错误:[%v]", str)
+ // 带 values
+ f = NewFailure("AB", nil, map[string]interface{}{"k1": "v1", "k2": 2})
+ if f.Action != "AB" || f.User != "" || len(f.Values) != 2 {
+ t.Error("err5")
}
-
- str = formatMessage(msg2, msg3)
- if str != "msg:2" {
- t.Errorf("formatMessage(msg2,msg3)返回信息错误:[%v]", str)
+ if s := DefaultFailureSprint(f); s != "AB 断言失败!反馈以下参数:\nk1=v1\nk2=2\n" {
+ t.Error("err6", s)
}
- str = formatMessage(nil, nil)
- if str != "<未提供任何错误信息>" {
- t.Errorf("formatMessage(nil,nil)返回信息错误:[%v]", str)
+ // 带 user,values
+ f = NewFailure("AB", []interface{}{1, 2}, map[string]interface{}{"k1": "v1", "k2": 2})
+ if f.Action != "AB" || f.User == "" || len(f.Values) != 2 {
+ t.Error("err7")
}
-
- str = formatMessage(nil, msg4)
- if str != "<无法正确转换错误提示信息>" {
- t.Errorf("formatMessage(nil,nil)返回信息错误:[%v]", str)
+ if s := DefaultFailureSprint(f); s != "AB 断言失败!反馈以下参数:\nk1=v1\nk2=2\n用户反馈信息:1 2" {
+ t.Error("err8", s)
}
}
-
-func TestTrue(t *testing.T) {
- True(t, true)
- True(t, 1 == 1, "True(1==1) falid")
-}
-
-func TestFalse(t *testing.T) {
- False(t, false, "False falid")
- False(t, 1 == 2, "False(1==2) falid")
-}
-
-func TestNil(t *testing.T) {
- Nil(t, nil, "Nil falid")
-
- var v interface{}
- Nil(t, v, "Nil(v) falid")
-}
-
-func TestNotNil(t *testing.T) {
- NotNil(t, 5, "NotNil falid")
-
- var v interface{} = 5
- NotNil(t, v, "NotNil falid")
-}
-
-func TestEqual(t *testing.T) {
- Equal(t, 5, 5, "Equal(5,5) falid")
-
- var v1, v2 interface{}
- v1 = 5
- v2 = 5
-
- Equal(t, 5, v1)
- Equal(t, v1, v2, "Equal(v1,v2) falid")
- Equal(t, int8(126), 126)
- Equal(t, int64(126), int8(126))
- Equal(t, uint(7), int(7))
-}
-
-func TestNotEqual(t *testing.T) {
- NotEqual(t, 5, 6, "NotEqual(5,6) falid")
-
- var v1, v2 interface{} = 5, 6
-
- NotEqual(t, 5, v2, "NotEqual(5,v2) falid")
- NotEqual(t, v1, v2, "NotEqual(v1,v2) falid")
- NotEqual(t, 128, int8(127))
-}
-
-func TestEmpty(t *testing.T) {
- Empty(t, 0, "Empty(0) falid")
- Empty(t, "", "Empty(``) falid")
- Empty(t, false, "Empty(false) falid")
- Empty(t, []string{}, "Empty(slice{}) falid")
- Empty(t, []int{}, "Empty(slice{}) falid")
-}
-
-func TestNotEmpty(t *testing.T) {
- NotEmpty(t, 1, "NotEmpty(1) falid")
- NotEmpty(t, true, "NotEmpty(true) falid")
- NotEmpty(t, []string{"ab"}, "NotEmpty(slice(abc)) falid")
-}
-
-type ErrorImpl struct {
- msg string
-}
-
-func (err *ErrorImpl) Error() string {
- return err.msg
-}
-
-func TestError(t *testing.T) {
- err1 := errors.New("test")
- Error(t, err1, "Error(err) falid")
-
- err2 := &ErrorImpl{msg: "msg"}
- Error(t, err2, "Error(ErrorImpl) falid")
-}
-
-func TestErrorString(t *testing.T) {
- err1 := errors.New("test")
- ErrorString(t, err1, "test", "Error(err1) falid")
-
- err2 := &ErrorImpl{msg: "msg"}
- Error(t, err2, "msg", "Error(ErrorImpl) falid")
-}
-
-func TestErrorType(t *testing.T) {
- ErrorType(t, errors.New("abc"), errors.New("def"), "ErrorType:errors.New(abc) != errors.New(def)")
-
- ErrorType(t, &ErrorImpl{msg: "abc"}, &ErrorImpl{}, "ErrorType:&ErrorImpl{} != &ErrorImpl{}")
-}
-
-func TestNotError(t *testing.T) {
- NotError(t, "123", "NotError(123) falid")
-
- var err1 error
- NotError(t, err1, "var err1 error falid")
-
- err2 := &ErrorImpl{msg: "msg"}
- Error(t, err2, "Error(ErrorImpl) falid")
-}
-
-func TestFileExists(t *testing.T) {
- FileExists(t, "./assert.go", "FileExists() falid")
- FileExists(t, "./", "FileExists() falid")
-}
-
-func TestFileNotExists(t *testing.T) {
- FileNotExists(t, "c:/win", "FileNotExists() falid")
- FileNotExists(t, "./abcefg/", "FileNotExists() falid")
-}
-
-func TestPanic(t *testing.T) {
- f1 := func() {
- panic("panic")
- }
-
- Panic(t, f1)
-}
-
-func TestPanicString(t *testing.T) {
- f1 := func() {
- panic("panic")
- }
-
- PanicString(t, f1, "pani")
-}
-
-func TestPanicType(t *testing.T) {
- f1 := func() {
- panic("panic")
- }
- PanicType(t, f1, "abc")
-
- f1 = func() {
- panic(errors.New("panic"))
- }
- PanicType(t, f1, errors.New("abc"))
-
- f1 = func() {
- panic(&ErrorImpl{msg: "panic"})
- }
- PanicType(t, f1, &ErrorImpl{msg: "abc"})
-}
-
-func TestNotPanic(t *testing.T) {
- f1 := func() {
- }
-
- NotPanic(t, f1)
-}
-
-func TestContains(t *testing.T) {
- Contains(t, []int{1, 2, 3}, []int8{1, 2})
-}
-
-func TestNotContains(t *testing.T) {
- NotContains(t, []int{1, 2, 3}, []int8{1, 3})
-}
diff --git a/assertion.go b/assertion.go
index 54629b0..cd94fb1 100644
--- a/assertion.go
+++ b/assertion.go
@@ -1,143 +1,345 @@
-// 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.
+// SPDX-License-Identifier: MIT
package assert
-import "testing"
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
-// Assertion 是对 testing.TB 进行了简单的封装。
-// 可以以对象的方式调用包中的各个断言函数。
+// Assertion 可以以对象的方式调用包中的各个断言函数
type Assertion struct {
- t testing.TB
+ tb testing.TB
+
+ fatal bool
+ print func(...interface{})
+ f FailureSprintFunc
}
-// New 返回 Assertion 对象。
-func New(t testing.TB) *Assertion {
- return &Assertion{t: t}
+// New 返回 Assertion 对象
+//
+// fatal 决定在出错时是调用 tb.Error 还是 tb.Fatal;
+func New(tb testing.TB, fatal bool) *Assertion {
+ p := tb.Error
+ if fatal {
+ p = tb.Fatal
+ }
+
+ return &Assertion{
+ tb: tb,
+
+ fatal: fatal,
+ print: p,
+ f: failureSprint,
+ }
}
-// TB 返回 testing.TB 接口
-func (a *Assertion) TB() testing.TB {
- return a.t
+// NewWithEnv 以指定的环境变量初始化 *Assertion 对象
+//
+// env 是以 t.Setenv 的形式调用。
+func NewWithEnv(tb testing.TB, fatal bool, env map[string]string) *Assertion {
+ for k, v := range env {
+ tb.Setenv(k, v)
+ }
+ return New(tb, fatal)
}
-// True 参照 assert.True() 函数
-func (a *Assertion) True(expr bool, msg ...interface{}) *Assertion {
- True(a.t, expr, msg...)
+// Assert 断言 expr 条件成立
+//
+// f 表示在断言失败时输出的信息
+//
+// 普通用户直接使用 True 效果是一样的,此函数主要供库调用。
+func (a *Assertion) Assert(expr bool, f *Failure) *Assertion {
+ if !expr {
+ a.TB().Helper()
+ a.print(a.f(f))
+ }
return a
}
-// False 参照 assert.False() 函数
+// TB 返回 [testing.TB] 接口
+func (a *Assertion) TB() testing.TB { return a.tb }
+
+// True 断言表达式 expr 为真
+//
+// args 对应 fmt.Printf() 函数中的参数,其中 args[0] 对应第一个参数 format,依次类推,
+// 具体可参数 Assert 方法的介绍。其它断言函数的 args 参数,功能与此相同。
+func (a *Assertion) True(expr bool, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(expr, NewFailure("True", msg, nil))
+}
+
func (a *Assertion) False(expr bool, msg ...interface{}) *Assertion {
- False(a.t, expr, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(!expr, NewFailure("False", msg, nil))
}
-// Nil 参照 assert.Nil() 函数
func (a *Assertion) Nil(expr interface{}, msg ...interface{}) *Assertion {
- Nil(a.t, expr, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(isNil(expr), NewFailure("Nil", msg, map[string]interface{}{"v": expr}))
}
-// NotNil 参照 assert.NotNil() 函数
func (a *Assertion) NotNil(expr interface{}, msg ...interface{}) *Assertion {
- NotNil(a.t, expr, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(!isNil(expr), NewFailure("NotNil", msg, map[string]interface{}{"v": expr}))
}
-// Equal 参照 assert.Equal() 函数
func (a *Assertion) Equal(v1, v2 interface{}, msg ...interface{}) *Assertion {
- Equal(a.t, v1, v2, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(isEqual(v1, v2), NewFailure("Equal", msg, map[string]interface{}{"v1": v1, "v2": v2}))
}
-// NotEqual 参照 assert.NotEqual() 函数
func (a *Assertion) NotEqual(v1, v2 interface{}, msg ...interface{}) *Assertion {
- NotEqual(a.t, v1, v2, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(!isEqual(v1, v2), NewFailure("NotEqual", msg, map[string]interface{}{"v1": v1, "v2": v2}))
}
-// Empty 参照 assert.Empty() 函数
func (a *Assertion) Empty(expr interface{}, msg ...interface{}) *Assertion {
- Empty(a.t, expr, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(isEmpty(expr), NewFailure("Empty", msg, map[string]interface{}{"v": expr}))
}
-// NotEmpty 参照 assert.NotEmpty() 函数
func (a *Assertion) NotEmpty(expr interface{}, msg ...interface{}) *Assertion {
- NotEmpty(a.t, expr, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(!isEmpty(expr), NewFailure("NotEmpty", msg, map[string]interface{}{"v": expr}))
}
-// Error 参照 assert.Error() 函数
-func (a *Assertion) Error(expr interface{}, msg ...interface{}) *Assertion {
- Error(a.t, expr, msg...)
- return a
+// Error 断言有错误发生
+//
+// 传递未初始化的 error 值(var err error = nil),将断言失败
+//
+// NotNil 的特化版本,限定了类型为 error。
+func (a *Assertion) Error(expr error, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(!isNil(expr), NewFailure("Error", msg, map[string]interface{}{"v": expr}))
}
-// ErrorString 参照 assert.ErrorString() 函数
-func (a *Assertion) ErrorString(expr interface{}, str string, msg ...interface{}) *Assertion {
- ErrorString(a.t, expr, str, msg...)
- return a
+// ErrorString 断言有错误发生且错误信息中包含指定的字符串 str
+//
+// 传递未初始化的 error 值(var err error = nil),将断言失败
+func (a *Assertion) ErrorString(expr error, str string, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+
+ if isNil(expr) { // 空值,必定没有错误
+ return a.Assert(false, NewFailure("ErrorString", msg, map[string]interface{}{"v": expr}))
+ }
+ return a.Assert(strings.Contains(expr.Error(), str), NewFailure("ErrorString", msg, map[string]interface{}{"v": expr}))
}
-// ErrorType 参照 assert.ErrorType() 函数
-func (a *Assertion) ErrorType(expr interface{}, typ error, msg ...interface{}) *Assertion {
- ErrorType(a.t, expr, typ, msg...)
- return a
+// ErrorIs 断言 expr 为 target 类型
+//
+// 相当于 a.True(errors.Is(expr, target))
+func (a *Assertion) ErrorIs(expr, target error, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(errors.Is(expr, target), NewFailure("ErrorIs", msg, map[string]interface{}{"err": expr}))
}
-// NotError 参照 assert.NotError() 函数
-func (a *Assertion) NotError(expr interface{}, msg ...interface{}) *Assertion {
- NotError(a.t, expr, msg...)
- return a
+// NotError 断言没有错误
+//
+// Nil 的特化版本,限定了类型为 error。
+func (a *Assertion) NotError(expr error, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(isNil(expr), NewFailure("NotError", msg, map[string]interface{}{"v": expr}))
}
-// FileExists 参照 assert.FileExists() 函数
func (a *Assertion) FileExists(path string, msg ...interface{}) *Assertion {
- FileExists(a.t, path, msg...)
+ a.TB().Helper()
+
+ if _, err := os.Stat(path); err != nil && !errors.Is(err, fs.ErrExist) {
+ return a.Assert(false, NewFailure("FileExists", msg, map[string]interface{}{"err": err}))
+ }
return a
}
-// FileNotExists 参照 assert.FileNotExists() 函数
func (a *Assertion) FileNotExists(path string, msg ...interface{}) *Assertion {
- FileNotExists(a.t, path, msg...)
+ a.TB().Helper()
+
+ _, err := os.Stat(path)
+ if err == nil {
+ return a.Assert(false, NewFailure("FileNotExists", msg, nil))
+ }
+ if errors.Is(err, fs.ErrExist) {
+ return a.Assert(false, NewFailure("FileNotExists", msg, map[string]interface{}{"err": err}))
+ }
+
return a
}
-// Panic 参照 assert.Panic() 函数
-func (a *Assertion) Panic(fn func(), msg ...interface{}) *Assertion {
- Panic(a.t, fn, msg...)
+func (a *Assertion) FileExistsFS(fsys fs.FS, path string, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+
+ if _, err := fs.Stat(fsys, path); err != nil && !errors.Is(err, fs.ErrExist) {
+ return a.Assert(false, NewFailure("FileExistsFS", msg, map[string]interface{}{"err": err}))
+ }
+
return a
}
-// PanicString 参照 assert.PanicString() 函数
-func (a *Assertion) PanicString(fn func(), str string, msg ...interface{}) *Assertion {
- PanicString(a.t, fn, str, msg...)
+func (a *Assertion) FileNotExistsFS(fsys fs.FS, path string, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+
+ _, err := fs.Stat(fsys, path)
+ if err == nil {
+ return a.Assert(false, NewFailure("FileNotExistsFS", msg, nil))
+ }
+ if errors.Is(err, fs.ErrExist) {
+ return a.Assert(false, NewFailure("FileNotExistsFS", msg, map[string]interface{}{"err": err}))
+ }
+
return a
}
-// PanicType 参照 assert.PanicType() 函数
+func (a *Assertion) Panic(fn func(), msg ...interface{}) *Assertion {
+ a.TB().Helper()
+
+ has, _ := hasPanic(fn)
+ return a.Assert(has, NewFailure("Panic", msg, nil))
+}
+
+// PanicString 断言函数会发生 panic 且 panic 信息中包含指定的字符串内容
+func (a *Assertion) PanicString(fn func(), str string, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+
+ if has, m := hasPanic(fn); has {
+ return a.Assert(strings.Contains(fmt.Sprint(m), str), NewFailure("PanicString", msg, map[string]interface{}{"msg": m}))
+ }
+ return a.Assert(false, NewFailure("PanicString", msg, nil))
+}
+
+// PanicType 断言函数会发生 panic 且抛出指定的类型
func (a *Assertion) PanicType(fn func(), typ interface{}, msg ...interface{}) *Assertion {
- PanicType(a.t, fn, typ, msg...)
- return a
+ a.TB().Helper()
+
+ if has, m := hasPanic(fn); has {
+ t1, t2 := getType(true, m, typ)
+ return a.Assert(t1 == t2, NewFailure("PanicType", msg, map[string]interface{}{"v1": t1, "v2": t2}))
+ }
+ return a.Assert(false, NewFailure("PanicType", msg, nil))
}
-// NotPanic 参照 assert.NotPanic() 函数
func (a *Assertion) NotPanic(fn func(), msg ...interface{}) *Assertion {
- NotPanic(a.t, fn, msg...)
- return a
+ a.TB().Helper()
+
+ has, m := hasPanic(fn)
+ return a.Assert(!has, NewFailure("NotPanic", msg, map[string]interface{}{"err": m}))
}
-// Contains 参照 assert.Contains() 函数
+// Contains 断言 container 包含 item 或是包含 item 中的所有项
+//
+// 若 container 是字符串(string、[]byte 和 []rune,不包含 fmt.Stringer 接口),
+// 都将会以字符串的形式判断其是否包含 item。
+// 若 container 是个列表(array、slice、map)则判断其元素中是否包含 item 中的
+// 的所有项,或是 item 本身就是 container 中的一个元素。
func (a *Assertion) Contains(container, item interface{}, msg ...interface{}) *Assertion {
- Contains(a.t, container, item, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(isContains(container, item), NewFailure("Contains", msg, map[string]interface{}{"container": container, "item": item}))
}
-// NotContains 参照 assert.NotContains() 函数
+// NotContains 断言 container 不包含 item 或是不包含 item 中的所有项
func (a *Assertion) NotContains(container, item interface{}, msg ...interface{}) *Assertion {
- NotContains(a.t, container, item, msg...)
- return a
+ a.TB().Helper()
+ return a.Assert(!isContains(container, item), NewFailure("NotContains", msg, map[string]interface{}{"container": container, "item": item}))
+}
+
+// Zero 断言是否为零值
+//
+// 最终调用的是 reflect.Value.IsZero 进行判断
+func (a *Assertion) Zero(v interface{}, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(isZero(v), NewFailure("Zero", msg, map[string]interface{}{"v": v}))
+}
+
+// NotZero 断言是否为非零值
+//
+// 最终调用的是 reflect.Value.IsZero 进行判断
+func (a *Assertion) NotZero(v interface{}, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(!isZero(v), NewFailure("NotZero", msg, map[string]interface{}{"v": v}))
+}
+
+// Length 断言长度是否为指定的值
+//
+// v 可以是以下类型:
+// - map
+// - string
+// - slice
+// - array
+func (a *Assertion) Length(v interface{}, l int, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+
+ rl, err := getLen(v)
+ if err != "" {
+ a.Assert(false, NewFailure("Length", msg, map[string]interface{}{"err": err}))
+ }
+ return a.Assert(rl == l, NewFailure("Length", msg, map[string]interface{}{"l1": rl, "l2": l}))
+}
+
+// NotLength 断言长度不是指定的值
+//
+// v 可以是以下类型:
+// - map
+// - string
+// - slice
+// - array
+func (a *Assertion) NotLength(v interface{}, l int, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+
+ rl, err := getLen(v)
+ if err != "" {
+ a.Assert(false, NewFailure("NotLength", msg, map[string]interface{}{"err": err}))
+ }
+ return a.Assert(rl != l, NewFailure("NotLength", msg, map[string]interface{}{"l": rl}))
+}
+
+// TypeEqual 断言两个值的类型是否相同
+//
+// ptr 如果为 true,则会在对象为指针时,查找其指向的对象。
+func (a *Assertion) TypeEqual(ptr bool, v1, v2 interface{}, msg ...interface{}) *Assertion {
+ if v1 == v2 {
+ return a
+ }
+
+ a.TB().Helper()
+
+ t1, t2 := getType(ptr, v1, v2)
+ return a.Assert(t1 == t2, NewFailure("TypeEquaal", msg, map[string]interface{}{"v1": t1, "v2": t2}))
+}
+
+// Same 断言为同一个对象
+func (a *Assertion) Same(v1, v2 interface{}, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(isSame(v1, v2), NewFailure("Same", msg, nil))
+}
+
+// NotSame 断言为不是同一个对象
+func (a *Assertion) NotSame(v1, v2 interface{}, msg ...interface{}) *Assertion {
+ a.TB().Helper()
+ return a.Assert(!isSame(v1, v2), NewFailure("NotSame", msg, nil))
+}
+
+func isSame(v1, v2 interface{}) bool {
+ rv1 := reflect.ValueOf(v1)
+ if !canPointer(rv1.Kind()) {
+ return false
+ }
+ rv2 := reflect.ValueOf(v2)
+ if !canPointer(rv2.Kind()) {
+ return false
+ }
+
+ return rv1.Pointer() == rv2.Pointer()
+}
+
+func canPointer(k reflect.Kind) bool {
+ switch k {
+ case reflect.Ptr, reflect.Map, reflect.Chan, reflect.Slice, reflect.UnsafePointer, reflect.Func:
+ return true
+ default:
+ return false
+ }
}
diff --git a/assertion_test.go b/assertion_test.go
index 94caeed..bbd895f 100644
--- a/assertion_test.go
+++ b/assertion_test.go
@@ -1,59 +1,207 @@
-// 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.
+// SPDX-License-Identifier: MIT
package assert
import (
+ "database/sql"
"errors"
+ "fmt"
+ "os"
"testing"
+ "time"
)
-func TestAssertion(t *testing.T) {
- a := New(t)
+type errorImpl struct {
+ msg string
+}
+
+func (err *errorImpl) Error() string {
+ return err.msg
+}
+
+func TestAssertion_True_False(t *testing.T) {
+ a := New(t, true)
if t != a.TB() {
t.Error("a.T与t不相等")
}
a.True(true)
- a.True(5 == 5, "a.True(5==5 falid")
+ a.True(true, "a.True(5==5 failed")
+
+ a.False(false, "a.False(false) failed")
+ a.False(false, "a.False(4==5) failed")
+}
- a.False(false, "a.False(false) falid")
- a.False(4 == 5, "a.False(4==5) falid")
+func TestAssertion_Equal_NotEqual_Nil_NotNil(t *testing.T) {
+ a := New(t, false)
v1 := 4
v2 := 4
v3 := 5
v4 := "5"
- a.Equal(4, 4, "a.Equal(4,4) falid")
- a.Equal(v1, v2, "a.Equal(v1,v2) falid")
+ a.Equal(4, 4, "a.Equal(4,4) failed")
+ a.Equal(v1, v2, "a.Equal(v1,v2) failed")
- a.NotEqual(4, 5, "a.NotEqual(4,5) falid").
- NotEqual(v1, v3, "a.NotEqual(v1,v3) falid").
- NotEqual(v3, v4, "a.NotEqual(v3,v4) falid")
+ a.NotEqual(4, 5, "a.NotEqual(4,5) failed").
+ NotEqual(v1, v3, "a.NotEqual(v1,v3) failed").
+ NotEqual(v3, v4, "a.NotEqual(v3,v4) failed")
var v5 interface{}
v6 := 0
v7 := []int{}
- a.Empty(v5, "a.Empty falid").
- Empty(v6, "a.Empty(0) falid").
- Empty(v7, "a.Empty(v7) falid")
+ a.Empty(v5, "a.Empty failed").
+ Empty(v6, "a.Empty(0) failed").
+ Empty(v7, "a.Empty(v7) failed")
- a.NotEmpty(1, "a.NotEmpty(1) falid")
+ a.NotEmpty(1, "a.NotEmpty(1) failed")
a.Nil(v5)
- a.NotNil(v7, "a.Nil(v7) falid").
- NotNil(v6, "a.NotNil(v6) falid")
+ a.NotNil(v7, "a.Nil(v7) failed").
+ NotNil(v6, "a.NotNil(v6) failed")
+}
+
+func TestAssertion_Error(t *testing.T) {
+ a := New(t, false)
+
+ err := errors.New("test")
+ a.Error(err, "a.Error(err) failed")
+ a.ErrorString(err, "test", "ErrorString(err) failed")
+
+ err2 := &errorImpl{msg: "msg"}
+ a.Error(err2, "ErrorString(errorImpl) failed")
+ a.ErrorString(err2, "msg", "ErrorString(errorImpl) failed")
+
+ var err3 error
+ a.NotError(err3, "var err1 error failed")
+
+ err4 := errors.New("err4")
+ err5 := fmt.Errorf("err5 with %w", err4)
+ a.ErrorIs(err5, err4)
+}
+
+func TestAssertion_FileExists_FileNotExists(t *testing.T) {
+ a := New(t, false)
+
+ a.FileExists("./assert.go", "a.FileExists(c:/windows) failed").
+ FileNotExists("c:/win", "a.FileNotExists(c:/win) failed")
+
+ a.FileExistsFS(os.DirFS("./"), "assert.go", "a.FileExistsFS(c:/windows) failed").
+ FileNotExistsFS(os.DirFS("c:/"), "win", "a.FileNotExistsFS(c:/win) failed")
+}
+
+func TestAssertion_Panic(t *testing.T) {
+ a := New(t, false)
+
+ f1 := func() {
+ panic("panic message")
+ }
+
+ a.Panic(f1)
+ a.PanicString(f1, "panic message")
+ a.PanicType(f1, "abc")
+
+ f1 = func() {
+ panic(errors.New("panic"))
+ }
+ a.PanicType(f1, errors.New("abc"))
+
+ f1 = func() {
+ panic(&errorImpl{msg: "panic"})
+ }
+ a.PanicType(f1, &errorImpl{msg: "abc"})
+
+ f1 = func() {}
+ a.NotPanic(f1)
+}
+
+func TestAssertion_Zero_NotZero(t *testing.T) {
+ a := New(t, false)
+
+ var v interface{}
+ a.Zero(0)
+ a.Zero(nil)
+ a.Zero(time.Time{})
+ a.Zero(v)
+ a.Zero([2]int{0, 0})
+ a.Zero([0]int{})
+ a.Zero(&time.Time{})
+ a.Zero(sql.NullTime{})
+
+ a.NotZero([]int{0, 0})
+ a.NotZero([]int{})
+}
+
+func TestAssertion_Length_NotLength(t *testing.T) {
+ a := New(t, false)
+
+ a.Length(nil, 0)
+ a.Length([]int{1, 2}, 2)
+ a.Length([3]int{1, 2, 3}, 3)
+ a.NotLength([3]int{1, 2, 3}, 2)
+ a.Length(map[string]string{"1": "1", "2": "2"}, 2)
+ a.NotLength(map[string]string{"1": "1", "2": "2"}, 3)
+ slices := []rune{'a', 'b', 'c'}
+ ps := &slices
+ pps := &ps
+ a.Length(pps, 3)
+ a.NotLength(pps, 2)
+ a.Length("string", 6)
+ a.NotLength("string", 4)
+}
+
+func TestAssertion_Contains(t *testing.T) {
+ a := New(t, false)
+
+ a.Contains([]int{1, 2, 3}, []int8{1, 2}).
+ NotContains([]int{1, 2, 3}, []int8{1, 3})
+}
+
+func TestAssertion_TypeEqual(t *testing.T) {
+ a := New(t, true)
+
+ a.TypeEqual(false, 1, 2)
+ a.TypeEqual(false, 1, 1)
+ a.TypeEqual(false, 1.0, 2.0)
+
+ v1 := 5
+ pv1 := &v1
+ a.TypeEqual(false, 1, v1)
+ a.TypeEqual(true, 1, &pv1)
+
+ v2 := &errorImpl{}
+ v3 := errorImpl{}
+ a.TypeEqual(false, v2, v2)
+ a.TypeEqual(true, v2, v3)
+ a.TypeEqual(true, v2, &v3)
+ a.TypeEqual(true, &v2, &v3)
+}
+
+func TestAssertion_Same(t *testing.T) {
+ a := New(t, false)
+
+ a.NotSame(5, 5)
+ a.NotSame(struct{}{}, struct{}{})
+ a.NotSame(func() {}, func() {})
+
+ i := 5
+ a.NotSame(i, i)
- v9 := errors.New("test")
- a.Error(v9, "a.Error(v9) falid")
+ empty := struct{}{}
+ empty2 := empty
+ a.NotSame(empty, empty)
+ a.NotSame(empty, empty2)
+ a.Same(&empty, &empty)
+ a.Same(&empty, &empty2)
- a.NotError("abc", "a.NotError falid")
+ f := func() {}
+ f2 := f
+ a.Same(f, f)
+ a.Same(f, f2)
- a.FileExists("./assert.go", "a.FileExists(c:/windows) falid").
- FileNotExists("c:/win", "a.FileNotExists(c:/win) falid")
+ a.NotSame(5, 5)
+ a.NotSame(f, 5)
}
diff --git a/debian/changelog b/debian/changelog
index 9c4863b..3898efd 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,9 @@
-golang-github-issue9-assert (0.0~git20170908.0.ceac1aa-6) UNRELEASED; urgency=medium
+golang-github-issue9-assert (3.0.4-1) UNRELEASED; urgency=medium
* Set upstream metadata fields: Repository-Browse.
+ * New upstream release.
- -- Debian Janitor <janitor@jelmer.uk> Thu, 10 Nov 2022 02:44:38 -0000
+ -- Debian Janitor <janitor@jelmer.uk> Tue, 03 Jan 2023 18:53:00 -0000
golang-github-issue9-assert (0.0~git20170908.0.ceac1aa-5) unstable; urgency=medium
diff --git a/doc.go b/doc.go
deleted file mode 100644
index 0ceefbf..0000000
--- a/doc.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 是对 testing 包的一些简单包装。
-// 方便在测试包里少写一点代码。
-//
-// 提供了两种操作方式:直接调用包函数;或是使用 Assertion 对象。
-// 两种方式完全等价,可以根据自己需要,选择一种。
-// func TestAssert(t *testing.T) {
-// var v interface{} = 5
-//
-// // 直接调用包函数
-// assert.True(t, v == 5, "v的值[%v]不等于5", v)
-// assert.Equal(t, 5, v, "v的值[%v]不等于5", v)
-// assert.Nil(t, v)
-//
-// // 以 Assertion 对象方式使用
-// a := assert.New(t)
-// a.True(v==5, "v的值[%v]不等于5", v)
-// a.Equal(5, v, "v的值[%v]不等于5", v)
-// a.Nil(v)
-// a.TB().Log("success")
-//
-// // 以函数链的形式调用 Assertion 对象的方法
-// a.True(false).Equal(5,6)
-// }
-// // 也可以对 testing.B 使用
-// func Benchmark1(b *testing.B) {
-// a := assert.New(b)
-// a.True(false)
-// for(i:=0; i<b.N; i++) {
-// // do something
-// }
-// }
-package assert
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..ec2ef52
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/issue9/assert/v3
+
+go 1.17
diff --git a/rest/request.go b/rest/request.go
new file mode 100644
index 0000000..cfc2f48
--- /dev/null
+++ b/rest/request.go
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: MIT
+
+package rest
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/issue9/assert/v3"
+)
+
+// Request 请求的参数封装
+type Request struct {
+ path string
+ method string
+ body io.Reader
+ queries url.Values
+ params map[string]string
+ headers map[string]string
+ a *assert.Assertion
+ client *http.Client
+}
+
+// NewRequest 获取一条请求的结果
+//
+// method 表示请求方法
+// path 表示请求的路径,域名部分无须填定。可以通过 {} 指定参数,比如:
+// r := NewRequest(http.MethodGet, "/users/{id}")
+// 之后就可以使用 Params 指定 id 的具体值,达到复用的目的:
+// resp1 := r.Param("id", "1").Do()
+// resp2 := r.Param("id", "2").Do()
+func (srv *Server) NewRequest(method, path string) *Request {
+ return NewRequest(srv.a, method, srv.URL()+path).Client(srv.client)
+}
+
+func (srv *Server) Get(path string) *Request {
+ return srv.NewRequest(http.MethodGet, path)
+}
+
+func (srv *Server) Put(path string, body []byte) *Request {
+ return srv.NewRequest(http.MethodPut, path).Body(body)
+}
+
+func (srv *Server) Post(path string, body []byte) *Request {
+ return srv.NewRequest(http.MethodPost, path).Body(body)
+}
+
+func (srv *Server) Patch(path string, body []byte) *Request {
+ return srv.NewRequest(http.MethodPatch, path).Body(body)
+}
+
+func (srv *Server) Delete(path string) *Request {
+ return srv.NewRequest(http.MethodDelete, path)
+}
+
+// NewRequest 以调用链的方式构建一个访问请求对象
+func NewRequest(a *assert.Assertion, method, path string) *Request {
+ return &Request{
+ a: a,
+ method: method,
+ path: path,
+ }
+}
+
+func Get(a *assert.Assertion, path string) *Request {
+ return NewRequest(a, http.MethodGet, path)
+}
+
+func Delete(a *assert.Assertion, path string) *Request {
+ return NewRequest(a, http.MethodDelete, path)
+}
+
+func Post(a *assert.Assertion, path string, body []byte) *Request {
+ return NewRequest(a, http.MethodPost, path).Body(body)
+}
+
+func Put(a *assert.Assertion, path string, body []byte) *Request {
+ return NewRequest(a, http.MethodPut, path).Body(body)
+}
+
+func Patch(a *assert.Assertion, path string, body []byte) *Request {
+ return NewRequest(a, http.MethodPatch, path).Body(body)
+}
+
+// Client 指定采用的客户端实例
+//
+// 可以为空,如果为空,那么在 Do 函数中的参数必不能为空。
+func (req *Request) Client(c *http.Client) *Request {
+ req.client = c
+ return req
+}
+
+// Query 添加一个请求参数
+func (req *Request) Query(key, val string) *Request {
+ if req.queries == nil {
+ req.queries = url.Values{}
+ }
+
+ req.queries.Add(key, val)
+
+ return req
+}
+
+// Header 指定请求时的报头
+func (req *Request) Header(key, val string) *Request {
+ if req.headers == nil {
+ req.headers = make(map[string]string, 5)
+ }
+
+ req.headers[key] = val
+
+ return req
+}
+
+// Param 替换参数
+func (req *Request) Param(key, val string) *Request {
+ if req.params == nil {
+ req.params = make(map[string]string, 5)
+ }
+
+ req.params[key] = val
+
+ return req
+}
+
+// Body 指定提交的内容
+func (req *Request) Body(body []byte) *Request {
+ req.body = bytes.NewReader(body)
+ return req
+}
+
+func (req *Request) StringBody(body string) *Request {
+ req.body = bytes.NewBufferString(body)
+ return req
+}
+
+// BodyFunc 指定一个未编码的对象
+//
+// marshal 对 obj 的编码函数,比如 json.Marshal 等。
+func (req *Request) BodyFunc(obj interface{}, marshal func(interface{}) ([]byte, error)) *Request {
+ req.a.TB().Helper()
+
+ data, err := marshal(obj)
+ req.a.NotError(err).NotNil(data)
+ return req.Body(data)
+}
+
+// JSONBody 指定一个 JSON 格式的 body
+//
+// NOTE: 此函并不会设置 content-type 报头。
+func (req *Request) JSONBody(obj interface{}) *Request {
+ return req.BodyFunc(obj, json.Marshal)
+}
+
+// XMLBody 指定一个 XML 格式的 body
+//
+// NOTE: 此函并不会设置 content-type 报头。
+func (req *Request) XMLBody(obj interface{}) *Request {
+ return req.BodyFunc(obj, xml.Marshal)
+}
+
+func (req *Request) buildPath() string {
+ path := req.path
+
+ for key, val := range req.params {
+ key = "{" + key + "}"
+ path = strings.Replace(path, key, val, -1)
+ }
+
+ if len(req.queries) > 0 {
+ path += ("?" + req.queries.Encode())
+ }
+
+ return path
+}
+
+// Request 返回标准库的 http.Request 实例
+func (req *Request) Request() *http.Request {
+ req.a.TB().Helper()
+
+ r, err := http.NewRequest(req.method, req.buildPath(), req.body)
+ req.a.NotError(err).NotNil(r)
+ r.Close = true
+
+ for k, v := range req.headers {
+ r.Header.Add(k, v)
+ }
+
+ return r
+}
diff --git a/rest/request_test.go b/rest/request_test.go
new file mode 100644
index 0000000..efaaaa2
--- /dev/null
+++ b/rest/request_test.go
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT
+
+package rest
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/issue9/assert/v3"
+)
+
+func TestRequest_buildPath(t *testing.T) {
+ srv := NewServer(assert.New(t, false), h, nil)
+ a := srv.Assertion()
+ a.NotNil(srv)
+
+ req := srv.NewRequest(http.MethodGet, "/get")
+ a.NotNil(req)
+ a.Equal(req.buildPath(), srv.URL()+"/get")
+
+ req.Param("id", "1").Query("page", "5")
+ a.Equal(req.buildPath(), srv.URL()+"/get?page=5")
+
+ req = srv.NewRequest(http.MethodGet, "/users/{id}/orders/{oid}")
+ a.NotNil(req)
+ a.Equal(req.buildPath(), srv.URL()+"/users/{id}/orders/{oid}")
+ req.Param("id", "1").Param("oid", "2").Query("page", "5")
+ a.Equal(req.buildPath(), srv.URL()+"/users/1/orders/2?page=5")
+}
diff --git a/rest/response.go b/rest/response.go
new file mode 100644
index 0000000..dbf1dfa
--- /dev/null
+++ b/rest/response.go
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: MIT
+
+package rest
+
+import (
+ "bytes"
+ "encoding/json"
+ "encoding/xml"
+ "errors"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+
+ "github.com/issue9/assert/v3"
+)
+
+// Response 测试请求的返回结构
+type Response struct {
+ resp *http.Response
+ a *assert.Assertion
+ body []byte
+}
+
+// Do 执行请求操作
+//
+// h 默认为空,如果不为空,则表示当前请求忽略 http.Client,而是访问 h.ServeHTTP 的内容。
+func (req *Request) Do(h http.Handler) *Response {
+ if req.client == nil && h == nil {
+ panic("h 不能为空")
+ }
+
+ req.a.TB().Helper()
+
+ r := req.Request()
+ var err error
+ var resp *http.Response
+ if h != nil {
+ w := httptest.NewRecorder()
+ h.ServeHTTP(w, r)
+ resp = w.Result()
+ } else {
+ resp, err = req.client.Do(r)
+ req.a.NotError(err).NotNil(resp)
+ }
+
+ var bs []byte
+ if resp.Body != nil {
+ bs, err = io.ReadAll(resp.Body)
+ if err != io.EOF {
+ req.a.NotError(err)
+ }
+ req.a.NotError(resp.Body.Close())
+ }
+
+ return &Response{
+ a: req.a,
+ resp: resp,
+ body: bs,
+ }
+}
+
+// Resp 返回 http.Response 实例
+//
+// NOTE: http.Response.Body 内容已经被读取且关闭。
+func (resp *Response) Resp() *http.Response { return resp.resp }
+
+func (resp *Response) assert(expr bool, f *assert.Failure) *Response {
+ resp.a.TB().Helper()
+ resp.a.Assert(expr, f)
+ return resp
+}
+
+// Success 状态码是否在 100-399 之间
+func (resp *Response) Success(msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ succ := resp.resp.StatusCode >= 100 && resp.resp.StatusCode < 400
+ return resp.assert(succ, assert.NewFailure("Success", msg, map[string]interface{}{"status": resp.resp.StatusCode}))
+}
+
+// Fail 状态码是否大于 399
+func (resp *Response) Fail(msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ fail := resp.resp.StatusCode >= 400
+ return resp.assert(fail, assert.NewFailure("Fail", msg, map[string]interface{}{"status": resp.resp.StatusCode}))
+}
+
+// Status 判断状态码是否与 status 相等
+func (resp *Response) Status(status int, msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ eq := resp.resp.StatusCode == status
+ return resp.assert(eq, assert.NewFailure("Status", msg, map[string]interface{}{"status1": resp.resp.StatusCode, "status2": status}))
+}
+
+// NotStatus 判断状态码是否与 status 不相等
+func (resp *Response) NotStatus(status int, msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ neq := resp.resp.StatusCode != status
+ return resp.assert(neq, assert.NewFailure("NotStatus", msg, map[string]interface{}{"status": resp.resp.StatusCode}))
+}
+
+// Header 判断指定的报头是否与 val 相同
+//
+// msg 可以为空,会返回一个默认的错误提示信息
+func (resp *Response) Header(key string, val string, msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ h := resp.resp.Header.Get(key)
+ return resp.assert(h == val, assert.NewFailure("Header", msg, map[string]interface{}{"header": key, "v1": h, "v2": val}))
+}
+
+// NotHeader 指定的报头必定不与 val 相同。
+func (resp *Response) NotHeader(key string, val string, msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ h := resp.resp.Header.Get(key)
+ return resp.assert(h != val, assert.NewFailure("NotHeader", msg, map[string]interface{}{"header": key, "v": h}))
+}
+
+// Body 断言内容与 val 相同
+func (resp *Response) Body(val []byte, msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ return resp.assert(bytes.Equal(resp.body, val), assert.NewFailure("Body", msg, map[string]interface{}{"v1": string(resp.body), "v2": string(val)}))
+}
+
+// StringBody 断言内容与 val 相同
+func (resp *Response) StringBody(val string, msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ b := string(resp.body)
+ return resp.assert(b == val, assert.NewFailure("StringBody", msg, map[string]interface{}{"v1": b, "v2": val}))
+}
+
+// BodyNotEmpty 报文内容是否不为空
+func (resp *Response) BodyNotEmpty(msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ return resp.assert(len(resp.body) > 0, assert.NewFailure("BodyNotEmpty", msg, nil))
+}
+
+// BodyEmpty 报文内容是否为空
+func (resp *Response) BodyEmpty(msg ...interface{}) *Response {
+ resp.a.TB().Helper()
+ return resp.assert(len(resp.body) == 0, assert.NewFailure("BodyEmpty", msg, map[string]interface{}{"v": resp.body}))
+}
+
+// JSONBody body 转换成 JSON 对象之后是否等价于 val
+func (resp *Response) JSONBody(val interface{}) *Response {
+ resp.a.TB().Helper()
+ return resp.BodyFunc(func(a *assert.Assertion, body []byte) {
+ a.TB().Helper()
+
+ // NOTE: 应当始终将 body 转换 val 相同的类型,然后再比较对象,
+ // 因为 val 转换成字符串,可能因为空格缩进等原因,未必会与 body 是相同的。
+ b, err := UnmarshalObject(body, val, json.Unmarshal)
+ a.NotError(err).Equal(b, val)
+ })
+}
+
+// XMLBody body 转换成 XML 对象之后是否等价于 val
+func (resp *Response) XMLBody(val interface{}) *Response {
+ resp.a.TB().Helper()
+ return resp.BodyFunc(func(a *assert.Assertion, body []byte) {
+ a.TB().Helper()
+
+ // NOTE: 应当始终将 body 转换 val 相同的类型,然后再比较对象,
+ // 因为 val 转换成字符串,可能因为空格缩进等原因,未必会与 body 是相同的。
+ b, err := UnmarshalObject(body, val, xml.Unmarshal)
+ a.NotError(err).Equal(b, val)
+ })
+}
+
+// BodyFunc 指定对 body 内容的断言方式
+func (resp *Response) BodyFunc(f func(a *assert.Assertion, body []byte)) *Response {
+ resp.a.TB().Helper()
+
+ b := make([]byte, len(resp.body))
+ copy(b, resp.body)
+ f(resp.a, b)
+
+ return resp
+}
+
+// UnmarshalObject 将 data 以 u 作为转换方式转换成与 val 相同的类型
+//
+// 如果 val 是指针,则会转换成其指向的类型,返回的对象是指针类型。
+func UnmarshalObject(data []byte, val interface{}, u func([]byte, interface{}) error) (interface{}, error) {
+ t := reflect.TypeOf(val)
+ for t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ bv := reflect.New(t)
+
+ if err := u(data, bv.Interface()); err != nil && !errors.Is(err, io.EOF) {
+ return nil, err
+ }
+ return bv.Interface(), nil
+}
diff --git a/rest/response_test.go b/rest/response_test.go
new file mode 100644
index 0000000..6e76b8c
--- /dev/null
+++ b/rest/response_test.go
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: MIT
+
+package rest
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/issue9/assert/v3"
+)
+
+func TestRequest_Do(t *testing.T) {
+ a := assert.New(t, false)
+ srv := NewServer(a, h, nil)
+
+ srv.Get("/get").
+ Do(nil).
+ Success().
+ Status(201)
+
+ srv.NewRequest(http.MethodGet, "/not-exists").
+ Do(nil).
+ Fail()
+
+ srv.NewRequest(http.MethodGet, "/get").
+ Do(BuildHandler(a, 202, "", nil)).
+ Status(202)
+
+ r := Get(a, "/get")
+ r.Do(BuildHandler(a, 202, "", nil)).Status(202)
+ r.Do(BuildHandler(a, 203, "", nil)).Status(203)
+ a.Panic(func() {
+ r.Do(nil)
+ })
+}
+
+func TestResponse(t *testing.T) {
+ srv := NewServer(assert.New(t, false), h, nil)
+
+ srv.NewRequest(http.MethodGet, "/body").
+ Header("content-type", "application/json").
+ Query("page", "5").
+ JSONBody(&bodyTest{ID: 5}).
+ Do(nil).
+ Status(http.StatusCreated).
+ NotStatus(http.StatusNotFound).
+ Header("content-type", "application/json;charset=utf-8").
+ NotHeader("content-type", "invalid value").
+ JSONBody(&bodyTest{ID: 6}).
+ Body([]byte(`{"id":6}`)).
+ StringBody(`{"id":6}`).
+ BodyNotEmpty()
+
+ srv.NewRequest(http.MethodGet, "/get").
+ Query("page", "5").
+ Do(nil).
+ Status(http.StatusCreated).
+ NotHeader("content-type", "invalid value").
+ BodyEmpty()
+
+ // xml
+
+ srv.NewRequest(http.MethodGet, "/body").
+ Header("content-type", "application/xml").
+ XMLBody(&bodyTest{ID: 5}).
+ Do(nil).
+ Success(http.StatusCreated).
+ Header("content-type", "application/xml;charset=utf-8").
+ XMLBody(&bodyTest{ID: 6})
+}
diff --git a/rest/rest.go b/rest/rest.go
new file mode 100644
index 0000000..b8312d0
--- /dev/null
+++ b/rest/rest.go
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: MIT
+
+// Package rest 简单的 API 测试库
+package rest
+
+import (
+ "bufio"
+ "bytes"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+
+ "github.com/issue9/assert/v3"
+)
+
+// BuildHandler 生成用于测试的 http.Handler 对象
+//
+// 仅是简单地按以下步骤输出内容:
+// - 输出状态码 code;
+// - 输出报头 headers,以 Add 方式,而不是 set,不会覆盖原来的数据;
+// - 输出 body,如果为空字符串,则不会输出;
+func BuildHandler(a *assert.Assertion, code int, body string, headers map[string]string) http.Handler {
+ return http.HandlerFunc(BuildHandlerFunc(a, code, body, headers))
+}
+
+func BuildHandlerFunc(a *assert.Assertion, code int, body string, headers map[string]string) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ a.TB().Helper()
+
+ for k, v := range headers {
+ w.Header().Add(k, v)
+ }
+ w.WriteHeader(code)
+
+ if body != "" {
+ _, err := w.Write([]byte(body))
+ a.NotError(err)
+ }
+ }
+}
+
+func (srv *Server) RawHTTP(req, resp string) *Server {
+ srv.Assertion().TB().Helper()
+ RawHTTP(srv.Assertion(), srv.client, req, resp)
+ return srv
+}
+
+// RawHTTP 通过原始数据进行比较请求和返回数据是符合要求
+//
+// reqRaw 表示原始的请求数据;
+// respRaw 表示返回之后的原始数据;
+//
+// NOTE: 仅判断状态码、报头和实际内容是否相同,而不是直接比较两个 http.Response 的值。
+func RawHTTP(a *assert.Assertion, client *http.Client, reqRaw, respRaw string) {
+ if client == nil {
+ client = &http.Client{}
+ }
+ a.TB().Helper()
+
+ r, resp := readRaw(a, reqRaw, respRaw)
+ if r == nil {
+ return
+ }
+
+ ret, err := client.Do(r)
+ a.NotError(err).NotNil(ret)
+
+ compare(a, resp, ret.StatusCode, ret.Header, ret.Body)
+ a.NotError(ret.Body.Close())
+}
+
+// RawHandler 通过原始数据进行比较请求和返回数据是符合要求
+//
+// 功能上与 RawHTTP 相似,处理方式从 http.Client 变成了 http.Handler。
+func RawHandler(a *assert.Assertion, h http.Handler, reqRaw, respRaw string) {
+ if h == nil {
+ panic("h 不能为空")
+ }
+ a.TB().Helper()
+
+ r, resp := readRaw(a, reqRaw, respRaw)
+ if r == nil {
+ return
+ }
+
+ ret := httptest.NewRecorder()
+ h.ServeHTTP(ret, r)
+
+ compare(a, resp, ret.Code, ret.Header(), ret.Body)
+}
+
+func readRaw(a *assert.Assertion, reqRaw, respRaw string) (*http.Request, *http.Response) {
+ a.TB().Helper()
+
+ resp, err := http.ReadResponse(bufio.NewReader(bytes.NewBufferString(respRaw)), nil)
+ a.NotError(err).NotNil(resp)
+
+ r, err := http.ReadRequest(bufio.NewReader(bytes.NewBufferString(reqRaw)))
+ a.NotError(err).NotNil(r)
+ u, err := url.Parse(r.Host + r.URL.String())
+ a.NotError(err).NotNil(u)
+ r.RequestURI = ""
+ r.URL = u
+
+ return r, resp
+}
+
+func compare(a *assert.Assertion, resp *http.Response, status int, header http.Header, body io.Reader) {
+ a.Equal(resp.StatusCode, status, "compare 断言失败,状态码的期望值 %d 与实际值 %d 不同", resp.StatusCode, status)
+
+ for k := range resp.Header {
+ respV := resp.Header.Get(k)
+ retV := header.Get(k)
+ a.Equal(respV, retV, "compare 断言失败,报头 %s 的期望值 %s 与实际值 %s 不相同", k, respV, retV)
+ }
+
+ retB, err := io.ReadAll(body)
+ a.NotError(err).NotNil(retB)
+ respB, err := io.ReadAll(resp.Body)
+ a.NotError(err).NotNil(respB)
+ retB = bytes.TrimSpace(retB)
+ respB = bytes.TrimSpace(respB)
+ a.Equal(respB, retB, "compare 断言失败,内容的期望值与实际值不相同\n%s\n\n%s\n", respB, retB)
+}
diff --git a/rest/rest_test.go b/rest/rest_test.go
new file mode 100644
index 0000000..ba1e431
--- /dev/null
+++ b/rest/rest_test.go
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: MIT
+
+package rest
+
+import (
+ "encoding/json"
+ "encoding/xml"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/issue9/assert/v3"
+)
+
+type bodyTest struct {
+ XMLName struct{} `json:"-" xml:"root"`
+ ID int `json:"id" xml:"id"`
+}
+
+var h = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/get" {
+ w.WriteHeader(http.StatusCreated)
+ return
+ }
+
+ if r.URL.Path == "/body" {
+ if r.Header.Get("content-type") == "application/json" {
+ b := &bodyTest{}
+ bs, err := io.ReadAll(r.Body)
+ if err != nil {
+ fmt.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ if err := json.Unmarshal(bs, b); err != nil {
+ fmt.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ b.ID++
+ bs, err = json.Marshal(b)
+ if err != nil {
+ fmt.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ w.Header().Add("content-Type", "application/json;charset=utf-8")
+ w.WriteHeader(http.StatusCreated)
+ w.Write(bs)
+ return
+ }
+
+ if r.Header.Get("content-type") == "application/xml" {
+ b := &bodyTest{}
+ bs, err := io.ReadAll(r.Body)
+ if err != nil {
+ fmt.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ if err := xml.Unmarshal(bs, b); err != nil {
+ fmt.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ b.ID++
+ bs, err = xml.Marshal(b)
+ if err != nil {
+ fmt.Println(err)
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ w.Header().Add("content-Type", "application/xml;charset=utf-8")
+ w.WriteHeader(http.StatusCreated)
+ w.Write(bs)
+ return
+ }
+
+ w.WriteHeader(http.StatusUnsupportedMediaType)
+ return
+ }
+
+ w.WriteHeader(http.StatusNotFound)
+})
+
+func TestBuildHandler(t *testing.T) {
+ a := assert.New(t, false)
+
+ h := BuildHandler(a, 201, "body", map[string]string{"k1": "v1"})
+ w := httptest.NewRecorder()
+ r, err := http.NewRequest(http.MethodGet, "/", nil)
+ a.NotError(err).NotNil(r)
+ h.ServeHTTP(w, r)
+ a.Equal(w.Code, 201).
+ Equal(w.Header().Get("k1"), "v1")
+}
+
+var raw = []*struct {
+ req, resp string
+}{
+ {
+ req: `GET /get HTTP/1.1
+Host: {host}
+
+`,
+ resp: `HTTP/1.1 201
+
+`,
+ },
+ {
+ req: `POST /body HTTP/1.1
+Host: {host}
+Content-Type: application/json
+Content-Length: 8
+
+{"id":5}
+
+`,
+ resp: `HTTP/1.1 201
+Content-Type: application/json;charset=utf-8
+
+{"id":6}
+
+`,
+ },
+ {
+ req: `DELETE /body?page=5 HTTP/1.0
+Host: {host}
+Content-Type: application/xml
+Content-Length: 23
+
+<root><id>6</id></root>
+
+`,
+ resp: `HTTP/1.0 201
+Content-Type: application/xml;charset=utf-8
+
+<root><id>7</id></root>
+
+`,
+ },
+}
+
+func TestServer_RawHTTP(t *testing.T) {
+ a := assert.New(t, true)
+ s := NewServer(a, h, nil)
+
+ for _, item := range raw {
+ req := strings.Replace(item.req, "{host}", s.URL(), 1)
+ s.RawHTTP(req, item.resp)
+ }
+}
+
+func TestRawHandler(t *testing.T) {
+ a := assert.New(t, true)
+ host := "http://localhost:88"
+
+ for _, item := range raw {
+ req := strings.Replace(item.req, "{host}", host, 1)
+ RawHandler(a, h, req, item.resp)
+ }
+}
diff --git a/rest/server.go b/rest/server.go
new file mode 100644
index 0000000..fbb3f22
--- /dev/null
+++ b/rest/server.go
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: MIT
+
+package rest
+
+import (
+ "net/http"
+ "net/http/httptest"
+
+ "github.com/issue9/assert/v3"
+)
+
+// Server 测试服务
+type Server struct {
+ a *assert.Assertion
+ server *httptest.Server
+ client *http.Client
+ closed bool
+}
+
+// NewServer 声明新的测试服务
+//
+// 如果 client 为 nil,则会采用 &http.Client{} 作为默认值
+func NewServer(a *assert.Assertion, h http.Handler, client *http.Client) *Server {
+ return newServer(a, httptest.NewServer(h), client)
+}
+
+// NewTLSServer 声明新的测试服务
+//
+// 如果 client 为 nil,则会采用 &http.Client{} 作为默认值
+func NewTLSServer(a *assert.Assertion, h http.Handler, client *http.Client) *Server {
+ return newServer(a, httptest.NewTLSServer(h), client)
+}
+
+func newServer(a *assert.Assertion, srv *httptest.Server, client *http.Client) *Server {
+ if client == nil {
+ client = &http.Client{}
+ }
+
+ s := &Server{
+ a: a,
+ server: srv,
+ client: client,
+ }
+
+ a.TB().Cleanup(func() {
+ s.Close()
+ })
+
+ return s
+}
+
+func (srv *Server) URL() string { return srv.server.URL }
+
+func (srv *Server) Assertion() *assert.Assertion { return srv.a }
+
+// Close 关闭服务
+//
+// 如果未手动调用,则在 testing.TB.Cleanup 中自动调用。
+func (srv *Server) Close() {
+ if srv.closed {
+ return
+ }
+
+ srv.server.Close()
+ srv.closed = true
+}
diff --git a/rest/server_test.go b/rest/server_test.go
new file mode 100644
index 0000000..38e7edb
--- /dev/null
+++ b/rest/server_test.go
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: MIT
+
+package rest
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/issue9/assert/v3"
+)
+
+func TestNew(t *testing.T) {
+ a := assert.New(t, false)
+
+ srv := NewTLSServer(a, nil, nil)
+ a.NotNil(srv)
+ a.Equal(srv.client, &http.Client{})
+ a.True(len(srv.server.URL) > 0)
+
+ client := &http.Client{}
+ srv = NewServer(a, nil, client)
+ a.NotNil(srv)
+ a.Equal(srv.client, client)
+
+ srv.Close()
+ a.True(srv.closed)
+ srv.Close()
+}
diff --git a/util.go b/util.go
index 9335ac8..ad7f99b 100644
--- a/util.go
+++ b/util.go
@@ -1,76 +1,48 @@
-// 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.
+// SPDX-License-Identifier: MIT
package assert
import (
"bytes"
+ "fmt"
"reflect"
"strings"
- "time"
)
-// IsEmpty 判断一个值是否为空(0, "", false, 空数组等)。
+// 判断一个值是否为空(0, "", false, 空数组等)。
// []string{""}空数组里套一个空字符串,不会被判断为空。
-func IsEmpty(expr interface{}) bool {
- if expr == nil {
+func isEmpty(expr interface{}) bool {
+ if isZero(expr) {
return true
}
- switch v := expr.(type) {
- case bool:
- return !v
- case int:
- return 0 == v
- case int8:
- return 0 == v
- case int16:
- return 0 == v
- case int32:
- return 0 == v
- case int64:
- return 0 == v
- case uint:
- return 0 == v
- case uint8:
- return 0 == v
- case uint16:
- return 0 == v
- case uint32:
- return 0 == v
- case uint64:
- return 0 == v
- case string:
- return len(v) == 0
- case float32:
- return 0 == v
- case float64:
- return 0 == v
- case time.Time:
- return v.IsZero()
- case *time.Time:
- return v.IsZero()
+ rv := reflect.ValueOf(expr)
+ for rv.Kind() == reflect.Ptr {
+ rv = rv.Elem()
+ }
+ switch rv.Kind() {
+ case reflect.Slice, reflect.Map, reflect.Array, reflect.Chan: // 长度为 0 的数组也是 empty
+ return rv.Len() == 0
+ default:
+ return false
}
+}
- // 符合IsNil条件的,都为Empty
- if IsNil(expr) {
+func isZero(v interface{}) bool {
+ if isNil(v) || reflect.ValueOf(v).IsZero() {
return true
}
- // 长度为0的数组也是empty
- v := reflect.ValueOf(expr)
- switch v.Kind() {
- case reflect.Slice, reflect.Map, reflect.Chan:
- return 0 == v.Len()
+ rv := reflect.ValueOf(v)
+ for rv.Kind() == reflect.Ptr {
+ rv = rv.Elem()
}
-
- return false
+ return rv.IsZero()
}
-// IsNil 判断一个值是否为 nil。
+// isNil 判断一个值是否为 nil。
// 当特定类型的变量,已经声明,但还未赋值时,也将返回 true
-func IsNil(expr interface{}) bool {
+func isNil(expr interface{}) bool {
if nil == expr {
return true
}
@@ -81,19 +53,20 @@ func IsNil(expr interface{}) bool {
return k >= reflect.Chan && k <= reflect.Slice && v.IsNil()
}
-// IsEqual 判断两个值是否相等。
+// 判断两个值是否相等
//
// 除了通过 reflect.DeepEqual() 判断值是否相等之外,一些类似
// 可转换的数值也能正确判断,比如以下值也将会被判断为相等:
-// int8(5) == int(5)
-// []int{1,2} == []int8{1,2}
-// []int{1,2} == [2]int8{1,2}
-// []int{1,2} == []float32{1,2}
-// map[string]int{"1":"2":2} == map[string]int8{"1":1,"2":2}
//
-// // map的键值不同,即使可相互转换也判断不相等。
-// map[int]int{1:1,2:2} != map[int8]int{1:1,2:2}
-func IsEqual(v1, v2 interface{}) bool {
+// int8(5) == int(5)
+// []int{1,2} == []int8{1,2}
+// []int{1,2} == [2]int8{1,2}
+// []int{1,2} == []float32{1,2}
+// map[string]int{"1":"2":2} == map[string]int8{"1":1,"2":2}
+//
+// // map 的键值不同,即使可相互转换也判断不相等。
+// map[int]int{1:1,2:2} != map[int8]int{1:1,2:2}
+func isEqual(v1, v2 interface{}) bool {
if reflect.DeepEqual(v1, v2) {
return true
}
@@ -101,9 +74,8 @@ func IsEqual(v1, v2 interface{}) bool {
vv1 := reflect.ValueOf(v1)
vv2 := reflect.ValueOf(v2)
- // NOTE: 这里返回false,而不是true
if !vv1.IsValid() || !vv2.IsValid() {
- return false
+ return vv1.IsValid() == vv2.IsValid()
}
if vv1 == vv2 {
@@ -113,32 +85,36 @@ func IsEqual(v1, v2 interface{}) bool {
vv1Type := vv1.Type()
vv2Type := vv2.Type()
- // 过滤掉已经在reflect.DeepEqual()进行处理的类型
+ if vv1Type.Comparable() && vv2Type.Comparable() && v1 == v2 {
+ return true
+ }
+
+ // 过滤掉已经在 reflect.DeepEqual() 进行处理的类型
switch vv1Type.Kind() {
case reflect.Struct, reflect.Ptr, reflect.Func, reflect.Interface:
return false
case reflect.Slice, reflect.Array:
- // vv2.Kind()与vv1的不相同
+ // vv2.Kind() 与 vv1 的不相同
if vv2.Kind() != reflect.Slice && vv2.Kind() != reflect.Array {
- // 虽然类型不同,但可以相互转换成vv1的,如:vv2是string,vv2是[]byte,
+ // 虽然类型不同,但可以相互转换成 vv1 的,如:vv2 是 string,vv2 是 []byte,
if vv2Type.ConvertibleTo(vv1Type) {
- return IsEqual(vv1.Interface(), vv2.Convert(vv1Type).Interface())
+ return isEqual(vv1.Interface(), vv2.Convert(vv1Type).Interface())
}
return false
}
- // reflect.DeepEqual()未考虑类型不同但是类型可转换的情况,比如:
- // []int{8,9} == []int8{8,9},此处重新对slice和array做比较处理。
+ // reflect.DeepEqual() 未考虑类型不同但是类型可转换的情况,比如:
+ // []int{8,9} == []int8{8,9},此处重新对 slice 和 array 做比较处理。
if vv1.Len() != vv2.Len() {
return false
}
for i := 0; i < vv1.Len(); i++ {
- if !IsEqual(vv1.Index(i).Interface(), vv2.Index(i).Interface()) {
+ if !isEqual(vv1.Index(i).Interface(), vv2.Index(i).Interface()) {
return false
}
}
- return true // for中所有的值比较都相等,返回true
+ return true // for 中所有的值比较都相等,返回 true
case reflect.Map:
if vv2.Kind() != reflect.Map {
return false
@@ -154,7 +130,7 @@ func IsEqual(v1, v2 interface{}) bool {
return true
}
- // 两个map的键名类型不同
+ // 两个 map 的键名类型不同
if vv2Type.Key().Kind() != vv1Type.Key().Kind() {
return false
}
@@ -165,17 +141,17 @@ func IsEqual(v1, v2 interface{}) bool {
return false
}
- if !IsEqual(vv1.MapIndex(index).Interface(), vv2Index.Interface()) {
+ if !isEqual(vv1.MapIndex(index).Interface(), vv2Index.Interface()) {
return false
}
}
- return true // for中所有的值比较都相等,返回true
+ return true // for 中所有的值比较都相等,返回 true
case reflect.String:
if vv2.Kind() == reflect.String {
return vv1.String() == vv2.String()
}
- if vv2Type.ConvertibleTo(vv1Type) { // 考虑v1是string,v2是[]byte的情况
- return IsEqual(vv1.Interface(), vv2.Convert(vv1Type).Interface())
+ if vv2Type.ConvertibleTo(vv1Type) { // 考虑 v1 是 string,v2 是 []byte 的情况
+ return isEqual(vv1.Interface(), vv2.Convert(vv1Type).Interface())
}
return false
@@ -190,9 +166,9 @@ func IsEqual(v1, v2 interface{}) bool {
return false
}
-// HasPanic 判断 fn 函数是否会发生 panic
+// hasPanic 判断 fn 函数是否会发生 panic
// 若发生了 panic,将把 msg 一起返回。
-func HasPanic(fn func()) (has bool, msg interface{}) {
+func hasPanic(fn func()) (has bool, msg interface{}) {
defer func() {
if msg = recover(); msg != nil {
has = true
@@ -203,14 +179,8 @@ func HasPanic(fn func()) (has bool, msg interface{}) {
return
}
-// IsContains 判断 container 是否包含了 item 的内容。若是指针,会判断指针指向的内容,
-// 但是不支持多重指针。
-//
-// 若 container 是字符串(string、[]byte和[]rune,不包含 fmt.Stringer 接口),
-// 都将会以字符串的形式判断其是否包含 item。
-// 若 container是个列表(array、slice、map)则判断其元素中是否包含 item 中的
-// 的所有项,或是 item 本身就是 container 中的一个元素。
-func IsContains(container, item interface{}) bool {
+// isContains 判断 container 是否包含了 item 的内容。若是指针,会判断指针指向的内容
+func isContains(container, item interface{}) bool {
if container == nil { // nil不包含任何东西
return false
}
@@ -218,15 +188,15 @@ func IsContains(container, item interface{}) bool {
cv := reflect.ValueOf(container)
iv := reflect.ValueOf(item)
- if cv.Kind() == reflect.Ptr {
+ for cv.Kind() == reflect.Ptr {
cv = cv.Elem()
}
- if iv.Kind() == reflect.Ptr {
+ for iv.Kind() == reflect.Ptr {
iv = iv.Elem()
}
- if IsEqual(container, item) {
+ if isEqual(container, item) {
return true
}
@@ -243,7 +213,7 @@ func IsContains(container, item interface{}) bool {
case byte:
return bytes.IndexByte([]byte(c), i) != -1
case rune:
- return bytes.IndexRune([]byte(c), i) != -1
+ return bytes.ContainsRune([]byte(c), i)
}
case []byte:
switch i := iv.Interface().(type) {
@@ -256,12 +226,12 @@ func IsContains(container, item interface{}) bool {
case byte:
return bytes.IndexByte(c, i) != -1
case rune:
- return bytes.IndexRune(c, i) != -1
+ return bytes.ContainsRune(c, i)
}
case []rune:
switch i := iv.Interface().(type) {
case string:
- return strings.Contains(string(c), string(i))
+ return strings.Contains(string(c), i)
case []byte:
return strings.Contains(string(c), string(i))
case []rune:
@@ -269,7 +239,7 @@ func IsContains(container, item interface{}) bool {
case byte:
return strings.IndexByte(string(c), i) != -1
case rune:
- return strings.IndexRune(string(c), i) != -1
+ return strings.ContainsRune(string(c), i)
}
}
@@ -284,7 +254,7 @@ func IsContains(container, item interface{}) bool {
// item 是 container 的一个元素
for i := 0; i < cv.Len(); i++ {
- if IsEqual(cv.Index(i).Interface(), iv.Interface()) {
+ if isEqual(cv.Index(i).Interface(), iv.Interface()) {
return true
}
}
@@ -296,7 +266,7 @@ func IsContains(container, item interface{}) bool {
return false
}
- // item 的元素比 container 的元素多,必须在判断完 item 不是 container 中的一个元素之
+ // item 的元素比 container 的元素多
if iv.Len() > cv.Len() {
return false
}
@@ -304,7 +274,7 @@ func IsContains(container, item interface{}) bool {
// 依次比较 item 的各个子元素是否都存在于 container,且下标都相同
ivIndex := 0
for i := 0; i < cv.Len(); i++ {
- if IsEqual(cv.Index(i).Interface(), iv.Index(ivIndex).Interface()) {
+ if isEqual(cv.Index(i).Interface(), iv.Index(ivIndex).Interface()) {
if (ivIndex == 0) && (i+iv.Len() > cv.Len()) {
return false
}
@@ -334,11 +304,11 @@ func IsContains(container, item interface{}) bool {
// 判断所有 item 的项都存在于 container 中
for _, key := range iv.MapKeys() {
- cvItem := iv.MapIndex(key)
+ cvItem := cv.MapIndex(key)
if !cvItem.IsValid() { // container 中不包含该值。
return false
}
- if !IsEqual(cvItem.Interface(), iv.MapIndex(key).Interface()) {
+ if !isEqual(cvItem.Interface(), iv.MapIndex(key).Interface()) {
return false
}
}
@@ -348,3 +318,36 @@ func IsContains(container, item interface{}) bool {
return false
}
+
+func getLen(v interface{}) (l int, msg string) {
+ r := reflect.ValueOf(v)
+ for r.Kind() == reflect.Ptr {
+ r = r.Elem()
+ }
+
+ if v == nil {
+ return 0, ""
+ }
+
+ switch r.Kind() {
+ case reflect.Array, reflect.String, reflect.Slice, reflect.Map:
+ return r.Len(), ""
+ }
+ return 0, fmt.Sprintf("无法获取 %s 类型的长度信息", r.Kind())
+}
+
+func getType(ptr bool, v1, v2 interface{}) (t1, t2 reflect.Type) {
+ t1 = reflect.TypeOf(v1)
+ t2 = reflect.TypeOf(v2)
+
+ if ptr {
+ for t1.Kind() == reflect.Ptr {
+ t1 = t1.Elem()
+ }
+ for t2.Kind() == reflect.Ptr {
+ t2 = t2.Elem()
+ }
+ }
+
+ return
+}
diff --git a/util_test.go b/util_test.go
index c35600b..e56a9c3 100644
--- a/util_test.go
+++ b/util_test.go
@@ -1,22 +1,37 @@
-// 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.
+// SPDX-License-Identifier: MIT
package assert
import (
"testing"
+ "time"
)
+func TestIsZero(t *testing.T) {
+ zero := func(v interface{}) {
+ t.Helper()
+ if !isZero(v) {
+ t.Errorf("zero: %v", v)
+ }
+ }
+
+ zero(nil)
+ zero(struct{}{})
+ zero(time.Time{})
+ zero(&time.Time{})
+}
+
func TestIsEqual(t *testing.T) {
eq := func(v1, v2 interface{}) {
- if !IsEqual(v1, v2) {
+ t.Helper()
+ if !isEqual(v1, v2) {
t.Errorf("eq:[%v]!=[%v]", v1, v2)
}
}
neq := func(v1, v2 interface{}) {
- if IsEqual(v1, v2) {
+ t.Helper()
+ if isEqual(v1, v2) {
t.Errorf("eq:[%v]==[%v]", v1, v2)
}
}
@@ -42,29 +57,29 @@ func TestIsEqual(t *testing.T) {
// 比较两个元素类型可相互转换的数组
eq(
[][]int{
- []int{1, 2},
- []int{3, 4},
+ {1, 2},
+ {3, 4},
},
[][]int8{
- []int8{1, 2},
- []int8{3, 4},
+ {1, 2},
+ {3, 4},
},
)
- // 比较两个元素类型可转换的map
+ // 比较两个元素类型可转换的 map
eq(
[]map[int]int{
- map[int]int{1: 1, 2: 2},
- map[int]int{3: 3, 4: 4},
+ {1: 1, 2: 2},
+ {3: 3, 4: 4},
},
[]map[int]int8{
- map[int]int8{1: 1, 2: 2},
- map[int]int8{3: 3, 4: 4},
+ {1: 1, 2: 2},
+ {3: 3, 4: 4},
},
)
eq(map[string]int{"1": 1, "2": 2}, map[string]int8{"1": 1, "2": 2})
- // 比较两个元素类型可转换的map
+ // 比较两个元素类型可转换的 map
eq(
map[int]string{
1: "1",
@@ -88,61 +103,99 @@ func TestIsEqual(t *testing.T) {
neq(true, "true")
neq(true, 1)
neq(true, "1")
- // 判断包含不同键名的两个map
+ // 判断包含不同键名的两个 map
neq(map[int]int{1: 1, 2: 2}, map[int]int{5: 5, 6: 6})
+
+ // time
+ loc := time.FixedZone("utf+8", 8*3600)
+ now := time.Now()
+ eq(time.Time{}, time.Time{})
+ neq(now.In(loc), now.In(time.UTC)) // 时区不同
+ n1 := time.Now()
+ n2 := n1.Add(0)
+ eq(n1, n2)
+
+ // 指针
+ v1 := 5
+ v2 := 5
+ p1 := &v1
+ p2 := &v1
+ eq(p1, p2) // 指针相等
+ p2 = &v2
+ eq(p1, p2) // 指向内容相等
}
func TestIsEmpty(t *testing.T) {
- if IsEmpty([]string{""}) {
- t.Error("IsEmpty([]string{\"\"})")
+ if isEmpty([]string{""}) {
+ t.Error("isEmpty([]string{\"\"})")
+ }
+
+ if !isEmpty([]string{}) {
+ t.Error("isEmpty([]string{})")
+ }
+
+ if !isEmpty([]int{}) {
+ t.Error("isEmpty([]int{})")
+ }
+
+ if !isEmpty(map[string]int{}) {
+ t.Error("isEmpty(map[string]int{})")
+ }
+
+ if !isEmpty(0) {
+ t.Error("isEmpty(0)")
+ }
+
+ if !isEmpty(int64(0)) {
+ t.Error("isEmpty(int64(0))")
}
- if !IsEmpty([]string{}) {
- t.Error("IsEmpty([]string{})")
+ if !isEmpty(uint64(0)) {
+ t.Error("isEmpty(uint64(0))")
}
- if !IsEmpty([]int{}) {
- t.Error("IsEmpty([]int{})")
+ if !isEmpty(0.0) {
+ t.Error("isEmpty(0.0)")
}
- if !IsEmpty(map[string]int{}) {
- t.Error("IsEmpty(map[string]int{})")
+ if !isEmpty(float32(0)) {
+ t.Error("isEmpty(0.0)")
}
- if !IsEmpty(0) {
- t.Error("IsEmpty(0)")
+ if !isEmpty("") {
+ t.Error("isEmpty(``)")
}
- if !IsEmpty(uint64(0)) {
- t.Error("IsEmpty(uint64(0))")
+ if !isEmpty([0]int{}) {
+ t.Error("isEmpty([0]int{})")
}
- if !IsEmpty(0.0) {
- t.Error("IsEmpty(0.0)")
+ if !isEmpty(time.Time{}) {
+ t.Error("isEmpty(time.Time{})")
}
- if !IsEmpty("") {
- t.Error("IsEmpty(``)")
+ if !isEmpty(&time.Time{}) {
+ t.Error("isEmpty(&time.Time{})")
}
- if IsEmpty(" ") {
- t.Error("IsEmpty(\" \")")
+ if isEmpty(" ") {
+ t.Error("isEmpty(\" \")")
}
}
func TestIsNil(t *testing.T) {
- if !IsNil(nil) {
- t.Error("IsNil(nil)")
+ if !isNil(nil) {
+ t.Error("isNil(nil)")
}
var v1 []int
- if !IsNil(v1) {
- t.Error("IsNil(v1)")
+ if !isNil(v1) {
+ t.Error("isNil(v1)")
}
var v2 map[string]string
- if !IsNil(v2) {
- t.Error("IsNil(v2)")
+ if !isNil(v2) {
+ t.Error("isNil(v2)")
}
}
@@ -151,7 +204,7 @@ func TestHasPanic(t *testing.T) {
panic("panic")
}
- if has, _ := HasPanic(f1); !has {
+ if has, _ := hasPanic(f1); !has {
t.Error("f1未发生panic")
}
@@ -159,7 +212,7 @@ func TestHasPanic(t *testing.T) {
f1()
}
- if has, msg := HasPanic(f2); !has {
+ if has, msg := hasPanic(f2); !has {
t.Error("f2未发生panic")
} else if msg != "panic" {
t.Errorf("f2发生了panic,但返回信息不正确,应为[panic],但其实返回了%v", msg)
@@ -175,7 +228,7 @@ func TestHasPanic(t *testing.T) {
f1()
}
- if has, msg := HasPanic(f3); has {
+ if has, msg := hasPanic(f3); has {
t.Errorf("f3发生了panic,其信息为:[%v]", msg)
}
@@ -183,25 +236,44 @@ func TestHasPanic(t *testing.T) {
//todo
}
- if has, msg := HasPanic(f4); has {
+ if has, msg := hasPanic(f4); has {
t.Errorf("f4发生panic,其信息为[%v]", msg)
}
}
func TestIsContains(t *testing.T) {
fn := func(result bool, container, item interface{}) {
- if result != IsContains(container, item) {
- t.Errorf("%v == (IsContains%v, %v)出错\n", result, container, item)
+ t.Helper()
+ if result != isContains(container, item) {
+ t.Errorf("%v == (isContains(%v, %v))出错\n", result, container, item)
}
}
fn(false, nil, nil)
fn(true, "abc", "a")
- fn(true, "abc", 'a') // string vs byte
- fn(true, "abc", rune('a')) // string vs rune
fn(true, "abc", "c")
fn(true, "abc", "bc")
+ fn(true, "abc", byte('a')) // string vs byte
+ fn(true, "abc", rune('a')) // string vs rune
+ fn(true, "abc", []byte("ab")) // string vs []byte
+ fn(true, "abc", []rune("ab")) // string vs []rune
+
+ fn(true, []byte("abc"), "a")
+ fn(true, []byte("abc"), "c")
+ fn(true, []byte("abc"), "bc")
+ fn(true, []byte("abc"), byte('a'))
+ fn(true, []byte("abc"), rune('a'))
+ fn(true, []byte("abc"), []byte("ab"))
+ fn(true, []byte("abc"), []rune("ab"))
+
+ fn(true, []rune("abc"), "a")
+ fn(true, []rune("abc"), "c")
+ fn(true, []rune("abc"), "bc")
+ fn(true, []rune("abc"), byte('a'))
+ fn(true, []rune("abc"), rune('a'))
+ fn(true, []rune("abc"), []byte("ab"))
+ fn(true, []rune("abc"), []rune("ab"))
fn(true, "中文a", "中")
fn(true, "中文a", "a")
@@ -216,17 +288,21 @@ func TestIsContains(t *testing.T) {
fn(true, map[string]int{"1": 1, "2": 2}, map[string]int8{"1": 1})
fn(true,
map[string][]int{
- "1": []int{1, 2, 3},
- "2": []int{4, 5, 6},
+ "1": {1, 2, 3},
+ "2": {4, 5, 6},
},
map[string][]int8{
- "1": []int8{1, 2, 3},
- "2": []int8{4, 5, 6},
+ "1": {1, 2, 3},
+ "2": {4, 5, 6},
},
)
fn(false, map[string]int{}, nil)
fn(false, map[string]int{"1": 1, "2": 2}, map[string]int8{})
+ fn(false, map[string]int{"1": 1, "2": 2}, map[string]int8{"1": 110}) // 同键名,不同值
+ fn(false, map[string]int{"1": 1, "2": 2}, map[string]int8{"5": 5})
fn(false, []int{1, 2, 3}, nil)
fn(false, []int{1, 2, 3}, []int8{1, 3})
+ fn(false, []int{1, 2, 3}, []int{1, 2, 3, 4})
+ fn(false, []int{}, []int{1}) // 空数组
}
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/go.mod -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/request.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/request_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/response.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/response_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/rest.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/rest_test.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/server.go -rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/rest/server_test.go
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/share/gocode/src/github.com/issue9/assert/doc.go
No differences were encountered in the control files