mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-11 16:59:59 +08:00
Compare commits
101 Commits
tools/goct
...
v1.5.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
452c9dbcaf | ||
|
|
3564e36a35 | ||
|
|
e479e47634 | ||
|
|
ad921a6419 | ||
|
|
44c8d6f269 | ||
|
|
8a4cc4f98d | ||
|
|
e751736516 | ||
|
|
032f2419a2 | ||
|
|
84adc054bc | ||
|
|
b92e706ce1 | ||
|
|
1b5946346e | ||
|
|
28d3905731 | ||
|
|
3726851c7f | ||
|
|
2f2ddd373b | ||
|
|
8d48e34eed | ||
|
|
32f78668db | ||
|
|
cd0f3726ed | ||
|
|
0217044900 | ||
|
|
8b4382dcec | ||
|
|
fa33329a44 | ||
|
|
d76a39ac26 | ||
|
|
76a7a17e57 | ||
|
|
4a2a8d9e45 | ||
|
|
ef26b39b4c | ||
|
|
3ca40001b4 | ||
|
|
278ae3d26a | ||
|
|
fa1d6d50a8 | ||
|
|
0f4973be06 | ||
|
|
a9aac7e420 | ||
|
|
925cf8d3d1 | ||
|
|
99ce24e2ab | ||
|
|
701bb31ed2 | ||
|
|
55e2c7ee83 | ||
|
|
90839965fa | ||
|
|
f7228e9af1 | ||
|
|
f95adae3c1 | ||
|
|
bff5b81ad9 | ||
|
|
f0bdfb928f | ||
|
|
e4a1b7bb39 | ||
|
|
b6906b5d21 | ||
|
|
116da96178 | ||
|
|
9fa98c2bd3 | ||
|
|
b1c4c4736f | ||
|
|
ef410e8083 | ||
|
|
c22bc1c8ea | ||
|
|
1853428011 | ||
|
|
3637e10815 | ||
|
|
93124329ac | ||
|
|
851a72f1cc | ||
|
|
a93c24ce84 | ||
|
|
9f42eda9ff | ||
|
|
8762a3b7ba | ||
|
|
2684a157ff | ||
|
|
63368d8b0c | ||
|
|
4f13fe8188 | ||
|
|
9fc7874336 | ||
|
|
e6518521eb | ||
|
|
8f5a0a2de7 | ||
|
|
774e8d1d08 | ||
|
|
8ad0668612 | ||
|
|
8a043d2443 | ||
|
|
0e2ee97a02 | ||
|
|
42300a7d83 | ||
|
|
fe97fab274 | ||
|
|
f93e752f98 | ||
|
|
3a66fc038f | ||
|
|
b028ed058d | ||
|
|
1fd0c3992b | ||
|
|
1aebb3e5e4 | ||
|
|
8ffe4c01d1 | ||
|
|
a31256b327 | ||
|
|
14caf5c799 | ||
|
|
c0f8a58ed7 | ||
|
|
3189ec7be6 | ||
|
|
f51e9f0ea7 | ||
|
|
ba9d510cdb | ||
|
|
8c9b619199 | ||
|
|
49f73265b9 | ||
|
|
7568674b2b | ||
|
|
3da740b7fc | ||
|
|
ce4eb6ed61 | ||
|
|
9970ff55cd | ||
|
|
d10740f871 | ||
|
|
027193dc99 | ||
|
|
de1e0f2410 | ||
|
|
062073ce58 | ||
|
|
e20b02f311 | ||
|
|
02357d2616 | ||
|
|
489d69f779 | ||
|
|
117611a170 | ||
|
|
0a46ad7ac1 | ||
|
|
bf905eaff3 | ||
|
|
88cb35e3d5 | ||
|
|
078825b4eb | ||
|
|
bbfce6abe9 | ||
|
|
0d11ce03a8 | ||
|
|
757ed19dc5 | ||
|
|
c5fd074aac | ||
|
|
8fa0bd1f1c | ||
|
|
ede19a89ec | ||
|
|
73664b92f0 |
@@ -3,4 +3,7 @@ comment:
|
||||
behavior: once
|
||||
require_changes: true
|
||||
ignore:
|
||||
- "tools"
|
||||
- "tools"
|
||||
- "**/mock"
|
||||
- "**/*_mock.go"
|
||||
- "**/*test"
|
||||
|
||||
9
.github/workflows/go.yml
vendored
9
.github/workflows/go.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.18
|
||||
go-version: 1.18
|
||||
check-latest: true
|
||||
cache: true
|
||||
id: go
|
||||
@@ -30,11 +30,6 @@ jobs:
|
||||
run: |
|
||||
go vet -stdmethods=false $(go list ./...)
|
||||
|
||||
if ! test -z "$(gofmt -l .)"; then
|
||||
echo "Please run 'gofmt -l -w .'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go mod tidy
|
||||
if ! test -z "$(git status --porcelain)"; then
|
||||
echo "Please run 'go mod tidy'"
|
||||
@@ -66,5 +61,5 @@ jobs:
|
||||
run: |
|
||||
go mod verify
|
||||
go mod download
|
||||
go test -v -race ./...
|
||||
go test ./...
|
||||
cd tools/goctl && go build -v goctl.go
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,9 +14,10 @@
|
||||
**/.idea
|
||||
**/.DS_Store
|
||||
**/logs
|
||||
**/adhoc
|
||||
**/coverage.txt
|
||||
|
||||
# for test purpose
|
||||
**/adhoc
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package codec
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -41,6 +42,7 @@ func TestCryption(t *testing.T) {
|
||||
|
||||
file, err := fs.TempFilenameWithText(priKey)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(file)
|
||||
dec, err := NewRsaDecrypter(file)
|
||||
assert.Nil(t, err)
|
||||
actual, err := dec.Decrypt(ret)
|
||||
|
||||
@@ -73,7 +73,7 @@ func LoadConfig(file string, v any, opts ...Option) error {
|
||||
|
||||
// LoadFromJsonBytes loads config into v from content json bytes.
|
||||
func LoadFromJsonBytes(content []byte, v any) error {
|
||||
info, err := buildFieldsInfo(reflect.TypeOf(v))
|
||||
info, err := buildFieldsInfo(reflect.TypeOf(v), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -127,13 +127,13 @@ func MustLoad(path string, v any, opts ...Option) {
|
||||
}
|
||||
}
|
||||
|
||||
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo) error {
|
||||
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName string) error {
|
||||
if prev, ok := info.children[key]; ok {
|
||||
if child.mapField != nil {
|
||||
return newConflictKeyError(key)
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
if err := mergeFields(prev, key, child.children); err != nil {
|
||||
if err := mergeFields(prev, key, child.children, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -143,27 +143,27 @@ func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type) error {
|
||||
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
|
||||
switch ft.Kind() {
|
||||
case reflect.Struct:
|
||||
fields, err := buildFieldsInfo(ft)
|
||||
fields, err := buildFieldsInfo(ft, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range fields.children {
|
||||
if err = addOrMergeFields(info, k, v); err != nil {
|
||||
if err = addOrMergeFields(info, k, v, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()))
|
||||
elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := info.children[lowerCaseName]; ok {
|
||||
return newConflictKeyError(lowerCaseName)
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
info.children[lowerCaseName] = &fieldInfo{
|
||||
@@ -172,7 +172,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
|
||||
}
|
||||
default:
|
||||
if _, ok := info.children[lowerCaseName]; ok {
|
||||
return newConflictKeyError(lowerCaseName)
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
info.children[lowerCaseName] = &fieldInfo{
|
||||
@@ -183,14 +183,14 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
|
||||
func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
|
||||
tp = mapping.Deref(tp)
|
||||
|
||||
switch tp.Kind() {
|
||||
case reflect.Struct:
|
||||
return buildStructFieldsInfo(tp)
|
||||
return buildStructFieldsInfo(tp, fullName)
|
||||
case reflect.Array, reflect.Slice:
|
||||
return buildFieldsInfo(mapping.Deref(tp.Elem()))
|
||||
return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName)
|
||||
case reflect.Chan, reflect.Func:
|
||||
return nil, fmt.Errorf("unsupported type: %s", tp.Kind())
|
||||
default:
|
||||
@@ -200,23 +200,23 @@ func buildFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type) error {
|
||||
func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
|
||||
var finfo *fieldInfo
|
||||
var err error
|
||||
|
||||
switch ft.Kind() {
|
||||
case reflect.Struct:
|
||||
finfo, err = buildFieldsInfo(ft)
|
||||
finfo, err = buildFieldsInfo(ft, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
finfo, err = buildFieldsInfo(ft.Elem())
|
||||
finfo, err = buildFieldsInfo(ft.Elem(), fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Map:
|
||||
elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()))
|
||||
elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -226,16 +226,16 @@ func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type)
|
||||
mapField: elemInfo,
|
||||
}
|
||||
default:
|
||||
finfo, err = buildFieldsInfo(ft)
|
||||
finfo, err = buildFieldsInfo(ft, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return addOrMergeFields(info, lowerCaseName, finfo)
|
||||
return addOrMergeFields(info, lowerCaseName, finfo, fullName)
|
||||
}
|
||||
|
||||
func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
|
||||
func buildStructFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
|
||||
info := &fieldInfo{
|
||||
children: make(map[string]*fieldInfo),
|
||||
}
|
||||
@@ -251,10 +251,12 @@ func buildStructFieldsInfo(tp reflect.Type) (*fieldInfo, error) {
|
||||
ft := mapping.Deref(field.Type)
|
||||
// flatten anonymous fields
|
||||
if field.Anonymous {
|
||||
if err := buildAnonymousFieldInfo(info, lowerCaseName, ft); err != nil {
|
||||
if err := buildAnonymousFieldInfo(info, lowerCaseName, ft,
|
||||
getFullName(fullName, lowerCaseName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err := buildNamedFieldInfo(info, lowerCaseName, ft); err != nil {
|
||||
} else if err := buildNamedFieldInfo(info, lowerCaseName, ft,
|
||||
getFullName(fullName, lowerCaseName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -279,15 +281,15 @@ func getTagName(field reflect.StructField) string {
|
||||
return field.Name
|
||||
}
|
||||
|
||||
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo) error {
|
||||
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo, fullName string) error {
|
||||
if len(prev.children) == 0 || len(children) == 0 {
|
||||
return newConflictKeyError(key)
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
// merge fields
|
||||
for k, v := range children {
|
||||
if _, ok := prev.children[k]; ok {
|
||||
return newConflictKeyError(k)
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
prev.children[k] = v
|
||||
@@ -349,3 +351,11 @@ func newConflictKeyError(key string) conflictKeyError {
|
||||
func (e conflictKeyError) Error() string {
|
||||
return fmt.Sprintf("conflict key %s, pay attention to anonymous fields", e.key)
|
||||
}
|
||||
|
||||
func getFullName(parent, child string) string {
|
||||
if len(parent) == 0 {
|
||||
return child
|
||||
}
|
||||
|
||||
return strings.Join([]string{parent, child}, ".")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package conf
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -34,11 +35,11 @@ func TestConfigJson(t *testing.T) {
|
||||
"c": "${FOO}",
|
||||
"d": "abcd!@#$112"
|
||||
}`
|
||||
t.Setenv("FOO", "2")
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test, func(t *testing.T) {
|
||||
os.Setenv("FOO", "2")
|
||||
defer os.Unsetenv("FOO")
|
||||
tmpfile, err := createTempFile(test, text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
@@ -80,8 +81,7 @@ b = 1
|
||||
c = "${FOO}"
|
||||
d = "abcd!@#$112"
|
||||
`
|
||||
os.Setenv("FOO", "2")
|
||||
defer os.Unsetenv("FOO")
|
||||
t.Setenv("FOO", "2")
|
||||
tmpfile, err := createTempFile(".toml", text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
@@ -206,8 +206,7 @@ b = 1
|
||||
c = "${FOO}"
|
||||
d = "abcd!@#112"
|
||||
`
|
||||
os.Setenv("FOO", "2")
|
||||
defer os.Unsetenv("FOO")
|
||||
t.Setenv("FOO", "2")
|
||||
tmpfile, err := createTempFile(".toml", text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
@@ -238,11 +237,10 @@ func TestConfigJsonEnv(t *testing.T) {
|
||||
"c": "${FOO}",
|
||||
"d": "abcd!@#$a12 3"
|
||||
}`
|
||||
t.Setenv("FOO", "2")
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test, func(t *testing.T) {
|
||||
os.Setenv("FOO", "2")
|
||||
defer os.Unsetenv("FOO")
|
||||
tmpfile, err := createTempFile(test, text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
@@ -733,7 +731,7 @@ func Test_FieldOverwrite(t *testing.T) {
|
||||
input := []byte(`{"Name": "hello"}`)
|
||||
err := LoadFromJsonBytes(input, val)
|
||||
assert.ErrorAs(t, err, &dupErr)
|
||||
assert.Equal(t, newConflictKeyError("name").Error(), err.Error())
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
validate(&St0{})
|
||||
@@ -1196,6 +1194,100 @@ Email = "bar"`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getFullName(t *testing.T) {
|
||||
assert.Equal(t, "a.b", getFullName("a", "b"))
|
||||
assert.Equal(t, "a", getFullName("", "a"))
|
||||
}
|
||||
|
||||
func Test_buildFieldsInfo(t *testing.T) {
|
||||
type ParentSt struct {
|
||||
Name string
|
||||
M map[string]int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
t reflect.Type
|
||||
ok bool
|
||||
containsKey string
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
t: reflect.TypeOf(struct{ A string }{}),
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "struct anonymous",
|
||||
t: reflect.TypeOf(struct {
|
||||
ParentSt
|
||||
Name string
|
||||
}{}),
|
||||
ok: false,
|
||||
containsKey: newConflictKeyError("name").Error(),
|
||||
},
|
||||
{
|
||||
name: "struct ptr anonymous",
|
||||
t: reflect.TypeOf(struct {
|
||||
*ParentSt
|
||||
Name string
|
||||
}{}),
|
||||
ok: false,
|
||||
containsKey: newConflictKeyError("name").Error(),
|
||||
},
|
||||
{
|
||||
name: "more struct anonymous",
|
||||
t: reflect.TypeOf(struct {
|
||||
Value struct {
|
||||
ParentSt
|
||||
Name string
|
||||
}
|
||||
}{}),
|
||||
ok: false,
|
||||
containsKey: newConflictKeyError("value.name").Error(),
|
||||
},
|
||||
{
|
||||
name: "map anonymous",
|
||||
t: reflect.TypeOf(struct {
|
||||
ParentSt
|
||||
M string
|
||||
}{}),
|
||||
ok: false,
|
||||
containsKey: newConflictKeyError("m").Error(),
|
||||
},
|
||||
{
|
||||
name: "map more anonymous",
|
||||
t: reflect.TypeOf(struct {
|
||||
Value struct {
|
||||
ParentSt
|
||||
M string
|
||||
}
|
||||
}{}),
|
||||
ok: false,
|
||||
containsKey: newConflictKeyError("value.m").Error(),
|
||||
},
|
||||
{
|
||||
name: "struct slice anonymous",
|
||||
t: reflect.TypeOf([]struct {
|
||||
ParentSt
|
||||
Name string
|
||||
}{}),
|
||||
ok: false,
|
||||
containsKey: newConflictKeyError("name").Error(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := buildFieldsInfo(tt.t, "")
|
||||
if tt.ok {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err.Error(), tt.containsKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createTempFile(ext, text string) (string, error) {
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
|
||||
if err != nil {
|
||||
|
||||
@@ -45,8 +45,7 @@ func TestPropertiesEnv(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
|
||||
os.Setenv("FOO", "2")
|
||||
defer os.Unsetenv("FOO")
|
||||
t.Setenv("FOO", "2")
|
||||
|
||||
props, err := LoadProperties(tmpfile, UseEnv())
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -337,13 +337,11 @@ func (c *cluster) watchConnState(cli EtcdClient) {
|
||||
// DialClient dials an etcd cluster with given endpoints.
|
||||
func DialClient(endpoints []string) (EtcdClient, error) {
|
||||
cfg := clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
AutoSyncInterval: autoSyncInterval,
|
||||
DialTimeout: DialTimeout,
|
||||
DialKeepAliveTime: dialKeepAliveTime,
|
||||
DialKeepAliveTimeout: DialTimeout,
|
||||
RejectOldCluster: true,
|
||||
PermitWithoutStream: true,
|
||||
Endpoints: endpoints,
|
||||
AutoSyncInterval: autoSyncInterval,
|
||||
DialTimeout: DialTimeout,
|
||||
RejectOldCluster: true,
|
||||
PermitWithoutStream: true,
|
||||
}
|
||||
if account, ok := GetAccount(endpoints); ok {
|
||||
cfg.Username = account.User
|
||||
|
||||
@@ -9,7 +9,6 @@ const (
|
||||
autoSyncInterval = time.Minute
|
||||
coolDownInterval = time.Second
|
||||
dialTimeout = 5 * time.Second
|
||||
dialKeepAliveTime = 5 * time.Second
|
||||
requestTimeout = 3 * time.Second
|
||||
endpointsSeparator = ","
|
||||
)
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestBulkExecutorFlush(t *testing.T) {
|
||||
wait.Wait()
|
||||
}
|
||||
|
||||
func TestBuldExecutorFlushSlowTasks(t *testing.T) {
|
||||
func TestBulkExecutorFlushSlowTasks(t *testing.T) {
|
||||
const total = 1500
|
||||
lock := new(sync.Mutex)
|
||||
result := make([]any, 0, 10000)
|
||||
|
||||
@@ -168,23 +168,23 @@ func TestPeriodicalExecutor_FlushPanic(t *testing.T) {
|
||||
|
||||
func TestPeriodicalExecutor_Wait(t *testing.T) {
|
||||
var lock sync.Mutex
|
||||
executer := NewBulkExecutor(func(tasks []any) {
|
||||
executor := NewBulkExecutor(func(tasks []any) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}, WithBulkTasks(1), WithBulkInterval(time.Second))
|
||||
for i := 0; i < 10; i++ {
|
||||
executer.Add(1)
|
||||
executor.Add(1)
|
||||
}
|
||||
executer.Flush()
|
||||
executer.Wait()
|
||||
executor.Flush()
|
||||
executor.Wait()
|
||||
}
|
||||
|
||||
func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
||||
const total = 3
|
||||
var cnt int
|
||||
var lock sync.Mutex
|
||||
executer := NewBulkExecutor(func(tasks []any) {
|
||||
executor := NewBulkExecutor(func(tasks []any) {
|
||||
defer func() {
|
||||
cnt++
|
||||
}()
|
||||
@@ -193,10 +193,10 @@ func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}, WithBulkTasks(1), WithBulkInterval(10*time.Millisecond))
|
||||
for i := 0; i < total; i++ {
|
||||
executer.Add(2)
|
||||
executor.Add(2)
|
||||
}
|
||||
executer.Flush()
|
||||
executer.Wait()
|
||||
executor.Flush()
|
||||
executor.Wait()
|
||||
assert.Equal(t, total, cnt)
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,11 @@ func TestFirstLineShort(t *testing.T) {
|
||||
assert.Equal(t, "first line", val)
|
||||
}
|
||||
|
||||
func TestFirstLineError(t *testing.T) {
|
||||
_, err := FirstLine("/tmp/does-not-exist")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestLastLine(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText(text)
|
||||
assert.Nil(t, err)
|
||||
@@ -113,3 +118,8 @@ func TestLastLineWithLastNewlineShort(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "last line", val)
|
||||
}
|
||||
|
||||
func TestLastLineError(t *testing.T) {
|
||||
_, err := LastLine("/tmp/does-not-exist")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -11,29 +11,29 @@ import (
|
||||
// The file is kept as open, the caller should close the file handle,
|
||||
// and remove the file by name.
|
||||
func TempFileWithText(text string) (*os.File, error) {
|
||||
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text)))
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
|
||||
if err := os.WriteFile(tmpFile.Name(), []byte(text), os.ModeTemporary); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tmpfile, nil
|
||||
return tmpFile, nil
|
||||
}
|
||||
|
||||
// TempFilenameWithText creates the file with the given content,
|
||||
// and returns the filename (full path).
|
||||
// The caller should remove the file after use.
|
||||
func TempFilenameWithText(text string) (string, error) {
|
||||
tmpfile, err := TempFileWithText(text)
|
||||
tmpFile, err := TempFileWithText(text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
filename := tmpfile.Name()
|
||||
if err = tmpfile.Close(); err != nil {
|
||||
filename := tmpFile.Name()
|
||||
if err = tmpFile.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,87 @@
|
||||
package fx
|
||||
|
||||
import "github.com/zeromicro/go-zero/core/errorx"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/errorx"
|
||||
)
|
||||
|
||||
const defaultRetryTimes = 3
|
||||
|
||||
var errTimeout = errors.New("retry timeout")
|
||||
|
||||
type (
|
||||
// RetryOption defines the method to customize DoWithRetry.
|
||||
RetryOption func(*retryOptions)
|
||||
|
||||
retryOptions struct {
|
||||
times int
|
||||
times int
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
)
|
||||
|
||||
// DoWithRetry runs fn, and retries if failed. Default to retry 3 times.
|
||||
// Note that if the fn function accesses global variables outside the function
|
||||
// and performs modification operations, it is best to lock them,
|
||||
// otherwise there may be data race issues
|
||||
func DoWithRetry(fn func() error, opts ...RetryOption) error {
|
||||
return retry(func(errChan chan error, retryCount int) {
|
||||
errChan <- fn()
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// DoWithRetryCtx runs fn, and retries if failed. Default to retry 3 times.
|
||||
// fn retryCount indicates the current number of retries, starting from 0
|
||||
// Note that if the fn function accesses global variables outside the function
|
||||
// and performs modification operations, it is best to lock them,
|
||||
// otherwise there may be data race issues
|
||||
func DoWithRetryCtx(ctx context.Context, fn func(ctx context.Context, retryCount int) error,
|
||||
opts ...RetryOption) error {
|
||||
return retry(func(errChan chan error, retryCount int) {
|
||||
errChan <- fn(ctx, retryCount)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
func retry(fn func(errChan chan error, retryCount int), opts ...RetryOption) error {
|
||||
options := newRetryOptions()
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
var berr errorx.BatchError
|
||||
var cancelFunc context.CancelFunc
|
||||
ctx := context.Background()
|
||||
if options.timeout > 0 {
|
||||
ctx, cancelFunc = context.WithTimeout(ctx, options.timeout)
|
||||
defer cancelFunc()
|
||||
}
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
for i := 0; i < options.times; i++ {
|
||||
if err := fn(); err != nil {
|
||||
berr.Add(err)
|
||||
} else {
|
||||
return nil
|
||||
go fn(errChan, i)
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
berr.Add(err)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
berr.Add(errTimeout)
|
||||
return berr.Err()
|
||||
}
|
||||
|
||||
if options.interval > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
berr.Add(errTimeout)
|
||||
return berr.Err()
|
||||
case <-time.After(options.interval):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +95,18 @@ func WithRetry(times int) RetryOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithInterval(interval time.Duration) RetryOption {
|
||||
return func(options *retryOptions) {
|
||||
options.interval = interval
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) RetryOption {
|
||||
return func(options *retryOptions) {
|
||||
options.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func newRetryOptions() *retryOptions {
|
||||
return &retryOptions{
|
||||
times: defaultRetryTimes,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package fx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -12,31 +14,103 @@ func TestRetry(t *testing.T) {
|
||||
return errors.New("any")
|
||||
}))
|
||||
|
||||
var times int
|
||||
times1 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times++
|
||||
if times == defaultRetryTimes {
|
||||
times1++
|
||||
if times1 == defaultRetryTimes {
|
||||
return nil
|
||||
}
|
||||
return errors.New("any")
|
||||
}))
|
||||
|
||||
times = 0
|
||||
times2 := 0
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
times++
|
||||
if times == defaultRetryTimes+1 {
|
||||
times2++
|
||||
if times2 == defaultRetryTimes+1 {
|
||||
return nil
|
||||
}
|
||||
return errors.New("any")
|
||||
}))
|
||||
|
||||
total := 2 * defaultRetryTimes
|
||||
times = 0
|
||||
times3 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times++
|
||||
if times == total {
|
||||
times3++
|
||||
if times3 == total {
|
||||
return nil
|
||||
}
|
||||
return errors.New("any")
|
||||
}, WithRetry(total)))
|
||||
}
|
||||
|
||||
func TestRetryWithTimeout(t *testing.T) {
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*500)))
|
||||
|
||||
times1 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times1++
|
||||
if times1 == 1 {
|
||||
return errors.New("any ")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*250)))
|
||||
|
||||
total := defaultRetryTimes
|
||||
times2 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times2++
|
||||
if times2 == total {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
return errors.New("any")
|
||||
}, WithTimeout(time.Millisecond*50*(time.Duration(total)+2))))
|
||||
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
return errors.New("any")
|
||||
}, WithTimeout(time.Millisecond*250)))
|
||||
}
|
||||
|
||||
func TestRetryWithInterval(t *testing.T) {
|
||||
times1 := 0
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
times1++
|
||||
if times1 == 1 {
|
||||
return errors.New("any")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
|
||||
times2 := 0
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
times2++
|
||||
if times2 == 2 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return errors.New("any ")
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
|
||||
}
|
||||
|
||||
func TestRetryCtx(t *testing.T) {
|
||||
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
||||
if retryCount == 0 {
|
||||
return errors.New("any")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
|
||||
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
||||
if retryCount == 1 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return errors.New("any ")
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
}
|
||||
|
||||
@@ -292,6 +292,18 @@ func (s Stream) Map(fn MapFunc, opts ...Option) Stream {
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// Max returns the maximum item from the underlying source.
|
||||
func (s Stream) Max(less LessFunc) any {
|
||||
var max any
|
||||
for item := range s.source {
|
||||
if max == nil || less(max, item) {
|
||||
max = item
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
// Merge merges all the items into a slice and generates a new stream.
|
||||
func (s Stream) Merge() Stream {
|
||||
var items []any
|
||||
@@ -306,6 +318,18 @@ func (s Stream) Merge() Stream {
|
||||
return Range(source)
|
||||
}
|
||||
|
||||
// Min returns the minimum item from the underlying source.
|
||||
func (s Stream) Min(less LessFunc) any {
|
||||
var min any
|
||||
for item := range s.source {
|
||||
if min == nil || less(item, min) {
|
||||
min = item
|
||||
}
|
||||
}
|
||||
|
||||
return min
|
||||
}
|
||||
|
||||
// NoneMatch returns whether all elements of this stream don't match the provided predicate.
|
||||
// May not evaluate the predicate on all elements if not necessary for determining the result.
|
||||
// If the stream is empty then true is returned and the predicate is not evaluated.
|
||||
|
||||
@@ -503,6 +503,83 @@ func TestStream_Concat(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStream_Max(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
elements []any
|
||||
max any
|
||||
}{
|
||||
{
|
||||
name: "no elements with nil",
|
||||
},
|
||||
{
|
||||
name: "no elements",
|
||||
elements: []any{},
|
||||
max: nil,
|
||||
},
|
||||
{
|
||||
name: "1 element",
|
||||
elements: []any{1},
|
||||
max: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
elements: []any{1, 2, 9, 5, 8},
|
||||
max: 9,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
val := Just(test.elements...).Max(func(a, b any) bool {
|
||||
return a.(int) < b.(int)
|
||||
})
|
||||
assetEqual(t, test.max, val)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStream_Min(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
elements []any
|
||||
min any
|
||||
}{
|
||||
{
|
||||
name: "no elements with nil",
|
||||
min: nil,
|
||||
},
|
||||
{
|
||||
name: "no elements",
|
||||
elements: []any{},
|
||||
min: nil,
|
||||
},
|
||||
{
|
||||
name: "1 element",
|
||||
elements: []any{1},
|
||||
min: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
elements: []any{-1, 1, 2, 9, 5, 8},
|
||||
min: -1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
val := Just(test.elements...).Min(func(a, b any) bool {
|
||||
return a.(int) < b.(int)
|
||||
})
|
||||
assetEqual(t, test.min, val)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParallelMapReduce(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@ b`,
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
tmpfile, err := fs.TempFilenameWithText(test.input)
|
||||
tmpFile, err := fs.TempFilenameWithText(test.input)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
defer os.Remove(tmpFile)
|
||||
|
||||
content, err := ReadText(tmpfile)
|
||||
content, err := ReadText(tmpFile)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.expect, content)
|
||||
})
|
||||
@@ -59,9 +59,9 @@ func TestReadTextLines(t *testing.T) {
|
||||
#a
|
||||
3`
|
||||
|
||||
tmpfile, err := fs.TempFilenameWithText(text)
|
||||
tmpFile, err := fs.TempFilenameWithText(text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
defer os.Remove(tmpFile)
|
||||
|
||||
tests := []struct {
|
||||
options []TextReadOption
|
||||
@@ -87,7 +87,7 @@ func TestReadTextLines(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.Rand(), func(t *testing.T) {
|
||||
lines, err := ReadTextLines(tmpfile, test.options...)
|
||||
lines, err := ReadTextLines(tmpFile, test.options...)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.expectLines, len(lines))
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package logc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -11,14 +10,11 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
)
|
||||
|
||||
func TestAddGlobalFields(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
buf := logtest.NewCollector(t)
|
||||
|
||||
Info(context.Background(), "hello")
|
||||
buf.Reset()
|
||||
@@ -34,155 +30,90 @@ func TestAddGlobalFields(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlert(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
Alert(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), "foo"), buf.String())
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Error(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestErrorf(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Errorf(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestErrorv(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Errorv(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestErrorw(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Errorw(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Info(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfof(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Infof(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfov(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Infov(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfow(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Infow(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebug(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debug(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebugf(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debugf(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebugv(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debugv(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebugw(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debugw(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
@@ -204,48 +135,28 @@ func TestMisc(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSlow(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Slow(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
}
|
||||
|
||||
func TestSlowf(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Slowf(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
}
|
||||
|
||||
func TestSlowv(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Slowv(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
}
|
||||
|
||||
func TestSloww(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Sloww(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
|
||||
@@ -32,7 +32,7 @@ type LogConf struct {
|
||||
StackCooldownMillis int `json:",default=100"`
|
||||
// MaxBackups represents how many backup log files will be kept. 0 means all files will be kept forever.
|
||||
// Only take effect when RotationRuleType is `size`.
|
||||
// Even thougth `MaxBackups` sets 0, log files will still be removed
|
||||
// Even though `MaxBackups` sets 0, log files will still be removed
|
||||
// if the `KeepDays` limitation is reached.
|
||||
MaxBackups int `json:",default=0"`
|
||||
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
|
||||
|
||||
@@ -358,14 +358,16 @@ func createOutput(path string) (io.WriteCloser, error) {
|
||||
return nil, ErrLogPathNotSet
|
||||
}
|
||||
|
||||
var rule RotateRule
|
||||
switch options.rotationRule {
|
||||
case sizeRotationRule:
|
||||
return NewLogger(path, NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays,
|
||||
options.maxSize, options.maxBackups, options.gzipEnabled), options.gzipEnabled)
|
||||
rule = NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays, options.maxSize,
|
||||
options.maxBackups, options.gzipEnabled)
|
||||
default:
|
||||
return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays,
|
||||
options.gzipEnabled), options.gzipEnabled)
|
||||
rule = DefaultRotateRule(path, backupFileDelimiter, options.keepDays, options.gzipEnabled)
|
||||
}
|
||||
|
||||
return NewLogger(path, rule, options.gzipEnabled)
|
||||
}
|
||||
|
||||
func getWriter() Writer {
|
||||
|
||||
84
core/logx/logtest/logtest.go
Normal file
84
core/logx/logtest/logtest.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package logtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
buf *bytes.Buffer
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func Discard(t *testing.T) {
|
||||
prev := logx.Reset()
|
||||
logx.SetWriter(logx.NewWriter(io.Discard))
|
||||
|
||||
t.Cleanup(func() {
|
||||
logx.SetWriter(prev)
|
||||
})
|
||||
}
|
||||
|
||||
func NewCollector(t *testing.T) *Buffer {
|
||||
var buf bytes.Buffer
|
||||
writer := logx.NewWriter(&buf)
|
||||
prev := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
|
||||
t.Cleanup(func() {
|
||||
logx.SetWriter(prev)
|
||||
})
|
||||
|
||||
return &Buffer{
|
||||
buf: &buf,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
return b.buf.Bytes()
|
||||
}
|
||||
|
||||
func (b *Buffer) Content() string {
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(b.buf.Bytes(), &m); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
content, ok := m["content"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch val := content.(type) {
|
||||
case string:
|
||||
return val
|
||||
default:
|
||||
// err is impossible to be not nil, unmarshaled from b.buf.Bytes()
|
||||
bs, _ := json.Marshal(content)
|
||||
return string(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Reset() {
|
||||
b.buf.Reset()
|
||||
}
|
||||
|
||||
func (b *Buffer) String() string {
|
||||
return b.buf.String()
|
||||
}
|
||||
|
||||
func PanicOnFatal(t *testing.T) {
|
||||
ok := logx.ExitOnFatal.CompareAndSwap(true, false)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
logx.ExitOnFatal.CompareAndSwap(false, true)
|
||||
})
|
||||
}
|
||||
44
core/logx/logtest/logtest_test.go
Normal file
44
core/logx/logtest/logtest_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package logtest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
const input = "hello"
|
||||
c := NewCollector(t)
|
||||
logx.Info(input)
|
||||
assert.Equal(t, input, c.Content())
|
||||
assert.Contains(t, c.String(), input)
|
||||
c.Reset()
|
||||
assert.Empty(t, c.Bytes())
|
||||
}
|
||||
|
||||
func TestPanicOnFatal(t *testing.T) {
|
||||
const input = "hello"
|
||||
Discard(t)
|
||||
logx.Info(input)
|
||||
|
||||
PanicOnFatal(t)
|
||||
PanicOnFatal(t)
|
||||
assert.Panics(t, func() {
|
||||
logx.Must(errors.New("foo"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCollectorContent(t *testing.T) {
|
||||
const input = "hello"
|
||||
c := NewCollector(t)
|
||||
c.buf.WriteString(input)
|
||||
assert.Empty(t, c.Content())
|
||||
c.Reset()
|
||||
c.buf.WriteString(`{}`)
|
||||
assert.Empty(t, c.Content())
|
||||
c.Reset()
|
||||
c.buf.WriteString(`{"content":1}`)
|
||||
assert.Equal(t, "1", c.Content())
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func (l *richLogger) Errorf(format string, v ...any) {
|
||||
}
|
||||
|
||||
func (l *richLogger) Errorv(v any) {
|
||||
l.err(fmt.Sprint(v))
|
||||
l.err(v)
|
||||
}
|
||||
|
||||
func (l *richLogger) Errorw(msg string, fields ...LogField) {
|
||||
|
||||
@@ -66,6 +66,9 @@ func TestTraceDebug(t *testing.T) {
|
||||
l.WithDuration(time.Second).Debugv(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Debugv(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Debugw(testlog, Field("foo", "bar"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
|
||||
@@ -103,6 +106,9 @@ func TestTraceError(t *testing.T) {
|
||||
l.WithDuration(time.Second).Errorv(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Errorv(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
||||
@@ -137,6 +143,9 @@ func TestTraceInfo(t *testing.T) {
|
||||
l.WithDuration(time.Second).Infov(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
||||
@@ -173,6 +182,9 @@ func TestTraceInfoConsole(t *testing.T) {
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
}
|
||||
|
||||
func TestTraceSlow(t *testing.T) {
|
||||
@@ -204,6 +216,9 @@ func TestTraceSlow(t *testing.T) {
|
||||
l.WithDuration(time.Second).Slowv(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Slowv(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
||||
@@ -311,8 +326,32 @@ func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
|
||||
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
|
||||
}
|
||||
|
||||
type mockValue struct {
|
||||
Trace string `json:"trace"`
|
||||
Span string `json:"span"`
|
||||
Foo string `json:"foo"`
|
||||
func validateContentType(t *testing.T, body string, expectedType any, expectedTrace, expectedSpan bool) {
|
||||
var val mockValue
|
||||
dec := json.NewDecoder(strings.NewReader(body))
|
||||
|
||||
for {
|
||||
var doc mockValue
|
||||
err := dec.Decode(&doc)
|
||||
if err == io.EOF {
|
||||
// all done
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
val = doc
|
||||
}
|
||||
|
||||
assert.IsType(t, expectedType, val.Content, body)
|
||||
assert.Equal(t, expectedTrace, len(val.Trace) > 0, body)
|
||||
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
|
||||
}
|
||||
|
||||
type mockValue struct {
|
||||
Trace string `json:"trace"`
|
||||
Span string `json:"span"`
|
||||
Foo string `json:"foo"`
|
||||
Content any `json:"content"`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package logx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -13,18 +14,58 @@ import (
|
||||
)
|
||||
|
||||
func TestDailyRotateRuleMarkRotated(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDate(), rule.rotatedTime)
|
||||
t.Run("daily rule", func(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDate(), rule.rotatedTime)
|
||||
})
|
||||
|
||||
t.Run("daily rule", func(t *testing.T) {
|
||||
rule := DefaultRotateRule("test", "-", 1, false)
|
||||
_, ok := rule.(*DailyRotateRule)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
t.Run("no files", func(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("bad files", func(t *testing.T) {
|
||||
rule := DailyRotateRule{
|
||||
filename: "[a-z",
|
||||
}
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("temp files", func(t *testing.T) {
|
||||
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
_ = f1.Close()
|
||||
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
_ = f2.Close()
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(f1.Name())
|
||||
_ = os.Remove(f2.Name())
|
||||
})
|
||||
rule := DailyRotateRule{
|
||||
filename: path.Join(os.TempDir(), "go-zero-test-"),
|
||||
days: 1,
|
||||
}
|
||||
assert.NotEmpty(t, rule.OutdatedFiles())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDailyRotateRuleShallRotate(t *testing.T) {
|
||||
@@ -34,20 +75,101 @@ func TestDailyRotateRuleShallRotate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
|
||||
t.Run("size limit rule", func(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
|
||||
})
|
||||
|
||||
t.Run("size limit rule", func(t *testing.T) {
|
||||
rule := NewSizeLimitRotateRule("foo", "-", 1, 1, 1, false)
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDateInRFC3339Format(), rule.(*SizeLimitRotateRule).rotatedTime)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSizeLimitRotateRuleOutdatedFiles(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.maxBackups = 0
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
t.Run("no files", func(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.maxBackups = 0
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("bad files", func(t *testing.T) {
|
||||
rule := SizeLimitRotateRule{
|
||||
DailyRotateRule: DailyRotateRule{
|
||||
filename: "[a-z",
|
||||
},
|
||||
}
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("temp files", func(t *testing.T) {
|
||||
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = f1.Close()
|
||||
_ = os.Remove(f1.Name())
|
||||
_ = f2.Close()
|
||||
_ = os.Remove(f2.Name())
|
||||
_ = f3.Close()
|
||||
_ = os.Remove(f3.Name())
|
||||
})
|
||||
rule := SizeLimitRotateRule{
|
||||
DailyRotateRule: DailyRotateRule{
|
||||
filename: path.Join(os.TempDir(), "go-zero-test-"),
|
||||
days: 1,
|
||||
},
|
||||
maxBackups: 3,
|
||||
}
|
||||
assert.NotEmpty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("no backups", func(t *testing.T) {
|
||||
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = f1.Close()
|
||||
_ = os.Remove(f1.Name())
|
||||
_ = f2.Close()
|
||||
_ = os.Remove(f2.Name())
|
||||
_ = f3.Close()
|
||||
_ = os.Remove(f3.Name())
|
||||
})
|
||||
rule := SizeLimitRotateRule{
|
||||
DailyRotateRule: DailyRotateRule{
|
||||
filename: path.Join(os.TempDir(), "go-zero-test-"),
|
||||
days: 1,
|
||||
},
|
||||
}
|
||||
assert.NotEmpty(t, rule.OutdatedFiles())
|
||||
|
||||
logger := new(RotateLogger)
|
||||
logger.rule = &rule
|
||||
logger.maybeDeleteOutdatedFiles()
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
|
||||
@@ -61,14 +183,26 @@ func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRotateLoggerClose(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer os.Remove(filename)
|
||||
}
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, logger.Close())
|
||||
t.Run("close", func(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer os.Remove(filename)
|
||||
}
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
_, err = logger.Write([]byte("foo"))
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, logger.Close())
|
||||
})
|
||||
|
||||
t.Run("close and write", func(t *testing.T) {
|
||||
logger := new(RotateLogger)
|
||||
logger.done = make(chan struct{})
|
||||
close(logger.done)
|
||||
_, err := logger.Write([]byte("foo"))
|
||||
assert.ErrorIs(t, err, ErrLogFileClosed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRotateLoggerGetBackupFilename(t *testing.T) {
|
||||
@@ -179,7 +313,7 @@ func TestRotateLoggerWithSizeLimitRotateRuleClose(t *testing.T) {
|
||||
}
|
||||
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, logger.Close())
|
||||
_ = logger.Close()
|
||||
}
|
||||
|
||||
func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
const testlog = "Stay hungry, stay foolish."
|
||||
|
||||
var testobj = map[string]any{"foo": "bar"}
|
||||
|
||||
func TestCollectSysLog(t *testing.T) {
|
||||
CollectSysLog()
|
||||
content := getContent(captureOutput(func() {
|
||||
|
||||
@@ -97,6 +97,15 @@ func TestConsoleWriter(t *testing.T) {
|
||||
w.(*concreteWriter).statLog = easyToCloseWriter{}
|
||||
}
|
||||
|
||||
func TestNewFileWriter(t *testing.T) {
|
||||
t.Run("access", func(t *testing.T) {
|
||||
_, err := newFileWriter(LogConf{
|
||||
Path: "/not-exists",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNopWriter(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
var w nopWriter
|
||||
|
||||
@@ -148,14 +148,17 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
baseType := fieldType.Elem()
|
||||
dereffedBaseType := Deref(baseType)
|
||||
dereffedBaseKind := dereffedBaseType.Kind()
|
||||
refValue := reflect.ValueOf(mapValue)
|
||||
if refValue.Kind() != reflect.Slice {
|
||||
return errTypeMismatch
|
||||
}
|
||||
if refValue.IsNil() {
|
||||
return nil
|
||||
}
|
||||
|
||||
baseType := fieldType.Elem()
|
||||
dereffedBaseType := Deref(baseType)
|
||||
dereffedBaseKind := dereffedBaseType.Kind()
|
||||
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
|
||||
if refValue.Len() == 0 {
|
||||
value.Set(conv)
|
||||
@@ -347,7 +350,12 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any)
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
|
||||
targetValue.SetMapIndex(key, reflect.ValueOf(v))
|
||||
val := reflect.ValueOf(v)
|
||||
if !val.Type().AssignableTo(dereffedElemType) {
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
|
||||
targetValue.SetMapIndex(key, val)
|
||||
case json.Number:
|
||||
target := reflect.New(dereffedElemType)
|
||||
if err := setValueFromString(dereffedElemKind, target.Elem(), v.String()); err != nil {
|
||||
@@ -490,7 +498,7 @@ func (u *Unmarshaler) processAnonymousStructFieldOptional(fieldType reflect.Type
|
||||
}
|
||||
|
||||
if filled && required != requiredFilled {
|
||||
return fmt.Errorf("%s is not fully set", key)
|
||||
return fmt.Errorf("%q is not fully set", key)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -513,8 +521,8 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
||||
vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error {
|
||||
derefedFieldType := Deref(fieldType)
|
||||
typeKind := derefedFieldType.Kind()
|
||||
valueKind := reflect.TypeOf(vp.value).Kind()
|
||||
mapValue := vp.value
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
|
||||
switch {
|
||||
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
||||
@@ -527,6 +535,8 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
||||
current: mapValuer(mv),
|
||||
parent: vp.parent,
|
||||
}, fullName)
|
||||
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
||||
return u.fillSlice(fieldType, value, mapValue)
|
||||
case valueKind == reflect.Map && typeKind == reflect.Map:
|
||||
return u.fillMap(fieldType, value, mapValue)
|
||||
case valueKind == reflect.String && typeKind == reflect.Map:
|
||||
@@ -545,23 +555,16 @@ func (u *Unmarshaler) processFieldPrimitive(fieldType reflect.Type, value reflec
|
||||
typeKind := Deref(fieldType).Kind()
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
|
||||
switch {
|
||||
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
||||
return u.fillSlice(fieldType, value, mapValue)
|
||||
case typeKind == reflect.Map && valueKind == reflect.Map:
|
||||
return u.fillMap(fieldType, value, mapValue)
|
||||
switch v := mapValue.(type) {
|
||||
case json.Number:
|
||||
return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName)
|
||||
default:
|
||||
switch v := mapValue.(type) {
|
||||
case json.Number:
|
||||
return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName)
|
||||
default:
|
||||
if typeKind == valueKind {
|
||||
if err := validateValueInOptions(mapValue, opts.options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fillWithSameType(fieldType, value, mapValue, opts)
|
||||
if typeKind == valueKind {
|
||||
if err := validateValueInOptions(mapValue, opts.options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fillWithSameType(fieldType, value, mapValue, opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,7 +726,7 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
||||
// When fillDefault is used, m is a null value, hasValue must be false, all priority judgments fillDefault.
|
||||
if u.opts.fillDefault {
|
||||
if !value.IsZero() {
|
||||
return fmt.Errorf("set the default value, %s must be zero", fullName)
|
||||
return fmt.Errorf("set the default value, %q must be zero", fullName)
|
||||
}
|
||||
return u.processNamedFieldWithoutValue(field.Type, value, opts, fullName)
|
||||
} else if !hasValue {
|
||||
@@ -744,11 +747,11 @@ func (u *Unmarshaler) processNamedFieldWithValue(fieldType reflect.Type, value r
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("field %s mustn't be nil", key)
|
||||
return fmt.Errorf("field %q mustn't be nil", key)
|
||||
}
|
||||
|
||||
if !value.CanSet() {
|
||||
return fmt.Errorf("field %s is not settable", key)
|
||||
return fmt.Errorf("field %q is not settable", key)
|
||||
}
|
||||
|
||||
maybeNewValue(fieldType, value)
|
||||
@@ -792,7 +795,7 @@ func (u *Unmarshaler) processNamedFieldWithValueFromString(fieldType reflect.Typ
|
||||
}
|
||||
|
||||
if !stringx.Contains(options, checkValue) {
|
||||
return fmt.Errorf(`value "%s" for field "%s" is not defined in options "%v"`,
|
||||
return fmt.Errorf(`value "%s" for field %q is not defined in options "%v"`,
|
||||
mapValue, key, options)
|
||||
}
|
||||
}
|
||||
@@ -881,6 +884,11 @@ func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName
|
||||
typeField := baseType.Field(i)
|
||||
valueField := valElem.Field(i)
|
||||
if err := u.processField(typeField, valueField, m, fullName); err != nil {
|
||||
if len(fullName) > 0 {
|
||||
err = fmt.Errorf("%w, fullName: %s, field: %s, type: %s",
|
||||
err, fullName, typeField.Name, valueField.Type().Name())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1034,11 +1042,11 @@ func join(elem ...string) string {
|
||||
}
|
||||
|
||||
func newInitError(name string) error {
|
||||
return fmt.Errorf("field %s is not set", name)
|
||||
return fmt.Errorf("field %q is not set", name)
|
||||
}
|
||||
|
||||
func newTypeMismatchError(name string) error {
|
||||
return fmt.Errorf("type mismatch for field %s", name)
|
||||
return fmt.Errorf("type mismatch for field %q", name)
|
||||
}
|
||||
|
||||
func readKeys(key string) []string {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -370,7 +370,7 @@ func parseOption(fieldOpts *fieldOptions, fieldName, option string) error {
|
||||
fieldOpts.Optional = true
|
||||
fieldOpts.OptionalDep = segs[1]
|
||||
default:
|
||||
return fmt.Errorf("field %s has wrong optional", fieldName)
|
||||
return fmt.Errorf("field %q has wrong optional", fieldName)
|
||||
}
|
||||
case option == optionalOption:
|
||||
fieldOpts.Optional = true
|
||||
@@ -429,7 +429,7 @@ func parseOptions(val string) []string {
|
||||
func parseProperty(field, tag, val string) (string, error) {
|
||||
segs := strings.Split(val, equalToken)
|
||||
if len(segs) != 2 {
|
||||
return "", fmt.Errorf("field %s has wrong %s", field, tag)
|
||||
return "", fmt.Errorf("field %q has wrong tag value %q", field, tag)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(segs[1]), nil
|
||||
@@ -628,7 +628,7 @@ func validateValueInOptions(val any, options []string) error {
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
if !stringx.Contains(options, v) {
|
||||
return fmt.Errorf(`error: value "%s" is not defined in options "%v"`, v, options)
|
||||
return fmt.Errorf(`error: value %q is not defined in options "%v"`, v, options)
|
||||
}
|
||||
default:
|
||||
if !stringx.Contains(options, Repr(v)) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package mr
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
var errDummy = errors.New("dummy")
|
||||
|
||||
func init() {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
log.SetOutput(io.Discard)
|
||||
}
|
||||
|
||||
func TestFinish(t *testing.T) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -21,13 +20,11 @@ func TestEnvInt(t *testing.T) {
|
||||
val, ok := EnvInt("any")
|
||||
assert.Equal(t, 0, val)
|
||||
assert.False(t, ok)
|
||||
err := os.Setenv("anyInt", "10")
|
||||
assert.Nil(t, err)
|
||||
t.Setenv("anyInt", "10")
|
||||
val, ok = EnvInt("anyInt")
|
||||
assert.Equal(t, 10, val)
|
||||
assert.True(t, ok)
|
||||
err = os.Setenv("anyString", "a")
|
||||
assert.Nil(t, err)
|
||||
t.Setenv("anyString", "a")
|
||||
val, ok = EnvInt("anyString")
|
||||
assert.Equal(t, 0, val)
|
||||
assert.False(t, ok)
|
||||
|
||||
@@ -5,19 +5,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
)
|
||||
|
||||
func TestDumpGoroutines(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
w := logx.NewWriter(&buf)
|
||||
o := logx.Reset()
|
||||
logx.SetWriter(w)
|
||||
defer func() {
|
||||
logx.Reset()
|
||||
logx.SetWriter(o)
|
||||
}()
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
dumpGoroutines()
|
||||
assert.True(t, strings.Contains(buf.String(), ".dump"))
|
||||
}
|
||||
|
||||
@@ -5,25 +5,16 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
)
|
||||
|
||||
func TestProfile(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
w := logx.NewWriter(&buf)
|
||||
o := logx.Reset()
|
||||
logx.SetWriter(w)
|
||||
|
||||
defer func() {
|
||||
logx.Reset()
|
||||
logx.SetWriter(o)
|
||||
}()
|
||||
|
||||
c := logtest.NewCollector(t)
|
||||
profiler := StartProfile()
|
||||
// start again should not work
|
||||
assert.NotNil(t, StartProfile())
|
||||
profiler.Stop()
|
||||
// stop twice
|
||||
profiler.Stop()
|
||||
assert.True(t, strings.Contains(buf.String(), ".pprof"))
|
||||
assert.True(t, strings.Contains(c.String(), ".pprof"))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package prof
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
@@ -13,6 +15,10 @@ const (
|
||||
|
||||
// DisplayStats prints the goroutine, memory, GC stats with given interval, default to 5 seconds.
|
||||
func DisplayStats(interval ...time.Duration) {
|
||||
displayStatsWithWriter(os.Stdout, interval...)
|
||||
}
|
||||
|
||||
func displayStatsWithWriter(writer io.Writer, interval ...time.Duration) {
|
||||
duration := defaultInterval
|
||||
for _, val := range interval {
|
||||
duration = val
|
||||
@@ -24,7 +30,7 @@ func DisplayStats(interval ...time.Duration) {
|
||||
for range ticker.C {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
fmt.Printf("Goroutines: %d, Alloc: %vm, TotalAlloc: %vm, Sys: %vm, NumGC: %v\n",
|
||||
fmt.Fprintf(writer, "Goroutines: %d, Alloc: %vm, TotalAlloc: %vm, Sys: %vm, NumGC: %v\n",
|
||||
runtime.NumGoroutine(), m.Alloc/mega, m.TotalAlloc/mega, m.Sys/mega, m.NumGC)
|
||||
}
|
||||
}()
|
||||
|
||||
36
core/prof/runtime_test.go
Normal file
36
core/prof/runtime_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package prof
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDisplayStats(t *testing.T) {
|
||||
writer := &threadSafeBuffer{
|
||||
buf: strings.Builder{},
|
||||
}
|
||||
displayStatsWithWriter(writer, time.Millisecond*10)
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
assert.Contains(t, writer.String(), "Goroutines: ")
|
||||
}
|
||||
|
||||
type threadSafeBuffer struct {
|
||||
buf strings.Builder
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (b *threadSafeBuffer) String() string {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
return b.buf.String()
|
||||
}
|
||||
|
||||
func (b *threadSafeBuffer) Write(p []byte) (n int, err error) {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
return b.buf.Write(p)
|
||||
}
|
||||
@@ -21,6 +21,11 @@ func Enabled() bool {
|
||||
return enabled.True()
|
||||
}
|
||||
|
||||
// Enable enables prometheus.
|
||||
func Enable() {
|
||||
enabled.Set(true)
|
||||
}
|
||||
|
||||
// StartAgent starts a prometheus agent.
|
||||
func StartAgent(c Config) {
|
||||
if len(c.Host) == 0 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -37,10 +38,82 @@ func TestQueue(t *testing.T) {
|
||||
assert.Equal(t, int32(rounds), atomic.LoadInt32(&consumer.count))
|
||||
}
|
||||
|
||||
func TestQueue_Broadcast(t *testing.T) {
|
||||
producer := newMockedProducer(rounds)
|
||||
consumer := newMockedConsumer()
|
||||
consumer.wait.Add(consumers)
|
||||
q := NewQueue(func() (Producer, error) {
|
||||
return producer, nil
|
||||
}, func() (Consumer, error) {
|
||||
return consumer, nil
|
||||
})
|
||||
q.AddListener(new(mockedListener))
|
||||
q.SetName("mockqueue")
|
||||
q.SetNumConsumer(consumers)
|
||||
q.SetNumProducer(1)
|
||||
q.Broadcast("message")
|
||||
go func() {
|
||||
producer.wait.Wait()
|
||||
q.Stop()
|
||||
}()
|
||||
q.Start()
|
||||
consumer.wait.Wait()
|
||||
assert.Equal(t, int32(rounds), atomic.LoadInt32(&consumer.count))
|
||||
assert.Equal(t, int32(consumers), atomic.LoadInt32(&consumer.events))
|
||||
}
|
||||
|
||||
func TestQueue_PauseResume(t *testing.T) {
|
||||
producer := newMockedProducer(rounds)
|
||||
consumer := newMockedConsumer()
|
||||
consumer.wait.Add(consumers)
|
||||
q := NewQueue(func() (Producer, error) {
|
||||
return producer, nil
|
||||
}, func() (Consumer, error) {
|
||||
return consumer, nil
|
||||
})
|
||||
q.AddListener(new(mockedListener))
|
||||
q.SetName("mockqueue")
|
||||
q.SetNumConsumer(consumers)
|
||||
q.SetNumProducer(1)
|
||||
go func() {
|
||||
producer.wait.Wait()
|
||||
q.Stop()
|
||||
}()
|
||||
q.Start()
|
||||
producer.listener.OnProducerPause()
|
||||
assert.Equal(t, int32(0), atomic.LoadInt32(&q.active))
|
||||
producer.listener.OnProducerResume()
|
||||
assert.Equal(t, int32(1), atomic.LoadInt32(&q.active))
|
||||
assert.Equal(t, int32(rounds), atomic.LoadInt32(&consumer.count))
|
||||
}
|
||||
|
||||
func TestQueue_ConsumeError(t *testing.T) {
|
||||
producer := newMockedProducer(rounds)
|
||||
consumer := newMockedConsumer()
|
||||
consumer.consumeErr = errors.New("consume error")
|
||||
consumer.wait.Add(consumers)
|
||||
q := NewQueue(func() (Producer, error) {
|
||||
return producer, nil
|
||||
}, func() (Consumer, error) {
|
||||
return consumer, nil
|
||||
})
|
||||
q.AddListener(new(mockedListener))
|
||||
q.SetName("mockqueue")
|
||||
q.SetNumConsumer(consumers)
|
||||
q.SetNumProducer(1)
|
||||
go func() {
|
||||
producer.wait.Wait()
|
||||
q.Stop()
|
||||
}()
|
||||
q.Start()
|
||||
assert.Equal(t, int32(rounds), atomic.LoadInt32(&consumer.count))
|
||||
}
|
||||
|
||||
type mockedConsumer struct {
|
||||
count int32
|
||||
events int32
|
||||
wait sync.WaitGroup
|
||||
count int32
|
||||
events int32
|
||||
consumeErr error
|
||||
wait sync.WaitGroup
|
||||
}
|
||||
|
||||
func newMockedConsumer() *mockedConsumer {
|
||||
@@ -49,7 +122,7 @@ func newMockedConsumer() *mockedConsumer {
|
||||
|
||||
func (c *mockedConsumer) Consume(string) error {
|
||||
atomic.AddInt32(&c.count, 1)
|
||||
return nil
|
||||
return c.consumeErr
|
||||
}
|
||||
|
||||
func (c *mockedConsumer) OnEvent(any) {
|
||||
@@ -59,9 +132,10 @@ func (c *mockedConsumer) OnEvent(any) {
|
||||
}
|
||||
|
||||
type mockedProducer struct {
|
||||
total int32
|
||||
count int32
|
||||
wait sync.WaitGroup
|
||||
total int32
|
||||
count int32
|
||||
listener ProduceListener
|
||||
wait sync.WaitGroup
|
||||
}
|
||||
|
||||
func newMockedProducer(total int32) *mockedProducer {
|
||||
@@ -72,6 +146,7 @@ func newMockedProducer(total int32) *mockedProducer {
|
||||
}
|
||||
|
||||
func (p *mockedProducer) AddListener(listener ProduceListener) {
|
||||
p.listener = listener
|
||||
}
|
||||
|
||||
func (p *mockedProducer) Produce() (string, bool) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package rescue
|
||||
|
||||
import "github.com/zeromicro/go-zero/core/logx"
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// Recover is used with defer to do cleanup on panics.
|
||||
// Use it like:
|
||||
@@ -15,3 +20,14 @@ func Recover(cleanups ...func()) {
|
||||
logx.ErrorStack(p)
|
||||
}
|
||||
}
|
||||
|
||||
// RecoverCtx is used with defer to do cleanup on panics.
|
||||
func RecoverCtx(ctx context.Context, cleanups ...func()) {
|
||||
for _, cleanup := range cleanups {
|
||||
cleanup()
|
||||
}
|
||||
|
||||
if p := recover(); p != nil {
|
||||
logx.WithContext(ctx).Errorf("%+v\n%s", p, debug.Stack())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package rescue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
@@ -25,3 +26,17 @@ func TestRescue(t *testing.T) {
|
||||
})
|
||||
assert.Equal(t, int32(5), atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
func TestRescueCtx(t *testing.T) {
|
||||
var count int32
|
||||
assert.NotPanics(t, func() {
|
||||
defer RecoverCtx(context.Background(), func() {
|
||||
atomic.AddInt32(&count, 2)
|
||||
}, func() {
|
||||
atomic.AddInt32(&count, 3)
|
||||
})
|
||||
|
||||
panic("hello")
|
||||
})
|
||||
assert.Equal(t, int32(5), atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/load"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/proc"
|
||||
@@ -39,9 +37,7 @@ type ServiceConf struct {
|
||||
|
||||
// MustSetUp sets up the service, exits on error.
|
||||
func (sc ServiceConf) MustSetUp() {
|
||||
if err := sc.SetUp(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
logx.Must(sc.SetUp())
|
||||
}
|
||||
|
||||
// SetUp sets up the service.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
package stat
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
@@ -12,8 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestReport(t *testing.T) {
|
||||
os.Setenv(clusterNameKey, "test-cluster")
|
||||
defer os.Unsetenv(clusterNameKey)
|
||||
t.Setenv(clusterNameKey, "test-cluster")
|
||||
|
||||
var count int32
|
||||
SetReporter(func(s string) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package internal
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
@@ -280,9 +281,10 @@ func runningInUserNS() bool {
|
||||
|
||||
// We assume we are in the initial user namespace if we have a full
|
||||
// range - 4294967295 uids starting at uid 0.
|
||||
if a == 0 && b == 0 && c == 4294967295 {
|
||||
if a == 0 && b == 0 && c == math.MaxUint32 {
|
||||
return
|
||||
}
|
||||
|
||||
inUserNS = true
|
||||
})
|
||||
|
||||
|
||||
27
core/stat/internal/cgroup_linux_test.go
Normal file
27
core/stat/internal/cgroup_linux_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRunningInUserNS(t *testing.T) {
|
||||
// should be false in docker
|
||||
assert.False(t, runningInUserNS())
|
||||
}
|
||||
|
||||
func TestCgroupV1(t *testing.T) {
|
||||
if isCgroup2UnifiedMode() {
|
||||
cg, err := currentCgroupV1()
|
||||
assert.NoError(t, err)
|
||||
_, err = cg.cpus()
|
||||
assert.Error(t, err)
|
||||
_, err = cg.cpuPeriodUs()
|
||||
assert.Error(t, err)
|
||||
_, err = cg.cpuQuotaUs()
|
||||
assert.Error(t, err)
|
||||
_, err = cg.usageAllCpus()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (c *metricsContainer) Execute(v any) {
|
||||
report.Median = float32(medianTask.Duration) / float32(time.Millisecond)
|
||||
tenPercent := fiftyPercent / 5
|
||||
if tenPercent > 0 {
|
||||
top10pTasks := topK(tasks, tenPercent)
|
||||
top10pTasks := topK(top50pTasks, tenPercent)
|
||||
task90th := top10pTasks[0]
|
||||
report.Top90th = float32(task90th.Duration) / float32(time.Millisecond)
|
||||
onePercent := tenPercent / 10
|
||||
@@ -163,7 +163,7 @@ func (c *metricsContainer) Execute(v any) {
|
||||
report.Top99p9th = mostDuration
|
||||
}
|
||||
} else {
|
||||
mostDuration := getTopDuration(tasks)
|
||||
mostDuration := getTopDuration(top50pTasks)
|
||||
report.Top90th = mostDuration
|
||||
report.Top99th = mostDuration
|
||||
report.Top99p9th = mostDuration
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package stat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
)
|
||||
|
||||
func TestBToMb(t *testing.T) {
|
||||
@@ -41,15 +40,11 @@ func TestBToMb(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPrintUsage(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
c := logtest.NewCollector(t)
|
||||
|
||||
printUsage()
|
||||
|
||||
output := buf.String()
|
||||
output := c.String()
|
||||
assert.Contains(t, output, "CPU:")
|
||||
assert.Contains(t, output, "MEMORY:")
|
||||
assert.Contains(t, output, "Alloc=")
|
||||
|
||||
@@ -69,3 +69,62 @@ func TestFieldNamesWithDashTagAndOptions(t *testing.T) {
|
||||
assert.Equal(t, expected, out)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostgreSqlJoin(t *testing.T) {
|
||||
// Test with empty input array
|
||||
var input []string
|
||||
var expectedOutput string
|
||||
assert.Equal(t, expectedOutput, PostgreSqlJoin(input))
|
||||
|
||||
// Test with single element input array
|
||||
input = []string{"foo"}
|
||||
expectedOutput = "foo = $2"
|
||||
assert.Equal(t, expectedOutput, PostgreSqlJoin(input))
|
||||
|
||||
// Test with multiple elements input array
|
||||
input = []string{"foo", "bar", "baz"}
|
||||
expectedOutput = "foo = $2, bar = $3, baz = $4"
|
||||
assert.Equal(t, expectedOutput, PostgreSqlJoin(input))
|
||||
}
|
||||
|
||||
type testStruct struct {
|
||||
Foo string `db:"foo"`
|
||||
Bar int `db:"bar"`
|
||||
Baz bool `db:"-"`
|
||||
}
|
||||
|
||||
func TestRawFieldNames(t *testing.T) {
|
||||
// Test with a struct without tags
|
||||
in := struct {
|
||||
Foo string
|
||||
Bar int
|
||||
}{}
|
||||
expectedOutput := []string{"`Foo`", "`Bar`"}
|
||||
assert.ElementsMatch(t, expectedOutput, RawFieldNames(in))
|
||||
|
||||
// Test pg without db tag
|
||||
expectedOutput = []string{"Foo", "Bar"}
|
||||
assert.ElementsMatch(t, expectedOutput, RawFieldNames(in, true))
|
||||
|
||||
// Test with a struct with tags
|
||||
input := testStruct{}
|
||||
expectedOutput = []string{"`foo`", "`bar`"}
|
||||
assert.ElementsMatch(t, expectedOutput, RawFieldNames(input))
|
||||
|
||||
// Test with nil input (pointer)
|
||||
var nilInput *testStruct
|
||||
assert.Panics(t, func() {
|
||||
RawFieldNames(nilInput)
|
||||
}, "RawFieldNames should panic with nil input")
|
||||
|
||||
// Test with non-struct input
|
||||
inputInt := 42
|
||||
assert.Panics(t, func() {
|
||||
RawFieldNames(inputInt)
|
||||
}, "RawFieldNames should panic with non-struct input")
|
||||
|
||||
// Test with PostgreSQL flag
|
||||
input = testStruct{}
|
||||
expectedOutput = []string{"foo", "bar"}
|
||||
assert.ElementsMatch(t, expectedOutput, RawFieldNames(input, true))
|
||||
}
|
||||
|
||||
136
core/stores/cache/cachenode_test.go
vendored
136
core/stores/cache/cachenode_test.go
vendored
@@ -1,6 +1,3 @@
|
||||
//go:build !race
|
||||
|
||||
// Disable data race detection is because of the timingWheel in cacheNode.
|
||||
package cache
|
||||
|
||||
import (
|
||||
@@ -34,8 +31,10 @@ func init() {
|
||||
|
||||
func TestCacheNode_DelCache(t *testing.T) {
|
||||
t.Run("del cache", func(t *testing.T) {
|
||||
store := redistest.CreateRedis(t)
|
||||
store.Type = redis.ClusterType
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
store := redis.New(r.Addr(), redis.Cluster())
|
||||
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
@@ -56,16 +55,16 @@ func TestCacheNode_DelCache(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("del cache with errors", func(t *testing.T) {
|
||||
old := timingWheel
|
||||
old := timingWheel.Load()
|
||||
ticker := timex.NewFakeTicker()
|
||||
var err error
|
||||
timingWheel, err = collection.NewTimingWheelWithTicker(
|
||||
tw, err := collection.NewTimingWheelWithTicker(
|
||||
time.Millisecond, timingWheelSlots, func(key, value any) {
|
||||
clean(key, value)
|
||||
}, ticker)
|
||||
timingWheel.Store(tw)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
timingWheel = old
|
||||
timingWheel.Store(old)
|
||||
})
|
||||
|
||||
r, err := miniredis.Run()
|
||||
@@ -166,40 +165,99 @@ func TestCacheNode_TakeBadRedis(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCacheNode_TakeNotFound(t *testing.T) {
|
||||
store := redistest.CreateRedis(t)
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
store := redistest.CreateRedis(t)
|
||||
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
errNotFound: errTestNotFound,
|
||||
}
|
||||
var str string
|
||||
err := cn.Take(&str, "any", func(v any) error {
|
||||
return errTestNotFound
|
||||
})
|
||||
assert.True(t, cn.IsNotFound(err))
|
||||
assert.True(t, cn.IsNotFound(cn.Get("any", &str)))
|
||||
val, err := store.Get("any")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, `*`, val)
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
errNotFound: errTestNotFound,
|
||||
}
|
||||
var str string
|
||||
err := cn.Take(&str, "any", func(v any) error {
|
||||
return errTestNotFound
|
||||
})
|
||||
assert.True(t, cn.IsNotFound(err))
|
||||
assert.True(t, cn.IsNotFound(cn.Get("any", &str)))
|
||||
val, err := store.Get("any")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, `*`, val)
|
||||
|
||||
store.Set("any", "*")
|
||||
err = cn.Take(&str, "any", func(v any) error {
|
||||
return nil
|
||||
})
|
||||
assert.True(t, cn.IsNotFound(err))
|
||||
assert.True(t, cn.IsNotFound(cn.Get("any", &str)))
|
||||
store.Set("any", "*")
|
||||
err = cn.Take(&str, "any", func(v any) error {
|
||||
return nil
|
||||
})
|
||||
assert.True(t, cn.IsNotFound(err))
|
||||
assert.True(t, cn.IsNotFound(cn.Get("any", &str)))
|
||||
|
||||
store.Del("any")
|
||||
errDummy := errors.New("dummy")
|
||||
err = cn.Take(&str, "any", func(v any) error {
|
||||
return errDummy
|
||||
store.Del("any")
|
||||
errDummy := errors.New("dummy")
|
||||
err = cn.Take(&str, "any", func(v any) error {
|
||||
return errDummy
|
||||
})
|
||||
assert.Equal(t, errDummy, err)
|
||||
})
|
||||
|
||||
t.Run("not found with redis error", func(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
store, err := redis.NewRedis(redis.RedisConf{
|
||||
Host: r.Addr(),
|
||||
Type: redis.NodeType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
errNotFound: errTestNotFound,
|
||||
}
|
||||
var str string
|
||||
err = cn.Take(&str, "any", func(v any) error {
|
||||
r.SetError("mock error")
|
||||
return errTestNotFound
|
||||
})
|
||||
assert.True(t, cn.IsNotFound(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCacheNode_TakeCtxWithRedisError(t *testing.T) {
|
||||
t.Run("not found with redis error", func(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
store, err := redis.NewRedis(redis.RedisConf{
|
||||
Host: r.Addr(),
|
||||
Type: redis.NodeType,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cn := cacheNode{
|
||||
rds: store,
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
lock: new(sync.Mutex),
|
||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||
stat: NewStat("any"),
|
||||
errNotFound: errTestNotFound,
|
||||
}
|
||||
var str string
|
||||
err = cn.Take(&str, "any", func(v any) error {
|
||||
str = "foo"
|
||||
r.SetError("mock error")
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
assert.Equal(t, errDummy, err)
|
||||
}
|
||||
|
||||
func TestCacheNode_TakeNotFoundButChangedByOthers(t *testing.T) {
|
||||
|
||||
28
core/stores/cache/cacheopt_test.go
vendored
Normal file
28
core/stores/cache/cacheopt_test.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCacheOptions(t *testing.T) {
|
||||
t.Run("default options", func(t *testing.T) {
|
||||
o := newOptions()
|
||||
assert.Equal(t, defaultExpiry, o.Expiry)
|
||||
assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry)
|
||||
})
|
||||
|
||||
t.Run("with expiry", func(t *testing.T) {
|
||||
o := newOptions(WithExpiry(time.Second))
|
||||
assert.Equal(t, time.Second, o.Expiry)
|
||||
assert.Equal(t, defaultNotFoundExpiry, o.NotFoundExpiry)
|
||||
})
|
||||
|
||||
t.Run("with not found expiry", func(t *testing.T) {
|
||||
o := newOptions(WithNotFoundExpiry(time.Second))
|
||||
assert.Equal(t, defaultExpiry, o.Expiry)
|
||||
assert.Equal(t, time.Second, o.NotFoundExpiry)
|
||||
})
|
||||
}
|
||||
24
core/stores/cache/cleaner.go
vendored
24
core/stores/cache/cleaner.go
vendored
@@ -2,6 +2,7 @@ package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/collection"
|
||||
@@ -19,7 +20,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
timingWheel *collection.TimingWheel
|
||||
// use atomic to avoid data race in unit tests
|
||||
timingWheel atomic.Value
|
||||
taskRunner = threading.NewTaskRunner(cleanWorkers)
|
||||
)
|
||||
|
||||
@@ -30,22 +32,27 @@ type delayTask struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
timingWheel, err = collection.NewTimingWheel(time.Second, timingWheelSlots, clean)
|
||||
tw, err := collection.NewTimingWheel(time.Second, timingWheelSlots, clean)
|
||||
logx.Must(err)
|
||||
timingWheel.Store(tw)
|
||||
|
||||
proc.AddShutdownListener(func() {
|
||||
timingWheel.Drain(clean)
|
||||
if err := tw.Drain(clean); err != nil {
|
||||
logx.Errorf("failed to drain timing wheel: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AddCleanTask adds a clean task on given keys.
|
||||
func AddCleanTask(task func() error, keys ...string) {
|
||||
timingWheel.SetTimer(stringx.Randn(taskKeyLen), delayTask{
|
||||
tw := timingWheel.Load().(*collection.TimingWheel)
|
||||
if err := tw.SetTimer(stringx.Randn(taskKeyLen), delayTask{
|
||||
delay: time.Second,
|
||||
task: task,
|
||||
keys: keys,
|
||||
}, time.Second)
|
||||
}, time.Second); err != nil {
|
||||
logx.Errorf("failed to set timer for keys: %q, error: %v", formatKeys(keys), err)
|
||||
}
|
||||
}
|
||||
|
||||
func clean(key, value any) {
|
||||
@@ -59,7 +66,10 @@ func clean(key, value any) {
|
||||
next, ok := nextDelay(dt.delay)
|
||||
if ok {
|
||||
dt.delay = next
|
||||
timingWheel.SetTimer(key, dt, next)
|
||||
tw := timingWheel.Load().(*collection.TimingWheel)
|
||||
if err = tw.SetTimer(key, dt, next); err != nil {
|
||||
logx.Errorf("failed to set timer for key: %s, error: %v", key, err)
|
||||
}
|
||||
} else {
|
||||
msg := fmt.Sprintf("retried but failed to clear cache with keys: %q, error: %v",
|
||||
formatKeys(dt.keys), err)
|
||||
|
||||
14
core/stores/cache/cleaner_test.go
vendored
14
core/stores/cache/cleaner_test.go
vendored
@@ -5,7 +5,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/collection"
|
||||
"github.com/zeromicro/go-zero/core/proc"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
func TestNextDelay(t *testing.T) {
|
||||
@@ -49,6 +51,18 @@ func TestNextDelay(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
old := timingWheel.Load()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, err := collection.NewTimingWheelWithTicker(
|
||||
time.Millisecond, timingWheelSlots, func(key, value any) {
|
||||
clean(key, value)
|
||||
}, ticker)
|
||||
timingWheel.Store(tw)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
timingWheel.Store(old)
|
||||
})
|
||||
|
||||
next, ok := nextDelay(test.input)
|
||||
assert.Equal(t, test.ok, ok)
|
||||
assert.Equal(t, test.output, next)
|
||||
|
||||
@@ -3,12 +3,11 @@ package mon
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
@@ -573,15 +572,7 @@ func TestDecoratedCollection_LogDuration(t *testing.T) {
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
w := logx.NewWriter(&buf)
|
||||
o := logx.Reset()
|
||||
logx.SetWriter(w)
|
||||
|
||||
defer func() {
|
||||
logx.Reset()
|
||||
logx.SetWriter(o)
|
||||
}()
|
||||
buf := logtest.NewCollector(t)
|
||||
|
||||
buf.Reset()
|
||||
c.logDuration(context.Background(), "foo", timex.Now(), nil, "bar")
|
||||
|
||||
@@ -2,10 +2,10 @@ package mon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
mopt "go.mongodb.org/mongo-driver/mongo/options"
|
||||
@@ -39,10 +39,7 @@ type (
|
||||
// MustNewModel returns a Model, exits on errors.
|
||||
func MustNewModel(uri, db, collection string, opts ...Option) *Model {
|
||||
model, err := NewModel(uri, db, collection, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
logx.Must(err)
|
||||
return model
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@ package mon
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
)
|
||||
|
||||
func TestFormatAddrs(t *testing.T) {
|
||||
@@ -40,15 +39,7 @@ func TestFormatAddrs(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_logDuration(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
w := logx.NewWriter(&buf)
|
||||
o := logx.Reset()
|
||||
logx.SetWriter(w)
|
||||
|
||||
defer func() {
|
||||
logx.Reset()
|
||||
logx.SetWriter(o)
|
||||
}()
|
||||
buf := logtest.NewCollector(t)
|
||||
|
||||
buf.Reset()
|
||||
logDuration(context.Background(), "foo", "bar", time.Millisecond, nil)
|
||||
|
||||
@@ -2,8 +2,8 @@ package monc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/cache"
|
||||
"github.com/zeromicro/go-zero/core/stores/mon"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
@@ -30,20 +30,14 @@ type Model struct {
|
||||
// MustNewModel returns a Model with a cache cluster, exists on errors.
|
||||
func MustNewModel(uri, db, collection string, c cache.CacheConf, opts ...cache.Option) *Model {
|
||||
model, err := NewModel(uri, db, collection, c, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
logx.Must(err)
|
||||
return model
|
||||
}
|
||||
|
||||
// MustNewNodeModel returns a Model with a cache node, exists on errors.
|
||||
func MustNewNodeModel(uri, db, collection string, rds *redis.Redis, opts ...cache.Option) *Model {
|
||||
model, err := NewNodeModel(uri, db, collection, rds, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
logx.Must(err)
|
||||
return model
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (h hook) AfterProcess(ctx context.Context, cmd red.Cmder) error {
|
||||
logDuration(ctx, []red.Cmder{cmd}, duration)
|
||||
}
|
||||
|
||||
metricReqDur.Observe(int64(duration/time.Millisecond), cmd.Name())
|
||||
metricReqDur.Observe(duration.Milliseconds(), cmd.Name())
|
||||
if msg := formatError(err); len(msg) > 0 {
|
||||
metricReqErr.Inc(cmd.Name(), msg)
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (h hook) AfterProcessPipeline(ctx context.Context, cmds []red.Cmder) error
|
||||
logDuration(ctx, cmds, duration)
|
||||
}
|
||||
|
||||
metricReqDur.Observe(int64(duration/time.Millisecond), "Pipeline")
|
||||
metricReqDur.Observe(duration.Milliseconds(), "Pipeline")
|
||||
if msg := formatError(batchError.Err()); len(msg) > 0 {
|
||||
metricReqErr.Inc("Pipeline", msg)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,18 @@ package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
red "github.com/go-redis/redis/v8"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
ztrace "github.com/zeromicro/go-zero/core/trace"
|
||||
tracesdk "go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -47,8 +51,7 @@ func TestHookProcessCase2(t *testing.T) {
|
||||
})
|
||||
defer ztrace.StopAgent()
|
||||
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
w := logtest.NewCollector(t)
|
||||
|
||||
ctx, err := durationHook.BeforeProcess(context.Background(), red.NewCmd(context.Background()))
|
||||
if err != nil {
|
||||
@@ -115,8 +118,7 @@ func TestHookProcessPipelineCase2(t *testing.T) {
|
||||
})
|
||||
defer ztrace.StopAgent()
|
||||
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
w := logtest.NewCollector(t)
|
||||
|
||||
ctx, err := durationHook.BeforeProcessPipeline(context.Background(), []red.Cmder{
|
||||
red.NewCmd(context.Background()),
|
||||
@@ -135,8 +137,7 @@ func TestHookProcessPipelineCase2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHookProcessPipelineCase3(t *testing.T) {
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
w := logtest.NewCollector(t)
|
||||
|
||||
assert.Nil(t, durationHook.AfterProcessPipeline(context.Background(), []red.Cmder{
|
||||
red.NewCmd(context.Background()),
|
||||
@@ -145,8 +146,7 @@ func TestHookProcessPipelineCase3(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHookProcessPipelineCase4(t *testing.T) {
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
w := logtest.NewCollector(t)
|
||||
|
||||
ctx := context.WithValue(context.Background(), startTimeKey, "foo")
|
||||
assert.Nil(t, durationHook.AfterProcessPipeline(ctx, []red.Cmder{
|
||||
@@ -169,8 +169,7 @@ func TestHookProcessPipelineCase5(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLogDuration(t *testing.T) {
|
||||
w, restore := injectLog()
|
||||
defer restore()
|
||||
w := logtest.NewCollector(t)
|
||||
|
||||
logDuration(context.Background(), []red.Cmder{
|
||||
red.NewCmd(context.Background(), "get", "foo"),
|
||||
@@ -184,14 +183,39 @@ func TestLogDuration(t *testing.T) {
|
||||
assert.True(t, strings.Contains(w.String(), `get foo\nset bar 0`))
|
||||
}
|
||||
|
||||
func injectLog() (r *strings.Builder, restore func()) {
|
||||
var buf strings.Builder
|
||||
w := logx.NewWriter(&buf)
|
||||
o := logx.Reset()
|
||||
logx.SetWriter(w)
|
||||
|
||||
return &buf, func() {
|
||||
logx.Reset()
|
||||
logx.SetWriter(o)
|
||||
func TestFormatError(t *testing.T) {
|
||||
// Test case: err is OpError
|
||||
err := &net.OpError{
|
||||
Err: mockOpError{},
|
||||
}
|
||||
assert.Equal(t, "timeout", formatError(err))
|
||||
|
||||
// Test case: err is nil
|
||||
assert.Equal(t, "", formatError(nil))
|
||||
|
||||
// Test case: err is red.Nil
|
||||
assert.Equal(t, "", formatError(red.Nil))
|
||||
|
||||
// Test case: err is io.EOF
|
||||
assert.Equal(t, "eof", formatError(io.EOF))
|
||||
|
||||
// Test case: err is context.DeadlineExceeded
|
||||
assert.Equal(t, "context deadline", formatError(context.DeadlineExceeded))
|
||||
|
||||
// Test case: err is breaker.ErrServiceUnavailable
|
||||
assert.Equal(t, "breaker", formatError(breaker.ErrServiceUnavailable))
|
||||
|
||||
// Test case: err is unknown
|
||||
assert.Equal(t, "unexpected error", formatError(errors.New("some error")))
|
||||
}
|
||||
|
||||
type mockOpError struct {
|
||||
}
|
||||
|
||||
func (mockOpError) Error() string {
|
||||
return "mock error"
|
||||
}
|
||||
|
||||
func (mockOpError) Timeout() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
red "github.com/go-redis/redis/v8"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/errorx"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/mapping"
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
)
|
||||
@@ -91,22 +91,19 @@ type (
|
||||
Script = red.Script
|
||||
)
|
||||
|
||||
// MustNewRedis returns a Redis with given options.
|
||||
func MustNewRedis(conf RedisConf, opts ...Option) *Redis {
|
||||
rds, err := NewRedis(conf, opts...)
|
||||
logx.Must(err)
|
||||
return rds
|
||||
}
|
||||
|
||||
// New returns a Redis with given options.
|
||||
// Deprecated: use MustNewRedis or NewRedis instead.
|
||||
func New(addr string, opts ...Option) *Redis {
|
||||
return newRedis(addr, opts...)
|
||||
}
|
||||
|
||||
// MustNewRedis returns a Redis with given options.
|
||||
func MustNewRedis(conf RedisConf, opts ...Option) *Redis {
|
||||
rds, err := NewRedis(conf, opts...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return rds
|
||||
}
|
||||
|
||||
// NewRedis returns a Redis with given options.
|
||||
func NewRedis(conf RedisConf, opts ...Option) (*Redis, error) {
|
||||
if err := conf.Validate(); err != nil {
|
||||
|
||||
@@ -214,6 +214,17 @@ func (cc CachedConn) SetCacheCtx(ctx context.Context, key string, val any) error
|
||||
return cc.cache.SetCtx(ctx, key, val)
|
||||
}
|
||||
|
||||
// SetCacheWithExpire sets v into cache with given key with given expire.
|
||||
func (cc CachedConn) SetCacheWithExpire(key string, val any, expire time.Duration) error {
|
||||
return cc.SetCacheWithExpireCtx(context.Background(), key, val, expire)
|
||||
}
|
||||
|
||||
// SetCacheWithExpireCtx sets v into cache with given key with given expire.
|
||||
func (cc CachedConn) SetCacheWithExpireCtx(ctx context.Context, key string, val any,
|
||||
expire time.Duration) error {
|
||||
return cc.cache.SetWithExpireCtx(ctx, key, val, expire)
|
||||
}
|
||||
|
||||
// Transact runs given fn in transaction mode.
|
||||
func (cc CachedConn) Transact(fn func(sqlx.Session) error) error {
|
||||
fnCtx := func(_ context.Context, session sqlx.Session) error {
|
||||
@@ -226,3 +237,15 @@ func (cc CachedConn) Transact(fn func(sqlx.Session) error) error {
|
||||
func (cc CachedConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
|
||||
return cc.db.TransactCtx(ctx, fn)
|
||||
}
|
||||
|
||||
// WithSession returns a new CachedConn with given session.
|
||||
// If query from session, the uncommitted data might be returned.
|
||||
// Don't query for the uncommitted data, you should just use it,
|
||||
// and don't use the cache for the uncommitted data.
|
||||
// Not recommend to use cache within transactions due to consistency problem.
|
||||
func (cc CachedConn) WithSession(session sqlx.Session) CachedConn {
|
||||
return CachedConn{
|
||||
db: sqlx.NewSqlConnFromSession(session),
|
||||
cache: cc.cache,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/fx"
|
||||
@@ -24,6 +25,8 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
"github.com/zeromicro/go-zero/internal/dbtest"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -39,7 +42,7 @@ func TestCachedConn_GetCache(t *testing.T) {
|
||||
var value string
|
||||
err := c.GetCache("any", &value)
|
||||
assert.Equal(t, ErrNotFound, err)
|
||||
r.Set("any", `"value"`)
|
||||
_ = r.Set("any", `"value"`)
|
||||
err = c.GetCache("any", &value)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "value", value)
|
||||
@@ -368,6 +371,24 @@ func TestStatFromMemory(t *testing.T) {
|
||||
assert.Equal(t, uint64(9), atomic.LoadUint64(&stats.Hit))
|
||||
}
|
||||
|
||||
func TestCachedConn_DelCache(t *testing.T) {
|
||||
r := redistest.CreateRedis(t)
|
||||
|
||||
const (
|
||||
key = "user"
|
||||
value = "any"
|
||||
)
|
||||
assert.NoError(t, r.Set(key, value))
|
||||
|
||||
c := NewNodeConn(&trackedConn{}, r, cache.WithExpiry(time.Second*30))
|
||||
err := c.DelCache(key)
|
||||
assert.Nil(t, err)
|
||||
|
||||
val, err := r.Get(key)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, val)
|
||||
}
|
||||
|
||||
func TestCachedConnQueryRow(t *testing.T) {
|
||||
r := redistest.CreateRedis(t)
|
||||
|
||||
@@ -477,6 +498,29 @@ func TestCachedConnExecDropCache(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestCachedConn_SetCacheWithExpire(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.Nil(t, err)
|
||||
defer fx.DoWithTimeout(func() error {
|
||||
r.Close()
|
||||
return nil
|
||||
}, time.Second)
|
||||
|
||||
const (
|
||||
key = "user"
|
||||
value = "any"
|
||||
)
|
||||
var conn trackedConn
|
||||
c := NewNodeConn(&conn, redis.New(r.Addr()), cache.WithExpiry(time.Second*30))
|
||||
assert.Nil(t, c.SetCacheWithExpire(key, value, time.Minute))
|
||||
val, err := r.Get(key)
|
||||
if assert.NoError(t, err) {
|
||||
ttl := r.TTL(key)
|
||||
assert.True(t, ttl > 0 && ttl <= time.Minute)
|
||||
assert.Equal(t, fmt.Sprintf("%q", value), val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
||||
const key = "user"
|
||||
var conn trackedConn
|
||||
@@ -543,6 +587,125 @@ func TestNewConnWithCache(t *testing.T) {
|
||||
assert.True(t, conn.execValue)
|
||||
}
|
||||
|
||||
func TestCachedConn_WithSession(t *testing.T) {
|
||||
dbtest.RunTxTest(t, func(tx *sql.Tx, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectExec("any").WillReturnResult(sqlmock.NewResult(2, 3))
|
||||
|
||||
r := redistest.CreateRedis(t)
|
||||
conn := CachedConn{
|
||||
cache: cache.NewNode(r, syncx.NewSingleFlight(), stats, sql.ErrNoRows),
|
||||
}
|
||||
conn = conn.WithSession(sqlx.NewSessionFromTx(tx))
|
||||
res, err := conn.Exec(func(conn sqlx.SqlConn) (sql.Result, error) {
|
||||
return conn.Exec("any")
|
||||
}, "foo")
|
||||
assert.NoError(t, err)
|
||||
last, err := res.LastInsertId()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), last)
|
||||
affected, err := res.RowsAffected()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), affected)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("any").WillReturnResult(sqlmock.NewResult(2, 3))
|
||||
mock.ExpectCommit()
|
||||
|
||||
r := redistest.CreateRedis(t)
|
||||
conn := CachedConn{
|
||||
db: sqlx.NewSqlConnFromDB(db),
|
||||
cache: cache.NewNode(r, syncx.NewSingleFlight(), stats, sql.ErrNoRows),
|
||||
}
|
||||
assert.NoError(t, conn.Transact(func(session sqlx.Session) error {
|
||||
conn = conn.WithSession(session)
|
||||
res, err := conn.Exec(func(conn sqlx.SqlConn) (sql.Result, error) {
|
||||
return conn.Exec("any")
|
||||
}, "foo")
|
||||
assert.NoError(t, err)
|
||||
last, err := res.LastInsertId()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), last)
|
||||
affected, err := res.RowsAffected()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), affected)
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("any").WillReturnError(errors.New("foo"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
r := redistest.CreateRedis(t)
|
||||
conn := CachedConn{
|
||||
db: sqlx.NewSqlConnFromDB(db),
|
||||
cache: cache.NewNode(r, syncx.NewSingleFlight(), stats, sql.ErrNoRows),
|
||||
}
|
||||
assert.Error(t, conn.Transact(func(session sqlx.Session) error {
|
||||
conn = conn.WithSession(session)
|
||||
_, err := conn.Exec(func(conn sqlx.SqlConn) (sql.Result, error) {
|
||||
return conn.Exec("any")
|
||||
}, "bar")
|
||||
return err
|
||||
}))
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("any").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(2))
|
||||
mock.ExpectCommit()
|
||||
|
||||
r := redistest.CreateRedis(t)
|
||||
conn := CachedConn{
|
||||
db: sqlx.NewSqlConnFromDB(db),
|
||||
cache: cache.NewNode(r, syncx.NewSingleFlight(), stats, sql.ErrNoRows),
|
||||
}
|
||||
assert.NoError(t, conn.Transact(func(session sqlx.Session) error {
|
||||
var val string
|
||||
conn = conn.WithSession(session)
|
||||
err := conn.QueryRow(&val, "foo", func(conn sqlx.SqlConn, v interface{}) error {
|
||||
return conn.QueryRow(v, "any")
|
||||
})
|
||||
assert.Equal(t, "2", val)
|
||||
return err
|
||||
}))
|
||||
val, err := r.Get("foo")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `"2"`, val)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("any").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(2))
|
||||
mock.ExpectExec("any").WillReturnResult(sqlmock.NewResult(2, 3))
|
||||
mock.ExpectCommit()
|
||||
|
||||
r := redistest.CreateRedis(t)
|
||||
conn := CachedConn{
|
||||
db: sqlx.NewSqlConnFromDB(db),
|
||||
cache: cache.NewNode(r, syncx.NewSingleFlight(), stats, sql.ErrNoRows),
|
||||
}
|
||||
assert.NoError(t, conn.Transact(func(session sqlx.Session) error {
|
||||
var val string
|
||||
conn = conn.WithSession(session)
|
||||
assert.NoError(t, conn.QueryRow(&val, "foo", func(conn sqlx.SqlConn, v interface{}) error {
|
||||
return conn.QueryRow(v, "any")
|
||||
}))
|
||||
assert.Equal(t, "2", val)
|
||||
_, err := conn.Exec(func(conn sqlx.SqlConn) (sql.Result, error) {
|
||||
return conn.Exec("any")
|
||||
}, "foo")
|
||||
return err
|
||||
}))
|
||||
val, err := r.Get("foo")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, val)
|
||||
})
|
||||
}
|
||||
|
||||
func resetStats() {
|
||||
atomic.StoreUint64(&stats.Total, 0)
|
||||
atomic.StoreUint64(&stats.Hit, 0)
|
||||
@@ -554,35 +717,35 @@ type dummySqlConn struct {
|
||||
queryRow func(any, string, ...any) error
|
||||
}
|
||||
|
||||
func (d dummySqlConn) ExecCtx(ctx context.Context, query string, args ...any) (sql.Result, error) {
|
||||
func (d dummySqlConn) ExecCtx(_ context.Context, _ string, _ ...any) (sql.Result, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) PrepareCtx(ctx context.Context, query string) (sqlx.StmtSession, error) {
|
||||
func (d dummySqlConn) PrepareCtx(_ context.Context, _ string) (sqlx.StmtSession, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) QueryRowPartialCtx(ctx context.Context, v any, query string, args ...any) error {
|
||||
func (d dummySqlConn) QueryRowPartialCtx(_ context.Context, _ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) QueryRowsCtx(ctx context.Context, v any, query string, args ...any) error {
|
||||
func (d dummySqlConn) QueryRowsCtx(_ context.Context, _ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) QueryRowsPartialCtx(ctx context.Context, v any, query string, args ...any) error {
|
||||
func (d dummySqlConn) QueryRowsPartialCtx(_ context.Context, _ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
|
||||
func (d dummySqlConn) TransactCtx(_ context.Context, _ func(context.Context, sqlx.Session) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) Exec(query string, args ...any) (sql.Result, error) {
|
||||
func (d dummySqlConn) Exec(_ string, _ ...any) (sql.Result, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) {
|
||||
func (d dummySqlConn) Prepare(_ string) (sqlx.StmtSession, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -597,15 +760,15 @@ func (d dummySqlConn) QueryRowCtx(_ context.Context, v any, query string, args .
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) QueryRowPartial(v any, query string, args ...any) error {
|
||||
func (d dummySqlConn) QueryRowPartial(_ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) QueryRows(v any, query string, args ...any) error {
|
||||
func (d dummySqlConn) QueryRows(_ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dummySqlConn) QueryRowsPartial(v any, query string, args ...any) error {
|
||||
func (d dummySqlConn) QueryRowsPartial(_ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/internal/dbtest"
|
||||
)
|
||||
|
||||
type mockedConn struct {
|
||||
@@ -81,7 +81,7 @@ func (c *mockedConn) Transact(func(session Session) error) error {
|
||||
}
|
||||
|
||||
func TestBulkInserter(t *testing.T) {
|
||||
runSqlTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var conn mockedConn
|
||||
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES(?, ?, ?)`)
|
||||
assert.Nil(t, err)
|
||||
@@ -98,7 +98,7 @@ func TestBulkInserter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBulkInserterSuffix(t *testing.T) {
|
||||
runSqlTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var conn mockedConn
|
||||
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES`+
|
||||
`(?, ?, ?) ON DUPLICATE KEY UPDATE is_overtime=VALUES(is_overtime)`)
|
||||
@@ -119,7 +119,7 @@ func TestBulkInserterSuffix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBulkInserterBadStatement(t *testing.T) {
|
||||
runSqlTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var conn mockedConn
|
||||
_, err := NewBulkInserter(&conn, "foo")
|
||||
assert.NotNil(t, err)
|
||||
@@ -144,19 +144,3 @@ func TestBulkInserter_Update(t *testing.T) {
|
||||
assert.NotNil(t, inserter.UpdateStmt("foo"))
|
||||
assert.NotNil(t, inserter.Insert("foo", "bar"))
|
||||
}
|
||||
|
||||
func runSqlTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
||||
logx.Disable()
|
||||
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
fn(db, mock)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
14
core/stores/sqlx/errors.go
Normal file
14
core/stores/sqlx/errors.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFound is an alias of sql.ErrNoRows
|
||||
ErrNotFound = sql.ErrNoRows
|
||||
|
||||
errCantNestTx = errors.New("cannot nest transactions")
|
||||
errNoRawDBFromTx = errors.New("cannot get raw db from transaction")
|
||||
)
|
||||
@@ -54,27 +54,39 @@ func getTaggedFieldValueMap(v reflect.Value) (map[string]any, error) {
|
||||
}
|
||||
|
||||
valueField := reflect.Indirect(v).Field(i)
|
||||
switch valueField.Kind() {
|
||||
case reflect.Ptr:
|
||||
if !valueField.CanInterface() {
|
||||
return nil, ErrNotReadableValue
|
||||
}
|
||||
if valueField.IsNil() {
|
||||
baseValueType := mapping.Deref(valueField.Type())
|
||||
valueField.Set(reflect.New(baseValueType))
|
||||
}
|
||||
result[key] = valueField.Interface()
|
||||
default:
|
||||
if !valueField.CanAddr() || !valueField.Addr().CanInterface() {
|
||||
return nil, ErrNotReadableValue
|
||||
}
|
||||
result[key] = valueField.Addr().Interface()
|
||||
valueData, err := getValueInterface(valueField)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result[key] = valueData
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getValueInterface(value reflect.Value) (any, error) {
|
||||
switch value.Kind() {
|
||||
case reflect.Ptr:
|
||||
if !value.CanInterface() {
|
||||
return nil, ErrNotReadableValue
|
||||
}
|
||||
|
||||
if value.IsNil() {
|
||||
baseValueType := mapping.Deref(value.Type())
|
||||
value.Set(reflect.New(baseValueType))
|
||||
}
|
||||
|
||||
return value.Interface(), nil
|
||||
default:
|
||||
if !value.CanAddr() || !value.Addr().CanInterface() {
|
||||
return nil, ErrNotReadableValue
|
||||
}
|
||||
|
||||
return value.Addr().Interface(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func mapStructFieldsIntoSlice(v reflect.Value, columns []string, strict bool) ([]any, error) {
|
||||
fields := unwrapFields(v)
|
||||
if strict && len(columns) < len(fields) {
|
||||
@@ -88,24 +100,18 @@ func mapStructFieldsIntoSlice(v reflect.Value, columns []string, strict bool) ([
|
||||
|
||||
values := make([]any, len(columns))
|
||||
if len(taggedMap) == 0 {
|
||||
if len(fields) < len(values) {
|
||||
return nil, ErrNotMatchDestination
|
||||
}
|
||||
|
||||
for i := 0; i < len(values); i++ {
|
||||
valueField := fields[i]
|
||||
switch valueField.Kind() {
|
||||
case reflect.Ptr:
|
||||
if !valueField.CanInterface() {
|
||||
return nil, ErrNotReadableValue
|
||||
}
|
||||
if valueField.IsNil() {
|
||||
baseValueType := mapping.Deref(valueField.Type())
|
||||
valueField.Set(reflect.New(baseValueType))
|
||||
}
|
||||
values[i] = valueField.Interface()
|
||||
default:
|
||||
if !valueField.CanAddr() || !valueField.Addr().CanInterface() {
|
||||
return nil, ErrNotReadableValue
|
||||
}
|
||||
values[i] = valueField.Addr().Interface()
|
||||
valueData, err := getValueInterface(valueField)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values[i] = valueData
|
||||
}
|
||||
} else {
|
||||
for i, column := range columns {
|
||||
@@ -152,11 +158,11 @@ func unmarshalRow(v any, scanner rowsScanner, strict bool) error {
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.String:
|
||||
if rve.CanSet() {
|
||||
return scanner.Scan(v)
|
||||
if !rve.CanSet() {
|
||||
return ErrNotSettable
|
||||
}
|
||||
|
||||
return ErrNotSettable
|
||||
return scanner.Scan(v)
|
||||
case reflect.Struct:
|
||||
columns, err := scanner.Columns()
|
||||
if err != nil {
|
||||
@@ -183,69 +189,66 @@ func unmarshalRows(v any, scanner rowsScanner, strict bool) error {
|
||||
rt := reflect.TypeOf(v)
|
||||
rte := rt.Elem()
|
||||
rve := rv.Elem()
|
||||
if !rve.CanSet() {
|
||||
return ErrNotSettable
|
||||
}
|
||||
|
||||
switch rte.Kind() {
|
||||
case reflect.Slice:
|
||||
if rve.CanSet() {
|
||||
ptr := rte.Elem().Kind() == reflect.Ptr
|
||||
appendFn := func(item reflect.Value) {
|
||||
if ptr {
|
||||
rve.Set(reflect.Append(rve, item))
|
||||
} else {
|
||||
rve.Set(reflect.Append(rve, reflect.Indirect(item)))
|
||||
}
|
||||
ptr := rte.Elem().Kind() == reflect.Ptr
|
||||
appendFn := func(item reflect.Value) {
|
||||
if ptr {
|
||||
rve.Set(reflect.Append(rve, item))
|
||||
} else {
|
||||
rve.Set(reflect.Append(rve, reflect.Indirect(item)))
|
||||
}
|
||||
fillFn := func(value any) error {
|
||||
if rve.CanSet() {
|
||||
if err := scanner.Scan(value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appendFn(reflect.ValueOf(value))
|
||||
return nil
|
||||
}
|
||||
return ErrNotSettable
|
||||
}
|
||||
fillFn := func(value any) error {
|
||||
if err := scanner.Scan(value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
base := mapping.Deref(rte.Elem())
|
||||
switch base.Kind() {
|
||||
case reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.String:
|
||||
for scanner.Next() {
|
||||
value := reflect.New(base)
|
||||
if err := fillFn(value.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
appendFn(reflect.ValueOf(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
base := mapping.Deref(rte.Elem())
|
||||
switch base.Kind() {
|
||||
case reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.String:
|
||||
for scanner.Next() {
|
||||
value := reflect.New(base)
|
||||
if err := fillFn(value.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Struct:
|
||||
columns, err := scanner.Columns()
|
||||
}
|
||||
case reflect.Struct:
|
||||
columns, err := scanner.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for scanner.Next() {
|
||||
value := reflect.New(base)
|
||||
values, err := mapStructFieldsIntoSlice(value, columns, strict)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for scanner.Next() {
|
||||
value := reflect.New(base)
|
||||
values, err := mapStructFieldsIntoSlice(value, columns, strict)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := scanner.Scan(values...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appendFn(value)
|
||||
if err := scanner.Scan(values...); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return ErrUnsupportedValueType
|
||||
}
|
||||
|
||||
return nil
|
||||
appendFn(value)
|
||||
}
|
||||
default:
|
||||
return ErrUnsupportedValueType
|
||||
}
|
||||
|
||||
return ErrNotSettable
|
||||
return nil
|
||||
default:
|
||||
return ErrUnsupportedValueType
|
||||
}
|
||||
@@ -257,6 +260,10 @@ func unwrapFields(v reflect.Value) []reflect.Value {
|
||||
|
||||
for i := 0; i < indirect.NumField(); i++ {
|
||||
child := indirect.Field(i)
|
||||
if !child.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
if child.Kind() == reflect.Ptr && child.IsNil() {
|
||||
baseValueType := mapping.Deref(child.Type())
|
||||
child.Set(reflect.New(baseValueType))
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/internal/dbtest"
|
||||
)
|
||||
|
||||
func TestUnmarshalRowBool(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -22,10 +22,22 @@ func TestUnmarshalRowBool(t *testing.T) {
|
||||
}, "select value from users where user=?", "anyone"))
|
||||
assert.True(t, value)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
var value struct {
|
||||
Value bool `db:"value"`
|
||||
}
|
||||
assert.Error(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, rows, true)
|
||||
}, "select value from users where user=?", "anyone"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowBoolNotSettable(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -37,7 +49,7 @@ func TestUnmarshalRowBoolNotSettable(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowInt(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -50,7 +62,7 @@ func TestUnmarshalRowInt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowInt8(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -63,7 +75,7 @@ func TestUnmarshalRowInt8(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowInt16(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("4")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -76,7 +88,7 @@ func TestUnmarshalRowInt16(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowInt32(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -89,7 +101,7 @@ func TestUnmarshalRowInt32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowInt64(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("6")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -102,7 +114,7 @@ func TestUnmarshalRowInt64(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowUint(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -115,7 +127,7 @@ func TestUnmarshalRowUint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowUint8(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -128,7 +140,7 @@ func TestUnmarshalRowUint8(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowUint16(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("4")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -141,7 +153,7 @@ func TestUnmarshalRowUint16(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowUint32(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -154,7 +166,7 @@ func TestUnmarshalRowUint32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowUint64(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("6")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -167,7 +179,7 @@ func TestUnmarshalRowUint64(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowFloat32(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("7")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -180,7 +192,7 @@ func TestUnmarshalRowFloat32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowFloat64(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("8")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -193,7 +205,7 @@ func TestUnmarshalRowFloat64(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowString(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
const expect = "hello"
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString(expect)
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -207,12 +219,12 @@ func TestUnmarshalRowString(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowStruct(t *testing.T) {
|
||||
value := new(struct {
|
||||
Name string
|
||||
Age int
|
||||
})
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
value := new(struct {
|
||||
Name string
|
||||
Age int
|
||||
})
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -222,15 +234,58 @@ func TestUnmarshalRowStruct(t *testing.T) {
|
||||
assert.Equal(t, "liao", value.Name)
|
||||
assert.Equal(t, 5, value.Age)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
value := new(struct {
|
||||
Name string
|
||||
Age int
|
||||
})
|
||||
|
||||
errAny := errors.New("any error")
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, &mockedScanner{
|
||||
colErr: errAny,
|
||||
next: 1,
|
||||
}, true)
|
||||
}, "select name, age from users where user=?", "anyone"), errAny)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
value := new(struct {
|
||||
Name string
|
||||
age *int
|
||||
})
|
||||
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, rows, true)
|
||||
}, "select name, age from users where user=?", "anyone"), ErrNotMatchDestination)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("8")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
type myString chan int
|
||||
var value myString
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(&value, rows, true)
|
||||
}, "select value from users where user=?", "anyone"), ErrUnsupportedValueType)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowStructWithTags(t *testing.T) {
|
||||
value := new(struct {
|
||||
Age int `db:"age"`
|
||||
Name string `db:"name"`
|
||||
})
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
value := new(struct {
|
||||
Age int `db:"age"`
|
||||
Name string `db:"name"`
|
||||
})
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -240,6 +295,51 @@ func TestUnmarshalRowStructWithTags(t *testing.T) {
|
||||
assert.Equal(t, "liao", value.Name)
|
||||
assert.Equal(t, 5, value.Age)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
value := new(struct {
|
||||
age *int `db:"age"`
|
||||
Name string `db:"name"`
|
||||
})
|
||||
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, rows, true)
|
||||
}, "select name, age from users where user=?", "anyone"), ErrNotReadableValue)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var value struct {
|
||||
Age *int `db:"age"`
|
||||
Name *string `db:"name"`
|
||||
}
|
||||
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(&value, rows, true)
|
||||
}, "select name, age from users where user=?", "anyone"))
|
||||
assert.Equal(t, "liao", *value.Name)
|
||||
assert.Equal(t, 5, *value.Age)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
value := new(struct {
|
||||
Age int `db:"age"`
|
||||
Name string
|
||||
})
|
||||
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("liao,5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRow(value, rows, true)
|
||||
}, "select name, age from users where user=?", "anyone"))
|
||||
assert.Equal(t, 5, value.Age)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
|
||||
@@ -248,7 +348,7 @@ func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
|
||||
Name string `db:"name"`
|
||||
})
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name"}).FromCSVString("liao")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -259,7 +359,7 @@ func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsBool(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []bool{true, false}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1\n0")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -270,10 +370,46 @@ func TestUnmarshalRowsBool(t *testing.T) {
|
||||
}, "select value from users where user=?", "anyone"))
|
||||
assert.EqualValues(t, expect, value)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1\n0")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
var value []bool
|
||||
assert.Error(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(value, rows, true)
|
||||
}, "select value from users where user=?", "anyone"))
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1\n0")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
var value struct {
|
||||
value []bool `db:"value"`
|
||||
}
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(&value, rows, true)
|
||||
}, "select value from users where user=?", "anyone"), ErrUnsupportedValueType)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1\n0")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
var value []bool
|
||||
errAny := errors.New("any")
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(&value, &mockedScanner{
|
||||
scanErr: errAny,
|
||||
next: 1,
|
||||
}, true)
|
||||
}, "select value from users where user=?", "anyone"), errAny)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsInt(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []int{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -287,7 +423,7 @@ func TestUnmarshalRowsInt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsInt8(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []int8{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -301,7 +437,7 @@ func TestUnmarshalRowsInt8(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsInt16(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []int16{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -315,7 +451,7 @@ func TestUnmarshalRowsInt16(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsInt32(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []int32{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -329,7 +465,7 @@ func TestUnmarshalRowsInt32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsInt64(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []int64{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -343,7 +479,7 @@ func TestUnmarshalRowsInt64(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsUint(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []uint{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -357,7 +493,7 @@ func TestUnmarshalRowsUint(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsUint8(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []uint8{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -371,7 +507,7 @@ func TestUnmarshalRowsUint8(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsUint16(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []uint16{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -385,7 +521,7 @@ func TestUnmarshalRowsUint16(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsUint32(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []uint32{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -399,7 +535,7 @@ func TestUnmarshalRowsUint32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsUint64(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []uint64{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -413,7 +549,7 @@ func TestUnmarshalRowsUint64(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsFloat32(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []float32{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -427,7 +563,7 @@ func TestUnmarshalRowsFloat32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsFloat64(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []float64{2, 3}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -441,7 +577,7 @@ func TestUnmarshalRowsFloat64(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsString(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []string{"hello", "world"}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("hello\nworld")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -457,7 +593,7 @@ func TestUnmarshalRowsString(t *testing.T) {
|
||||
func TestUnmarshalRowsBoolPtr(t *testing.T) {
|
||||
yes := true
|
||||
no := false
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*bool{&yes, &no}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1\n0")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -473,7 +609,7 @@ func TestUnmarshalRowsBoolPtr(t *testing.T) {
|
||||
func TestUnmarshalRowsIntPtr(t *testing.T) {
|
||||
two := 2
|
||||
three := 3
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*int{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -489,7 +625,7 @@ func TestUnmarshalRowsIntPtr(t *testing.T) {
|
||||
func TestUnmarshalRowsInt8Ptr(t *testing.T) {
|
||||
two := int8(2)
|
||||
three := int8(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*int8{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -505,7 +641,7 @@ func TestUnmarshalRowsInt8Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsInt16Ptr(t *testing.T) {
|
||||
two := int16(2)
|
||||
three := int16(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*int16{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -521,7 +657,7 @@ func TestUnmarshalRowsInt16Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsInt32Ptr(t *testing.T) {
|
||||
two := int32(2)
|
||||
three := int32(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*int32{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -537,7 +673,7 @@ func TestUnmarshalRowsInt32Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsInt64Ptr(t *testing.T) {
|
||||
two := int64(2)
|
||||
three := int64(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*int64{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -553,7 +689,7 @@ func TestUnmarshalRowsInt64Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsUintPtr(t *testing.T) {
|
||||
two := uint(2)
|
||||
three := uint(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*uint{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -569,7 +705,7 @@ func TestUnmarshalRowsUintPtr(t *testing.T) {
|
||||
func TestUnmarshalRowsUint8Ptr(t *testing.T) {
|
||||
two := uint8(2)
|
||||
three := uint8(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*uint8{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -585,7 +721,7 @@ func TestUnmarshalRowsUint8Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsUint16Ptr(t *testing.T) {
|
||||
two := uint16(2)
|
||||
three := uint16(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*uint16{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -601,7 +737,7 @@ func TestUnmarshalRowsUint16Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsUint32Ptr(t *testing.T) {
|
||||
two := uint32(2)
|
||||
three := uint32(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*uint32{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -617,7 +753,7 @@ func TestUnmarshalRowsUint32Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsUint64Ptr(t *testing.T) {
|
||||
two := uint64(2)
|
||||
three := uint64(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*uint64{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -633,7 +769,7 @@ func TestUnmarshalRowsUint64Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsFloat32Ptr(t *testing.T) {
|
||||
two := float32(2)
|
||||
three := float32(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*float32{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -649,7 +785,7 @@ func TestUnmarshalRowsFloat32Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsFloat64Ptr(t *testing.T) {
|
||||
two := float64(2)
|
||||
three := float64(3)
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*float64{&two, &three}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2\n3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -665,7 +801,7 @@ func TestUnmarshalRowsFloat64Ptr(t *testing.T) {
|
||||
func TestUnmarshalRowsStringPtr(t *testing.T) {
|
||||
hello := "hello"
|
||||
world := "world"
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []*string{&hello, &world}
|
||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("hello\nworld")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -679,25 +815,25 @@ func TestUnmarshalRowsStringPtr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsStruct(t *testing.T) {
|
||||
expect := []struct {
|
||||
Name string
|
||||
Age int64
|
||||
}{
|
||||
{
|
||||
Name: "first",
|
||||
Age: 2,
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
Age: 3,
|
||||
},
|
||||
}
|
||||
var value []struct {
|
||||
Name string
|
||||
Age int64
|
||||
}
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
expect := []struct {
|
||||
Name string
|
||||
Age int64
|
||||
}{
|
||||
{
|
||||
Name: "first",
|
||||
Age: 2,
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
Age: 3,
|
||||
},
|
||||
}
|
||||
var value []struct {
|
||||
Name string
|
||||
Age int64
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
@@ -709,6 +845,56 @@ func TestUnmarshalRowsStruct(t *testing.T) {
|
||||
assert.Equal(t, each.Age, value[i].Age)
|
||||
}
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var value []struct {
|
||||
Name string
|
||||
Age int64
|
||||
}
|
||||
|
||||
errAny := errors.New("any error")
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(&value, &mockedScanner{
|
||||
colErr: errAny,
|
||||
next: 1,
|
||||
}, true)
|
||||
}, "select name, age from users where user=?", "anyone"), errAny)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var value []struct {
|
||||
Name string
|
||||
Age int64
|
||||
}
|
||||
|
||||
errAny := errors.New("any error")
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(&value, &mockedScanner{
|
||||
cols: []string{"name", "age"},
|
||||
scanErr: errAny,
|
||||
next: 1,
|
||||
}, true)
|
||||
}, "select name, age from users where user=?", "anyone"), errAny)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
var value []chan int
|
||||
|
||||
errAny := errors.New("any error")
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.ErrorIs(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(&value, &mockedScanner{
|
||||
cols: []string{"name", "age"},
|
||||
scanErr: errAny,
|
||||
next: 1,
|
||||
}, true)
|
||||
}, "select name, age from users where user=?", "anyone"), ErrUnsupportedValueType)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalRowsStructWithNullStringType(t *testing.T) {
|
||||
@@ -736,7 +922,7 @@ func TestUnmarshalRowsStructWithNullStringType(t *testing.T) {
|
||||
NullString sql.NullString `db:"value"`
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "value"}).AddRow(
|
||||
"first", "firstnullstring").AddRow("second", nil)
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
@@ -771,7 +957,7 @@ func TestUnmarshalRowsStructWithTags(t *testing.T) {
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
@@ -812,7 +998,7 @@ func TestUnmarshalRowsStructAndEmbeddedAnonymousStructWithTags(t *testing.T) {
|
||||
Embed
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age", "value"}).FromCSVString("first,2,3\nsecond,3,4")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
@@ -854,7 +1040,7 @@ func TestUnmarshalRowsStructAndEmbeddedStructPtrAnonymousWithTags(t *testing.T)
|
||||
*Embed
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age", "value"}).FromCSVString("first,2,3\nsecond,3,4")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
@@ -888,7 +1074,7 @@ func TestUnmarshalRowsStructPtr(t *testing.T) {
|
||||
Age int64
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
@@ -921,7 +1107,7 @@ func TestUnmarshalRowsStructWithTagsPtr(t *testing.T) {
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
@@ -954,7 +1140,7 @@ func TestUnmarshalRowsStructWithTagsPtrWithInnerPtr(t *testing.T) {
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"name", "age"}).FromCSVString("first,2\nsecond,3")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Nil(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
@@ -969,7 +1155,7 @@ func TestUnmarshalRowsStructWithTagsPtrWithInnerPtr(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCommonSqlConn_QueryRowOptional(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"age"}).FromCSVString("5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||
|
||||
@@ -1019,7 +1205,7 @@ func TestUnmarshalRowError(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{"age"}).FromCSVString("5")
|
||||
mock.ExpectQuery("select (.+) from users where user=?").WithArgs(
|
||||
"anyone").WillReturnRows(rs)
|
||||
@@ -1091,7 +1277,7 @@ func TestAnonymousStructPr(t *testing.T) {
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{
|
||||
"name",
|
||||
"age",
|
||||
@@ -1139,7 +1325,7 @@ func TestAnonymousStructPrError(t *testing.T) {
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rs := sqlmock.NewRows([]string{
|
||||
"name",
|
||||
"age",
|
||||
@@ -1154,7 +1340,7 @@ func TestAnonymousStructPrError(t *testing.T) {
|
||||
WithArgs("anyone").WillReturnRows(rs)
|
||||
assert.Error(t, query(context.Background(), db, func(rows *sql.Rows) error {
|
||||
return unmarshalRows(&value, rows, true)
|
||||
}, "select name, age,grade,discipline,class_name,score from users where user=?",
|
||||
}, "select name, age, grade, discipline, class_name, score from users where user=?",
|
||||
"anyone"))
|
||||
if len(value) > 0 {
|
||||
assert.Equal(t, value[0].score, 0)
|
||||
@@ -1162,23 +1348,8 @@ func TestAnonymousStructPrError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
||||
logx.Disable()
|
||||
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
fn(db, mock)
|
||||
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type mockedScanner struct {
|
||||
cols []string
|
||||
colErr error
|
||||
scanErr error
|
||||
err error
|
||||
@@ -1186,7 +1357,7 @@ type mockedScanner struct {
|
||||
}
|
||||
|
||||
func (m *mockedScanner) Columns() ([]string, error) {
|
||||
return nil, m.colErr
|
||||
return m.cols, m.colErr
|
||||
}
|
||||
|
||||
func (m *mockedScanner) Err() error {
|
||||
|
||||
@@ -11,9 +11,6 @@ import (
|
||||
// spanName is used to identify the span name for the SQL execution.
|
||||
const spanName = "sql"
|
||||
|
||||
// ErrNotFound is an alias of sql.ErrNoRows
|
||||
var ErrNotFound = sql.ErrNoRows
|
||||
|
||||
type (
|
||||
// Session stands for raw connections or transaction sessions
|
||||
Session interface {
|
||||
@@ -131,6 +128,13 @@ func NewSqlConnFromDB(db *sql.DB, opts ...SqlOption) SqlConn {
|
||||
return conn
|
||||
}
|
||||
|
||||
// NewSqlConnFromSession returns a SqlConn with the given session.
|
||||
func NewSqlConnFromSession(session Session) SqlConn {
|
||||
return txConn{
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *commonSqlConn) Exec(q string, args ...any) (result sql.Result, err error) {
|
||||
return db.ExecCtx(context.Background(), q, args...)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@ package sqlx
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/trace/tracetest"
|
||||
"github.com/zeromicro/go-zero/internal/dbtest"
|
||||
)
|
||||
|
||||
const mockedDatasource = "sqlmock"
|
||||
@@ -54,8 +57,187 @@ func TestSqlConn(t *testing.T) {
|
||||
assert.Equal(t, 14, len(me.GetSpans()))
|
||||
}
|
||||
|
||||
func TestSqlConn_RawDB(t *testing.T) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"foo"}).AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
conn := NewSqlConnFromDB(db)
|
||||
var val string
|
||||
assert.NoError(t, conn.QueryRow(&val, "any"))
|
||||
assert.Equal(t, "bar", val)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"foo"}).AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
conn := NewSqlConnFromDB(db)
|
||||
var val string
|
||||
assert.NoError(t, conn.QueryRowPartial(&val, "any"))
|
||||
assert.Equal(t, "bar", val)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"any"}).AddRow("foo").AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
conn := NewSqlConnFromDB(db)
|
||||
var vals []string
|
||||
assert.NoError(t, conn.QueryRows(&vals, "any"))
|
||||
assert.ElementsMatch(t, []string{"foo", "bar"}, vals)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"any"}).AddRow("foo").AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
conn := NewSqlConnFromDB(db)
|
||||
var vals []string
|
||||
assert.NoError(t, conn.QueryRowsPartial(&vals, "any"))
|
||||
assert.ElementsMatch(t, []string{"foo", "bar"}, vals)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlConn_Errors(t *testing.T) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
conn := NewSqlConnFromDB(db)
|
||||
conn.(*commonSqlConn).connProv = func() (*sql.DB, error) {
|
||||
return nil, errors.New("error")
|
||||
}
|
||||
_, err := conn.Prepare("any")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectExec("any").WillReturnError(breaker.ErrServiceUnavailable)
|
||||
conn := NewSqlConnFromDB(db)
|
||||
_, err := conn.Exec("any")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any").WillReturnError(breaker.ErrServiceUnavailable)
|
||||
conn := NewSqlConnFromDB(db)
|
||||
_, err := conn.Prepare("any")
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectRollback()
|
||||
conn := NewSqlConnFromDB(db)
|
||||
err := conn.Transact(func(session Session) error {
|
||||
return breaker.ErrServiceUnavailable
|
||||
})
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, err)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectQuery("any").WillReturnError(breaker.ErrServiceUnavailable)
|
||||
conn := NewSqlConnFromDB(db)
|
||||
var vals []string
|
||||
err := conn.QueryRows(&vals, "any")
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStatement(t *testing.T) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any").WillBeClosed()
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
stmt, err := conn.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, stmt.Close())
|
||||
})
|
||||
|
||||
dbtest.RunTxTest(t, func(tx *sql.Tx, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any").WillBeClosed()
|
||||
|
||||
stmt, err := tx.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
st := statement{
|
||||
query: "foo",
|
||||
stmt: stmt,
|
||||
}
|
||||
assert.NoError(t, st.Close())
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any")
|
||||
mock.ExpectExec("any").WillReturnResult(sqlmock.NewResult(2, 3))
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
stmt, err := conn.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
res, err := stmt.Exec()
|
||||
assert.NoError(t, err)
|
||||
lastInsertID, err := res.LastInsertId()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), lastInsertID)
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), rowsAffected)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any")
|
||||
row := sqlmock.NewRows([]string{"foo"}).AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(row)
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
stmt, err := conn.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var val string
|
||||
err = stmt.QueryRow(&val)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "bar", val)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any")
|
||||
row := sqlmock.NewRows([]string{"foo"}).AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(row)
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
stmt, err := conn.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var val string
|
||||
err = stmt.QueryRowPartial(&val)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "bar", val)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any")
|
||||
rows := sqlmock.NewRows([]string{"any"}).AddRow("foo").AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
stmt, err := conn.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var vals []string
|
||||
assert.NoError(t, stmt.QueryRows(&vals))
|
||||
assert.ElementsMatch(t, []string{"foo", "bar"}, vals)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any")
|
||||
rows := sqlmock.NewRows([]string{"any"}).AddRow("foo").AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
stmt, err := conn.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var vals []string
|
||||
assert.NoError(t, stmt.QueryRowsPartial(&vals))
|
||||
assert.ElementsMatch(t, []string{"foo", "bar"}, vals)
|
||||
})
|
||||
}
|
||||
|
||||
func buildConn() (mock sqlmock.Sqlmock, err error) {
|
||||
connManager.GetResource(mockedDatasource, func() (io.Closer, error) {
|
||||
_, err = connManager.GetResource(mockedDatasource, func() (io.Closer, error) {
|
||||
var db *sql.DB
|
||||
var err error
|
||||
db, mock, err = sqlmock.New()
|
||||
|
||||
@@ -136,7 +136,7 @@ func (e *realSqlGuard) finish(ctx context.Context, err error) {
|
||||
logSqlError(ctx, e.stmt, err)
|
||||
}
|
||||
|
||||
metricReqDur.Observe(int64(duration/time.Millisecond), e.command)
|
||||
metricReqDur.Observe(duration.Milliseconds(), e.command)
|
||||
}
|
||||
|
||||
func (e *realSqlGuard) start(q string, args ...any) error {
|
||||
|
||||
@@ -15,11 +15,27 @@ type (
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
txConn struct {
|
||||
Session
|
||||
}
|
||||
|
||||
txSession struct {
|
||||
*sql.Tx
|
||||
}
|
||||
)
|
||||
|
||||
func (s txConn) RawDB() (*sql.DB, error) {
|
||||
return nil, errNoRawDBFromTx
|
||||
}
|
||||
|
||||
func (s txConn) Transact(_ func(Session) error) error {
|
||||
return errCantNestTx
|
||||
}
|
||||
|
||||
func (s txConn) TransactCtx(_ context.Context, _ func(context.Context, Session) error) error {
|
||||
return errCantNestTx
|
||||
}
|
||||
|
||||
// NewSessionFromTx returns a Session with the given sql.Tx.
|
||||
// Use it with caution, it's provided for other ORM to interact with.
|
||||
func NewSessionFromTx(tx *sql.Tx) Session {
|
||||
|
||||
@@ -6,7 +6,10 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/internal/dbtest"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,51 +26,51 @@ func (mt *mockTx) Commit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) Exec(q string, args ...any) (sql.Result, error) {
|
||||
func (mt *mockTx) Exec(_ string, _ ...any) (sql.Result, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) ExecCtx(ctx context.Context, query string, args ...any) (sql.Result, error) {
|
||||
func (mt *mockTx) ExecCtx(_ context.Context, _ string, _ ...any) (sql.Result, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) Prepare(query string) (StmtSession, error) {
|
||||
func (mt *mockTx) Prepare(_ string) (StmtSession, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) PrepareCtx(ctx context.Context, query string) (StmtSession, error) {
|
||||
func (mt *mockTx) PrepareCtx(_ context.Context, _ string) (StmtSession, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRow(v any, q string, args ...any) error {
|
||||
func (mt *mockTx) QueryRow(_ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRowCtx(ctx context.Context, v any, query string, args ...any) error {
|
||||
func (mt *mockTx) QueryRowCtx(_ context.Context, _ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRowPartial(v any, q string, args ...any) error {
|
||||
func (mt *mockTx) QueryRowPartial(_ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRowPartialCtx(ctx context.Context, v any, query string, args ...any) error {
|
||||
func (mt *mockTx) QueryRowPartialCtx(_ context.Context, _ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRows(v any, q string, args ...any) error {
|
||||
func (mt *mockTx) QueryRows(_ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRowsCtx(ctx context.Context, v any, query string, args ...any) error {
|
||||
func (mt *mockTx) QueryRowsCtx(_ context.Context, _ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRowsPartial(v any, q string, args ...any) error {
|
||||
func (mt *mockTx) QueryRowsPartial(_ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *mockTx) QueryRowsPartialCtx(ctx context.Context, v any, query string, args ...any) error {
|
||||
func (mt *mockTx) QueryRowsPartialCtx(_ context.Context, _ any, _ string, _ ...any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -101,3 +104,209 @@ func TestTransactRollback(t *testing.T) {
|
||||
assert.Equal(t, mockRollback, mock.status)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestTxExceptions(t *testing.T) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectCommit()
|
||||
conn := NewSqlConnFromDB(db)
|
||||
assert.NoError(t, conn.Transact(func(session Session) error {
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
conn := &commonSqlConn{
|
||||
connProv: func() (*sql.DB, error) {
|
||||
return nil, errors.New("foo")
|
||||
},
|
||||
beginTx: begin,
|
||||
onError: func(ctx context.Context, err error) {},
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
assert.Error(t, conn.Transact(func(session Session) error {
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
|
||||
runTxTest(t, func(conn SqlConn, mock sqlmock.Sqlmock) {
|
||||
_, err := conn.RawDB()
|
||||
assert.Equal(t, errNoRawDBFromTx, err)
|
||||
assert.Equal(t, errCantNestTx, conn.Transact(nil))
|
||||
assert.Equal(t, errCantNestTx, conn.TransactCtx(context.Background(), nil))
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
conn := NewSqlConnFromDB(db)
|
||||
assert.Error(t, conn.Transact(func(session Session) error {
|
||||
return errors.New("foo")
|
||||
}))
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectRollback().WillReturnError(errors.New("foo"))
|
||||
conn := NewSqlConnFromDB(db)
|
||||
assert.Error(t, conn.Transact(func(session Session) error {
|
||||
panic("foo")
|
||||
}))
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectRollback()
|
||||
conn := NewSqlConnFromDB(db)
|
||||
assert.Error(t, conn.Transact(func(session Session) error {
|
||||
panic(errors.New("foo"))
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTxSession(t *testing.T) {
|
||||
runTxTest(t, func(conn SqlConn, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectExec("any").WillReturnResult(sqlmock.NewResult(2, 3))
|
||||
res, err := conn.Exec("any")
|
||||
assert.NoError(t, err)
|
||||
last, err := res.LastInsertId()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(2), last)
|
||||
affected, err := res.RowsAffected()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), affected)
|
||||
|
||||
mock.ExpectExec("any").WillReturnError(errors.New("foo"))
|
||||
_, err = conn.Exec("any")
|
||||
assert.Equal(t, "foo", err.Error())
|
||||
})
|
||||
|
||||
runTxTest(t, func(conn SqlConn, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectPrepare("any")
|
||||
stmt, err := conn.Prepare("any")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, stmt)
|
||||
|
||||
mock.ExpectPrepare("any").WillReturnError(errors.New("foo"))
|
||||
_, err = conn.Prepare("any")
|
||||
assert.Equal(t, "foo", err.Error())
|
||||
})
|
||||
|
||||
runTxTest(t, func(conn SqlConn, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"col"}).AddRow("foo")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
var val string
|
||||
err := conn.QueryRow(&val, "any")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "foo", val)
|
||||
|
||||
mock.ExpectQuery("any").WillReturnError(errors.New("foo"))
|
||||
err = conn.QueryRow(&val, "any")
|
||||
assert.Equal(t, "foo", err.Error())
|
||||
})
|
||||
|
||||
runTxTest(t, func(conn SqlConn, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"col"}).AddRow("foo")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
var val string
|
||||
err := conn.QueryRowPartial(&val, "any")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "foo", val)
|
||||
|
||||
mock.ExpectQuery("any").WillReturnError(errors.New("foo"))
|
||||
err = conn.QueryRowPartial(&val, "any")
|
||||
assert.Equal(t, "foo", err.Error())
|
||||
})
|
||||
|
||||
runTxTest(t, func(conn SqlConn, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"col"}).AddRow("foo").AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
var val []string
|
||||
err := conn.QueryRows(&val, "any")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"foo", "bar"}, val)
|
||||
|
||||
mock.ExpectQuery("any").WillReturnError(errors.New("foo"))
|
||||
err = conn.QueryRows(&val, "any")
|
||||
assert.Equal(t, "foo", err.Error())
|
||||
})
|
||||
|
||||
runTxTest(t, func(conn SqlConn, mock sqlmock.Sqlmock) {
|
||||
rows := sqlmock.NewRows([]string{"col"}).AddRow("foo").AddRow("bar")
|
||||
mock.ExpectQuery("any").WillReturnRows(rows)
|
||||
var val []string
|
||||
err := conn.QueryRowsPartial(&val, "any")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"foo", "bar"}, val)
|
||||
|
||||
mock.ExpectQuery("any").WillReturnError(errors.New("foo"))
|
||||
err = conn.QueryRowsPartial(&val, "any")
|
||||
assert.Equal(t, "foo", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestTxRollback(t *testing.T) {
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("any").WillReturnResult(sqlmock.NewResult(2, 3))
|
||||
mock.ExpectQuery("foo").WillReturnError(errors.New("foo"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
err := conn.Transact(func(session Session) error {
|
||||
c := NewSqlConnFromSession(session)
|
||||
_, err := c.Exec("any")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var val string
|
||||
return c.QueryRow(&val, "foo")
|
||||
})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("any").WillReturnError(errors.New("foo"))
|
||||
mock.ExpectRollback()
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
err := conn.Transact(func(session Session) error {
|
||||
c := NewSqlConnFromSession(session)
|
||||
if _, err := c.Exec("any"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var val string
|
||||
assert.NoError(t, c.QueryRow(&val, "foo"))
|
||||
return nil
|
||||
})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
dbtest.RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("any").WillReturnResult(sqlmock.NewResult(2, 3))
|
||||
mock.ExpectQuery("foo").WillReturnRows(sqlmock.NewRows([]string{"col"}).AddRow("bar"))
|
||||
mock.ExpectCommit()
|
||||
|
||||
conn := NewSqlConnFromDB(db)
|
||||
err := conn.Transact(func(session Session) error {
|
||||
c := NewSqlConnFromSession(session)
|
||||
_, err := c.Exec("any")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var val string
|
||||
assert.NoError(t, c.QueryRow(&val, "foo"))
|
||||
assert.Equal(t, "bar", val)
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func runTxTest(t *testing.T, f func(conn SqlConn, mock sqlmock.Sqlmock)) {
|
||||
dbtest.RunTxTest(t, func(tx *sql.Tx, mock sqlmock.Sqlmock) {
|
||||
sess := NewSessionFromTx(tx)
|
||||
conn := NewSqlConnFromSession(sess)
|
||||
f(conn, mock)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,12 +20,14 @@ func ForAtomicBool(val bool) *AtomicBool {
|
||||
// CompareAndSwap compares current value with given old, if equals, set to given val.
|
||||
func (b *AtomicBool) CompareAndSwap(old, val bool) bool {
|
||||
var ov, nv uint32
|
||||
|
||||
if old {
|
||||
ov = 1
|
||||
}
|
||||
if val {
|
||||
nv = 1
|
||||
}
|
||||
|
||||
return atomic.CompareAndSwapUint32((*uint32)(b), ov, nv)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package threading
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
@@ -13,6 +14,11 @@ func GoSafe(fn func()) {
|
||||
go RunSafe(fn)
|
||||
}
|
||||
|
||||
// GoSafeCtx runs the given fn using another goroutine, recovers if fn panics with ctx.
|
||||
func GoSafeCtx(ctx context.Context, fn func()) {
|
||||
go RunSafeCtx(ctx, fn)
|
||||
}
|
||||
|
||||
// RoutineId is only for debug, never use it in production.
|
||||
func RoutineId() uint64 {
|
||||
b := make([]byte, 64)
|
||||
@@ -31,3 +37,10 @@ func RunSafe(fn func()) {
|
||||
|
||||
fn()
|
||||
}
|
||||
|
||||
// RunSafeCtx runs the given fn, recovers if fn panics with ctx.
|
||||
func RunSafeCtx(ctx context.Context, fn func()) {
|
||||
defer rescue.RecoverCtx(ctx)
|
||||
|
||||
fn()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package threading
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestRoutineId(t *testing.T) {
|
||||
@@ -34,3 +37,51 @@ func TestRunSafe(t *testing.T) {
|
||||
<-ch
|
||||
i++
|
||||
}
|
||||
|
||||
func TestRunSafeCtx(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
logx.SetWriter(logx.NewWriter(&buf))
|
||||
ctx := context.Background()
|
||||
ch := make(chan lang.PlaceholderType)
|
||||
|
||||
i := 0
|
||||
|
||||
defer func() {
|
||||
assert.Equal(t, 1, i)
|
||||
}()
|
||||
|
||||
go RunSafeCtx(ctx, func() {
|
||||
defer func() {
|
||||
ch <- lang.Placeholder
|
||||
}()
|
||||
|
||||
panic("panic")
|
||||
})
|
||||
|
||||
<-ch
|
||||
i++
|
||||
}
|
||||
|
||||
func TestGoSafeCtx(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
logx.SetWriter(logx.NewWriter(&buf))
|
||||
ctx := context.Background()
|
||||
ch := make(chan lang.PlaceholderType)
|
||||
|
||||
i := 0
|
||||
|
||||
defer func() {
|
||||
assert.Equal(t, 1, i)
|
||||
}()
|
||||
|
||||
GoSafeCtx(ctx, func() {
|
||||
defer func() {
|
||||
ch <- lang.Placeholder
|
||||
}()
|
||||
|
||||
panic("panic")
|
||||
})
|
||||
|
||||
<-ch
|
||||
i++
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"go.opentelemetry.io/otel/exporters/jaeger"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
"go.opentelemetry.io/otel/exporters/zipkin"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
@@ -23,6 +25,7 @@ const (
|
||||
kindZipkin = "zipkin"
|
||||
kindOtlpGrpc = "otlpgrpc"
|
||||
kindOtlpHttp = "otlphttp"
|
||||
kindFile = "file"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -33,6 +36,10 @@ var (
|
||||
|
||||
// StartAgent starts an opentelemetry agent.
|
||||
func StartAgent(c Config) {
|
||||
if c.Disabled {
|
||||
return
|
||||
}
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
@@ -71,18 +78,33 @@ func createExporter(c Config) (sdktrace.SpanExporter, error) {
|
||||
// endpoint can not reach.
|
||||
// If the connection not dial success, the global otel ErrorHandler will catch error
|
||||
// when reporting data like other exporters.
|
||||
return otlptracegrpc.New(
|
||||
context.Background(),
|
||||
opts := []otlptracegrpc.Option{
|
||||
otlptracegrpc.WithInsecure(),
|
||||
otlptracegrpc.WithEndpoint(c.Endpoint),
|
||||
)
|
||||
}
|
||||
if len(c.OtlpHeaders) > 0 {
|
||||
opts = append(opts, otlptracegrpc.WithHeaders(c.OtlpHeaders))
|
||||
}
|
||||
return otlptracegrpc.New(context.Background(), opts...)
|
||||
case kindOtlpHttp:
|
||||
// Not support flexible configuration now.
|
||||
return otlptracehttp.New(
|
||||
context.Background(),
|
||||
opts := []otlptracehttp.Option{
|
||||
otlptracehttp.WithInsecure(),
|
||||
otlptracehttp.WithEndpoint(c.Endpoint),
|
||||
)
|
||||
}
|
||||
if len(c.OtlpHeaders) > 0 {
|
||||
opts = append(opts, otlptracehttp.WithHeaders(c.OtlpHeaders))
|
||||
}
|
||||
if len(c.OtlpHttpPath) > 0 {
|
||||
opts = append(opts, otlptracehttp.WithURLPath(c.OtlpHttpPath))
|
||||
}
|
||||
return otlptracehttp.New(context.Background(), opts...)
|
||||
case kindFile:
|
||||
f, err := os.OpenFile(c.Endpoint, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("file exporter endpoint error: %s", err.Error())
|
||||
}
|
||||
return stdouttrace.New(stdouttrace.WithWriter(f))
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown exporter: %s", c.Batcher)
|
||||
}
|
||||
|
||||
@@ -11,11 +11,14 @@ func TestStartAgent(t *testing.T) {
|
||||
logx.Disable()
|
||||
|
||||
const (
|
||||
endpoint1 = "localhost:1234"
|
||||
endpoint2 = "remotehost:1234"
|
||||
endpoint3 = "localhost:1235"
|
||||
endpoint4 = "localhost:1236"
|
||||
endpoint5 = "udp://localhost:6831"
|
||||
endpoint1 = "localhost:1234"
|
||||
endpoint2 = "remotehost:1234"
|
||||
endpoint3 = "localhost:1235"
|
||||
endpoint4 = "localhost:1236"
|
||||
endpoint5 = "udp://localhost:6831"
|
||||
endpoint6 = "localhost:1237"
|
||||
endpoint71 = "/tmp/trace.log"
|
||||
endpoint72 = "/not-exist-fs/trace.log"
|
||||
)
|
||||
c1 := Config{
|
||||
Name: "foo",
|
||||
@@ -36,20 +39,42 @@ func TestStartAgent(t *testing.T) {
|
||||
Batcher: "otlp",
|
||||
}
|
||||
c5 := Config{
|
||||
Name: "grpc",
|
||||
Name: "otlpgrpc",
|
||||
Endpoint: endpoint3,
|
||||
Batcher: kindOtlpGrpc,
|
||||
OtlpHeaders: map[string]string{
|
||||
"uptrace-dsn": "http://project2_secret_token@localhost:14317/2",
|
||||
},
|
||||
}
|
||||
c6 := Config{
|
||||
Name: "otlphttp",
|
||||
Endpoint: endpoint4,
|
||||
Batcher: kindOtlpHttp,
|
||||
OtlpHeaders: map[string]string{
|
||||
"uptrace-dsn": "http://project2_secret_token@localhost:14318/2",
|
||||
},
|
||||
OtlpHttpPath: "/v1/traces",
|
||||
}
|
||||
c7 := Config{
|
||||
Name: "UDP",
|
||||
Endpoint: endpoint5,
|
||||
Batcher: kindJaeger,
|
||||
}
|
||||
c8 := Config{
|
||||
Disabled: true,
|
||||
Endpoint: endpoint6,
|
||||
Batcher: kindJaeger,
|
||||
}
|
||||
c9 := Config{
|
||||
Name: "file",
|
||||
Endpoint: endpoint71,
|
||||
Batcher: kindFile,
|
||||
}
|
||||
c10 := Config{
|
||||
Name: "file",
|
||||
Endpoint: endpoint72,
|
||||
Batcher: kindFile,
|
||||
}
|
||||
|
||||
StartAgent(c1)
|
||||
StartAgent(c1)
|
||||
@@ -59,12 +84,16 @@ func TestStartAgent(t *testing.T) {
|
||||
StartAgent(c5)
|
||||
StartAgent(c6)
|
||||
StartAgent(c7)
|
||||
StartAgent(c8)
|
||||
StartAgent(c9)
|
||||
StartAgent(c10)
|
||||
defer StopAgent()
|
||||
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
// because remotehost cannot be resolved
|
||||
assert.Equal(t, 5, len(agents))
|
||||
assert.Equal(t, 6, len(agents))
|
||||
_, ok := agents[""]
|
||||
assert.True(t, ok)
|
||||
_, ok = agents[endpoint1]
|
||||
@@ -73,4 +102,10 @@ func TestStartAgent(t *testing.T) {
|
||||
assert.False(t, ok)
|
||||
_, ok = agents[endpoint5]
|
||||
assert.True(t, ok)
|
||||
_, ok = agents[endpoint6]
|
||||
assert.False(t, ok)
|
||||
_, ok = agents[endpoint71]
|
||||
assert.True(t, ok)
|
||||
_, ok = agents[endpoint72]
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
@@ -8,5 +8,15 @@ type Config struct {
|
||||
Name string `json:",optional"`
|
||||
Endpoint string `json:",optional"`
|
||||
Sampler float64 `json:",default=1.0"`
|
||||
Batcher string `json:",default=jaeger,options=jaeger|zipkin|otlpgrpc|otlphttp"`
|
||||
Batcher string `json:",default=jaeger,options=jaeger|zipkin|otlpgrpc|otlphttp|file"`
|
||||
// OtlpHeaders represents the headers for OTLP gRPC or HTTP transport.
|
||||
// For example:
|
||||
// uptrace-dsn: 'http://project2_secret_token@localhost:14317/2'
|
||||
OtlpHeaders map[string]string `json:",optional"`
|
||||
// OtlpHttpPath represents the path for OTLP HTTP transport.
|
||||
// For example
|
||||
// /v1/traces
|
||||
OtlpHttpPath string `json:",optional"`
|
||||
// Disabled indicates whether StartAgent starts the agent.
|
||||
Disabled bool `json:",optional"`
|
||||
}
|
||||
|
||||
@@ -16,5 +16,6 @@ func NewInMemoryExporter(t *testing.T) *tracetest.InMemoryExporter {
|
||||
me.Reset()
|
||||
})
|
||||
otel.SetTracerProvider(trace.NewTracerProvider(trace.WithSyncer(me)))
|
||||
|
||||
return me
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fullstorydev/grpcurl"
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
@@ -17,7 +16,6 @@ import (
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -25,8 +23,8 @@ type (
|
||||
Server struct {
|
||||
*rest.Server
|
||||
upstreams []Upstream
|
||||
timeout time.Duration
|
||||
processHeader func(http.Header) []string
|
||||
dialer func(conf zrpc.RpcClientConf) zrpc.Client
|
||||
}
|
||||
|
||||
// Option defines the method to customize Server.
|
||||
@@ -36,8 +34,8 @@ type (
|
||||
// MustNewServer creates a new gateway server.
|
||||
func MustNewServer(c GatewayConf, opts ...Option) *Server {
|
||||
svr := &Server{
|
||||
Server: rest.MustNewServer(c.RestConf),
|
||||
upstreams: c.Upstreams,
|
||||
Server: rest.MustNewServer(c.RestConf),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(svr)
|
||||
@@ -67,7 +65,13 @@ func (s *Server) build() error {
|
||||
source <- up
|
||||
}
|
||||
}, func(up Upstream, writer mr.Writer[rest.Route], cancel func(error)) {
|
||||
cli := zrpc.MustNewClient(up.Grpc)
|
||||
var cli zrpc.Client
|
||||
if s.dialer != nil {
|
||||
cli = s.dialer(up.Grpc)
|
||||
} else {
|
||||
cli = zrpc.MustNewClient(up.Grpc)
|
||||
}
|
||||
|
||||
source, err := s.createDescriptorSource(cli, up)
|
||||
if err != nil {
|
||||
cancel(fmt.Errorf("%s: %w", up.Name, err))
|
||||
@@ -123,13 +127,9 @@ func (s *Server) buildHandler(source grpcurl.DescriptorSource, resolver jsonpb.A
|
||||
return
|
||||
}
|
||||
|
||||
timeout := internal.GetTimeout(r.Header, s.timeout)
|
||||
ctx, can := context.WithTimeout(r.Context(), timeout)
|
||||
defer can()
|
||||
|
||||
w.Header().Set(httpx.ContentType, httpx.JsonContentType)
|
||||
handler := internal.NewEventHandler(w, resolver)
|
||||
if err := grpcurl.InvokeRPC(ctx, source, cli.Conn(), rpcPath, s.prepareMetadata(r.Header),
|
||||
if err := grpcurl.InvokeRPC(r.Context(), source, cli.Conn(), rpcPath, s.prepareMetadata(r.Header),
|
||||
handler, parser.Next); err != nil {
|
||||
httpx.ErrorCtx(r.Context(), w, err)
|
||||
}
|
||||
@@ -151,8 +151,7 @@ func (s *Server) createDescriptorSource(cli zrpc.Client, up Upstream) (grpcurl.D
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
refCli := grpc_reflection_v1alpha.NewServerReflectionClient(cli.Conn())
|
||||
client := grpcreflect.NewClient(context.Background(), refCli)
|
||||
client := grpcreflect.NewClientAuto(context.Background(), cli.Conn())
|
||||
source = grpcurl.DescriptorSourceFromServer(context.Background(), client)
|
||||
}
|
||||
|
||||
@@ -160,13 +159,13 @@ func (s *Server) createDescriptorSource(cli zrpc.Client, up Upstream) (grpcurl.D
|
||||
}
|
||||
|
||||
func (s *Server) ensureUpstreamNames() error {
|
||||
for _, up := range s.upstreams {
|
||||
target, err := up.Grpc.BuildTarget()
|
||||
for i := 0; i < len(s.upstreams); i++ {
|
||||
target, err := s.upstreams[i].Grpc.BuildTarget()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
up.Name = target
|
||||
s.upstreams[i].Name = target
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -188,3 +187,10 @@ func WithHeaderProcessor(processHeader func(http.Header) []string) func(*Server)
|
||||
s.processHeader = processHeader
|
||||
}
|
||||
}
|
||||
|
||||
// withDialer sets a dialer to create a gRPC client.
|
||||
func withDialer(dialer func(conf zrpc.RpcClientConf) zrpc.Client) func(*Server) {
|
||||
return func(s *Server) {
|
||||
s.dialer = dialer
|
||||
}
|
||||
}
|
||||
|
||||
127
gateway/server_test.go
Normal file
127
gateway/server_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/conf"
|
||||
"github.com/zeromicro/go-zero/core/discov"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
"github.com/zeromicro/go-zero/internal/mock"
|
||||
"github.com/zeromicro/go-zero/rest/httpc"
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/test/bufconn"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logx.Disable()
|
||||
}
|
||||
|
||||
func dialer() func(context.Context, string) (net.Conn, error) {
|
||||
listener := bufconn.Listen(1024 * 1024)
|
||||
server := grpc.NewServer()
|
||||
mock.RegisterDepositServiceServer(server, &mock.DepositServer{})
|
||||
|
||||
reflection.Register(server)
|
||||
|
||||
go func() {
|
||||
if err := server.Serve(listener); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return func(context.Context, string) (net.Conn, error) {
|
||||
return listener.Dial()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMustNewServer(t *testing.T) {
|
||||
var c GatewayConf
|
||||
assert.NoError(t, conf.FillDefault(&c))
|
||||
// avoid popup alert on macos for asking permissions
|
||||
c.DevServer.Host = "localhost"
|
||||
c.Host = "localhost"
|
||||
c.Port = 18881
|
||||
|
||||
s := MustNewServer(c, withDialer(func(conf zrpc.RpcClientConf) zrpc.Client {
|
||||
return zrpc.MustNewClient(conf, zrpc.WithDialOption(grpc.WithContextDialer(dialer())))
|
||||
}), WithHeaderProcessor(func(header http.Header) []string {
|
||||
return []string{"foo"}
|
||||
}))
|
||||
s.upstreams = []Upstream{
|
||||
{
|
||||
Mappings: []RouteMapping{
|
||||
{
|
||||
Method: "get",
|
||||
Path: "/deposit/:amount",
|
||||
RpcPath: "mock.DepositService/Deposit",
|
||||
},
|
||||
},
|
||||
Grpc: zrpc.RpcClientConf{
|
||||
Endpoints: []string{"foo"},
|
||||
Timeout: 1000,
|
||||
Middlewares: zrpc.ClientMiddlewaresConf{
|
||||
Trace: true,
|
||||
Duration: true,
|
||||
Prometheus: true,
|
||||
Breaker: true,
|
||||
Timeout: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(t, s.build())
|
||||
go s.Server.Start()
|
||||
defer s.Stop()
|
||||
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
|
||||
resp, err := httpc.Do(context.Background(), http.MethodGet, "http://localhost:18881/deposit/100", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
resp, err = httpc.Do(context.Background(), http.MethodGet, "http://localhost:18881/deposit_fail/100", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestServer_ensureUpstreamNames(t *testing.T) {
|
||||
var s = Server{
|
||||
upstreams: []Upstream{
|
||||
{
|
||||
Grpc: zrpc.RpcClientConf{
|
||||
Target: "target",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(t, s.ensureUpstreamNames())
|
||||
assert.Equal(t, "target", s.upstreams[0].Name)
|
||||
}
|
||||
|
||||
func TestServer_ensureUpstreamNames_badEtcd(t *testing.T) {
|
||||
var s = Server{
|
||||
upstreams: []Upstream{
|
||||
{
|
||||
Grpc: zrpc.RpcClientConf{
|
||||
Etcd: discov.EtcdConf{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
logtest.PanicOnFatal(t)
|
||||
assert.Panics(t, func() {
|
||||
s.Start()
|
||||
})
|
||||
}
|
||||
57
go.mod
57
go.mod
@@ -4,12 +4,11 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/alicebob/miniredis/v2 v2.30.1
|
||||
github.com/alicebob/miniredis/v2 v2.30.3
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/felixge/fgprof v0.9.3
|
||||
github.com/fullstorydev/grpcurl v1.8.7
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/golang/protobuf v1.5.3
|
||||
@@ -17,35 +16,36 @@ require (
|
||||
github.com/jackc/pgx/v5 v5.3.1
|
||||
github.com/jhump/protoreflect v1.15.1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pelletier/go-toml/v2 v2.0.7
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/pelletier/go-toml/v2 v2.0.8
|
||||
github.com/prometheus/client_golang v1.15.1
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
go.etcd.io/etcd/api/v3 v3.5.7
|
||||
go.etcd.io/etcd/client/v3 v3.5.7
|
||||
go.mongodb.org/mongo-driver v1.11.4
|
||||
github.com/stretchr/testify v1.8.4
|
||||
go.etcd.io/etcd/api/v3 v3.5.9
|
||||
go.etcd.io/etcd/client/v3 v3.5.9
|
||||
go.mongodb.org/mongo-driver v1.11.6
|
||||
go.opentelemetry.io/otel v1.14.0
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.14.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0
|
||||
go.opentelemetry.io/otel/sdk v1.14.0
|
||||
go.opentelemetry.io/otel/trace v1.14.0
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
go.uber.org/goleak v1.2.1
|
||||
golang.org/x/net v0.9.0
|
||||
golang.org/x/sys v0.7.0
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/sys v0.8.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197
|
||||
google.golang.org/grpc v1.54.0
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4
|
||||
google.golang.org/grpc v1.55.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.26.3
|
||||
k8s.io/apimachinery v0.26.3
|
||||
k8s.io/apimachinery v0.27.0-alpha.3
|
||||
k8s.io/client-go v0.26.3
|
||||
k8s.io/utils v0.0.0-20230115233650-391b47cb4029
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -61,15 +61,14 @@ require (
|
||||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.1 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
@@ -77,11 +76,11 @@ require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
@@ -90,15 +89,15 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
github.com/yuin/gopher-lua v1.1.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.14.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
@@ -106,16 +105,16 @@ require (
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/oauth2 v0.4.0 // indirect
|
||||
golang.org/x/oauth2 v0.6.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.80.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20230307230338-69ee2d25a840 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
215
go.sum
215
go.sum
@@ -36,19 +36,12 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.30.1 h1:HM1rlQjq1bm9yQcsawJqSZBJ9AYgxvjkMsNtddh90+g=
|
||||
github.com/alicebob/miniredis/v2 v2.30.1/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
|
||||
github.com/alicebob/miniredis/v2 v2.30.3 h1:hrqDB4cHFSHQf4gO3xu6YKQg8PqJpNjLYsQAFYHstqw=
|
||||
github.com/alicebob/miniredis/v2 v2.30.3/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||
@@ -58,7 +51,6 @@ github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
@@ -97,8 +89,6 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fullstorydev/grpcurl v1.8.7 h1:xJWosq3BQovQ4QrdPO72OrPiWuGgEsxY8ldYsJbPrqI=
|
||||
github.com/fullstorydev/grpcurl v1.8.7/go.mod h1:pVtM4qe3CMoLaIzYS8uvTuDj2jVYmXqMUkZeijnXp/E=
|
||||
@@ -106,42 +96,30 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8=
|
||||
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -187,7 +165,6 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
@@ -204,8 +181,6 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
@@ -221,7 +196,6 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplb
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
@@ -237,35 +211,25 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
@@ -274,87 +238,57 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
|
||||
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
|
||||
github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow=
|
||||
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
|
||||
github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A=
|
||||
github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM=
|
||||
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
|
||||
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
|
||||
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@@ -363,8 +297,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
@@ -382,14 +317,14 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
|
||||
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
|
||||
go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
|
||||
go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
|
||||
go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
|
||||
go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
|
||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||
go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs=
|
||||
go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
|
||||
go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E=
|
||||
go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
|
||||
go.mongodb.org/mongo-driver v1.11.6 h1:XM7G6PjiGAO5betLF13BIa5TlLUUE3uJ/2Ox3Lz1K+o=
|
||||
go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@@ -407,6 +342,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0 h1:ap+y8
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.14.0/go.mod h1:5w41DY6S9gZrbjuq6Y+753e96WfPha5IcsOSZTtullM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0 h1:3jAYbRHQAqzLjd9I4tzxwJ8Pk/N6AqBcF6m1ZHrxG94=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.14.0/go.mod h1:+N7zNjIJv4K+DeX67XXET0P+eIciESgaFDBqh+ZJFS4=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0 h1:sEL90JjOO/4yhquXl5zTAkLLsZ5+MycAgX99SDsxGc8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.14.0/go.mod h1:oCslUcizYdpKYyS9e8srZEqM6BB8fq41VJBjLAE6z1w=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0 h1:reEVE1upBF9tcujgvSqLJS0SrI7JQPaTKP4s4rymnSs=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.14.0/go.mod h1:RcjvOAcvhzcufQP8aHmzRw1gE9g/VEZufDdo2w+s4sk=
|
||||
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
|
||||
@@ -426,7 +363,6 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -468,7 +404,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -476,7 +411,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -496,22 +430,17 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
|
||||
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -521,18 +450,14 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -541,7 +466,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -554,28 +478,20 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -694,8 +610,8 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197 h1:BwjeHhu4HS48EZmu1nS7flldBIDPC3qn+HqaSQ1K4x8=
|
||||
google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -714,8 +630,8 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -732,12 +648,11 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
@@ -746,13 +661,9 @@ gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaD
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -768,21 +679,21 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.26.3 h1:emf74GIQMTik01Aum9dPP0gAypL8JTLl/lHa4V9RFSU=
|
||||
k8s.io/api v0.26.3/go.mod h1:PXsqwPMXBSBcL1lJ9CYDKy7kIReUydukS5JiRlxC3qE=
|
||||
k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k=
|
||||
k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
|
||||
k8s.io/apimachinery v0.27.0-alpha.3 h1:uujqsdFrbqF+cEbqFHrkLKp+s3XxRgphTpc6Yg84qLo=
|
||||
k8s.io/apimachinery v0.27.0-alpha.3/go.mod h1:TO4higCGNMwebVSdb1XPJdXMU4kk+nmMY/cTMVCGa6M=
|
||||
k8s.io/client-go v0.26.3 h1:k1UY+KXfkxV2ScEL3gilKcF7761xkYsSD6BC9szIu8s=
|
||||
k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ=
|
||||
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
|
||||
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
|
||||
k8s.io/utils v0.0.0-20230115233650-391b47cb4029 h1:L8zDtT4jrxj+TaQYD0k8KNlr556WaVQylDXswKmX+dE=
|
||||
k8s.io/utils v0.0.0-20230115233650-391b47cb4029/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
|
||||
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20230307230338-69ee2d25a840 h1:1Q4XWtrQQh04ZweCpL7aMNYafFMoPEiST4dl5b4PmYw=
|
||||
k8s.io/kube-openapi v0.0.0-20230307230338-69ee2d25a840/go.mod h1:y5VtZWM9sHHc2ZodIH/6SHzXj+TPU5USoA8lcIeKEKY=
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
|
||||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
|
||||
37
internal/dbtest/sql.go
Normal file
37
internal/dbtest/sql.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package dbtest
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// RunTest runs a test function with a mock database.
|
||||
func RunTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
fn(db, mock)
|
||||
|
||||
if err = mock.ExpectationsWereMet(); err != nil {
|
||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RunTxTest runs a test function with a mock database in a transaction.
|
||||
func RunTxTest(t *testing.T, f func(tx *sql.Tx, mock sqlmock.Sqlmock)) {
|
||||
RunTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||
mock.ExpectBegin()
|
||||
tx, err := db.Begin()
|
||||
if assert.NoError(t, err) {
|
||||
f(tx, mock)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -4,7 +4,7 @@ package devserver
|
||||
type Config struct {
|
||||
Enabled bool `json:",default=true"`
|
||||
Host string `json:",optional"`
|
||||
Port int `json:",default=6470"`
|
||||
Port int `json:",default=6060"`
|
||||
MetricsPath string `json:",default=/metrics"`
|
||||
HealthPath string `json:",default=/healthz"`
|
||||
EnableMetrics bool `json:",default=true"`
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"net/http/pprof"
|
||||
"sync"
|
||||
|
||||
"github.com/felixge/fgprof"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/prometheus"
|
||||
"github.com/zeromicro/go-zero/core/threading"
|
||||
"github.com/zeromicro/go-zero/internal/health"
|
||||
)
|
||||
@@ -42,11 +42,12 @@ func (s *Server) addRoutes() {
|
||||
|
||||
// metrics
|
||||
if s.config.EnableMetrics {
|
||||
// enable prometheus global switch
|
||||
prometheus.Enable()
|
||||
s.handleFunc(s.config.MetricsPath, promhttp.Handler().ServeHTTP)
|
||||
}
|
||||
// pprof
|
||||
if s.config.EnablePprof {
|
||||
s.handleFunc("/debug/fgprof", fgprof.Handler().(http.HandlerFunc))
|
||||
s.handleFunc("/debug/pprof/", pprof.Index)
|
||||
s.handleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||
s.handleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
>
|
||||
> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
|
||||
>
|
||||
> `goctl migrate —verbose —version v1.5.0`
|
||||
> `goctl migrate —verbose —version v1.5.2`
|
||||
|
||||
## 0. go-zero 介绍
|
||||
|
||||
@@ -299,6 +299,9 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
|
||||
>87. 天枢数链(浙江)科技有限公司
|
||||
>88. 北京娱人共享智能科技有限公司
|
||||
>89. 北京数智方科技有限公司
|
||||
>90. 元匠科技
|
||||
>91. 宁波甬风信息科技有限公司
|
||||
>92. 深圳市万佳安物联科技股份有限公司
|
||||
|
||||
如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||||
```
|
||||
|
||||
```shell
|
||||
goctl migrate —verbose —version v1.5.0
|
||||
goctl migrate —verbose —version v1.5.2
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -136,7 +136,7 @@ func (ng *engine) buildChainWithNativeMiddlewares(fr featuredRoutes, route Route
|
||||
chn = chn.Append(ng.getLogHandler())
|
||||
}
|
||||
if ng.conf.Middlewares.Prometheus {
|
||||
chn = chn.Append(handler.PrometheusHandler(route.Path))
|
||||
chn = chn.Append(handler.PrometheusHandler(route.Path, route.Method))
|
||||
}
|
||||
if ng.conf.Middlewares.MaxConns {
|
||||
chn = chn.Append(handler.MaxConnsHandler(ng.conf.MaxConns))
|
||||
@@ -292,12 +292,13 @@ func (ng *engine) signatureVerifier(signature signatureSetting) (func(chain.Chai
|
||||
}
|
||||
|
||||
return func(chn chain.Chain) chain.Chain {
|
||||
if ng.unsignedCallback != nil {
|
||||
return chn.Append(handler.ContentSecurityHandler(
|
||||
decrypters, signature.Expiry, signature.Strict, ng.unsignedCallback))
|
||||
if ng.unsignedCallback == nil {
|
||||
return chn.Append(handler.LimitContentSecurityHandler(ng.conf.MaxBytes,
|
||||
decrypters, signature.Expiry, signature.Strict))
|
||||
}
|
||||
|
||||
return chn.Append(handler.ContentSecurityHandler(decrypters, signature.Expiry, signature.Strict))
|
||||
return chn.Append(handler.LimitContentSecurityHandler(ng.conf.MaxBytes,
|
||||
decrypters, signature.Expiry, signature.Strict, ng.unsignedCallback))
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,20 +2,46 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/conf"
|
||||
"github.com/zeromicro/go-zero/core/fs"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/rest/router"
|
||||
)
|
||||
|
||||
const (
|
||||
priKey = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQC4TJk3onpqb2RYE3wwt23J9SHLFstHGSkUYFLe+nl1dEKHbD+/
|
||||
Zt95L757J3xGTrwoTc7KCTxbrgn+stn0w52BNjj/kIE2ko4lbh/v8Fl14AyVR9ms
|
||||
fKtKOnhe5FCT72mdtApr+qvzcC3q9hfXwkyQU32pv7q5UimZ205iKSBmgQIDAQAB
|
||||
AoGAM5mWqGIAXj5z3MkP01/4CDxuyrrGDVD5FHBno3CDgyQa4Gmpa4B0/ywj671B
|
||||
aTnwKmSmiiCN2qleuQYASixes2zY5fgTzt+7KNkl9JHsy7i606eH2eCKzsUa/s6u
|
||||
WD8V3w/hGCQ9zYI18ihwyXlGHIgcRz/eeRh+nWcWVJzGOPUCQQD5nr6It/1yHb1p
|
||||
C6l4fC4xXF19l4KxJjGu1xv/sOpSx0pOqBDEX3Mh//FU954392rUWDXV1/I65BPt
|
||||
TLphdsu3AkEAvQJ2Qay/lffFj9FaUrvXuftJZ/Ypn0FpaSiUh3Ak3obBT6UvSZS0
|
||||
bcYdCJCNHDtBOsWHnIN1x+BcWAPrdU7PhwJBAIQ0dUlH2S3VXnoCOTGc44I1Hzbj
|
||||
Rc65IdsuBqA3fQN2lX5vOOIog3vgaFrOArg1jBkG1wx5IMvb/EnUN2pjVqUCQCza
|
||||
KLXtCInOAlPemlCHwumfeAvznmzsWNdbieOZ+SXVVIpR6KbNYwOpv7oIk3Pfm9sW
|
||||
hNffWlPUKhW42Gc+DIECQQDmk20YgBXwXWRM5DRPbhisIV088N5Z58K9DtFWkZsd
|
||||
OBDT3dFcgZONtlmR1MqZO0pTh30lA4qovYj3Bx7A8i36
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
)
|
||||
|
||||
func TestNewEngine(t *testing.T) {
|
||||
priKeyfile, err := fs.TempFilenameWithText(priKey)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(priKeyfile)
|
||||
|
||||
yamls := []string{
|
||||
`Name: foo
|
||||
Host: localhost
|
||||
@@ -151,8 +177,32 @@ Verbose: true
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {},
|
||||
}},
|
||||
},
|
||||
{
|
||||
priority: true,
|
||||
jwt: jwtSetting{
|
||||
enabled: true,
|
||||
},
|
||||
signature: signatureSetting{
|
||||
enabled: true,
|
||||
SignatureConf: SignatureConf{
|
||||
Strict: true,
|
||||
PrivateKeys: []PrivateKeyConf{
|
||||
{
|
||||
Fingerprint: "a",
|
||||
KeyFile: priKeyfile,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
routes: []Route{{
|
||||
Method: http.MethodGet,
|
||||
Path: "/",
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
var index int32
|
||||
for _, yaml := range yamls {
|
||||
yaml := yaml
|
||||
for _, route := range routes {
|
||||
@@ -161,6 +211,11 @@ Verbose: true
|
||||
var cnf RestConf
|
||||
assert.Nil(t, conf.LoadFromYamlBytes([]byte(yaml), &cnf))
|
||||
ng := newEngine(cnf)
|
||||
if atomic.AddInt32(&index, 1)%2 == 0 {
|
||||
ng.setUnsignedCallback(func(w http.ResponseWriter, r *http.Request,
|
||||
next http.Handler, strict bool, code int) {
|
||||
})
|
||||
}
|
||||
ng.addRoutes(route)
|
||||
ng.use(func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -351,6 +406,29 @@ func TestEngine_withTimeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngine_start(t *testing.T) {
|
||||
logx.Disable()
|
||||
|
||||
t.Run("http", func(t *testing.T) {
|
||||
ng := newEngine(RestConf{
|
||||
Host: "localhost",
|
||||
Port: -1,
|
||||
})
|
||||
assert.Error(t, ng.start(router.NewRouter()))
|
||||
})
|
||||
|
||||
t.Run("https", func(t *testing.T) {
|
||||
ng := newEngine(RestConf{
|
||||
Host: "localhost",
|
||||
Port: -1,
|
||||
CertFile: "foo",
|
||||
KeyFile: "bar",
|
||||
})
|
||||
ng.tlsConfig = &tls.Config{}
|
||||
assert.Error(t, ng.start(router.NewRouter()))
|
||||
})
|
||||
}
|
||||
|
||||
type mockedRouter struct {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stat"
|
||||
)
|
||||
|
||||
func init() {
|
||||
logx.Disable()
|
||||
stat.SetReporter(nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ type UnsignedCallback func(w http.ResponseWriter, r *http.Request, next http.Han
|
||||
// ContentSecurityHandler returns a middleware to verify content security.
|
||||
func ContentSecurityHandler(decrypters map[string]codec.RsaDecrypter, tolerance time.Duration,
|
||||
strict bool, callbacks ...UnsignedCallback) func(http.Handler) http.Handler {
|
||||
return LimitContentSecurityHandler(maxBytes, decrypters, tolerance, strict, callbacks...)
|
||||
}
|
||||
|
||||
// LimitContentSecurityHandler returns a middleware to verify content security.
|
||||
func LimitContentSecurityHandler(limitBytes int64, decrypters map[string]codec.RsaDecrypter,
|
||||
tolerance time.Duration, strict bool, callbacks ...UnsignedCallback) func(http.Handler) http.Handler {
|
||||
if len(callbacks) == 0 {
|
||||
callbacks = append(callbacks, handleVerificationFailure)
|
||||
}
|
||||
@@ -36,7 +42,7 @@ func ContentSecurityHandler(decrypters map[string]codec.RsaDecrypter, tolerance
|
||||
r.Header.Get(contentSecurity))
|
||||
executeCallbacks(w, r, next, strict, code, callbacks)
|
||||
} else if r.ContentLength > 0 && header.Encrypted() {
|
||||
CryptionHandler(header.Key)(next).ServeHTTP(w, r)
|
||||
LimitCryptionHandler(limitBytes, header.Key)(next).ServeHTTP(w, r)
|
||||
} else {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
@@ -62,10 +62,6 @@ type requestSettings struct {
|
||||
signature string
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetOutput(io.Discard)
|
||||
}
|
||||
|
||||
func TestContentSecurityHandler(t *testing.T) {
|
||||
tests := []struct {
|
||||
method string
|
||||
@@ -347,7 +343,7 @@ func buildRequest(rs requestSettings) (*http.Request, error) {
|
||||
"time=" + strconv.FormatInt(rs.timestamp, 10),
|
||||
}, "; ")
|
||||
|
||||
encrypter, err := codec.NewRsaEncrypter([]byte(pubKey))
|
||||
encrypter, err := codec.NewRsaEncrypter(pubKey)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ var errContentLengthExceeded = errors.New("content length exceeded")
|
||||
|
||||
// CryptionHandler returns a middleware to handle cryption.
|
||||
func CryptionHandler(key []byte) func(http.Handler) http.Handler {
|
||||
return LimitCryptionHandler(maxBytes, key)
|
||||
}
|
||||
|
||||
// LimitCryptionHandler returns a middleware to handle cryption.
|
||||
func LimitCryptionHandler(limitBytes int64, key []byte) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cw := newCryptionResponseWriter(w)
|
||||
@@ -29,7 +34,7 @@ func CryptionHandler(key []byte) func(http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
if err := decryptBody(key, r); err != nil {
|
||||
if err := decryptBody(limitBytes, key, r); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -39,8 +44,8 @@ func CryptionHandler(key []byte) func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
func decryptBody(key []byte, r *http.Request) error {
|
||||
if r.ContentLength > maxBytes {
|
||||
func decryptBody(limitBytes int64, key []byte, r *http.Request) error {
|
||||
if limitBytes > 0 && r.ContentLength > limitBytes {
|
||||
return errContentLengthExceeded
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -21,10 +20,6 @@ const (
|
||||
|
||||
var aesKey = []byte(`PdSgVkYp3s6v9y$B&E)H+MbQeThWmZq4`)
|
||||
|
||||
func init() {
|
||||
log.SetOutput(io.Discard)
|
||||
}
|
||||
|
||||
func TestCryptionHandlerGet(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/any", http.NoBody)
|
||||
handler := CryptionHandler(aesKey)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package handler
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -14,10 +13,6 @@ import (
|
||||
"github.com/zeromicro/go-zero/rest/internal/response"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetOutput(io.Discard)
|
||||
}
|
||||
|
||||
func TestLogHandler(t *testing.T) {
|
||||
handlers := []func(handler http.Handler) http.Handler{
|
||||
LogHandler,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
@@ -14,10 +12,6 @@ import (
|
||||
|
||||
const conns = 4
|
||||
|
||||
func init() {
|
||||
log.SetOutput(io.Discard)
|
||||
}
|
||||
|
||||
func TestMaxConnsHandler(t *testing.T) {
|
||||
var waitGroup sync.WaitGroup
|
||||
waitGroup.Add(conns)
|
||||
|
||||
@@ -3,7 +3,6 @@ package handler
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/metric"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
@@ -18,7 +17,7 @@ var (
|
||||
Subsystem: "requests",
|
||||
Name: "duration_ms",
|
||||
Help: "http server requests duration(ms).",
|
||||
Labels: []string{"path"},
|
||||
Labels: []string{"path", "method"},
|
||||
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000},
|
||||
})
|
||||
|
||||
@@ -27,19 +26,19 @@ var (
|
||||
Subsystem: "requests",
|
||||
Name: "code_total",
|
||||
Help: "http server requests error count.",
|
||||
Labels: []string{"path", "code"},
|
||||
Labels: []string{"path", "code", "method"},
|
||||
})
|
||||
)
|
||||
|
||||
// PrometheusHandler returns a middleware that reports stats to prometheus.
|
||||
func PrometheusHandler(path string) func(http.Handler) http.Handler {
|
||||
func PrometheusHandler(path, method string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
startTime := timex.Now()
|
||||
cw := &response.WithCodeResponseWriter{Writer: w}
|
||||
defer func() {
|
||||
metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path)
|
||||
metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code))
|
||||
metricServerReqDur.Observe(timex.Since(startTime).Milliseconds(), path, method)
|
||||
metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code), method)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(cw, r)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPromMetricHandler_Disabled(t *testing.T) {
|
||||
promMetricHandler := PrometheusHandler("/user/login")
|
||||
promMetricHandler := PrometheusHandler("/user/login", http.MethodGet)
|
||||
handler := promMetricHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
@@ -26,7 +26,7 @@ func TestPromMetricHandler_Enabled(t *testing.T) {
|
||||
Host: "localhost",
|
||||
Path: "/",
|
||||
})
|
||||
promMetricHandler := PrometheusHandler("/user/login")
|
||||
promMetricHandler := PrometheusHandler("/user/login", http.MethodGet)
|
||||
handler := promMetricHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user