Compare commits

...

77 Commits

Author SHA1 Message Date
Kevin Wan
452c9dbcaf chore: add more tests (#3315) 2023-06-01 21:08:44 +08:00
dependabot[bot]
3564e36a35 chore(deps): bump github.com/alicebob/miniredis/v2 from 2.30.2 to 2.30.3 (#3309)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-01 10:42:26 +08:00
dependabot[bot]
e479e47634 chore(deps): bump github.com/stretchr/testify from 1.8.3 to 1.8.4 in /tools/goctl (#3306)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-31 11:28:15 +08:00
dependabot[bot]
ad921a6419 chore(deps): bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#3305)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-31 11:20:12 +08:00
Kevin Wan
44c8d6f269 chore: add more tests (#3304) 2023-05-30 23:27:27 +08:00
Kevin Wan
8a4cc4f98d chore: add more tests (#3299) 2023-05-29 23:44:36 +08:00
kangqi
e751736516 trace exporter: add new type file (#3298)
Co-authored-by: zhaikangqi <794556486@qq.com>
2023-05-29 18:24:59 +08:00
Kevin Wan
032f2419a2 Update readme-cn.md 2023-05-29 17:36:10 +08:00
Kevin Wan
84adc054bc chore: add more tests (#3296) 2023-05-29 07:39:41 +08:00
Kevin Wan
b92e706ce1 chore: refactor (#3295) 2023-05-28 21:31:36 +08:00
MiNG
1b5946346e feat: support optional otel global initialization for #3284 (#3292) 2023-05-28 11:41:48 +00:00
Kevin Wan
28d3905731 chore: add more tests (#3294) 2023-05-28 19:26:45 +08:00
hc
3726851c7f feat: sqlc add SetCacheWithExpire method (#3249)
Co-authored-by: luohancai <luohancai@taqu.cn>
2023-05-28 12:27:30 +08:00
Kevin Wan
2f2ddd373b chore: refactor retry (#3291) 2023-05-28 12:11:55 +08:00
Xiaoju Jiang
8d48e34eed update: expand the retry method to support timeout and interval control (#3283) 2023-05-28 10:17:50 +08:00
Kevin Wan
32f78668db chore: add more tests (#3290) 2023-05-27 23:57:33 +08:00
Kevin Wan
cd0f3726ed chore: add more tests (#3288) 2023-05-27 21:49:11 +08:00
me-cs
0217044900 update:Use the Milliseconds method of duration to get the number of milliseconds (#3285)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2023-05-26 14:32:46 +00:00
Kevin Wan
8b4382dcec chore: add more tests (#3286) 2023-05-26 22:30:03 +08:00
Kevin Wan
fa33329a44 chore: add more tests (#3282) 2023-05-26 00:21:47 +08:00
dependabot[bot]
d76a39ac26 chore(deps): bump github.com/pelletier/go-toml/v2 from 2.0.7 to 2.0.8 (#3280)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-25 16:50:36 +08:00
guangwu
76a7a17e57 typo (#3281) 2023-05-25 15:58:11 +08:00
Kevin Wan
4a2a8d9e45 chore: add more tests (#3279) 2023-05-24 23:58:45 +08:00
guangwu
ef26b39b4c misspelling (#3248) 2023-05-24 08:15:27 +00:00
anqiansong
3ca40001b4 feat(goctl): Add with session for model tpl (#3272) 2023-05-24 07:34:26 +00:00
Toby
278ae3d26a feat: add OtlpHttpPath config support for ZincObserve Telemetry (#3271)
Signed-off-by: Toby Yan <me@tobyan.com>
Co-authored-by: cong <zhangcong1992@gmail.com>
2023-05-23 03:11:58 +00:00
dependabot[bot]
fa1d6d50a8 chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#3267) 2023-05-22 09:41:17 +08:00
dependabot[bot]
0f4973be06 chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.3 in /tools/goctl (#3268) 2023-05-21 21:43:24 +08:00
Kevin Wan
a9aac7e420 chore: add more tests (#3265) 2023-05-19 23:29:30 +08:00
Kevin Wan
925cf8d3d1 chore: add more tests (#3261) 2023-05-19 12:15:43 +08:00
Kevin Wan
99ce24e2ab chore: add more tests (#3260) 2023-05-19 00:56:50 +08:00
Kevin Wan
701bb31ed2 chore: add more tests (#3259) 2023-05-18 23:43:50 +08:00
Kevin Wan
55e2c7ee83 chore: add more tests (#3258) 2023-05-18 23:11:32 +08:00
Kevin Wan
90839965fa chore: remove directive for tests (#3257) 2023-05-18 21:34:33 +08:00
Kevin Wan
f7228e9af1 chore: add more tests (#3256) 2023-05-18 12:24:04 +08:00
Kevin Wan
f95adae3c1 Update readme-cn.md 2023-05-18 11:00:03 +08:00
Kevin Wan
bff5b81ad9 feat: support using session to execute statements in transaction (#3252) 2023-05-17 14:15:24 +00:00
guangwu
f0bdfb928f fix call error func (#3245) 2023-05-12 04:34:08 +00:00
Kevin Wan
e4a1b7bb39 chore: format the code (#3243) 2023-05-12 10:38:05 +08:00
dependabot[bot]
b6906b5d21 chore(deps): bump go.etcd.io/etcd/client/v3 from 3.5.8 to 3.5.9 (#3242)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-12 08:27:46 +08:00
guangwu
116da96178 add ignore file (#3240) 2023-05-11 08:53:13 +00:00
lchjczw
9fa98c2bd3 api imports take the form of relative paths (#3201)
Co-authored-by: 李春华 <lichunhua@threesoft.cn>
2023-05-10 03:40:07 +00:00
Kevin Wan
b1c4c4736f chore: better comments (#3232) 2023-05-09 22:58:40 +08:00
Thirteen
ef410e8083 fix: generate client directory for goctl (#3166) 2023-05-08 21:03:48 +00:00
fondoger
c22bc1c8ea [dart-gen] Fix lists containing atomic types (#3210) 2023-05-08 21:00:46 +00:00
Toby
1853428011 feat: add otlptracegrpc otlptracehttp headers support for Uptrace (#3219)
Signed-off-by: Toby Yan <me@tobyan.com>
Co-authored-by: cong <zhangcong1992@gmail.com>
2023-05-08 20:58:29 +00:00
dependabot[bot]
3637e10815 chore(deps): bump golang.org/x/net from 0.9.0 to 0.10.0 (#3230) 2023-05-09 04:53:33 +08:00
Kevin Wan
93124329ac chore: add more tests (#3229) 2023-05-08 23:49:13 +08:00
yangtao
851a72f1cc Add RunSafe with context (#3224)
Co-authored-by: yangtao <mrynag8614@163.com>
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2023-05-08 23:08:31 +08:00
SleeplessBot
a93c24ce84 Add method label for prometheus middleware metrics (#3226)
Co-authored-by: 蓝益尤 <lan.yiyou@intellif.com>
2023-05-08 12:59:20 +00:00
Kevin Wan
9f42eda9ff fix: timeout handler not implementing http.Flusher (#3225) 2023-05-08 18:07:02 +08:00
soasurs
8762a3b7ba fix: Errorv should generate JSON Object for content field in log (#3222)
Signed-off-by: soasurs <soasurs@gmail.com>
2023-05-08 09:16:44 +00:00
Kevin Wan
2684a157ff chore: remove fgprof, use pprof directly (#3220) 2023-05-07 18:21:10 +08:00
guangwu
63368d8b0c io/ioutil deprecated (#3217) 2023-05-06 09:50:54 +00:00
guangwu
4f13fe8188 io/ioutil deprecated (#3215) 2023-05-06 16:37:43 +08:00
Kevin Wan
9fc7874336 chore: optimize stat calculation (#3213) 2023-05-06 15:31:56 +08:00
dependabot[bot]
e6518521eb chore(deps): bump google.golang.org/grpc from 1.54.0 to 1.55.0 in /tools/goctl (#3209)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-06 14:29:51 +08:00
Kevin Wan
8f5a0a2de7 fix: remove etcd pings to avoid too-many-pings error (#3212) 2023-05-06 12:39:19 +08:00
ALMAS
774e8d1d08 feat: replaced color package to support Windows (#3207) 2023-05-05 13:09:54 +00:00
cong
8ad0668612 fix(zrpc): remove default keepalive params for NewClientWithTarget (#3208) 2023-05-05 13:00:59 +00:00
dependabot[bot]
8a043d2443 chore(deps): bump golang.org/x/sys from 0.7.0 to 0.8.0 (#3204)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-05 11:03:18 +08:00
dependabot[bot]
0e2ee97a02 chore(deps): bump go.mongodb.org/mongo-driver from 1.11.4 to 1.11.6 (#3205)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-05 10:41:23 +08:00
Kevin Wan
42300a7d83 chore: add more tests (#3203) 2023-05-04 23:43:34 +08:00
dependabot[bot]
fe97fab274 chore(deps): bump github.com/prometheus/client_golang from 1.15.0 to 1.15.1 (#3200)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-04 11:00:51 +08:00
dependabot[bot]
f93e752f98 chore(deps): bump github.com/emicklei/proto from 1.11.1 to 1.11.2 in /tools/goctl (#3199)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-04 10:24:24 +08:00
fondoger
3a66fc038f [dart-gen] Fix nullable list item issue (#3192) 2023-05-01 15:01:28 +08:00
Kevin Wan
b028ed058d chore: change port to 6060 by default in devserver (#3191) 2023-05-01 11:18:15 +08:00
guonaihong
1fd0c3992b fix panic (#3176) 2023-04-30 14:58:30 +00:00
Kevin Wan
1aebb3e5e4 chore: update readme (#3190) 2023-04-30 11:21:28 +08:00
Kevin Wan
8ffe4c01d1 chore: use logx.Must instead of log.Fatal (#3189) 2023-04-29 23:46:04 +08:00
Kevin Wan
a31256b327 chore: add more tests (#3187) 2023-04-29 22:59:07 +08:00
Kevin Wan
14caf5c799 chore: simplify tests with logtest (#3184) 2023-04-29 20:36:29 +08:00
chensy
c0f8a58ed7 no \t to space line (#3177)
Co-authored-by: chenjieping <chenjieping@kezaihui.com>
2023-04-29 08:35:04 +00:00
dependabot[bot]
3189ec7be6 chore(deps): bump github.com/go-sql-driver/mysql from 1.7.0 to 1.7.1 (#3174)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 23:27:01 +08:00
dependabot[bot]
f51e9f0ea7 chore(deps): bump github.com/go-sql-driver/mysql from 1.7.0 to 1.7.1 in /tools/goctl (#3175)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-27 23:37:28 +08:00
cong
ba9d510cdb fix(metrics): enable prometheus global switch when user enable devsever metrics (#3169) 2023-04-24 20:02:04 +08:00
Kevin Wan
8c9b619199 chore: update go-zero for goctl (#3165) 2023-04-24 13:02:26 +08:00
156 changed files with 3558 additions and 1485 deletions

View File

@@ -6,3 +6,4 @@ ignore:
- "tools"
- "**/mock"
- "**/*_mock.go"
- "**/*test"

View File

@@ -61,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
View File

@@ -14,9 +14,10 @@
**/.idea
**/.DS_Store
**/logs
**/adhoc
**/coverage.txt
# for test purpose
**/adhoc
go.work
go.work.sum

View File

@@ -35,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)
@@ -81,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)
@@ -207,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)
@@ -239,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)

View File

@@ -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)

View File

@@ -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

View File

@@ -9,7 +9,6 @@ const (
autoSyncInterval = time.Minute
coolDownInterval = time.Second
dialTimeout = 5 * time.Second
dialKeepAliveTime = 5 * time.Second
requestTimeout = 3 * time.Second
endpointsSeparator = ","
)

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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)))
}

View File

@@ -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))
})

View File

@@ -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())

View File

@@ -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`.

View File

@@ -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 {

View 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)
})
}

View 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())
}

View File

@@ -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) {

View File

@@ -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"`
}

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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

View File

@@ -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 {
@@ -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)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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"))
}

View File

@@ -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"))
}

View File

@@ -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
View 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)
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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())
}
}

View File

@@ -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))
}

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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
})

View 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)
}
}

View File

@@ -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

View File

@@ -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=")

View File

@@ -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))
}

View File

@@ -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
View 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)
})
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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)
}
}

View 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")
)

View File

@@ -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))

View File

@@ -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 {

View File

@@ -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...)
}

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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)
})
}

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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++
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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"`
}

View File

@@ -16,5 +16,6 @@ func NewInMemoryExporter(t *testing.T) *tracetest.InMemoryExporter {
me.Reset()
})
otel.SetTracerProvider(trace.NewTracerProvider(trace.WithSyncer(me)))
return me
}

View File

@@ -10,7 +10,9 @@ import (
"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"
@@ -51,6 +53,8 @@ func TestMustNewServer(t *testing.T) {
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{
{
@@ -77,6 +81,7 @@ func TestMustNewServer(t *testing.T) {
assert.NoError(t, s.build())
go s.Server.Start()
defer s.Stop()
time.Sleep(time.Millisecond * 200)
@@ -103,3 +108,20 @@ func TestServer_ensureUpstreamNames(t *testing.T) {
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()
})
}

33
go.mod
View File

@@ -4,12 +4,11 @@ go 1.18
require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/alicebob/miniredis/v2 v2.30.2
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,27 +16,28 @@ 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.15.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.8
go.etcd.io/etcd/client/v3 v3.5.8
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
@@ -69,7 +69,6 @@ require (
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
@@ -98,7 +97,7 @@ require (
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.8 // 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,9 +105,9 @@ 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.5.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

71
go.sum
View File

@@ -38,8 +38,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
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.2 h1:lc1UAUT9ZA7h4srlfBmBt2aorm5Yftk9nBjxz7EyY9I=
github.com/alicebob/miniredis/v2 v2.30.2/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 v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -89,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=
@@ -112,16 +110,16 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/
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-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.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=
@@ -183,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=
@@ -200,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=
@@ -264,15 +259,15 @@ 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/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 v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
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.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
@@ -302,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=
@@ -321,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.8 h1:Zf44zJszoU7zRV0X/nStPenegNXoFDWcB/MwrJbA+L4=
go.etcd.io/etcd/api/v3 v3.5.8/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
go.etcd.io/etcd/client/pkg/v3 v3.5.8 h1:tPp9YRn/UBFAHdhOQUII9eUs7aOK35eulpMhX4YBd+M=
go.etcd.io/etcd/client/pkg/v3 v3.5.8/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
go.etcd.io/etcd/client/v3 v3.5.8 h1:B6ngTKZSWWowHEoaucOKHQR/AtZKaoHLiUpWxOLG4l4=
go.etcd.io/etcd/client/v3 v3.5.8/go.mod h1:idZYIPVkttBJBiRigkB5EM0MmEyx8jcl18zCV3F5noc=
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=
@@ -346,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=
@@ -433,16 +431,16 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
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=
@@ -488,13 +486,12 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
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-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-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.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=
@@ -613,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=
@@ -633,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=

37
internal/dbtest/sql.go Normal file
View 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)
}
})
}

View File

@@ -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"`

View File

@@ -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)

View File

@@ -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 介绍
@@ -300,6 +300,8 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电
>88. 北京娱人共享智能科技有限公司
>89. 北京数智方科技有限公司
>90. 元匠科技
>91. 宁波甬风信息科技有限公司
>92. 深圳市万佳安物联科技股份有限公司
如果贵公司也已使用 go-zero欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。

View File

@@ -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

View File

@@ -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))

View File

@@ -2,6 +2,7 @@ package rest
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net/http"
@@ -15,6 +16,7 @@ import (
"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 (
@@ -200,6 +202,7 @@ Verbose: true
},
}
var index int32
for _, yaml := range yamls {
yaml := yaml
for _, route := range routes {
@@ -208,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) {
@@ -398,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 {
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}))

View File

@@ -1,8 +1,6 @@
package handler
import (
"io"
"log"
"net/http"
"net/http/httptest"
"testing"
@@ -10,10 +8,6 @@ import (
"github.com/stretchr/testify/assert"
)
func init() {
log.SetOutput(io.Discard)
}
func TestWithPanic(t *testing.T) {
handler := RecoverHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic("whatever")

View File

@@ -1,8 +1,6 @@
package handler
import (
"io"
"log"
"net/http"
"net/http/httptest"
"testing"
@@ -12,10 +10,6 @@ import (
"github.com/zeromicro/go-zero/core/stat"
)
func init() {
log.SetOutput(io.Discard)
}
func TestSheddingHandlerAccept(t *testing.T) {
metrics := stat.NewMetrics("unit-test")
shedder := mockShedder{

View File

@@ -31,14 +31,14 @@ const (
// Notice: even if canceled in server side, 499 will be logged as well.
func TimeoutHandler(duration time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
if duration > 0 {
return &timeoutHandler{
handler: next,
dt: duration,
}
if duration <= 0 {
return next
}
return next
return &timeoutHandler{
handler: next,
dt: duration,
}
}
}
@@ -127,6 +127,12 @@ type timeoutWriter struct {
var _ http.Pusher = (*timeoutWriter)(nil)
func (tw *timeoutWriter) Flush() {
if flusher, ok := tw.w.(http.Flusher); ok {
flusher.Flush()
}
}
func (tw *timeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hijacked, ok := tw.w.(http.Hijacker); ok {
return hijacked.Hijack()
@@ -207,9 +213,11 @@ func relevantCaller() runtime.Frame {
if !strings.HasPrefix(frame.Function, "net/http.") {
return frame
}
if !more {
break
}
}
return frame
}

View File

@@ -2,21 +2,16 @@ package handler
import (
"context"
"io"
"log"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx/logtest"
"github.com/zeromicro/go-zero/rest/internal/response"
)
func init() {
log.SetOutput(io.Discard)
}
func TestTimeout(t *testing.T) {
timeoutHandler := TimeoutHandler(time.Millisecond)
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -45,7 +40,12 @@ func TestWithTimeoutTimedout(t *testing.T) {
timeoutHandler := TimeoutHandler(time.Millisecond)
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(time.Millisecond * 10)
w.Write([]byte(`foo`))
_, err := w.Write([]byte(`foo`))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}))
@@ -96,7 +96,12 @@ func TestTimeoutWebsocket(t *testing.T) {
func TestTimeoutWroteHeaderTwice(t *testing.T) {
timeoutHandler := TimeoutHandler(time.Minute)
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`hello`))
_, err := w.Write([]byte(`hello`))
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("foo", "bar")
w.WriteHeader(http.StatusOK)
}))
@@ -145,7 +150,7 @@ func TestTimeoutHijack(t *testing.T) {
}
assert.NotPanics(t, func() {
writer.Hijack()
_, _, _ = writer.Hijack()
})
writer = &timeoutWriter{
@@ -155,17 +160,35 @@ func TestTimeoutHijack(t *testing.T) {
}
assert.NotPanics(t, func() {
writer.Hijack()
_, _, _ = writer.Hijack()
})
}
func TestTimeoutFlush(t *testing.T) {
timeoutHandler := TimeoutHandler(time.Minute)
handler := timeoutHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
flusher.Flush()
}))
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
assert.Equal(t, http.StatusOK, resp.Code)
}
func TestTimeoutPusher(t *testing.T) {
handler := &timeoutWriter{
w: mockedPusher{},
}
assert.Panics(t, func() {
handler.Push("any", nil)
_ = handler.Push("any", nil)
})
handler = &timeoutWriter{
@@ -174,20 +197,44 @@ func TestTimeoutPusher(t *testing.T) {
assert.Equal(t, http.ErrNotSupported, handler.Push("any", nil))
}
func TestTimeoutWriter_Hijack(t *testing.T) {
writer := &timeoutWriter{
w: httptest.NewRecorder(),
h: make(http.Header),
req: httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody),
}
_, _, err := writer.Hijack()
assert.Error(t, err)
}
func TestTimeoutWroteTwice(t *testing.T) {
c := logtest.NewCollector(t)
writer := &timeoutWriter{
w: &response.WithCodeResponseWriter{
Writer: httptest.NewRecorder(),
},
h: make(http.Header),
req: httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody),
}
writer.writeHeaderLocked(http.StatusOK)
writer.writeHeaderLocked(http.StatusOK)
assert.Contains(t, c.String(), "superfluous response.WriteHeader call")
}
type mockedPusher struct{}
func (m mockedPusher) Header() http.Header {
panic("implement me")
}
func (m mockedPusher) Write(bytes []byte) (int, error) {
func (m mockedPusher) Write(_ []byte) (int, error) {
panic("implement me")
}
func (m mockedPusher) WriteHeader(statusCode int) {
func (m mockedPusher) WriteHeader(_ int) {
panic("implement me")
}
func (m mockedPusher) Push(target string, opts *http.PushOptions) error {
func (m mockedPusher) Push(_ string, _ *http.PushOptions) error {
panic("implement me")
}

View File

@@ -8,7 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/logx/logtest"
)
func TestInfo(t *testing.T) {
@@ -25,20 +25,11 @@ func TestInfo(t *testing.T) {
}
func TestError(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)
req := httptest.NewRequest(http.MethodGet, "http://localhost", http.NoBody)
Error(req, "first")
Errorf(req, "second %s", "third")
val := buf.String()
val := c.String()
assert.True(t, strings.Contains(val, "first"))
assert.True(t, strings.Contains(val, "second"))
assert.True(t, strings.Contains(val, "third"))

View File

@@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/logx/logtest"
"github.com/zeromicro/go-zero/rest/chain"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/internal/cors"
@@ -22,9 +22,7 @@ import (
)
func TestNewServer(t *testing.T) {
writer := logx.Reset()
defer logx.SetWriter(writer)
logx.SetWriter(logx.NewWriter(io.Discard))
logtest.Discard(t)
const configYaml = `
Name: foo

View File

@@ -8,7 +8,7 @@ import (
"path/filepath"
"strings"
"github.com/logrusorgru/aurora"
"github.com/gookit/color"
"github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
@@ -73,6 +73,6 @@ func CreateApiTemplate(_ *cobra.Command, _ []string) error {
return err
}
fmt.Println(aurora.Green("Done."))
fmt.Println(color.Green.Render("Done."))
return nil
}

View File

@@ -45,8 +45,10 @@ class {{.Name}} {
return {{.Name}}(
{{range .Members}}
{{lowCamelCase .Name}}: {{appendNullCoalescing .}}
{{if isDirectType .Type.Name}}
{{if isAtomicType .Type.Name}}
m['{{getPropertyFromMember .}}'] {{appendDefaultEmptyValue .Type.Name}}
{{else if isAtomicListType .Type.Name}}
m['{{getPropertyFromMember .}}']?.cast<{{getCoreType .Type.Name}}>() {{appendDefaultEmptyValue .Type.Name}}
{{else if isClassListType .Type.Name}}
((m['{{getPropertyFromMember .}}'] {{appendDefaultEmptyValue .Type.Name}}) as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)).toList()
{{else}}
@@ -60,7 +62,7 @@ class {{.Name}} {
{{if isDirectType .Type.Name}}
{{lowCamelCase .Name}}
{{else if isClassListType .Type.Name}}
{{lowCamelCase .Name}}{{if isNullableType .Type.Name}}?{{end}}.map((i) => i.toJson())
{{lowCamelCase .Name}}{{if isNullableType .Type.Name}}?{{end}}.map((i) => i{{if isListItemsNullable .Type.Name}}?{{end}}.toJson())
{{else}}
{{lowCamelCase .Name}}{{if isNullableType .Type.Name}}?{{end}}.toJson()
{{end}}

View File

@@ -2,7 +2,6 @@ package dartgen
import (
"fmt"
"io/ioutil"
"os"
)
@@ -83,7 +82,7 @@ func genVars(dir string, isLegacy bool, scheme string, hostname string) error {
}
if !fileExists(dir + "vars.dart") {
err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s://%s';`, scheme, hostname)), 0o644)
err = os.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s://%s';`, scheme, hostname)), 0o644)
if err != nil {
return err
}
@@ -94,7 +93,7 @@ func genVars(dir string, isLegacy bool, scheme string, hostname string) error {
if isLegacy {
tpl = varTemplate
}
err = ioutil.WriteFile(dir+"kv.dart", []byte(tpl), 0o644)
err = os.WriteFile(dir+"kv.dart", []byte(tpl), 0o644)
if err != nil {
return err
}

View File

@@ -71,7 +71,15 @@ func isListType(s string) bool {
}
func isClassListType(s string) bool {
return strings.HasPrefix(s, "List<") && !isAtomicType(getCoreType(s))
return isListType(s) && !isAtomicType(getCoreType(s))
}
func isAtomicListType(s string) bool {
return isListType(s) && isAtomicType(getCoreType(s))
}
func isListItemsNullable(s string) bool {
return isListType(s) && isNullableType(getCoreType(s))
}
func isMapType(s string) bool {

View File

@@ -6,8 +6,11 @@ var funcMap = template.FuncMap{
"getBaseName": getBaseName,
"getPropertyFromMember": getPropertyFromMember,
"isDirectType": isDirectType,
"isAtomicType": isAtomicType,
"isNumberType": isNumberType,
"isClassListType": isClassListType,
"isAtomicListType": isAtomicListType,
"isListItemsNullable": isListItemsNullable,
"isNullableType": isNullableType,
"appendNullCoalescing": appendNullCoalescing,
"appendDefaultEmptyValue": appendDefaultEmptyValue,

Some files were not shown because too many files have changed in this diff Show More