Compare commits

..

244 Commits

Author SHA1 Message Date
Kevin Wan
57b73d8b49 make sure offset less than size even it's checked inside (#354) 2021-01-05 16:06:36 +08:00
Kevin Wan
a79cee12ee add godoc for RollingWindow (#351) 2021-01-04 22:43:55 +08:00
zjbztianya
7a921f66e6 simple rolling windows code (#346) 2021-01-04 22:11:18 +08:00
kingxt
12e235efb0 optimized goctl format (#336)
* fix format

* refactor

* refactor

* optimized

* refactor

* refactor

* refactor

* add js path prefix
2021-01-04 18:59:48 +08:00
Kevin Wan
01060cf16d close issue of #337 (#347) 2021-01-04 16:36:27 +08:00
Kevin Wan
0786862a35 align bucket boundary to interval in rolling window (#345) 2021-01-04 11:17:59 +08:00
Kevin Wan
efa43483b2 fix potential data race in PeriodicalExecutor (#344)
* fix potential data race in PeriodicalExecutor

* add comment
2021-01-03 20:56:17 +08:00
Kevin Wan
771371e051 simplify rolling window code, and make tests run faster (#343) 2021-01-03 20:47:29 +08:00
zjbztianya
2ee95f8981 fix rolling window bug (#340) 2021-01-03 20:27:47 +08:00
Kevin Wan
5bc01e4bfd set guarded to false only on quitting background flush (#342)
* set guarded to false only on quitting background flush

* set guarded to false only on quitting background flush, cont.
2021-01-03 19:54:11 +08:00
Kevin Wan
510e966982 simplify periodical executor background routine (#339) 2021-01-03 14:02:51 +08:00
Kevin Wan
10e3b8ac80 optimize code that fixes issue #317 (#338) 2021-01-02 19:01:37 +08:00
Kevin Wan
04059bbf5a add discord chat group in readme 2021-01-02 18:35:33 +08:00
weibobo
d643007c79 fix bug #317 (#335)
* fix bug #317.
* add counter for current task. If it's bigger then zero, do not quit background thread

* Revert "fix issue #317 (#331)"

This reverts commit fc43876cc5.
2021-01-02 18:04:04 +08:00
Kevin Wan
fc43876cc5 fix issue #317 (#331) 2021-01-01 13:24:28 +08:00
FengZhang
a926cb514f modify the goctl gensvc template (#323) 2020-12-30 10:05:26 +08:00
kingxt
25cab2f273 Java (#327)
* add g4 file

* new define api by g4

* reactor parser to g4gen

* add syntax parser & test

* add syntax parser & test

* add syntax parser & test

* update g4 file

* add import parse & test

* ractor AT lexer

* panic with error

* revert AT

* update g4 file

* update g4 file

* update g4 file

* optimize parser

* update g4 file

* parse info

* optimized java generator

* revert

* optimize java generator

* update java generator

* update java generator

* update java generator

* update java generator

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2020-12-29 17:50:41 +08:00
Kevin Wan
8d2e2753a2 simplify http.Flusher implementation (#326)
* simplify code with http.Flusher type conversion

* simplify code with http.Flusher type conversion, better version
2020-12-29 15:02:36 +08:00
Kevin Wan
cc4c50e3eb fix broken link. 2020-12-29 11:54:32 +08:00
Kevin Wan
751072bdb0 fix broken doc link 2020-12-29 11:52:55 +08:00
Kevin Wan
e97e1f10db simplify code with http.Flusher type conversion (#325)
* simplify code with http.Flusher type conversion

* simplify code with http.Flusher type conversion, better version
2020-12-29 10:25:55 +08:00
jichangyun
0bd2a0656c The ResponseWriters defined in rest.handler add Flush interface. (#318) 2020-12-28 21:30:24 +08:00
Kevin Wan
71a2b20301 add more tests for prof (#322) 2020-12-27 14:45:14 +08:00
Kevin Wan
8df7de94e3 add more tests for zrpc (#321) 2020-12-27 14:08:24 +08:00
Kevin Wan
bf21203297 add more tests (#320) 2020-12-27 12:26:31 +08:00
Kevin Wan
ae98375194 add more tests (#319) 2020-12-26 20:30:02 +08:00
Kevin Wan
82d1ccf376 fixes #286 (#315) 2020-12-25 19:47:27 +08:00
Kevin Wan
bb6d49c17e add go report card back (#313)
* add go report card back

* avoid test failure, run tests sequentially
2020-12-25 12:09:59 +08:00
Kevin Wan
ed735ec47c Update codeql-analysis.yml
disable python code analysis, python code is in examples.
2020-12-25 12:09:43 +08:00
Kevin Wan
ba4bac3a03 format code (#312) 2020-12-25 11:53:37 +08:00
FengZhang
08433d7e04 add config load support env var (#309) 2020-12-25 11:42:19 +08:00
anqiansong
a3b525b50d feature model fix (#296)
* add raw stirng quote for sql field

* remove unused code
2020-12-21 09:43:32 +08:00
Kevin Wan
097f6886f2 Update readme.md 2020-12-15 23:47:41 +08:00
Kevin Wan
07a1549634 add wechat micro practice qrcode image (#289) 2020-12-14 17:49:58 +08:00
Kevin Wan
befca26c58 Update readme.md
add goproxy.cn download badge
2020-12-13 00:02:32 +08:00
Kevin Wan
3556a2eef4 Update readme-en.md
goreportcard is not working, submitted an issue to them.
2020-12-12 23:40:26 +08:00
Kevin Wan
807765f77e Update readme.md
goreportcard is not working, submitted a issue to them.
2020-12-12 23:39:28 +08:00
Kevin Wan
e44584e549 Create codeql-analysis.yml 2020-12-12 23:01:15 +08:00
Kevin Wan
acd48f0abb optimize dockerfile generation (#284) 2020-12-12 16:53:06 +08:00
kingxt
f919bc6713 refactor (#283) 2020-12-12 11:18:22 +08:00
Kevin Wan
a0030b8f45 format dockerfile on non-chinese mode (#282) 2020-12-12 10:13:33 +08:00
Kevin Wan
a5f0cce1b1 Update readme-en.md 2020-12-12 09:06:09 +08:00
Kevin Wan
4d13dda605 add EXPOSE in dockerfile generation (#281) 2020-12-12 08:18:01 +08:00
songmeizi
b56cc8e459 optimize test case of TestRpcGenerate (#279)
Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2020-12-11 21:57:04 +08:00
Kevin Wan
c435811479 fix gocyclo warnings (#278) 2020-12-11 20:57:48 +08:00
Kevin Wan
c686c93fb5 fix dockerfile generation bug (#277) 2020-12-11 20:31:31 +08:00
Kevin Wan
da8f76e6bd add category docker & kube (#276) 2020-12-11 18:53:40 +08:00
Kevin Wan
99596a4149 fix issue #266 (#275)
* optimize dockerfile

* fix issue #266
2020-12-11 16:12:33 +08:00
wayne
ec2a9f2c57 fix tracelogger_test TestTraceLog (#271) 2020-12-10 17:04:57 +08:00
Kevin Wan
fd73ced6dc optimize dockerfile (#272) 2020-12-10 16:21:06 +08:00
Kevin Wan
5071736ab4 fmt code (#270) 2020-12-10 15:16:13 +08:00
Kevin Wan
0d7f1d23b4 require go 1.14 (#263)
* refactor & format code

* optimized parse tag (#256)

* feature plugin custom flag (#251)

* support plugin custom flags

* add short name

* remove log

* remove log

* require go 1.14

Co-authored-by: kingxt <kingxt4job@gmail.com>
Co-authored-by: songmeizi <anqiansong@xiaoheiban.cn>
2020-12-09 22:43:42 +08:00
songmeizi
84ab11ac09 feature plugin custom flag (#251)
* support plugin custom flags

* add short name

* remove log

* remove log
2020-12-09 18:08:17 +08:00
kingxt
67804a6bb2 optimized parse tag (#256) 2020-12-09 11:16:38 +08:00
Kevin Wan
65ee877236 refactor & format code (#255) 2020-12-08 23:01:25 +08:00
songmeizi
b060867009 Feature bookstore update (#253)
* update bookstore

* update bookstore
2020-12-08 22:36:48 +08:00
songmeizi
4d53045c6b improve data type conversion (#236)
* improve data type conversion

* update doc
2020-12-08 18:06:15 +08:00
kingxt
cecd4b1b75 goctl add plugin support (#243)
* add plugin support

* add plugin support

* add plugin support

* add plugin support

* add plugin support

* add plugin support

* add plugin support

* add plugin support

* add plugin support

* add plugin support

* add plugin support

* remove no need

* add plugin support

* rename

* rename

* add plugin support

* refactor

* update plugin

* refactor

* refactor

* refactor

* update plugin

* newline

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
2020-12-07 14:55:10 +08:00
Kevin Wan
7cd0463953 fix lint errors (#249)
* simplify code, format makefile

* simplify code

* some optimize by kevwan and benying (#240)

Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>

* optimization (#241)

* optimize docker file generation, make docker build faster

* support k8s deployment yaml generation

* fix lint errors

Co-authored-by: benying <31179034+benyingY@users.noreply.github.com>
Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>
Co-authored-by: bittoy <bittoy@qq.com>
2020-12-07 11:12:02 +08:00
Kevin Wan
7a82cf80ce support k8s deployment yaml generation (#247)
* simplify code, format makefile

* simplify code

* some optimize by kevwan and benying (#240)

Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>

* optimization (#241)

* optimize docker file generation, make docker build faster

* support k8s deployment yaml generation

Co-authored-by: benying <31179034+benyingY@users.noreply.github.com>
Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>
Co-authored-by: bittoy <bittoy@qq.com>
2020-12-07 00:07:50 +08:00
Kevin Wan
f997aee3ba optimize docker file generation, make docker build faster (#244)
* simplify code, format makefile

* simplify code

* some optimize by kevwan and benying (#240)

Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>

* optimization (#241)

* optimize docker file generation, make docker build faster

Co-authored-by: benying <31179034+benyingY@users.noreply.github.com>
Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>
Co-authored-by: bittoy <bittoy@qq.com>
2020-12-05 21:48:09 +08:00
bittoy
88ec89bdbd optimization (#241) 2020-12-02 15:00:07 +08:00
benying
7d1b43780a some optimize by kevwan and benying (#240)
Co-authored-by: 杨志泉 <zhiquan.yang@yiducloud.cn>
2020-12-01 06:44:32 +08:00
Kevin Wan
4b5c2de376 simplify code (#234)
* simplify code, format makefile

* simplify code
2020-11-29 12:41:42 +08:00
Kevin Wan
e5c560e8ba simplify code, format makefile (#233) 2020-11-28 22:27:58 +08:00
xuezonggui
bed494d904 optimization (#221) 2020-11-28 19:43:39 +08:00
Keson
2dfecda465 modify the service name from proto (#230) 2020-11-28 11:48:44 +08:00
voidint
3ebb1e0221 Improve Makefile robustness (#224) 2020-11-27 23:40:07 +08:00
kingxt
348184904c set default handler value (#228)
* set default value

* set default value
2020-11-26 11:57:02 +08:00
Keson
7a27fa50a1 update version (#226) 2020-11-25 12:04:22 +08:00
Kevin Wan
8d4951c990 check go.mod before build docker image (#225) 2020-11-24 23:19:31 +08:00
Keson
6e57f6c527 feature model interface (#222)
* make variable declaration more concise

* add model interface

* optimize interface methods

* fix: go test failed

* warp returns

* optimize
2020-11-24 22:36:23 +08:00
kingxt
b9ac51b6c3 feature: file namestyle (#223)
* add api filename style

* new feature: config.yaml

* optimize

* optimize logic generation

* check hanlder valid

* optimize

* reactor naming style

* optimize

* optimize test

* optimize gen middleware

* format

Co-authored-by: anqiansong <anqiansong@xiaoheiban.cn>
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-24 15:11:18 +08:00
kevin
702e8d79ce fix doc errors 2020-11-24 10:39:38 +08:00
kevin
95a9dabf8b format import 2020-11-23 16:35:39 +08:00
Chris
bae66c49c2 1.use local variable i; 2.make sure limiter larger than timer period (#218)
Co-authored-by: chris <feilee1987@163.com>
2020-11-23 16:34:51 +08:00
kingxt
e0afe0b4bb optimize api new (#216) 2020-11-19 16:48:48 +08:00
Keson
24fb29a356 patch model&rpc (#207)
* change column to read from information_schema

* reactor generate mode from datasource

* reactor generate mode from datasource

* add primary key check logic

* resolve rebase conflicts

* add naming style

* add filename test case

* resolve rebase conflicts

* reactor test

* add test case

* change shell script to makefile

* update rpc new

* update gen_test.go

* format code

* format code

* update test

* generates alias
2020-11-18 15:32:53 +08:00
kevin
71083b5e64 update readme 2020-11-17 19:01:14 +08:00
kingxt
1174f17bd9 modify image url (#213) 2020-11-17 18:50:22 +08:00
kingxt
d6d8fc21d8 type should not define nested (#212)
* nest type should not supported

* nest type should not supported

* nest type should not supported

* nest type should not supported

* new test

* new test
2020-11-17 18:08:55 +08:00
kevin
9592639cb4 add error handle tests 2020-11-17 18:04:48 +08:00
kevin
abcb28e506 support error customization 2020-11-17 17:11:06 +08:00
kingxt
a92f65580c support type def without struct token (#210)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* support type def without struct token

* optimized

* optimized

* optimized

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-17 15:25:13 +08:00
bittoy
3819f67cf4 add redis geospatial (#209)
* add redis geospatial

* fix go test error
2020-11-16 19:45:43 +08:00
kevin
295c8d2934 fix issue #205 2020-11-16 19:23:24 +08:00
kingxt
88da8685dd optimize parser (#206)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* optimized parser

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-16 10:08:28 +08:00
kevin
c7831ac96d update goctl readme 2020-11-15 21:18:02 +08:00
kevin
e898761762 update example 2020-11-15 21:15:29 +08:00
kevin
13d1c5cd00 update example 2020-11-14 22:01:35 +08:00
kingxt
16bfb1b7be refactor parser and remove deprecated code (#204)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-13 23:01:19 +08:00
kingxt
ef4d4968d6 1. group support multi level folder 2. remove force flag (#203)
* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-12 19:47:32 +08:00
kingxt
7b4a5e3ec6 api support for comment double slash // (#201)
* add comment support

* add comment support

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-12 16:57:28 +08:00
kevin
e6df21e0d2 format code 2020-11-11 17:20:56 +08:00
SunJun
0a2c2d1eca change grpc interceptor to chain interceptor (#200)
* change grpc interceptor to chain interceptor

* change server rpc interceptors, del testing code
2020-11-11 17:15:22 +08:00
kevin
a5fb29a6f0 update etcd yaml to avoid no such nost resolve problem 2020-11-11 11:06:23 +08:00
zhoushuguang
f8da301e57 no default metric (#199)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-11-10 11:47:08 +08:00
kevin
cb9075b737 add dockerfile into template 2020-11-09 18:02:16 +08:00
kingxt
3f389a55c2 format service and add test (#197)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-09 17:41:07 +08:00
kevin
afbd565d87 rename postgres 2020-11-09 17:22:51 +08:00
zhoushuguang
d629acc2b7 default metric host (#196)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-11-09 16:03:07 +08:00
kingxt
f32c6a9b28 rewrite (#194)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-09 10:06:45 +08:00
kevin
95aa65efb9 add dockerfile generator 2020-11-08 21:28:58 +08:00
kevin
3806e66cf1 simplify http server starter 2020-11-08 13:17:14 +08:00
kevin
bd430baf52 graceful shutdown refined 2020-11-08 13:08:00 +08:00
Keson
48f4154ea8 update doc (#193) 2020-11-08 13:02:48 +08:00
super_mario
2599e0d28d Close the process when shutdown is finished (#157)
Co-authored-by: Kevin Wan <wanjunfeng@gmail.com>
2020-11-08 12:50:58 +08:00
kingxt
12327fa07d break generator when happen error (#192)
Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-07 21:25:52 +08:00
kevin
57079bf4a4 update cli package 2020-11-07 20:01:25 +08:00
kingxt
7f6eceb5a3 add more test (#189)
* new test

* import bug when with quotation

* new test

* add test condition

* rpc template command use -o param

Co-authored-by: kim <xutao@xiaoheiban.cn>
2020-11-07 17:13:40 +08:00
kevin
7d7cb836af fix issue #186 2020-11-06 12:25:48 +08:00
kevin
f87d9d1dda refine code style 2020-11-06 12:13:28 +08:00
Keson
856b5aadb1 rpc generation fix (#184)
* reactor alert

* optimize

* add test case

* update the target directory in case proto contains option

* fix missing comments and format code
2020-11-05 19:08:34 +08:00
Keson
f7d778e0ed fix duplicate alias (#183) 2020-11-05 18:12:23 +08:00
kevin
88333ee77f faster the tests 2020-11-05 16:04:00 +08:00
Keson
e76f44a35b reactor rpc (#179)
* reactor rpc generation

* update flag

* update command

* update command

* update unit test

* delete test file

* optimize code

* update doc

* update gen pb

* rename target dir

* update mysql data type convert rule

* add done flag

* optimize req/reply parameter

* optimize req/reply parameter

* remove waste code

* remove duplicate parameter

* format code

* format code

* optimize naming

* reactor rpcv2 to rpc

* remove new line

* format code

* rename underline to snake

* reactor getParentPackage

* remove debug log

* reactor background
2020-11-05 14:12:47 +08:00
kevin
c9ec22d5f4 add https listen and serve 2020-11-05 11:56:40 +08:00
Dashuang Li
afffc1048b fix url 404 (#180) 2020-11-04 12:03:07 +08:00
kevin
d0b76b1d9a move redistest into redis package 2020-11-03 16:35:34 +08:00
kevin
b004b070d7 refine tests 2020-11-02 17:51:33 +08:00
kevin
677d581bd1 update doc 2020-11-02 17:05:09 +08:00
kingxt
b776468e69 route support no request and response (#178)
* add more test and support no request and response

* fix slash when run on windows

* optimize test
2020-11-02 13:48:16 +08:00
kevin
4c9315e984 add more tests 2020-10-31 22:10:11 +08:00
kevin
668a7011c4 add more tests 2020-10-31 20:11:12 +08:00
吴亲库里
cc07a1d69b Update sharedcalls.go (#174)
Removes unused parameters
2020-10-31 19:40:07 +08:00
kevin
7f99a3baa8 add gitee url 2020-10-31 13:58:33 +08:00
kevin
9504418462 update doc 2020-10-31 12:41:29 +08:00
kevin
b144a2335c update bookstore example for generation prototype 2020-10-31 11:42:44 +08:00
kevin
7b9ed7a313 update doc 2020-10-30 15:20:19 +08:00
kevin
3d2e9fcb84 remove wechat image 2020-10-30 11:57:32 +08:00
kevin
2b993424c1 update wechat qrcode 2020-10-30 11:54:06 +08:00
kevin
5e87b33b23 support https in rest 2020-10-29 17:44:51 +08:00
kevin
9b7cc43dcb update wechat qrcode 2020-10-29 15:32:08 +08:00
kevin
000b28cf84 update readme 2020-10-29 11:31:35 +08:00
kevin
9fd16cd278 add images back because of gitee not showing 2020-10-29 11:27:40 +08:00
kevin
b71429e16b add images back because of gitee not showing 2020-10-29 11:26:10 +08:00
codingfanlt
a13b48c33e goctl add stdin flag (#170)
* add stdin flag to use stdin receive api doc and use stdout output formatted result

* optimize code and output error through stderr

* fix mistake

* add dir parameter legality verify
2020-10-28 22:37:59 +08:00
kevin
033525fea8 update doc using raw images 2020-10-28 21:04:06 +08:00
Keson
607fc3297a model template fix (#169)
* replace quote

* rpc disable override main.go

* reactor template

* add model flag -style

* add model flag -style

* reactor model  template name of error
2020-10-27 22:42:53 +08:00
cuisongliu
4287877b74 update deployment version (#165) 2020-10-26 16:33:24 +08:00
Keson
2b7545ce11 spell fix (#167) 2020-10-26 16:33:02 +08:00
Keson
60925c1164 fix bug: generate incomplete model code in case findOneByField (#160)
* fix bug: generate incompletely in case findOneByField

* code break line

* add test

* revert command.go

* add test

* remove incorrect test
2020-10-25 23:21:55 +08:00
kingxt
1c9e81aa28 refactor middleware generator (#159)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* remove no need

* add anonymous annotation

* optimized

* rename

* rename

* update test

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* simple logic

* optimized

* optimized generator formatted code

* optimized generator formatted code

* add more test

* refactor middleware generator

* revert test

* revert test

* revert test

* revert test

* revert test

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-23 21:53:45 +08:00
sjatsh
db7dcaa120 gen api svc add middleware implement temp code (#151) 2020-10-23 21:00:38 +08:00
kevin
099d44054d add logo in readme 2020-10-23 17:01:18 +08:00
Keson
f5f873c6bd api handler generate incompletely while has no request (#158)
* fix: api handler generate incompletely while has no request

* fix: api handler generate incompletely while has no request

* add handler generate test
2020-10-23 16:10:33 +08:00
Keson
6dbd3eada9 update api template (#156)
* update template

* update template
2020-10-23 14:42:57 +08:00
kevin
cf2d20a211 add vote link 2020-10-23 12:02:03 +08:00
maiyang
91bfc093f4 docs: format markdown and add go mod in demo (#155) 2020-10-22 22:24:35 +08:00
kingxt
cf33aae91d ignore blank between bracket and service tag (#154)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* remove no need

* add anonymous annotation

* optimized

* rename

* rename

* update test

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* simple logic

* optimized

* optimized generator formatted code

* optimized generator formatted code

* add more test

* ignore black between bracket and service tag

* use join instead

* format

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-22 22:19:06 +08:00
Keson
c9494c8bc7 model support globbing patterns (#153)
* model support globbing patterns

* optimize model

* optimize model

* format code
2020-10-22 18:33:09 +08:00
kevin
1fd2ef9347 make tests faster 2020-10-21 21:43:41 +08:00
kevin
efffb40fa3 update wechat info 2020-10-21 20:26:35 +08:00
kevin
9c8f31cf83 can only specify one origin in cors 2020-10-21 16:47:49 +08:00
kevin
96cb7af728 make tests faster 2020-10-21 15:18:22 +08:00
Keson
41964f9d52 gozero template (#147)
* model/rpc generate code from template cache

* delete unused(deprecated) code

* support template init|update|clean|revert

* model: return the execute result for insert and update operation

* // deprecated: containsAny

* add template test

* add default buildVersion

* update build version
2020-10-21 14:59:35 +08:00
kevin
fe0d0687f5 support cors in rest server 2020-10-21 14:10:36 +08:00
kingxt
1c1e4bca86 optimized generator formatted code (#148)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* remove no need

* add anonymous annotation

* optimized

* rename

* rename

* update test

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* simple logic

* optimized

* optimized generator formatted code

* optimized generator formatted code

* add more test

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-20 19:43:20 +08:00
kevin
1abe21aa2a export WithUnaryClientInterceptor 2020-10-20 18:03:05 +08:00
kevin
cee170f3e9 fix zrpc client interceptor calling problem 2020-10-20 17:57:41 +08:00
kevin
907efd92c9 let balancer to be customizable 2020-10-20 17:01:53 +08:00
kevin
737cd4751a rename NewPatRouter to NewRouter 2020-10-20 14:23:21 +08:00
kevin
dfe6e88529 use goctl template to generate all kinds of templates 2020-10-19 23:13:18 +08:00
kingxt
85a815bea0 fix name typo and format with newline (#143)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* remove no need

* add anonymous annotation

* optimized

* rename

* rename

* update test

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* simple logic

* optimized

* bugs fix for name typo and format with newline

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-19 21:05:00 +08:00
kingxt
aa3c391919 api add middleware support (#140)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* remove no need

* add anonymous annotation

* optimized

* rename

* rename

* update test

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* api add middleware support: usage:

@server(
    middleware: M1, M2
)

* simple logic

* should reverse middlewares

* optimized

* optimized

* rename

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-19 18:34:10 +08:00
kevin
c9b0ac1ee4 add more tests 2020-10-19 15:49:11 +08:00
mywaystay
33faab61a3 add redis Zrevrank (#137)
* update goctl rpc template log print url

* add redis Zrevrank

Co-authored-by: zhangkai <zhangkai@laoyuegou.com>
2020-10-19 15:30:19 +08:00
kevin
81bf122fa4 update breaker doc 2020-10-17 22:58:30 +08:00
firefantasy
a14bd309a9 to correct breaker interface annotation (#136) 2020-10-17 22:55:36 +08:00
kevin
ea7e410145 update doc 2020-10-17 19:25:30 +08:00
kevin
e81358e7fa update doc 2020-10-17 19:20:01 +08:00
kevin
695ea69bfc add logx.Alert 2020-10-17 19:11:01 +08:00
kevin
d2ed14002c add fx.Split 2020-10-17 12:51:46 +08:00
kingxt
1d9c4a4c4b add anonymous annotation (#134)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* remove no need

* add anonymous annotation

* optimized

* rename

* rename

* update test

* optimized new command

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-16 19:35:18 +08:00
mywaystay
7e83895c6e update goctl rpc template log print url (#133) 2020-10-16 16:21:22 +08:00
kingxt
dc0534573c print more message when parse error (#131)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* print more error info when parse error

* remove no need

* refactor

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-16 15:56:29 +08:00
kevin
fe3739b7f3 fix golint issues 2020-10-16 11:13:55 +08:00
kevin
94645481b1 fix golint issues 2020-10-16 10:50:43 +08:00
sjatsh
338caf9927 delete goctl rpc main tpl no use import (#130) 2020-10-16 10:44:04 +08:00
kevin
9cc979960f update doc 2020-10-15 17:39:49 +08:00
kevin
f904710811 support api templates 2020-10-15 16:36:49 +08:00
kevin
8291eabc2c assert len > 0 2020-10-15 14:25:10 +08:00
codingfanlt
901fadb5d3 fix: fx/fn.Head func will forever block when n is less than 1 (#128)
* fix fx/Stream Head func will forever block when n is less than 1

* update test case

* update test case
2020-10-15 14:10:37 +08:00
kevin
c824e9e118 fail fast when rolling window size is zero 2020-10-15 11:40:31 +08:00
codingfanlt
6f49639f80 fix syncx/barrier test case (#123) 2020-10-13 19:29:20 +08:00
Keson
7d4a548d29 fix: template cache key (#121) 2020-10-12 14:34:11 +08:00
kevin
936dd67008 simplify code generation 2020-10-12 11:39:50 +08:00
super_mario
84cc41df42 stop rpc server when main function exit (#120)
add defer s.Stop() to mainTemplate, in order to stop rpc server when main function exit
2020-10-12 11:37:43 +08:00
kevin
da1a93e932 faster the tests 2020-10-11 22:07:50 +08:00
Keson
7e61555d42 Gozero sqlgen patch (#119)
* merge upstream

* optimize insert logic

* reactor functions
2020-10-11 21:55:44 +08:00
kevin
7a134ec64d update readme 2020-10-11 20:13:03 +08:00
kevin
d123b00e73 add qq qrcode 2020-10-11 20:02:06 +08:00
kevin
20d53add46 update readme 2020-10-11 19:42:40 +08:00
kevin
a1b141d31a make tests faster 2020-10-10 18:22:49 +08:00
Keson
0a9c427443 Goctl rpc patch (#117)
* remove mock generation

* add: proto project import

* update document

* remove mock generation

* add: proto project import

* update document

* remove NL

* update document

* optimize code

* add test

* add test
2020-10-10 16:19:46 +08:00
kevin
c32759d735 make tests race-free 2020-10-10 15:36:07 +08:00
kevin
fe855c52f1 avoid bigint converted into float64 when unmarshaling 2020-10-10 15:24:29 +08:00
kevin
3f8b080882 add more tests 2020-10-10 13:47:55 +08:00
kevin
adc275872d add more tests 2020-10-10 11:53:49 +08:00
kevin
be39133dba fix data race in tests 2020-10-09 19:13:10 +08:00
kingxt
15a9ab1d18 parser ad test (#116)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* remove no need

* remove no need

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl support import

* goctl support import

* support return ()

* revert

* refactor and rename folder to group

* parser add test

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-10-09 16:03:00 +08:00
kevin
7c354dcc38 add more tests 2020-10-09 14:53:13 +08:00
kevin
3733b06f1b fix data race in tests 2020-10-09 14:15:27 +08:00
kevin
8115a0932e add more tests 2020-10-09 13:59:38 +08:00
kevin
4df5eb760c add more tests 2020-10-08 22:39:07 +08:00
kevin
4a639b853c add more tests 2020-10-08 09:42:20 +08:00
kevin
1023425c1d add more tests 2020-10-07 23:15:34 +08:00
kevin
360fbfd0fa add more tests 2020-10-07 23:02:58 +08:00
kevin
09b7625f06 add more tests 2020-10-07 22:54:51 +08:00
kevin
6db294b5cc add more tests 2020-10-07 19:33:52 +08:00
kevin
305b6749fd add more tests 2020-10-07 19:13:19 +08:00
kevin
10b855713d add more tests 2020-10-07 19:00:15 +08:00
kevin
1cc0f071d9 add more tests 2020-10-07 18:07:54 +08:00
kevin
02ce8f82c8 add more tests 2020-10-07 11:43:02 +08:00
kevin
8a585afbf0 add more tests 2020-10-07 11:19:10 +08:00
kevin
e356025cef add more tests 2020-10-07 08:11:20 +08:00
kevin
14dee114dd add more tests 2020-10-06 10:12:35 +08:00
kevin
637a94a189 add fx.Count 2020-10-05 18:17:59 +08:00
kevin
173b347c90 add more tests 2020-10-05 12:19:54 +08:00
kevin
6749c5b94a add more tests 2020-10-04 17:52:54 +08:00
刘青
e66cca3710 breaker: remover useless code (#114) 2020-10-04 16:25:26 +08:00
kevin
f90c0aa98e update wechat qrcode 2020-10-04 10:14:08 +08:00
kevin
f00b5416a3 update codecov settings 2020-10-03 23:09:29 +08:00
kevin
f49694d6b6 fix data race 2020-10-02 22:41:25 +08:00
kevin
d809bf2dca add more tests 2020-10-02 22:37:15 +08:00
kevin
44ae5463bc add more tests 2020-10-02 09:00:25 +08:00
kevin
40dbd722d7 add more tests 2020-10-01 23:29:49 +08:00
kevin
709574133b add more tests 2020-10-01 23:22:53 +08:00
kevin
cb1c593108 remove markdown linter 2020-10-01 21:11:19 +08:00
kevin
6ecf575c00 add more tests 2020-10-01 20:58:12 +08:00
kevin
b8fcdd5460 add more tests 2020-10-01 17:50:53 +08:00
kevin
ce42281568 add more tests 2020-10-01 17:27:21 +08:00
kevin
40230d79e7 fix data race 2020-10-01 16:58:07 +08:00
kevin
ba7851795b add more tests 2020-10-01 16:49:39 +08:00
kevin
096fe3bc47 add more tests 2020-10-01 11:57:06 +08:00
kevin
e37858295a add more tests 2020-10-01 11:49:17 +08:00
kevin
5a4afb1518 add more tests 2020-10-01 10:29:03 +08:00
kevin
63f1f39c40 fix int64 primary key problem 2020-09-30 22:25:47 +08:00
kevin
481895d1e4 add more tests 2020-09-30 17:47:56 +08:00
shenbaise9527
9e9ce3bf48 GetBreaker need double-check (#112) 2020-09-30 16:50:02 +08:00
kevin
0ce654968d add more tests 2020-09-30 15:36:13 +08:00
Percy Gauguin
2703493541 update: fix wrong word (#110) 2020-09-30 15:08:47 +08:00
janetyu
d4240cd4b0 perfect the bookstore and shorturl doc (#109)
* perfect the bookstore and shorturl doc

* 避免歧义
2020-09-30 14:22:37 +08:00
kevin
a22bcc84a3 better lock practice in sharedcalls 2020-09-30 12:31:35 +08:00
430 changed files with 14073 additions and 9529 deletions

67
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '18 19 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

2
.gitignore vendored
View File

@@ -4,6 +4,7 @@
# Unignore all with extensions # Unignore all with extensions
!*.* !*.*
!**/Dockerfile !**/Dockerfile
!**/Makefile
# Unignore all dirs # Unignore all dirs
!*/ !*/
@@ -12,7 +13,6 @@
.idea .idea
**/.DS_Store **/.DS_Store
**/logs **/logs
!Makefile
# gitlab ci # gitlab ci
.cache .cache

View File

@@ -1,6 +0,0 @@
{
"MD010": false,
"MD013": false,
"MD033": false,
"MD034": false
}

View File

@@ -37,7 +37,6 @@ type (
BloomFilter struct { BloomFilter struct {
bits uint bits uint
maps uint
bitSet BitSetProvider bitSet BitSetProvider
} }
) )

View File

@@ -3,19 +3,15 @@ package bloom
import ( import (
"testing" "testing"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis/redistest"
) )
func TestRedisBitSet_New_Set_Test(t *testing.T) { func TestRedisBitSet_New_Set_Test(t *testing.T) {
s, err := miniredis.Run() store, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error("Miniredis could not start") defer clean()
}
defer s.Close()
store := redis.NewRedis(s.Addr(), redis.NodeType)
bitSet := newRedisBitSet(store, "test_key", 1024) bitSet := newRedisBitSet(store, "test_key", 1024)
isSetBefore, err := bitSet.check([]uint{0}) isSetBefore, err := bitSet.check([]uint{0})
if err != nil { if err != nil {
@@ -46,13 +42,10 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
} }
func TestRedisBitSet_Add(t *testing.T) { func TestRedisBitSet_Add(t *testing.T) {
s, err := miniredis.Run() store, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error("Miniredis could not start") defer clean()
}
defer s.Close()
store := redis.NewRedis(s.Addr(), redis.NodeType)
filter := New(store, "test_key", 64) filter := New(store, "test_key", 64)
assert.Nil(t, filter.Add([]byte("hello"))) assert.Nil(t, filter.Add([]byte("hello")))
assert.Nil(t, filter.Add([]byte("world"))) assert.Nil(t, filter.Add([]byte("world")))

View File

@@ -13,11 +13,6 @@ import (
"github.com/tal-tech/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
const (
StateClosed State = iota
StateOpen
)
const ( const (
numHistoryReasons = 5 numHistoryReasons = 5
timeFormat = "15:04:05" timeFormat = "15:04:05"
@@ -27,11 +22,10 @@ const (
var ErrServiceUnavailable = errors.New("circuit breaker is open") var ErrServiceUnavailable = errors.New("circuit breaker is open")
type ( type (
State = int32
Acceptable func(err error) bool Acceptable func(err error) bool
Breaker interface { Breaker interface {
// Name returns the name of the netflixBreaker. // Name returns the name of the Breaker.
Name() string Name() string
// Allow checks if the request is allowed. // Allow checks if the request is allowed.
@@ -40,34 +34,34 @@ type (
// If not allow, ErrServiceUnavailable will be returned. // If not allow, ErrServiceUnavailable will be returned.
Allow() (Promise, error) Allow() (Promise, error)
// Do runs the given request if the netflixBreaker accepts it. // Do runs the given request if the Breaker accepts it.
// Do returns an error instantly if the netflixBreaker rejects the request. // Do returns an error instantly if the Breaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error // If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again. // and causes the same panic again.
Do(req func() error) error Do(req func() error) error
// DoWithAcceptable runs the given request if the netflixBreaker accepts it. // DoWithAcceptable runs the given request if the Breaker accepts it.
// Do returns an error instantly if the netflixBreaker rejects the request. // DoWithAcceptable returns an error instantly if the Breaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error // If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again. // and causes the same panic again.
// acceptable checks if it's a successful call, even if the err is not nil. // acceptable checks if it's a successful call, even if the err is not nil.
DoWithAcceptable(req func() error, acceptable Acceptable) error DoWithAcceptable(req func() error, acceptable Acceptable) error
// DoWithFallback runs the given request if the netflixBreaker accepts it. // DoWithFallback runs the given request if the Breaker accepts it.
// DoWithFallback runs the fallback if the netflixBreaker rejects the request. // DoWithFallback runs the fallback if the Breaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error // If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again. // and causes the same panic again.
DoWithFallback(req func() error, fallback func(err error) error) error DoWithFallback(req func() error, fallback func(err error) error) error
// DoWithFallbackAcceptable runs the given request if the netflixBreaker accepts it. // DoWithFallbackAcceptable runs the given request if the Breaker accepts it.
// DoWithFallback runs the fallback if the netflixBreaker rejects the request. // DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error // If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again. // and causes the same panic again.
// acceptable checks if it's a successful call, even if the err is not nil. // acceptable checks if it's a successful call, even if the err is not nil.
DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
} }
BreakerOption func(breaker *circuitBreaker) Option func(breaker *circuitBreaker)
Promise interface { Promise interface {
Accept() Accept()
@@ -95,7 +89,7 @@ type (
} }
) )
func NewBreaker(opts ...BreakerOption) Breaker { func NewBreaker(opts ...Option) Breaker {
var b circuitBreaker var b circuitBreaker
for _, opt := range opts { for _, opt := range opts {
opt(&b) opt(&b)
@@ -133,7 +127,7 @@ func (cb *circuitBreaker) Name() string {
return cb.name return cb.name
} }
func WithName(name string) BreakerOption { func WithName(name string) Option {
return func(b *circuitBreaker) { return func(b *circuitBreaker) {
b.name = name b.name = name
} }

View File

@@ -41,10 +41,13 @@ func GetBreaker(name string) Breaker {
} }
lock.Lock() lock.Lock()
defer lock.Unlock() b, ok = breakers[name]
if !ok {
b = NewBreaker(WithName(name))
breakers[name] = b
}
lock.Unlock()
b = NewBreaker()
breakers[name] = b
return b return b
} }
@@ -55,20 +58,5 @@ func NoBreakFor(name string) {
} }
func do(name string, execute func(b Breaker) error) error { func do(name string, execute func(b Breaker) error) error {
lock.RLock() return execute(GetBreaker(name))
b, ok := breakers[name]
lock.RUnlock()
if ok {
return execute(b)
}
lock.Lock()
b, ok = breakers[name]
if !ok {
b = NewBreaker(WithName(name))
breakers[name] = b
}
lock.Unlock()
return execute(b)
} }

View File

@@ -2,7 +2,6 @@ package breaker
import ( import (
"math" "math"
"sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/collection" "github.com/tal-tech/go-zero/core/collection"
@@ -21,7 +20,6 @@ const (
// see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/ // see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/
type googleBreaker struct { type googleBreaker struct {
k float64 k float64
state int32
stat *collection.RollingWindow stat *collection.RollingWindow
proba *mathx.Proba proba *mathx.Proba
} }
@@ -32,7 +30,6 @@ func newGoogleBreaker() *googleBreaker {
return &googleBreaker{ return &googleBreaker{
stat: st, stat: st,
k: k, k: k,
state: StateClosed,
proba: mathx.NewProba(), proba: mathx.NewProba(),
} }
} }
@@ -43,15 +40,9 @@ func (b *googleBreaker) accept() error {
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101 // https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1)) dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
if dropRatio <= 0 { if dropRatio <= 0 {
if atomic.LoadInt32(&b.state) == StateOpen {
atomic.CompareAndSwapInt32(&b.state, StateOpen, StateClosed)
}
return nil return nil
} }
if atomic.LoadInt32(&b.state) == StateClosed {
atomic.CompareAndSwapInt32(&b.state, StateClosed, StateOpen)
}
if b.proba.TrueOnProba(dropRatio) { if b.proba.TrueOnProba(dropRatio) {
return ErrServiceUnavailable return ErrServiceUnavailable
} }

View File

@@ -2,7 +2,6 @@ package breaker
import ( import (
"errors" "errors"
"math"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
@@ -27,7 +26,6 @@ func getGoogleBreaker() *googleBreaker {
return &googleBreaker{ return &googleBreaker{
stat: st, stat: st,
k: 5, k: 5,
state: StateClosed,
proba: mathx.NewProba(), proba: mathx.NewProba(),
} }
} }
@@ -158,7 +156,7 @@ func TestGoogleBreakerSelfProtection(t *testing.T) {
t.Run("total request > 100, total < 2 * success", func(t *testing.T) { t.Run("total request > 100, total < 2 * success", func(t *testing.T) {
b := getGoogleBreaker() b := getGoogleBreaker()
size := rand.Intn(10000) size := rand.Intn(10000)
accepts := int(math.Ceil(float64(size))) + 1 accepts := size + 1
markSuccess(b, accepts) markSuccess(b, accepts)
markFailed(b, size-accepts) markFailed(b, size-accepts)
assert.Nil(t, b.accept()) assert.Nil(t, b.accept())

View File

@@ -0,0 +1,80 @@
package cmdline
import (
"fmt"
"os"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/lang"
)
func TestEnterToContinue(t *testing.T) {
restore, err := iox.RedirectInOut()
assert.Nil(t, err)
defer restore()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println()
}()
go func() {
defer wg.Done()
EnterToContinue()
}()
wait := make(chan lang.PlaceholderType)
go func() {
wg.Wait()
close(wait)
}()
select {
case <-time.After(time.Second):
t.Error("timeout")
case <-wait:
}
}
func TestReadLine(t *testing.T) {
r, w, err := os.Pipe()
assert.Nil(t, err)
ow := os.Stdout
os.Stdout = w
or := os.Stdin
os.Stdin = r
defer func() {
os.Stdin = or
os.Stdout = ow
}()
const message = "hello"
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println(message)
}()
go func() {
defer wg.Done()
input := ReadLine("")
assert.Equal(t, message, input)
}()
wait := make(chan lang.PlaceholderType)
go func() {
wg.Wait()
close(wait)
}()
select {
case <-time.After(time.Second):
t.Error("timeout")
case <-wait:
}
}

View File

@@ -71,3 +71,12 @@ func TestDiffieHellmanMiddleManAttack(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, string(src), string(decryptedSrc)) assert.Equal(t, string(src), string(decryptedSrc))
} }
func TestKeyBytes(t *testing.T) {
var empty DhKey
assert.Equal(t, 0, len(empty.Bytes()))
key, err := GenerateKey()
assert.Nil(t, err)
assert.True(t, len(key.Bytes()) > 0)
}

View File

@@ -6,6 +6,8 @@ import (
"io" "io"
) )
const unzipLimit = 100 * 1024 * 1024 // 100MB
func Gzip(bs []byte) []byte { func Gzip(bs []byte) []byte {
var b bytes.Buffer var b bytes.Buffer
@@ -24,8 +26,7 @@ func Gunzip(bs []byte) ([]byte, error) {
defer r.Close() defer r.Close()
var c bytes.Buffer var c bytes.Buffer
_, err = io.Copy(&c, r) if _, err = io.Copy(&c, io.LimitReader(r, unzipLimit)); err != nil {
if err != nil {
return nil, err return nil, err
} }

19
core/codec/hmac_test.go Normal file
View File

@@ -0,0 +1,19 @@
package codec
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHmac(t *testing.T) {
ret := Hmac([]byte("foo"), "bar")
assert.Equal(t, "f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317",
fmt.Sprintf("%x", ret))
}
func TestHmacBase64(t *testing.T) {
ret := HmacBase64([]byte("foo"), "bar")
assert.Equal(t, "+TILrwJJFp5zhQzWFW3tAQbiu2rYyrAbe7vr5tEGUxc=", ret)
}

58
core/codec/rsa_test.go Normal file
View File

@@ -0,0 +1,58 @@
package codec
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs"
)
const (
priKey = `-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC4TJk3onpqb2RYE3wwt23J9SHLFstHGSkUYFLe+nl1dEKHbD+/
Zt95L757J3xGTrwoTc7KCTxbrgn+stn0w52BNjj/kIE2ko4lbh/v8Fl14AyVR9ms
fKtKOnhe5FCT72mdtApr+qvzcC3q9hfXwkyQU32pv7q5UimZ205iKSBmgQIDAQAB
AoGAM5mWqGIAXj5z3MkP01/4CDxuyrrGDVD5FHBno3CDgyQa4Gmpa4B0/ywj671B
aTnwKmSmiiCN2qleuQYASixes2zY5fgTzt+7KNkl9JHsy7i606eH2eCKzsUa/s6u
WD8V3w/hGCQ9zYI18ihwyXlGHIgcRz/eeRh+nWcWVJzGOPUCQQD5nr6It/1yHb1p
C6l4fC4xXF19l4KxJjGu1xv/sOpSx0pOqBDEX3Mh//FU954392rUWDXV1/I65BPt
TLphdsu3AkEAvQJ2Qay/lffFj9FaUrvXuftJZ/Ypn0FpaSiUh3Ak3obBT6UvSZS0
bcYdCJCNHDtBOsWHnIN1x+BcWAPrdU7PhwJBAIQ0dUlH2S3VXnoCOTGc44I1Hzbj
Rc65IdsuBqA3fQN2lX5vOOIog3vgaFrOArg1jBkG1wx5IMvb/EnUN2pjVqUCQCza
KLXtCInOAlPemlCHwumfeAvznmzsWNdbieOZ+SXVVIpR6KbNYwOpv7oIk3Pfm9sW
hNffWlPUKhW42Gc+DIECQQDmk20YgBXwXWRM5DRPbhisIV088N5Z58K9DtFWkZsd
OBDT3dFcgZONtlmR1MqZO0pTh30lA4qovYj3Bx7A8i36
-----END RSA PRIVATE KEY-----`
pubKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4TJk3onpqb2RYE3wwt23J9SHL
FstHGSkUYFLe+nl1dEKHbD+/Zt95L757J3xGTrwoTc7KCTxbrgn+stn0w52BNjj/
kIE2ko4lbh/v8Fl14AyVR9msfKtKOnhe5FCT72mdtApr+qvzcC3q9hfXwkyQU32p
v7q5UimZ205iKSBmgQIDAQAB
-----END PUBLIC KEY-----`
testBody = `this is the content`
)
func TestCryption(t *testing.T) {
enc, err := NewRsaEncrypter([]byte(pubKey))
assert.Nil(t, err)
ret, err := enc.Encrypt([]byte(testBody))
assert.Nil(t, err)
file, err := fs.TempFilenameWithText(priKey)
assert.Nil(t, err)
dec, err := NewRsaDecrypter(file)
assert.Nil(t, err)
actual, err := dec.Decrypt(ret)
assert.Nil(t, err)
assert.Equal(t, testBody, string(actual))
actual, err = dec.DecryptBase64(base64.StdEncoding.EncodeToString(ret))
assert.Nil(t, err)
assert.Equal(t, testBody, string(actual))
}
func TestBadPubKey(t *testing.T) {
_, err := NewRsaEncrypter([]byte("foo"))
assert.Equal(t, ErrPublicKey, err)
}

View File

@@ -29,7 +29,6 @@ type (
name string name string
lock sync.Mutex lock sync.Mutex
data map[string]interface{} data map[string]interface{}
evicts *list.List
expire time.Duration expire time.Duration
timingWheel *TimingWheel timingWheel *TimingWheel
lruCache lru lruCache lru
@@ -278,18 +277,15 @@ func (cs *cacheStat) statLoop() {
ticker := time.NewTicker(statInterval) ticker := time.NewTicker(statInterval)
defer ticker.Stop() defer ticker.Stop()
for { for range ticker.C {
select { hit := atomic.SwapUint64(&cs.hit, 0)
case <-ticker.C: miss := atomic.SwapUint64(&cs.miss, 0)
hit := atomic.SwapUint64(&cs.hit, 0) total := hit + miss
miss := atomic.SwapUint64(&cs.miss, 0) if total == 0 {
total := hit + miss continue
if total == 0 {
continue
}
percent := 100 * float32(hit) / float32(total)
logx.Statf("cache(%s) - qpm: %d, hit_ratio: %.1f%%, elements: %d, hit: %d, miss: %d",
cs.name, total, percent, cs.sizeCallback(), hit, miss)
} }
percent := 100 * float32(hit) / float32(total)
logx.Statf("cache(%s) - qpm: %d, hit_ratio: %.1f%%, elements: %d, hit: %d, miss: %d",
cs.name, total, percent, cs.sizeCallback(), hit, miss)
} }
} }

View File

@@ -6,6 +6,10 @@ type Ring struct {
} }
func NewRing(n int) *Ring { func NewRing(n int) *Ring {
if n < 1 {
panic("n should be greater than 0")
}
return &Ring{ return &Ring{
elements: make([]interface{}, n), elements: make([]interface{}, n),
} }

View File

@@ -6,6 +6,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestNewRing(t *testing.T) {
assert.Panics(t, func() {
NewRing(0)
})
}
func TestRingLess(t *testing.T) { func TestRingLess(t *testing.T) {
ring := NewRing(5) ring := NewRing(5)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {

View File

@@ -8,8 +8,10 @@ import (
) )
type ( type (
// RollingWindowOption let callers customize the RollingWindow.
RollingWindowOption func(rollingWindow *RollingWindow) RollingWindowOption func(rollingWindow *RollingWindow)
// RollingWindow defines a rolling window to calculate the events in buckets with time interval.
RollingWindow struct { RollingWindow struct {
lock sync.RWMutex lock sync.RWMutex
size int size int
@@ -17,11 +19,17 @@ type (
interval time.Duration interval time.Duration
offset int offset int
ignoreCurrent bool ignoreCurrent bool
lastTime time.Duration lastTime time.Duration // start time of the last bucket
} }
) )
// NewRollingWindow returns a RollingWindow that with size buckets and time interval,
// use opts to customize the RollingWindow.
func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow { func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow {
if size < 1 {
panic("size must be greater than 0")
}
w := &RollingWindow{ w := &RollingWindow{
size: size, size: size,
win: newWindow(size), win: newWindow(size),
@@ -34,6 +42,7 @@ func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOpt
return w return w
} }
// Add adds value to current bucket.
func (rw *RollingWindow) Add(v float64) { func (rw *RollingWindow) Add(v float64) {
rw.lock.Lock() rw.lock.Lock()
defer rw.lock.Unlock() defer rw.lock.Unlock()
@@ -41,6 +50,7 @@ func (rw *RollingWindow) Add(v float64) {
rw.win.add(rw.offset, v) rw.win.add(rw.offset, v)
} }
// Reduce runs fn on all buckets, ignore current bucket if ignoreCurrent was set.
func (rw *RollingWindow) Reduce(fn func(b *Bucket)) { func (rw *RollingWindow) Reduce(fn func(b *Bucket)) {
rw.lock.RLock() rw.lock.RLock()
defer rw.lock.RUnlock() defer rw.lock.RUnlock()
@@ -70,29 +80,23 @@ func (rw *RollingWindow) span() int {
func (rw *RollingWindow) updateOffset() { func (rw *RollingWindow) updateOffset() {
span := rw.span() span := rw.span()
if span > 0 { if span <= 0 {
offset := rw.offset return
// reset expired buckets
start := offset + 1
steps := start + span
var remainder int
if steps > rw.size {
remainder = steps - rw.size
steps = rw.size
}
for i := start; i < steps; i++ {
rw.win.resetBucket(i)
offset = i
}
for i := 0; i < remainder; i++ {
rw.win.resetBucket(i)
offset = i
}
rw.offset = offset
rw.lastTime = timex.Now()
} }
offset := rw.offset
// reset expired buckets
for i := 0; i < span; i++ {
rw.win.resetBucket((offset + i + 1) % rw.size)
}
rw.offset = (offset + span) % rw.size
now := timex.Now()
// align to interval time boundary
rw.lastTime = now - (now-rw.lastTime)%rw.interval
} }
// Bucket defines the bucket that holds sum and num of additions.
type Bucket struct { type Bucket struct {
Sum float64 Sum float64
Count int64 Count int64
@@ -114,9 +118,9 @@ type window struct {
} }
func newWindow(size int) *window { func newWindow(size int) *window {
var buckets []*Bucket buckets := make([]*Bucket, size)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
buckets = append(buckets, new(Bucket)) buckets[i] = new(Bucket)
} }
return &window{ return &window{
buckets: buckets, buckets: buckets,
@@ -130,14 +134,15 @@ func (w *window) add(offset int, v float64) {
func (w *window) reduce(start, count int, fn func(b *Bucket)) { func (w *window) reduce(start, count int, fn func(b *Bucket)) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
fn(w.buckets[(start+i)%len(w.buckets)]) fn(w.buckets[(start+i)%w.size])
} }
} }
func (w *window) resetBucket(offset int) { func (w *window) resetBucket(offset int) {
w.buckets[offset].reset() w.buckets[offset%w.size].reset()
} }
// IgnoreCurrentBucket lets the Reduce call ignore current bucket.
func IgnoreCurrentBucket() RollingWindowOption { func IgnoreCurrentBucket() RollingWindowOption {
return func(w *RollingWindow) { return func(w *RollingWindow) {
w.ignoreCurrent = true w.ignoreCurrent = true

View File

@@ -11,6 +11,13 @@ import (
const duration = time.Millisecond * 50 const duration = time.Millisecond * 50
func TestNewRollingWindow(t *testing.T) {
assert.NotNil(t, NewRollingWindow(10, time.Second))
assert.Panics(t, func() {
NewRollingWindow(0, time.Second)
})
}
func TestRollingWindowAdd(t *testing.T) { func TestRollingWindowAdd(t *testing.T) {
const size = 3 const size = 3
r := NewRollingWindow(size, duration) r := NewRollingWindow(size, duration)
@@ -81,7 +88,7 @@ func TestRollingWindowReduce(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(stringx.Rand(), func(t *testing.T) { t.Run(stringx.Rand(), func(t *testing.T) {
r := test.win r := test.win
for x := 0; x < size; x = x + 1 { for x := 0; x < size; x++ {
for i := 0; i <= x; i++ { for i := 0; i <= x; i++ {
r.Add(float64(i)) r.Add(float64(i))
} }
@@ -98,6 +105,37 @@ func TestRollingWindowReduce(t *testing.T) {
} }
} }
func TestRollingWindowBucketTimeBoundary(t *testing.T) {
const size = 3
interval := time.Millisecond * 30
r := NewRollingWindow(size, interval)
listBuckets := func() []float64 {
var buckets []float64
r.Reduce(func(b *Bucket) {
buckets = append(buckets, b.Sum)
})
return buckets
}
assert.Equal(t, []float64{0, 0, 0}, listBuckets())
r.Add(1)
assert.Equal(t, []float64{0, 0, 1}, listBuckets())
time.Sleep(time.Millisecond * 45)
r.Add(2)
r.Add(3)
assert.Equal(t, []float64{0, 1, 5}, listBuckets())
// sleep time should be less than interval, and make the bucket change happen
time.Sleep(time.Millisecond * 20)
r.Add(4)
r.Add(5)
r.Add(6)
assert.Equal(t, []float64{1, 5, 15}, listBuckets())
time.Sleep(time.Millisecond * 100)
r.Add(7)
r.Add(8)
r.Add(9)
assert.Equal(t, []float64{0, 0, 24}, listBuckets())
}
func TestRollingWindowDataRace(t *testing.T) { func TestRollingWindowDataRace(t *testing.T) {
const size = 3 const size = 3
r := NewRollingWindow(size, duration) r := NewRollingWindow(size, duration)

View File

@@ -15,6 +15,7 @@ const (
stringType stringType
) )
// Set is not thread-safe, for concurrent use, make sure to use it with synchronization.
type Set struct { type Set struct {
data map[interface{}]lang.PlaceholderType data map[interface{}]lang.PlaceholderType
tp int tp int
@@ -182,10 +183,7 @@ func (s *Set) add(i interface{}) {
} }
func (s *Set) setType(i interface{}) { func (s *Set) setType(i interface{}) {
if s.tp != untyped { // s.tp can only be untyped here
return
}
switch i.(type) { switch i.(type) {
case int: case int:
s.tp = intType s.tp = intType

View File

@@ -5,8 +5,13 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
) )
func init() {
logx.Disable()
}
func BenchmarkRawSet(b *testing.B) { func BenchmarkRawSet(b *testing.B) {
m := make(map[interface{}]struct{}) m := make(map[interface{}]struct{})
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@@ -147,3 +152,51 @@ func TestCount(t *testing.T) {
// then // then
assert.Equal(t, set.Count(), 3) assert.Equal(t, set.Count(), 3)
} }
func TestKeysIntMismatch(t *testing.T) {
set := NewSet()
set.add(int64(1))
set.add(2)
vals := set.KeysInt()
assert.EqualValues(t, []int{2}, vals)
}
func TestKeysInt64Mismatch(t *testing.T) {
set := NewSet()
set.add(1)
set.add(int64(2))
vals := set.KeysInt64()
assert.EqualValues(t, []int64{2}, vals)
}
func TestKeysUintMismatch(t *testing.T) {
set := NewSet()
set.add(1)
set.add(uint(2))
vals := set.KeysUint()
assert.EqualValues(t, []uint{2}, vals)
}
func TestKeysUint64Mismatch(t *testing.T) {
set := NewSet()
set.add(1)
set.add(uint64(2))
vals := set.KeysUint64()
assert.EqualValues(t, []uint64{2}, vals)
}
func TestKeysStrMismatch(t *testing.T) {
set := NewSet()
set.add(1)
set.add("2")
vals := set.KeysStr()
assert.EqualValues(t, []string{"2"}, vals)
}
func TestSetType(t *testing.T) {
set := NewUnmanagedSet()
set.add(1)
set.add("2")
vals := set.Keys()
assert.ElementsMatch(t, []interface{}{1, "2"}, vals)
}

View File

@@ -204,6 +204,7 @@ func (tw *TimingWheel) removeTask(key interface{}) {
timer := val.(*positionEntry) timer := val.(*positionEntry)
timer.item.removed = true timer.item.removed = true
tw.timers.Del(key)
} }
func (tw *TimingWheel) run() { func (tw *TimingWheel) run() {
@@ -248,7 +249,6 @@ func (tw *TimingWheel) scanAndRunTasks(l *list.List) {
if task.removed { if task.removed {
next := e.Next() next := e.Next()
l.Remove(e) l.Remove(e)
tw.timers.Del(task.key)
e = next e = next
continue continue
} else if task.circle > 0 { } else if task.circle > 0 {
@@ -301,6 +301,7 @@ func (tw *TimingWheel) setTask(task *timingEntry) {
func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) { func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) {
if val, ok := tw.timers.Get(task.key); ok { if val, ok := tw.timers.Get(task.key); ok {
timer := val.(*positionEntry) timer := val.(*positionEntry)
timer.item = task
timer.pos = pos timer.pos = pos
} else { } else {
tw.timers.Set(task.key, &positionEntry{ tw.timers.Set(task.key, &positionEntry{

View File

@@ -594,6 +594,31 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
} }
} }
func TestMoveAndRemoveTask(t *testing.T) {
ticker := timex.NewFakeTicker()
tick := func(v int) {
for i := 0; i < v; i++ {
ticker.Tick()
}
}
var keys []int
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
assert.Equal(t, "any", k)
assert.Equal(t, 3, v.(int))
keys = append(keys, v.(int))
ticker.Done()
}, ticker)
defer tw.Stop()
tw.SetTimer("any", 3, testStep*8)
tick(6)
tw.MoveTimer("any", testStep*7)
tick(3)
tw.RemoveTimer("any")
tick(30)
time.Sleep(time.Millisecond)
assert.Equal(t, 0, len(keys))
}
func BenchmarkTimingWheel(b *testing.B) { func BenchmarkTimingWheel(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"path" "path"
"github.com/tal-tech/go-zero/core/mapping" "github.com/tal-tech/go-zero/core/mapping"
@@ -19,7 +20,7 @@ func LoadConfig(file string, v interface{}) error {
if content, err := ioutil.ReadFile(file); err != nil { if content, err := ioutil.ReadFile(file); err != nil {
return err return err
} else if loader, ok := loaders[path.Ext(file)]; ok { } else if loader, ok := loaders[path.Ext(file)]; ok {
return loader(content, v) return loader([]byte(os.ExpandEnv(string(content))), v)
} else { } else {
return fmt.Errorf("unrecoginized file type: %s", file) return fmt.Errorf("unrecoginized file type: %s", file)
} }

View File

@@ -17,13 +17,14 @@ func TestConfigJson(t *testing.T) {
} }
text := `{ text := `{
"a": "foo", "a": "foo",
"b": 1 "b": 1,
"c": "${FOO}"
}` }`
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test, func(t *testing.T) { t.Run(test, func(t *testing.T) {
t.Parallel() os.Setenv("FOO", "2")
defer os.Unsetenv("FOO")
tmpfile, err := createTempFile(test, text) tmpfile, err := createTempFile(test, text)
assert.Nil(t, err) assert.Nil(t, err)
defer os.Remove(tmpfile) defer os.Remove(tmpfile)
@@ -31,11 +32,12 @@ func TestConfigJson(t *testing.T) {
var val struct { var val struct {
A string `json:"a"` A string `json:"a"`
B int `json:"b"` B int `json:"b"`
C string `json:"c"`
} }
err = LoadConfig(tmpfile, &val) MustLoad(tmpfile, &val)
assert.Nil(t, err)
assert.Equal(t, "foo", val.A) assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B) assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
}) })
} }
} }

View File

@@ -30,12 +30,12 @@ type mapBasedProperties struct {
lock sync.RWMutex lock sync.RWMutex
} }
// Loads the properties into a properties configuration instance. May return the // Loads the properties into a properties configuration instance.
// configuration itself along with an error that indicates if there was a problem loading the configuration. // Returns an error that indicates if there was a problem loading the configuration.
func LoadProperties(filename string) (Properties, error) { func LoadProperties(filename string) (Properties, error) {
lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#")) lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#"))
if err != nil { if err != nil {
return nil, nil return nil, err
} }
raw := make(map[string]string) raw := make(map[string]string)

View File

@@ -41,3 +41,8 @@ func TestSetInt(t *testing.T) {
props.SetInt(key, value) props.SetInt(key, value)
assert.Equal(t, value, props.GetInt(key)) assert.Equal(t, value, props.GetInt(key))
} }
func TestLoadBadFile(t *testing.T) {
_, err := LoadProperties("nosuchfile")
assert.NotNil(t, err)
}

View File

@@ -10,8 +10,10 @@ import (
func TestShrinkDeadlineLess(t *testing.T) { func TestShrinkDeadlineLess(t *testing.T) {
deadline := time.Now().Add(time.Second) deadline := time.Now().Add(time.Second)
ctx, _ := context.WithDeadline(context.Background(), deadline) ctx, cancel := context.WithDeadline(context.Background(), deadline)
ctx, _ = ShrinkDeadline(ctx, time.Minute) defer cancel()
ctx, cancel = ShrinkDeadline(ctx, time.Minute)
defer cancel()
dl, ok := ctx.Deadline() dl, ok := ctx.Deadline()
assert.True(t, ok) assert.True(t, ok)
assert.Equal(t, deadline, dl) assert.Equal(t, deadline, dl)
@@ -19,8 +21,10 @@ func TestShrinkDeadlineLess(t *testing.T) {
func TestShrinkDeadlineMore(t *testing.T) { func TestShrinkDeadlineMore(t *testing.T) {
deadline := time.Now().Add(time.Minute) deadline := time.Now().Add(time.Minute)
ctx, _ := context.WithDeadline(context.Background(), deadline) ctx, cancel := context.WithDeadline(context.Background(), deadline)
ctx, _ = ShrinkDeadline(ctx, time.Second) defer cancel()
ctx, cancel = ShrinkDeadline(ctx, time.Second)
defer cancel()
dl, ok := ctx.Deadline() dl, ok := ctx.Deadline()
assert.True(t, ok) assert.True(t, ok)
assert.True(t, dl.Before(deadline)) assert.True(t, dl.Before(deadline))

View File

@@ -12,7 +12,8 @@ func TestContextCancel(t *testing.T) {
c := context.WithValue(context.Background(), "key", "value") c := context.WithValue(context.Background(), "key", "value")
c1, cancel := context.WithCancel(c) c1, cancel := context.WithCancel(c)
o := ValueOnlyFrom(c1) o := ValueOnlyFrom(c1)
c2, _ := context.WithCancel(o) c2, cancel2 := context.WithCancel(o)
defer cancel2()
contexts := []context.Context{c1, c2} contexts := []context.Context{c1, c2}
for _, c := range contexts { for _, c := range contexts {
@@ -35,7 +36,8 @@ func TestContextCancel(t *testing.T) {
} }
func TestContextDeadline(t *testing.T) { func TestContextDeadline(t *testing.T) {
c, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond)) c, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond))
cancel()
o := ValueOnlyFrom(c) o := ValueOnlyFrom(c)
select { select {
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
@@ -43,9 +45,11 @@ func TestContextDeadline(t *testing.T) {
t.Fatal("ValueOnlyContext: context should not have timed out") t.Fatal("ValueOnlyContext: context should not have timed out")
} }
c, _ = context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond)) c, cancel = context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond))
cancel()
o = ValueOnlyFrom(c) o = ValueOnlyFrom(c)
c, _ = context.WithDeadline(o, time.Now().Add(20*time.Millisecond)) c, cancel = context.WithDeadline(o, time.Now().Add(20*time.Millisecond))
defer cancel()
select { select {
case <-time.After(100 * time.Millisecond): case <-time.After(100 * time.Millisecond):
t.Fatal("ValueOnlyContext+Deadline: context should have timed out") t.Fatal("ValueOnlyContext+Deadline: context should have timed out")

View File

@@ -8,7 +8,7 @@ import (
) )
const ( const (
indexOfKey = iota _ = iota
indexOfId indexOfId
) )

View File

@@ -1,4 +1,3 @@
//go:generate mockgen -package internal -destination listener_mock.go -source listener.go Listener
package internal package internal
type Listener interface { type Listener interface {

View File

@@ -1,45 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: listener.go
// Package internal is a generated GoMock package.
package internal
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockListener is a mock of Listener interface
type MockListener struct {
ctrl *gomock.Controller
recorder *MockListenerMockRecorder
}
// MockListenerMockRecorder is the mock recorder for MockListener
type MockListenerMockRecorder struct {
mock *MockListener
}
// NewMockListener creates a new mock instance
func NewMockListener(ctrl *gomock.Controller) *MockListener {
mock := &MockListener{ctrl: ctrl}
mock.recorder = &MockListenerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
return m.recorder
}
// OnUpdate mocks base method
func (m *MockListener) OnUpdate(keys, values []string, newKey string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "OnUpdate", keys, values, newKey)
}
// OnUpdate indicates an expected call of OnUpdate
func (mr *MockListenerMockRecorder) OnUpdate(keys, values, newKey interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnUpdate", reflect.TypeOf((*MockListener)(nil).OnUpdate), keys, values, newKey)
}

View File

@@ -35,7 +35,7 @@ spec:
- --listen-client-urls - --listen-client-urls
- http://0.0.0.0:2379 - http://0.0.0.0:2379
- --advertise-client-urls - --advertise-client-urls
- http://etcd0:2379 - http://etcd0.discov:2379
- --initial-cluster - --initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380 - etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state - --initial-cluster-state
@@ -107,7 +107,7 @@ spec:
- --listen-client-urls - --listen-client-urls
- http://0.0.0.0:2379 - http://0.0.0.0:2379
- --advertise-client-urls - --advertise-client-urls
- http://etcd1:2379 - http://etcd1.discov:2379
- --initial-cluster - --initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380 - etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state - --initial-cluster-state
@@ -179,7 +179,7 @@ spec:
- --listen-client-urls - --listen-client-urls
- http://0.0.0.0:2379 - http://0.0.0.0:2379
- --advertise-client-urls - --advertise-client-urls
- http://etcd2:2379 - http://etcd2.discov:2379
- --initial-cluster - --initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380 - etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state - --initial-cluster-state
@@ -251,7 +251,7 @@ spec:
- --listen-client-urls - --listen-client-urls
- http://0.0.0.0:2379 - http://0.0.0.0:2379
- --advertise-client-urls - --advertise-client-urls
- http://etcd3:2379 - http://etcd3.discov:2379
- --initial-cluster - --initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380 - etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state - --initial-cluster-state
@@ -323,7 +323,7 @@ spec:
- --listen-client-urls - --listen-client-urls
- http://0.0.0.0:2379 - http://0.0.0.0:2379
- --advertise-client-urls - --advertise-client-urls
- http://etcd4:2379 - http://etcd4.discov:2379
- --initial-cluster - --initial-cluster
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380 - etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state - --initial-cluster-state

View File

@@ -111,6 +111,10 @@ func TestPublisher_keepAliveAsyncQuit(t *testing.T) {
defer ctrl.Finish() defer ctrl.Finish()
const id clientv3.LeaseID = 1 const id clientv3.LeaseID = 1
cli := internal.NewMockEtcdClient(ctrl) cli := internal.NewMockEtcdClient(ctrl)
cli.EXPECT().ActiveConnection()
cli.EXPECT().Close()
defer cli.Close()
cli.ActiveConnection()
restore := setMockClient(cli) restore := setMockClient(cli)
defer restore() defer restore()
cli.EXPECT().Ctx().AnyTimes() cli.EXPECT().Ctx().AnyTimes()

11
core/errorx/callchain.go Normal file
View File

@@ -0,0 +1,11 @@
package errorx
func Chain(fns ...func() error) error {
for _, fn := range fns {
if err := fn(); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,27 @@
package errorx
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestChain(t *testing.T) {
var errDummy = errors.New("dummy")
assert.Nil(t, Chain(func() error {
return nil
}, func() error {
return nil
}))
assert.Equal(t, errDummy, Chain(func() error {
return errDummy
}, func() error {
return nil
}))
assert.Equal(t, errDummy, Chain(func() error {
return nil
}, func() error {
return errDummy
}))
}

View File

@@ -86,9 +86,7 @@ func TestBuldExecutorFlushSlowTasks(t *testing.T) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
for _, i := range tasks { result = append(result, tasks...)
result = append(result, i)
}
}, WithBulkTasks(1000)) }, WithBulkTasks(1000))
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
assert.Nil(t, exec.Add(i)) assert.Nil(t, exec.Add(i))

View File

@@ -3,6 +3,7 @@ package executors
import ( import (
"reflect" "reflect"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
@@ -35,6 +36,7 @@ type (
// avoid race condition on waitGroup when calling wg.Add/Done/Wait(...) // avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
wgBarrier syncx.Barrier wgBarrier syncx.Barrier
confirmChan chan lang.PlaceholderType confirmChan chan lang.PlaceholderType
inflight int32
guarded bool guarded bool
newTicker func(duration time.Duration) timex.Ticker newTicker func(duration time.Duration) timex.Ticker
lock sync.Mutex lock sync.Mutex
@@ -82,6 +84,7 @@ func (pe *PeriodicalExecutor) Sync(fn func()) {
} }
func (pe *PeriodicalExecutor) Wait() { func (pe *PeriodicalExecutor) Wait() {
pe.Flush()
pe.wgBarrier.Guard(func() { pe.wgBarrier.Guard(func() {
pe.waitGroup.Wait() pe.waitGroup.Wait()
}) })
@@ -90,18 +93,16 @@ func (pe *PeriodicalExecutor) Wait() {
func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) { func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) {
pe.lock.Lock() pe.lock.Lock()
defer func() { defer func() {
var start bool
if !pe.guarded { if !pe.guarded {
pe.guarded = true pe.guarded = true
start = true // defer to unlock quickly
defer pe.backgroundFlush()
} }
pe.lock.Unlock() pe.lock.Unlock()
if start {
pe.backgroundFlush()
}
}() }()
if pe.container.AddTask(task) { if pe.container.AddTask(task) {
atomic.AddInt32(&pe.inflight, 1)
return pe.container.RemoveAll(), true return pe.container.RemoveAll(), true
} }
@@ -110,6 +111,9 @@ func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool)
func (pe *PeriodicalExecutor) backgroundFlush() { func (pe *PeriodicalExecutor) backgroundFlush() {
threading.GoSafe(func() { threading.GoSafe(func() {
// flush before quit goroutine to avoid missing tasks
defer pe.Flush()
ticker := pe.newTicker(pe.interval) ticker := pe.newTicker(pe.interval)
defer ticker.Stop() defer ticker.Stop()
@@ -119,6 +123,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
select { select {
case vals := <-pe.commander: case vals := <-pe.commander:
commanded = true commanded = true
atomic.AddInt32(&pe.inflight, -1)
pe.enterExecution() pe.enterExecution()
pe.confirmChan <- lang.Placeholder pe.confirmChan <- lang.Placeholder
pe.executeTasks(vals) pe.executeTasks(vals)
@@ -128,13 +133,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
commanded = false commanded = false
} else if pe.Flush() { } else if pe.Flush() {
last = timex.Now() last = timex.Now()
} else if timex.Since(last) > pe.interval*idleRound { } else if pe.shallQuit(last) {
pe.lock.Lock()
pe.guarded = false
pe.lock.Unlock()
// flush again to avoid missing tasks
pe.Flush()
return return
} }
} }
@@ -177,3 +176,19 @@ func (pe *PeriodicalExecutor) hasTasks(tasks interface{}) bool {
return true return true
} }
} }
func (pe *PeriodicalExecutor) shallQuit(last time.Duration) (stop bool) {
if timex.Since(last) <= pe.interval*idleRound {
return
}
// checking pe.inflight and setting pe.guarded should be locked together
pe.lock.Lock()
if atomic.LoadInt32(&pe.inflight) == 0 {
pe.guarded = false
stop = true
}
pe.lock.Unlock()
return
}

View File

@@ -140,6 +140,26 @@ func TestPeriodicalExecutor_WaitFast(t *testing.T) {
assert.Equal(t, total, cnt) assert.Equal(t, total, cnt)
} }
func TestPeriodicalExecutor_Deadlock(t *testing.T) {
executor := NewBulkExecutor(func(tasks []interface{}) {
}, WithBulkTasks(1), WithBulkInterval(time.Millisecond))
for i := 0; i < 1e5; i++ {
executor.Add(1)
}
}
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
ticker := timex.NewFakeTicker()
defer ticker.Stop()
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
exec.newTicker = func(d time.Duration) timex.Ticker {
return ticker
}
assert.False(t, exec.hasTasks(nil))
assert.True(t, exec.hasTasks(1))
}
// go test -benchtime 10s -bench . // go test -benchtime 10s -bench .
func BenchmarkExecutor(b *testing.B) { func BenchmarkExecutor(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()

View File

@@ -68,6 +68,7 @@ func Range(source <-chan interface{}) Stream {
} }
// Buffer buffers the items into a queue with size n. // Buffer buffers the items into a queue with size n.
// It can balance the producer and the consumer if their processing throughput don't match.
func (p Stream) Buffer(n int) Stream { func (p Stream) Buffer(n int) Stream {
if n < 0 { if n < 0 {
n = 0 n = 0
@@ -84,6 +85,14 @@ func (p Stream) Buffer(n int) Stream {
return Range(source) return Range(source)
} }
// Count counts the number of elements in the result.
func (p Stream) Count() (count int) {
for range p.source {
count++
}
return
}
// Distinct removes the duplicated items base on the given KeyFunc. // Distinct removes the duplicated items base on the given KeyFunc.
func (p Stream) Distinct(fn KeyFunc) Stream { func (p Stream) Distinct(fn KeyFunc) Stream {
source := make(chan interface{}) source := make(chan interface{})
@@ -151,6 +160,10 @@ func (p Stream) Group(fn KeyFunc) Stream {
} }
func (p Stream) Head(n int64) Stream { func (p Stream) Head(n int64) Stream {
if n < 1 {
panic("n must be greater than 0")
}
source := make(chan interface{}) source := make(chan interface{})
go func() { go func() {
@@ -235,7 +248,37 @@ func (p Stream) Sort(less LessFunc) Stream {
return Just(items...) return Just(items...)
} }
// Split splits the elements into chunk with size up to n,
// might be less than n on tailing elements.
func (p Stream) Split(n int) Stream {
if n < 1 {
panic("n should be greater than 0")
}
source := make(chan interface{})
go func() {
var chunk []interface{}
for item := range p.source {
chunk = append(chunk, item)
if len(chunk) == n {
source <- chunk
chunk = nil
}
}
if chunk != nil {
source <- chunk
}
close(source)
}()
return Range(source)
}
func (p Stream) Tail(n int64) Stream { func (p Stream) Tail(n int64) Stream {
if n < 1 {
panic("n should be greater than 0")
}
source := make(chan interface{}) source := make(chan interface{})
go func() { go func() {

View File

@@ -49,6 +49,36 @@ func TestBufferNegative(t *testing.T) {
assert.Equal(t, 10, result) assert.Equal(t, 10, result)
} }
func TestCount(t *testing.T) {
tests := []struct {
name string
elements []interface{}
}{
{
name: "no elements with nil",
},
{
name: "no elements",
elements: []interface{}{},
},
{
name: "1 element",
elements: []interface{}{1},
},
{
name: "multiple elements",
elements: []interface{}{1, 2, 3},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
val := Just(test.elements...).Count()
assert.Equal(t, len(test.elements), val)
})
}
}
func TestDone(t *testing.T) { func TestDone(t *testing.T) {
var count int32 var count int32
Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) { Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) {
@@ -139,6 +169,14 @@ func TestHead(t *testing.T) {
assert.Equal(t, 3, result) assert.Equal(t, 3, result)
} }
func TestHeadZero(t *testing.T) {
assert.Panics(t, func() {
Just(1, 2, 3, 4).Head(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
return nil, nil
})
})
}
func TestHeadMore(t *testing.T) { func TestHeadMore(t *testing.T) {
var result int var result int
Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan interface{}) (interface{}, error) { Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
@@ -245,6 +283,22 @@ func TestSort(t *testing.T) {
}) })
} }
func TestSplit(t *testing.T) {
assert.Panics(t, func() {
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(0).Done()
})
var chunks [][]interface{}
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(4).ForEach(func(item interface{}) {
chunk := item.([]interface{})
chunks = append(chunks, chunk)
})
assert.EqualValues(t, [][]interface{}{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10},
}, chunks)
}
func TestTail(t *testing.T) { func TestTail(t *testing.T) {
var result int var result int
Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) { Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
@@ -256,6 +310,14 @@ func TestTail(t *testing.T) {
assert.Equal(t, 7, result) assert.Equal(t, 7, result)
} }
func TestTailZero(t *testing.T) {
assert.Panics(t, func() {
Just(1, 2, 3, 4).Tail(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
return nil, nil
})
})
}
func TestWalk(t *testing.T) { func TestWalk(t *testing.T) {
var result int var result int
Just(1, 2, 3, 4, 5).Walk(func(item interface{}, pipe chan<- interface{}) { Just(1, 2, 3, 4, 5).Walk(func(item interface{}, pipe chan<- interface{}) {

23
core/iox/pipe.go Normal file
View File

@@ -0,0 +1,23 @@
package iox
import "os"
// RedirectInOut redirects stdin to r, stdout to w, and callers need to call restore afterwards.
func RedirectInOut() (restore func(), err error) {
var r, w *os.File
r, w, err = os.Pipe()
if err != nil {
return
}
ow := os.Stdout
os.Stdout = w
or := os.Stdin
os.Stdin = r
restore = func() {
os.Stdin = or
os.Stdout = ow
}
return
}

13
core/iox/pipe_test.go Normal file
View File

@@ -0,0 +1,13 @@
package iox
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRedirectInOut(t *testing.T) {
restore, err := RedirectInOut()
assert.Nil(t, err)
defer restore()
}

View File

@@ -3,9 +3,10 @@ package limit
import ( import (
"testing" "testing"
"github.com/alicebob/miniredis" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
) )
func TestPeriodLimit_Take(t *testing.T) { func TestPeriodLimit_Take(t *testing.T) {
@@ -33,16 +34,16 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
} }
func testPeriodLimit(t *testing.T, opts ...LimitOption) { func testPeriodLimit(t *testing.T, opts ...LimitOption) {
s, err := miniredis.Run() store, clean, err := redistest.CreateRedis()
assert.Nil(t, err) assert.Nil(t, err)
defer s.Close() defer clean()
const ( const (
seconds = 1 seconds = 1
total = 100 total = 100
quota = 5 quota = 5
) )
l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit", opts...) l := NewPeriodLimit(seconds, quota, store, "periodlimit", opts...)
var allowed, hitQuota, overQuota int var allowed, hitQuota, overQuota int
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
val, err := l.Take("first") val, err := l.Take("first")

View File

@@ -153,13 +153,10 @@ func (lim *TokenLimiter) waitForRedis() {
lim.rescueLock.Unlock() lim.rescueLock.Unlock()
}() }()
for { for range ticker.C {
select { if lim.store.Ping() {
case <-ticker.C: atomic.StoreUint32(&lim.redisAlive, 1)
if lim.store.Ping() { return
atomic.StoreUint32(&lim.redisAlive, 1)
return
}
} }
} }
} }

View File

@@ -4,10 +4,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
) )
func init() { func init() {
@@ -44,16 +45,16 @@ func TestTokenLimit_Rescue(t *testing.T) {
} }
func TestTokenLimit_Take(t *testing.T) { func TestTokenLimit_Take(t *testing.T) {
s, err := miniredis.Run() store, clean, err := redistest.CreateRedis()
assert.Nil(t, err) assert.Nil(t, err)
defer s.Close() defer clean()
const ( const (
total = 100 total = 100
rate = 5 rate = 5
burst = 10 burst = 10
) )
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit") l := NewTokenLimiter(rate, burst, store, "tokenlimit")
var allowed int var allowed int
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
time.Sleep(time.Second / time.Duration(total)) time.Sleep(time.Second / time.Duration(total))
@@ -66,16 +67,16 @@ func TestTokenLimit_Take(t *testing.T) {
} }
func TestTokenLimit_TakeBurst(t *testing.T) { func TestTokenLimit_TakeBurst(t *testing.T) {
s, err := miniredis.Run() store, clean, err := redistest.CreateRedis()
assert.Nil(t, err) assert.Nil(t, err)
defer s.Close() defer clean()
const ( const (
total = 100 total = 100
rate = 5 rate = 5
burst = 10 burst = 10
) )
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit") l := NewTokenLimiter(rate, burst, store, "tokenlimit")
var allowed int var allowed int
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
if l.Allow() { if l.Allow() {

View File

@@ -135,6 +135,7 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
passCounter: passCounter, passCounter: passCounter,
rtCounter: rtCounter, rtCounter: rtCounter,
windows: buckets, windows: buckets,
dropTime: syncx.NewAtomicDuration(),
droppedRecently: syncx.NewAtomicBool(), droppedRecently: syncx.NewAtomicBool(),
} }
// cpu >= 800, inflight < maxPass // cpu >= 800, inflight < maxPass
@@ -160,6 +161,40 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
} }
shedder.avgFlying = 80 shedder.avgFlying = 80
assert.False(t, shedder.shouldDrop()) assert.False(t, shedder.shouldDrop())
// cpu >= 800, inflight < maxPass
systemOverloadChecker = func(int64) bool {
return true
}
shedder.avgFlying = 80
shedder.flying = 80
_, err := shedder.Allow()
assert.NotNil(t, err)
}
func TestAdaptiveShedderStillHot(t *testing.T) {
logx.Disable()
passCounter := newRollingWindow()
rtCounter := newRollingWindow()
for i := 0; i < 10; i++ {
if i > 0 {
time.Sleep(bucketDuration)
}
passCounter.Add(float64((i + 1) * 100))
for j := i*10 + 1; j <= i*10+10; j++ {
rtCounter.Add(float64(j))
}
}
shedder := &adaptiveShedder{
passCounter: passCounter,
rtCounter: rtCounter,
windows: buckets,
dropTime: syncx.NewAtomicDuration(),
droppedRecently: syncx.ForAtomicBool(true),
}
assert.False(t, shedder.stillHot())
shedder.dropTime.Set(-coolOffDuration * 2)
assert.False(t, shedder.stillHot())
} }
func BenchmarkAdaptiveShedder_Allow(b *testing.B) { func BenchmarkAdaptiveShedder_Allow(b *testing.B) {

View File

@@ -13,3 +13,8 @@ func TestGroup(t *testing.T) {
assert.NotNil(t, limiter) assert.NotNil(t, limiter)
}) })
} }
func TestShedderClose(t *testing.T) {
var nop nopCloser
assert.Nil(t, nop.Close())
}

View File

@@ -43,6 +43,7 @@ const (
consoleMode = "console" consoleMode = "console"
volumeMode = "volume" volumeMode = "volume"
levelAlert = "alert"
levelInfo = "info" levelInfo = "info"
levelError = "error" levelError = "error"
levelSevere = "severe" levelSevere = "severe"
@@ -121,6 +122,10 @@ func SetUp(c LogConf) error {
} }
} }
func Alert(v string) {
output(errorLog, levelAlert, v)
}
func Close() error { func Close() error {
if writeConsole { if writeConsole {
return nil return nil

View File

@@ -6,8 +6,10 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"runtime" "runtime"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
@@ -21,10 +23,13 @@ var (
) )
type mockWriter struct { type mockWriter struct {
lock sync.Mutex
builder strings.Builder builder strings.Builder
} }
func (mw *mockWriter) Write(data []byte) (int, error) { func (mw *mockWriter) Write(data []byte) (int, error) {
mw.lock.Lock()
defer mw.lock.Unlock()
return mw.builder.Write(data) return mw.builder.Write(data)
} }
@@ -32,12 +37,22 @@ func (mw *mockWriter) Close() error {
return nil return nil
} }
func (mw *mockWriter) Contains(text string) bool {
mw.lock.Lock()
defer mw.lock.Unlock()
return strings.Contains(mw.builder.String(), text)
}
func (mw *mockWriter) Reset() { func (mw *mockWriter) Reset() {
mw.lock.Lock()
defer mw.lock.Unlock()
mw.builder.Reset() mw.builder.Reset()
} }
func (mw *mockWriter) Contains(text string) bool { func (mw *mockWriter) String() string {
return strings.Contains(mw.builder.String(), text) mw.lock.Lock()
defer mw.lock.Unlock()
return mw.builder.String()
} }
func TestFileLineFileMode(t *testing.T) { func TestFileLineFileMode(t *testing.T) {
@@ -69,6 +84,14 @@ func TestFileLineConsoleMode(t *testing.T) {
assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1))) assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1)))
} }
func TestStructedLogAlert(t *testing.T) {
doTestStructedLog(t, levelAlert, func(writer io.WriteCloser) {
errorLog = writer
}, func(v ...interface{}) {
Alert(fmt.Sprint(v...))
})
}
func TestStructedLogInfo(t *testing.T) { func TestStructedLogInfo(t *testing.T) {
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) { doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
infoLog = writer infoLog = writer
@@ -85,6 +108,46 @@ func TestStructedLogSlow(t *testing.T) {
}) })
} }
func TestStructedLogSlowf(t *testing.T) {
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
slowLog = writer
}, func(v ...interface{}) {
Slowf(fmt.Sprint(v...))
})
}
func TestStructedLogStat(t *testing.T) {
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
statLog = writer
}, func(v ...interface{}) {
Stat(v...)
})
}
func TestStructedLogStatf(t *testing.T) {
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
statLog = writer
}, func(v ...interface{}) {
Statf(fmt.Sprint(v...))
})
}
func TestStructedLogSevere(t *testing.T) {
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
severeLog = writer
}, func(v ...interface{}) {
Severe(v...)
})
}
func TestStructedLogSeveref(t *testing.T) {
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
severeLog = writer
}, func(v ...interface{}) {
Severef(fmt.Sprint(v...))
})
}
func TestStructedLogWithDuration(t *testing.T) { func TestStructedLogWithDuration(t *testing.T) {
const message = "hello there" const message = "hello there"
writer := new(mockWriter) writer := new(mockWriter)
@@ -135,6 +198,68 @@ func TestMustNil(t *testing.T) {
Must(nil) Must(nil)
} }
func TestSetup(t *testing.T) {
MustSetup(LogConf{
ServiceName: "any",
Mode: "console",
})
MustSetup(LogConf{
ServiceName: "any",
Mode: "file",
Path: os.TempDir(),
})
MustSetup(LogConf{
ServiceName: "any",
Mode: "volume",
Path: os.TempDir(),
})
assert.NotNil(t, setupWithVolume(LogConf{}))
assert.NotNil(t, setupWithFiles(LogConf{}))
assert.Nil(t, setupWithFiles(LogConf{
ServiceName: "any",
Path: os.TempDir(),
Compress: true,
KeepDays: 1,
}))
setupLogLevel(LogConf{
Level: levelInfo,
})
setupLogLevel(LogConf{
Level: levelError,
})
setupLogLevel(LogConf{
Level: levelSevere,
})
_, err := createOutput("")
assert.NotNil(t, err)
Disable()
}
func TestDisable(t *testing.T) {
Disable()
var opt logOptions
WithKeepDays(1)(&opt)
WithGzip()(&opt)
assert.Nil(t, Close())
writeConsole = false
assert.Nil(t, Close())
}
func TestWithGzip(t *testing.T) {
fn := WithGzip()
var opt logOptions
fn(&opt)
assert.True(t, opt.gzipEnabled)
}
func TestWithKeepDays(t *testing.T) {
fn := WithKeepDays(1)
var opt logOptions
fn(&opt)
assert.Equal(t, 1, opt.keepDays)
}
func BenchmarkCopyByteSliceAppend(b *testing.B) { func BenchmarkCopyByteSliceAppend(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var buf []byte var buf []byte
@@ -232,7 +357,7 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
t.Error(err) t.Error(err)
} }
assert.Equal(t, level, entry.Level) assert.Equal(t, level, entry.Level)
assert.Equal(t, message, entry.Content) assert.True(t, strings.Contains(entry.Content, message))
} }
func testSetLevelTwiceWithMode(t *testing.T, mode string) { func testSetLevelTwiceWithMode(t *testing.T, mode string) {
@@ -252,4 +377,10 @@ func testSetLevelTwiceWithMode(t *testing.T, mode string) {
atomic.StoreUint32(&initialized, 1) atomic.StoreUint32(&initialized, 1)
Info(message) Info(message)
assert.Equal(t, 0, writer.builder.Len()) assert.Equal(t, 0, writer.builder.Len())
Infof(message)
assert.Equal(t, 0, writer.builder.Len())
ErrorStack(message)
assert.Equal(t, 0, writer.builder.Len())
ErrorStackf(message)
assert.Equal(t, 0, writer.builder.Len())
} }

View File

@@ -192,14 +192,16 @@ func (l *RotateLogger) init() error {
} }
func (l *RotateLogger) maybeCompressFile(file string) { func (l *RotateLogger) maybeCompressFile(file string) {
if l.compress { if !l.compress {
defer func() { return
if r := recover(); r != nil {
ErrorStack(r)
}
}()
compressLogFile(file)
} }
defer func() {
if r := recover(); r != nil {
ErrorStack(r)
}
}()
compressLogFile(file)
} }
func (l *RotateLogger) maybeDeleteOutdatedFiles() { func (l *RotateLogger) maybeDeleteOutdatedFiles() {

View File

@@ -0,0 +1,119 @@
package logx
import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fs"
)
func TestDailyRotateRuleMarkRotated(t *testing.T) {
var rule DailyRotateRule
rule.MarkRotated()
assert.Equal(t, getNowDate(), rule.rotatedTime)
}
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())
}
func TestDailyRotateRuleShallRotate(t *testing.T) {
var rule DailyRotateRule
rule.rotatedTime = time.Now().Add(time.Hour * 24).Format(dateFormat)
assert.True(t, rule.ShallRotate())
}
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())
}
func TestRotateLoggerGetBackupFilename(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.True(t, len(logger.getBackupFilename()) > 0)
logger.backup = ""
assert.True(t, len(logger.getBackupFilename()) > 0)
}
func TestRotateLoggerMayCompressFile(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)
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.Nil(t, err)
}
func TestRotateLoggerMayCompressFileTrue(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(DailyRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(filename)
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
logger.maybeCompressFile(filename)
_, err = os.Stat(filename)
assert.NotNil(t, err)
}
func TestRotateLoggerRotate(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
logger, err := NewLogger(filename, new(DailyRotateRule), true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(filename)
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
err = logger.rotate()
assert.Nil(t, err)
}
func TestRotateLoggerWrite(t *testing.T) {
filename, err := fs.TempFilenameWithText("foo")
assert.Nil(t, err)
rule := new(DailyRotateRule)
logger, err := NewLogger(filename, rule, true)
assert.Nil(t, err)
if len(filename) > 0 {
defer func() {
os.Remove(filename)
os.Remove(logger.getBackupFilename())
os.Remove(filepath.Base(logger.getBackupFilename()) + ".gz")
}()
}
logger.write([]byte(`foo`))
rule.rotatedTime = time.Now().Add(-time.Hour * 24).Format(dateFormat)
logger.write([]byte(`bar`))
}

View File

@@ -15,7 +15,7 @@ const testlog = "Stay hungry, stay foolish."
func TestCollectSysLog(t *testing.T) { func TestCollectSysLog(t *testing.T) {
CollectSysLog() CollectSysLog()
content := getContent(captureOutput(func() { content := getContent(captureOutput(func() {
log.Printf(testlog) log.Print(testlog)
})) }))
assert.True(t, strings.Contains(content, testlog)) assert.True(t, strings.Contains(content, testlog))
} }
@@ -33,10 +33,10 @@ func captureOutput(f func()) string {
writer := new(mockWriter) writer := new(mockWriter)
infoLog = writer infoLog = writer
prevLevel := logLevel prevLevel := atomic.LoadUint32(&logLevel)
logLevel = InfoLevel SetLevel(InfoLevel)
f() f()
logLevel = prevLevel SetLevel(prevLevel)
return writer.builder.String() return writer.builder.String()
} }

View File

@@ -2,8 +2,11 @@ package logx
import ( import (
"context" "context"
"log"
"strings" "strings"
"sync/atomic"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/trace/tracespec" "github.com/tal-tech/go-zero/core/trace/tracespec"
@@ -17,13 +20,77 @@ const (
var mock tracespec.Trace = new(mockTrace) var mock tracespec.Trace = new(mockTrace)
func TestTraceLog(t *testing.T) { func TestTraceLog(t *testing.T) {
var buf strings.Builder var buf mockWriter
atomic.StoreUint32(&initialized, 1)
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock) ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog) WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId)) assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId)) assert.True(t, strings.Contains(buf.String(), mockSpanId))
} }
func TestTraceError(t *testing.T) {
var buf mockWriter
atomic.StoreUint32(&initialized, 1)
errorLog = newLogWriter(log.New(&buf, "", flags))
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
l := WithContext(ctx).(*traceLogger)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Error(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
buf.Reset()
l.WithDuration(time.Second).Errorf(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
}
func TestTraceInfo(t *testing.T) {
var buf mockWriter
atomic.StoreUint32(&initialized, 1)
infoLog = newLogWriter(log.New(&buf, "", flags))
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
l := WithContext(ctx).(*traceLogger)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
buf.Reset()
l.WithDuration(time.Second).Infof(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
}
func TestTraceSlow(t *testing.T) {
var buf mockWriter
atomic.StoreUint32(&initialized, 1)
slowLog = newLogWriter(log.New(&buf, "", flags))
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
l := WithContext(ctx).(*traceLogger)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Slow(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
buf.Reset()
l.WithDuration(time.Second).Slowf(testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
}
func TestTraceWithoutContext(t *testing.T) {
var buf mockWriter
atomic.StoreUint32(&initialized, 1)
infoLog = newLogWriter(log.New(&buf, "", flags))
l := WithContext(context.Background()).(*traceLogger)
SetLevel(InfoLevel)
l.WithDuration(time.Second).Info(testlog)
assert.False(t, strings.Contains(buf.String(), mockTraceId))
assert.False(t, strings.Contains(buf.String(), mockSpanId))
buf.Reset()
l.WithDuration(time.Second).Infof(testlog)
assert.False(t, strings.Contains(buf.String(), mockTraceId))
assert.False(t, strings.Contains(buf.String(), mockSpanId))
}
type mockTrace struct{} type mockTrace struct{}
func (t mockTrace) TraceId() string { func (t mockTrace) TraceId() string {

View File

@@ -153,58 +153,57 @@ func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fie
key := strings.TrimSpace(segments[0]) key := strings.TrimSpace(segments[0])
options := segments[1:] options := segments[1:]
if len(options) > 0 { if len(options) == 0 {
var fieldOpts fieldOptions return key, nil, nil
for _, segment := range options {
option := strings.TrimSpace(segment)
switch {
case option == stringOption:
fieldOpts.FromString = true
case strings.HasPrefix(option, optionalOption):
segs := strings.Split(option, equalToken)
switch len(segs) {
case 1:
fieldOpts.Optional = true
case 2:
fieldOpts.Optional = true
fieldOpts.OptionalDep = segs[1]
default:
return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
}
case option == optionalOption:
fieldOpts.Optional = true
case strings.HasPrefix(option, optionsOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
} else {
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
}
case strings.HasPrefix(option, defaultOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
} else {
fieldOpts.Default = strings.TrimSpace(segs[1])
}
case strings.HasPrefix(option, rangeOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
}
if nr, err := parseNumberRange(segs[1]); err != nil {
return "", nil, err
} else {
fieldOpts.Range = nr
}
}
}
return key, &fieldOpts, nil
} }
return key, nil, nil var fieldOpts fieldOptions
for _, segment := range options {
option := strings.TrimSpace(segment)
switch {
case option == stringOption:
fieldOpts.FromString = true
case strings.HasPrefix(option, optionalOption):
segs := strings.Split(option, equalToken)
switch len(segs) {
case 1:
fieldOpts.Optional = true
case 2:
fieldOpts.Optional = true
fieldOpts.OptionalDep = segs[1]
default:
return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
}
case option == optionalOption:
fieldOpts.Optional = true
case strings.HasPrefix(option, optionsOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
} else {
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
}
case strings.HasPrefix(option, defaultOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
} else {
fieldOpts.Default = strings.TrimSpace(segs[1])
}
case strings.HasPrefix(option, rangeOption):
segs := strings.Split(option, equalToken)
if len(segs) != 2 {
return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
}
if nr, err := parseNumberRange(segs[1]); err != nil {
return "", nil, err
} else {
fieldOpts.Range = nr
}
}
}
return key, &fieldOpts, nil
} }
func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) { func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {

View File

@@ -31,6 +31,7 @@ func TestMaxInt(t *testing.T) {
} }
for _, each := range cases { for _, each := range cases {
each := each
t.Run(stringx.Rand(), func(t *testing.T) { t.Run(stringx.Rand(), func(t *testing.T) {
actual := MaxInt(each.a, each.b) actual := MaxInt(each.a, each.b)
assert.Equal(t, each.expect, actual) assert.Equal(t, each.expect, actual)

View File

@@ -26,10 +26,6 @@ var started uint32
// Profile represents an active profiling session. // Profile represents an active profiling session.
type Profile struct { type Profile struct {
// path holds the base path where various profiling files are written.
// If blank, the base path will be generated by ioutil.TempDir.
path string
// closers holds cleanup functions that run after each profile // closers holds cleanup functions that run after each profile
closers []func() closers []func()

View File

@@ -0,0 +1,16 @@
package prof
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestReport(t *testing.T) {
once.Do(func() {})
assert.NotContains(t, generateReport(), "foo")
report("foo", time.Second)
assert.Contains(t, generateReport(), "foo")
report("foo", time.Second)
}

View File

@@ -0,0 +1,23 @@
package prof
import (
"testing"
"github.com/tal-tech/go-zero/core/utils"
)
func TestProfiler(t *testing.T) {
EnableProfiling()
Start()
Report("foo", ProfilePoint{
ElapsedTimer: utils.NewElapsedTimer(),
})
}
func TestNullProfiler(t *testing.T) {
p := newNullProfiler()
p.Start()
p.Report("foo", ProfilePoint{
ElapsedTimer: utils.NewElapsedTimer(),
})
}

View File

@@ -27,7 +27,7 @@ func newMockedService(multiplier int) *mockedService {
func (s *mockedService) Start() { func (s *mockedService) Start() {
mutex.Lock() mutex.Lock()
number = number * s.multiplier number *= s.multiplier
mutex.Unlock() mutex.Unlock()
done <- struct{}{} done <- struct{}{}
<-s.quit <-s.quit

View File

@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/tal-tech/go-zero/core/executors" "github.com/tal-tech/go-zero/core/executors"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/proc" "github.com/tal-tech/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/sysx" "github.com/tal-tech/go-zero/core/sysx"
"github.com/tal-tech/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
@@ -23,7 +24,7 @@ const (
) )
var ( var (
reporter func(string) reporter = logx.Alert
lock sync.RWMutex lock sync.RWMutex
lessExecutor = executors.NewLessExecutor(time.Minute * 5) lessExecutor = executors.NewLessExecutor(time.Minute * 5)
dropped int32 dropped int32

22
core/stat/alert_test.go Normal file
View File

@@ -0,0 +1,22 @@
// +build linux
package stat
import (
"strconv"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestReport(t *testing.T) {
var count int32
SetReporter(func(s string) {
atomic.AddInt32(&count, 1)
})
for i := 0; i < 10; i++ {
Report(strconv.Itoa(i))
}
assert.Equal(t, int32(1), count)
}

View File

@@ -1,6 +1,14 @@
package internal package internal
import "testing" import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRefreshCpu(t *testing.T) {
assert.True(t, RefreshCpu() >= 0)
}
func BenchmarkRefreshCpu(b *testing.B) { func BenchmarkRefreshCpu(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {

37
core/stat/metrics_test.go Normal file
View File

@@ -0,0 +1,37 @@
package stat
import (
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestMetrics(t *testing.T) {
counts := []int{1, 5, 10, 100, 1000, 1000}
for _, count := range counts {
m := NewMetrics("foo")
m.SetName("bar")
for i := 0; i < count; i++ {
m.Add(Task{
Duration: time.Millisecond * time.Duration(i),
Description: strconv.Itoa(i),
})
}
m.AddDrop()
var writer mockedWriter
SetReportWriter(&writer)
m.executor.Flush()
assert.Equal(t, "bar", writer.report.Name)
}
}
type mockedWriter struct {
report *StatReport
}
func (m *mockedWriter) Write(report *StatReport) error {
m.report = report
return nil
}

View File

@@ -0,0 +1,30 @@
package stat
import (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
)
func TestRemoteWriter(t *testing.T) {
defer gock.Off()
gock.New("http://foo.com").Reply(200).BodyString("foo")
writer := NewRemoteWriter("http://foo.com")
err := writer.Write(&StatReport{
Name: "bar",
})
assert.Nil(t, err)
}
func TestRemoteWriterFail(t *testing.T) {
defer gock.Off()
gock.New("http://foo.com").Reply(503).BodyString("foo")
writer := NewRemoteWriter("http://foo.com")
err := writer.Write(&StatReport{
Name: "bar",
})
assert.NotNil(t, err)
}

View File

@@ -8,11 +8,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/errorx" "github.com/tal-tech/go-zero/core/errorx"
"github.com/tal-tech/go-zero/core/hash" "github.com/tal-tech/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
"github.com/tal-tech/go-zero/core/syncx" "github.com/tal-tech/go-zero/core/syncx"
) )
@@ -76,23 +76,23 @@ func (mc *mockedNode) TakeWithExpire(v interface{}, key string, query func(v int
func TestCache_SetDel(t *testing.T) { func TestCache_SetDel(t *testing.T) {
const total = 1000 const total = 1000
r1 := miniredis.NewMiniRedis() r1, clean1, err := redistest.CreateRedis()
assert.Nil(t, r1.Start()) assert.Nil(t, err)
defer r1.Close() defer clean1()
r2 := miniredis.NewMiniRedis() r2, clean2, err := redistest.CreateRedis()
assert.Nil(t, r2.Start()) assert.Nil(t, err)
defer r2.Close() defer clean2()
conf := ClusterConf{ conf := ClusterConf{
{ {
RedisConf: redis.RedisConf{ RedisConf: redis.RedisConf{
Host: r1.Addr(), Host: r1.Addr,
Type: redis.NodeType, Type: redis.NodeType,
}, },
Weight: 100, Weight: 100,
}, },
{ {
RedisConf: redis.RedisConf{ RedisConf: redis.RedisConf{
Host: r2.Addr(), Host: r2.Addr,
Type: redis.NodeType, Type: redis.NodeType,
}, },
Weight: 100, Weight: 100,
@@ -111,6 +111,45 @@ func TestCache_SetDel(t *testing.T) {
assert.Nil(t, c.GetCache(fmt.Sprintf("key/%d", i), &v)) assert.Nil(t, c.GetCache(fmt.Sprintf("key/%d", i), &v))
assert.Equal(t, i, v) assert.Equal(t, i, v)
} }
assert.Nil(t, c.DelCache())
for i := 0; i < total; i++ {
assert.Nil(t, c.DelCache(fmt.Sprintf("key/%d", i)))
}
for i := 0; i < total; i++ {
var v int
assert.Equal(t, errPlaceholder, c.GetCache(fmt.Sprintf("key/%d", i), &v))
assert.Equal(t, 0, v)
}
}
func TestCache_OneNode(t *testing.T) {
const total = 1000
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
conf := ClusterConf{
{
RedisConf: redis.RedisConf{
Host: r.Addr,
Type: redis.NodeType,
},
Weight: 100,
},
}
c := NewCache(conf, syncx.NewSharedCalls(), NewCacheStat("mock"), errPlaceholder)
for i := 0; i < total; i++ {
if i%2 == 0 {
assert.Nil(t, c.SetCache(fmt.Sprintf("key/%d", i), i))
} else {
assert.Nil(t, c.SetCacheWithExpire(fmt.Sprintf("key/%d", i), i, 0))
}
}
for i := 0; i < total; i++ {
var v int
assert.Nil(t, c.GetCache(fmt.Sprintf("key/%d", i), &v))
assert.Equal(t, i, v)
}
assert.Nil(t, c.DelCache())
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
assert.Nil(t, c.DelCache(fmt.Sprintf("key/%d", i))) assert.Nil(t, c.DelCache(fmt.Sprintf("key/%d", i)))
} }
@@ -188,6 +227,25 @@ func TestCache_Balance(t *testing.T) {
assert.Equal(t, total/10, count) assert.Equal(t, total/10, count)
} }
func TestCacheNoNode(t *testing.T) {
dispatcher := hash.NewConsistentHash()
c := cacheCluster{
dispatcher: dispatcher,
errNotFound: errPlaceholder,
}
assert.NotNil(t, c.DelCache("foo"))
assert.NotNil(t, c.DelCache("foo", "bar", "any"))
assert.NotNil(t, c.GetCache("foo", nil))
assert.NotNil(t, c.SetCache("foo", nil))
assert.NotNil(t, c.SetCacheWithExpire("foo", nil, time.Second))
assert.NotNil(t, c.Take(nil, "foo", func(v interface{}) error {
return nil
}))
assert.NotNil(t, c.TakeWithExpire(nil, "foo", func(v interface{}, duration time.Duration) error {
return nil
}))
}
func calcEntropy(m map[int]int, total int) float64 { func calcEntropy(m map[int]int, total int) float64 {
var entropy float64 var entropy float64

View File

@@ -1,13 +1,13 @@
package cache package cache
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
"sync" "sync"
"time" "time"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
@@ -79,7 +79,7 @@ func (c cacheNode) SetCache(key string, v interface{}) error {
} }
func (c cacheNode) SetCacheWithExpire(key string, v interface{}, expire time.Duration) error { func (c cacheNode) SetCacheWithExpire(key string, v interface{}, expire time.Duration) error {
data, err := json.Marshal(v) data, err := jsonx.Marshal(v)
if err != nil { if err != nil {
return err return err
} }
@@ -168,24 +168,24 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
} }
} }
return json.Marshal(v) return jsonx.Marshal(v)
}) })
if err != nil { if err != nil {
return err return err
} }
if fresh { if fresh {
return nil return nil
} else {
// got the result from previous ongoing query
c.stat.IncrementTotal()
c.stat.IncrementHit()
} }
return json.Unmarshal(val.([]byte), v) // got the result from previous ongoing query
c.stat.IncrementTotal()
c.stat.IncrementHit()
return jsonx.Unmarshal(val.([]byte), v)
} }
func (c cacheNode) processCache(key string, data string, v interface{}) error { func (c cacheNode) processCache(key string, data string, v interface{}) error {
err := json.Unmarshal([]byte(data), v) err := jsonx.Unmarshal([]byte(data), v)
if err == nil { if err == nil {
return nil return nil
} }

View File

@@ -2,36 +2,42 @@ package cache
import ( import (
"errors" "errors"
"fmt"
"math/rand" "math/rand"
"strconv"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx" "github.com/tal-tech/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
"github.com/tal-tech/go-zero/core/syncx"
) )
var errTestNotFound = errors.New("not found")
func init() { func init() {
logx.Disable() logx.Disable()
stat.SetReporter(nil) stat.SetReporter(nil)
} }
func TestCacheNode_DelCache(t *testing.T) { func TestCacheNode_DelCache(t *testing.T) {
s, err := miniredis.Run() store, clean, err := redistest.CreateRedis()
assert.Nil(t, err) assert.Nil(t, err)
defer s.Close() defer clean()
cn := cacheNode{ cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType), rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())), r: rand.New(rand.NewSource(time.Now().UnixNano())),
lock: new(sync.Mutex), lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation), unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"), stat: NewCacheStat("any"),
errNotFound: errors.New("any"), errNotFound: errTestNotFound,
} }
assert.Nil(t, cn.DelCache()) assert.Nil(t, cn.DelCache())
assert.Nil(t, cn.DelCache([]string{}...)) assert.Nil(t, cn.DelCache([]string{}...))
@@ -54,7 +60,7 @@ func TestCacheNode_InvalidCache(t *testing.T) {
lock: new(sync.Mutex), lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation), unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"), stat: NewCacheStat("any"),
errNotFound: errors.New("any"), errNotFound: errTestNotFound,
} }
s.Set("any", "value") s.Set("any", "value")
var str string var str string
@@ -63,3 +69,139 @@ func TestCacheNode_InvalidCache(t *testing.T) {
_, err = s.Get("any") _, err = s.Get("any")
assert.Equal(t, miniredis.ErrKeyNotFound, err) assert.Equal(t, miniredis.ErrKeyNotFound, err)
} }
func TestCacheNode_Take(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errTestNotFound,
}
var str string
err = cn.Take(&str, "any", func(v interface{}) error {
*v.(*string) = "value"
return nil
})
assert.Nil(t, err)
assert.Equal(t, "value", str)
assert.Nil(t, cn.GetCache("any", &str))
val, err := store.Get("any")
assert.Nil(t, err)
assert.Equal(t, `"value"`, val)
}
func TestCacheNode_TakeNotFound(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errTestNotFound,
}
var str string
err = cn.Take(&str, "any", func(v interface{}) error {
return errTestNotFound
})
assert.Equal(t, errTestNotFound, err)
assert.Equal(t, errTestNotFound, cn.GetCache("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 interface{}) error {
return nil
})
assert.Equal(t, errTestNotFound, err)
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
store.Del("any")
var errDummy = errors.New("dummy")
err = cn.Take(&str, "any", func(v interface{}) error {
return errDummy
})
assert.Equal(t, errDummy, err)
}
func TestCacheNode_TakeWithExpire(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errors.New("any"),
}
var str string
err = cn.TakeWithExpire(&str, "any", func(v interface{}, expire time.Duration) error {
*v.(*string) = "value"
return nil
})
assert.Nil(t, err)
assert.Equal(t, "value", str)
assert.Nil(t, cn.GetCache("any", &str))
val, err := store.Get("any")
assert.Nil(t, err)
assert.Equal(t, `"value"`, val)
}
func TestCacheNode_String(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errors.New("any"),
}
assert.Equal(t, store.Addr, cn.String())
}
func TestCacheValueWithBigInt(t *testing.T) {
store, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: store,
r: rand.New(rand.NewSource(time.Now().UnixNano())),
barrier: syncx.NewSharedCalls(),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errors.New("any"),
}
const (
key = "key"
value int64 = 323427211229009810
)
assert.Nil(t, cn.SetCache(key, value))
var val interface{}
assert.Nil(t, cn.GetCache(key, &val))
assert.Equal(t, strconv.FormatInt(value, 10), fmt.Sprintf("%v", val))
}

View File

@@ -48,20 +48,17 @@ func (cs *CacheStat) statLoop() {
ticker := time.NewTicker(statInterval) ticker := time.NewTicker(statInterval)
defer ticker.Stop() defer ticker.Stop()
for { for range ticker.C {
select { total := atomic.SwapUint64(&cs.Total, 0)
case <-ticker.C: if total == 0 {
total := atomic.SwapUint64(&cs.Total, 0) continue
if total == 0 {
continue
}
hit := atomic.SwapUint64(&cs.Hit, 0)
percent := 100 * float32(hit) / float32(total)
miss := atomic.SwapUint64(&cs.Miss, 0)
dbf := atomic.SwapUint64(&cs.DbFails, 0)
logx.Statf("dbcache(%s) - qpm: %d, hit_ratio: %.1f%%, hit: %d, miss: %d, db_fails: %d",
cs.name, total, percent, hit, miss, dbf)
} }
hit := atomic.SwapUint64(&cs.Hit, 0)
percent := 100 * float32(hit) / float32(total)
miss := atomic.SwapUint64(&cs.Miss, 0)
dbf := atomic.SwapUint64(&cs.DbFails, 0)
logx.Statf("dbcache(%s) - qpm: %d, hit_ratio: %.1f%%, hit: %d, miss: %d, db_fails: %d",
cs.name, total, percent, hit, miss, dbf)
} }
} }

56
core/stores/cache/cleaner_test.go vendored Normal file
View File

@@ -0,0 +1,56 @@
package cache
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestNextDelay(t *testing.T) {
tests := []struct {
name string
input time.Duration
output time.Duration
ok bool
}{
{
name: "second",
input: time.Second,
output: time.Second * 5,
ok: true,
},
{
name: "5 seconds",
input: time.Second * 5,
output: time.Minute,
ok: true,
},
{
name: "minute",
input: time.Minute,
output: time.Minute * 5,
ok: true,
},
{
name: "5 minutes",
input: time.Minute * 5,
output: time.Hour,
ok: true,
},
{
name: "hour",
input: time.Hour,
output: 0,
ok: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
next, ok := nextDelay(test.input)
assert.Equal(t, test.ok, ok)
assert.Equal(t, test.output, next)
})
}
}

26
core/stores/cache/util_test.go vendored Normal file
View File

@@ -0,0 +1,26 @@
package cache
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFormatKeys(t *testing.T) {
assert.Equal(t, "a,b", formatKeys([]string{"a", "b"}))
}
func TestTotalWeights(t *testing.T) {
val := TotalWeights([]NodeConf{
{
Weight: -1,
},
{
Weight: 0,
},
{
Weight: 1,
},
})
assert.Equal(t, 1, val)
}

View File

@@ -73,6 +73,7 @@ type (
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error) ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
Zscore(key string, value string) (int64, error) Zscore(key string, value string) (int64, error)
Zrevrank(key, field string) (int64, error)
} }
clusterStore struct { clusterStore struct {
@@ -635,6 +636,15 @@ func (cs clusterStore) ZrevrangebyscoreWithScoresAndLimit(key string, start, sto
return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size) return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
} }
func (cs clusterStore) Zrevrank(key, field string) (int64, error) {
node, err := cs.getRedis(key)
if err != nil {
return 0, err
}
return node.Zrevrank(key, field)
}
func (cs clusterStore) Zscore(key string, value string) (int64, error) { func (cs clusterStore) Zscore(key string, value string) (int64, error) {
node, err := cs.getRedis(key) node, err := cs.getRedis(key)
if err != nil { if err != nil {

View File

@@ -4,8 +4,9 @@ import (
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
@@ -15,6 +16,10 @@ var s1, _ = miniredis.Run()
var s2, _ = miniredis.Run() var s2, _ = miniredis.Run()
func TestRedis_Exists(t *testing.T) { func TestRedis_Exists(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Exists("foo")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
ok, err := client.Exists("a") ok, err := client.Exists("a")
assert.Nil(t, err) assert.Nil(t, err)
@@ -27,6 +32,10 @@ func TestRedis_Exists(t *testing.T) {
} }
func TestRedis_Eval(t *testing.T) { func TestRedis_Eval(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Eval(`redis.call("EXISTS", KEYS[1])`, "key1")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, "notexist") _, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, "notexist")
assert.Equal(t, redis.Nil, err) assert.Equal(t, redis.Nil, err)
@@ -41,6 +50,12 @@ func TestRedis_Eval(t *testing.T) {
} }
func TestRedis_Hgetall(t *testing.T) { func TestRedis_Hgetall(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
err := store.Hset("a", "aa", "aaa")
assert.NotNil(t, err)
_, err = store.Hgetall("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -54,6 +69,10 @@ func TestRedis_Hgetall(t *testing.T) {
} }
func TestRedis_Hvals(t *testing.T) { func TestRedis_Hvals(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Hvals("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -64,6 +83,10 @@ func TestRedis_Hvals(t *testing.T) {
} }
func TestRedis_Hsetnx(t *testing.T) { func TestRedis_Hsetnx(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Hsetnx("a", "dd", "ddd")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -80,6 +103,12 @@ func TestRedis_Hsetnx(t *testing.T) {
} }
func TestRedis_HdelHlen(t *testing.T) { func TestRedis_HdelHlen(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Hdel("a", "aa")
assert.NotNil(t, err)
_, err = store.Hlen("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -96,6 +125,10 @@ func TestRedis_HdelHlen(t *testing.T) {
} }
func TestRedis_HIncrBy(t *testing.T) { func TestRedis_HIncrBy(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Hincrby("key", "field", 3)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
val, err := client.Hincrby("key", "field", 2) val, err := client.Hincrby("key", "field", 2)
assert.Nil(t, err) assert.Nil(t, err)
@@ -107,6 +140,10 @@ func TestRedis_HIncrBy(t *testing.T) {
} }
func TestRedis_Hkeys(t *testing.T) { func TestRedis_Hkeys(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Hkeys("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -117,6 +154,10 @@ func TestRedis_Hkeys(t *testing.T) {
} }
func TestRedis_Hmget(t *testing.T) { func TestRedis_Hmget(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Hmget("a", "aa", "bb")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -130,6 +171,12 @@ func TestRedis_Hmget(t *testing.T) {
} }
func TestRedis_Hmset(t *testing.T) { func TestRedis_Hmset(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
err := store.Hmset("a", map[string]string{
"aa": "aaa",
})
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
assert.Nil(t, client.Hmset("a", map[string]string{ assert.Nil(t, client.Hmset("a", map[string]string{
"aa": "aaa", "aa": "aaa",
@@ -142,6 +189,10 @@ func TestRedis_Hmset(t *testing.T) {
} }
func TestRedis_Incr(t *testing.T) { func TestRedis_Incr(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Incr("a")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
val, err := client.Incr("a") val, err := client.Incr("a")
assert.Nil(t, err) assert.Nil(t, err)
@@ -153,6 +204,10 @@ func TestRedis_Incr(t *testing.T) {
} }
func TestRedis_IncrBy(t *testing.T) { func TestRedis_IncrBy(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Incrby("a", 2)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
val, err := client.Incrby("a", 2) val, err := client.Incrby("a", 2)
assert.Nil(t, err) assert.Nil(t, err)
@@ -164,6 +219,20 @@ func TestRedis_IncrBy(t *testing.T) {
} }
func TestRedis_List(t *testing.T) { func TestRedis_List(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Lpush("key", "value1", "value2")
assert.NotNil(t, err)
_, err = store.Rpush("key", "value3", "value4")
assert.NotNil(t, err)
_, err = store.Llen("key")
assert.NotNil(t, err)
_, err = store.Lrange("key", 0, 10)
assert.NotNil(t, err)
_, err = store.Lpop("key")
assert.NotNil(t, err)
_, err = store.Lrem("key", 0, "val")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
val, err := client.Lpush("key", "value1", "value2") val, err := client.Lpush("key", "value1", "value2")
assert.Nil(t, err) assert.Nil(t, err)
@@ -202,6 +271,14 @@ func TestRedis_List(t *testing.T) {
} }
func TestRedis_Persist(t *testing.T) { func TestRedis_Persist(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Persist("key")
assert.NotNil(t, err)
err = store.Expire("key", 5)
assert.NotNil(t, err)
err = store.Expireat("key", time.Now().Unix()+5)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
ok, err := client.Persist("key") ok, err := client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
@@ -225,8 +302,16 @@ func TestRedis_Persist(t *testing.T) {
} }
func TestRedis_Sscan(t *testing.T) { func TestRedis_Sscan(t *testing.T) {
key := "list"
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Sadd(key, nil)
assert.NotNil(t, err)
_, _, err = store.Sscan(key, 0, "", 100)
assert.NotNil(t, err)
_, err = store.Del(key)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
key := "list"
var list []string var list []string
for i := 0; i < 1550; i++ { for i := 0; i < 1550; i++ {
list = append(list, stringx.Randn(i)) list = append(list, stringx.Randn(i))
@@ -254,6 +339,20 @@ func TestRedis_Sscan(t *testing.T) {
} }
func TestRedis_Set(t *testing.T) { func TestRedis_Set(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Scard("key")
assert.NotNil(t, err)
_, err = store.Sismember("key", 2)
assert.NotNil(t, err)
_, err = store.Srem("key", 3, 4)
assert.NotNil(t, err)
_, err = store.Smembers("key")
assert.NotNil(t, err)
_, err = store.Srandmember("key", 1)
assert.NotNil(t, err)
_, err = store.Spop("key")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
num, err := client.Sadd("key", 1, 2, 3, 4) num, err := client.Sadd("key", 1, 2, 3, 4)
assert.Nil(t, err) assert.Nil(t, err)
@@ -290,6 +389,14 @@ func TestRedis_Set(t *testing.T) {
} }
func TestRedis_SetGetDel(t *testing.T) { func TestRedis_SetGetDel(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
err := store.Set("hello", "world")
assert.NotNil(t, err)
_, err = store.Get("hello")
assert.NotNil(t, err)
_, err = store.Del("hello")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
err := client.Set("hello", "world") err := client.Set("hello", "world")
assert.Nil(t, err) assert.Nil(t, err)
@@ -303,6 +410,16 @@ func TestRedis_SetGetDel(t *testing.T) {
} }
func TestRedis_SetExNx(t *testing.T) { func TestRedis_SetExNx(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
err := store.Setex("hello", "world", 5)
assert.NotNil(t, err)
_, err = store.Setnx("newhello", "newworld")
assert.NotNil(t, err)
_, err = store.Ttl("hello")
assert.NotNil(t, err)
_, err = store.SetnxEx("newhello", "newworld", 5)
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
err := client.Setex("hello", "world", 5) err := client.Setex("hello", "world", 5)
assert.Nil(t, err) assert.Nil(t, err)
@@ -337,6 +454,16 @@ func TestRedis_SetExNx(t *testing.T) {
} }
func TestRedis_SetGetDelHashField(t *testing.T) { func TestRedis_SetGetDelHashField(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
err := store.Hset("key", "field", "value")
assert.NotNil(t, err)
_, err = store.Hget("key", "field")
assert.NotNil(t, err)
_, err = store.Hexists("key", "field")
assert.NotNil(t, err)
_, err = store.Hdel("key", "field")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
err := client.Hset("key", "field", "value") err := client.Hset("key", "field", "value")
assert.Nil(t, err) assert.Nil(t, err)
@@ -356,6 +483,50 @@ func TestRedis_SetGetDelHashField(t *testing.T) {
} }
func TestRedis_SortedSet(t *testing.T) { func TestRedis_SortedSet(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Zadd("key", 1, "value1")
assert.NotNil(t, err)
_, err = store.Zscore("key", "value1")
assert.NotNil(t, err)
_, err = store.Zcount("key", 6, 7)
assert.NotNil(t, err)
_, err = store.Zincrby("key", 3, "value1")
assert.NotNil(t, err)
_, err = store.Zrank("key", "value2")
assert.NotNil(t, err)
_, err = store.Zrem("key", "value2", "value3")
assert.NotNil(t, err)
_, err = store.Zremrangebyscore("key", 6, 7)
assert.NotNil(t, err)
_, err = store.Zremrangebyrank("key", 1, 2)
assert.NotNil(t, err)
_, err = store.Zcard("key")
assert.NotNil(t, err)
_, err = store.Zrange("key", 0, -1)
assert.NotNil(t, err)
_, err = store.Zrevrange("key", 0, -1)
assert.NotNil(t, err)
_, err = store.ZrangeWithScores("key", 0, -1)
assert.NotNil(t, err)
_, err = store.ZrangebyscoreWithScores("key", 5, 8)
assert.NotNil(t, err)
_, err = store.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
assert.NotNil(t, err)
_, err = store.ZrevrangebyscoreWithScores("key", 5, 8)
assert.NotNil(t, err)
_, err = store.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
assert.NotNil(t, err)
_, err = store.Zrevrank("key", "value")
assert.NotNil(t, err)
_, err = store.Zadds("key", redis.Pair{
Key: "value2",
Score: 6,
}, redis.Pair{
Key: "value3",
Score: 7,
})
assert.NotNil(t, err)
runOnCluster(t, func(client Store) { runOnCluster(t, func(client Store) {
ok, err := client.Zadd("key", 1, "value1") ok, err := client.Zadd("key", 1, "value1")
assert.Nil(t, err) assert.Nil(t, err)
@@ -471,6 +642,33 @@ func TestRedis_SortedSet(t *testing.T) {
Score: 5, Score: 5,
}, },
}, pairs) }, pairs)
rank, err = client.Zrevrank("key", "value1")
assert.Nil(t, err)
assert.Equal(t, int64(1), rank)
val, err = client.Zadds("key", redis.Pair{
Key: "value2",
Score: 6,
}, redis.Pair{
Key: "value3",
Score: 7,
})
assert.Nil(t, err)
assert.Equal(t, int64(2), val)
})
}
func TestRedis_HyperLogLog(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Pfadd("key")
assert.NotNil(t, err)
_, err = store.Pfcount("key")
assert.NotNil(t, err)
runOnCluster(t, func(cluster Store) {
_, err := cluster.Pfadd("key")
assert.NotNil(t, err)
_, err = cluster.Pfcount("key")
assert.NotNil(t, err)
}) })
} }

View File

@@ -7,6 +7,7 @@ import (
"github.com/globalsign/mgo" "github.com/globalsign/mgo"
"github.com/tal-tech/go-zero/core/breaker" "github.com/tal-tech/go-zero/core/breaker"
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/mongo/internal"
"github.com/tal-tech/go-zero/core/timex" "github.com/tal-tech/go-zero/core/timex"
) )
@@ -29,8 +30,9 @@ type (
} }
decoratedCollection struct { decoratedCollection struct {
*mgo.Collection name string
brk breaker.Breaker collection internal.MgoCollection
brk breaker.Breaker
} }
keepablePromise struct { keepablePromise struct {
@@ -41,7 +43,8 @@ type (
func newCollection(collection *mgo.Collection) Collection { func newCollection(collection *mgo.Collection) Collection {
return &decoratedCollection{ return &decoratedCollection{
Collection: collection, name: collection.FullName,
collection: collection,
brk: breaker.NewBreaker(), brk: breaker.NewBreaker(),
} }
} }
@@ -54,7 +57,7 @@ func (c *decoratedCollection) Find(query interface{}) Query {
startTime := timex.Now() startTime := timex.Now()
return promisedQuery{ return promisedQuery{
Query: c.Collection.Find(query), Query: c.collection.Find(query),
promise: keepablePromise{ promise: keepablePromise{
promise: promise, promise: promise,
log: func(err error) { log: func(err error) {
@@ -73,7 +76,7 @@ func (c *decoratedCollection) FindId(id interface{}) Query {
startTime := timex.Now() startTime := timex.Now()
return promisedQuery{ return promisedQuery{
Query: c.Collection.FindId(id), Query: c.collection.FindId(id),
promise: keepablePromise{ promise: keepablePromise{
promise: promise, promise: promise,
log: func(err error) { log: func(err error) {
@@ -92,7 +95,7 @@ func (c *decoratedCollection) Insert(docs ...interface{}) (err error) {
c.logDuration("insert", duration, err, docs...) c.logDuration("insert", duration, err, docs...)
}() }()
return c.Collection.Insert(docs...) return c.collection.Insert(docs...)
}, acceptable) }, acceptable)
} }
@@ -104,7 +107,7 @@ func (c *decoratedCollection) Pipe(pipeline interface{}) Pipe {
startTime := timex.Now() startTime := timex.Now()
return promisedPipe{ return promisedPipe{
Pipe: c.Collection.Pipe(pipeline), Pipe: c.collection.Pipe(pipeline),
promise: keepablePromise{ promise: keepablePromise{
promise: promise, promise: promise,
log: func(err error) { log: func(err error) {
@@ -123,7 +126,7 @@ func (c *decoratedCollection) Remove(selector interface{}) (err error) {
c.logDuration("remove", duration, err, selector) c.logDuration("remove", duration, err, selector)
}() }()
return c.Collection.Remove(selector) return c.collection.Remove(selector)
}, acceptable) }, acceptable)
} }
@@ -135,7 +138,7 @@ func (c *decoratedCollection) RemoveAll(selector interface{}) (info *mgo.ChangeI
c.logDuration("removeAll", duration, err, selector) c.logDuration("removeAll", duration, err, selector)
}() }()
info, err = c.Collection.RemoveAll(selector) info, err = c.collection.RemoveAll(selector)
return err return err
}, acceptable) }, acceptable)
@@ -150,7 +153,7 @@ func (c *decoratedCollection) RemoveId(id interface{}) (err error) {
c.logDuration("removeId", duration, err, id) c.logDuration("removeId", duration, err, id)
}() }()
return c.Collection.RemoveId(id) return c.collection.RemoveId(id)
}, acceptable) }, acceptable)
} }
@@ -162,7 +165,7 @@ func (c *decoratedCollection) Update(selector, update interface{}) (err error) {
c.logDuration("update", duration, err, selector, update) c.logDuration("update", duration, err, selector, update)
}() }()
return c.Collection.Update(selector, update) return c.collection.Update(selector, update)
}, acceptable) }, acceptable)
} }
@@ -174,7 +177,7 @@ func (c *decoratedCollection) UpdateId(id, update interface{}) (err error) {
c.logDuration("updateId", duration, err, id, update) c.logDuration("updateId", duration, err, id, update)
}() }()
return c.Collection.UpdateId(id, update) return c.collection.UpdateId(id, update)
}, acceptable) }, acceptable)
} }
@@ -186,7 +189,7 @@ func (c *decoratedCollection) Upsert(selector, update interface{}) (info *mgo.Ch
c.logDuration("upsert", duration, err, selector, update) c.logDuration("upsert", duration, err, selector, update)
}() }()
info, err = c.Collection.Upsert(selector, update) info, err = c.collection.Upsert(selector, update)
return err return err
}, acceptable) }, acceptable)
@@ -200,17 +203,17 @@ func (c *decoratedCollection) logDuration(method string, duration time.Duration,
} else if err != nil { } else if err != nil {
if duration > slowThreshold { if duration > slowThreshold {
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s", logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
c.FullName, method, err.Error(), string(content)) c.name, method, err.Error(), string(content))
} else { } else {
logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s) - %s", logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s) - %s",
c.FullName, method, err.Error(), string(content)) c.name, method, err.Error(), string(content))
} }
} else { } else {
if duration > slowThreshold { if duration > slowThreshold {
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s", logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
c.FullName, method, string(content)) c.name, method, string(content))
} else { } else {
logx.WithDuration(duration).Infof("mongo(%s) - %s - ok - %s", c.FullName, method, string(content)) logx.WithDuration(duration).Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content))
} }
} }
} }

View File

@@ -5,10 +5,20 @@ import (
"testing" "testing"
"github.com/globalsign/mgo" "github.com/globalsign/mgo"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/breaker"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stores/mongo/internal"
"github.com/tal-tech/go-zero/core/stringx" "github.com/tal-tech/go-zero/core/stringx"
) )
var errDummy = errors.New("dummy")
func init() {
logx.Disable()
}
func TestKeepPromise_accept(t *testing.T) { func TestKeepPromise_accept(t *testing.T) {
p := new(mockPromise) p := new(mockPromise)
kp := keepablePromise{ kp := keepablePromise{
@@ -56,6 +66,206 @@ func TestKeepPromise_keep(t *testing.T) {
} }
} }
func TestNewCollection(t *testing.T) {
col := newCollection(&mgo.Collection{
Database: nil,
Name: "foo",
FullName: "bar",
})
assert.Equal(t, "bar", col.(*decoratedCollection).name)
}
func TestCollectionFind(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
var query mgo.Query
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().Find(gomock.Any()).Return(&query)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
actual := c.Find(nil)
switch v := actual.(type) {
case promisedQuery:
assert.Equal(t, &query, v.Query)
assert.Equal(t, errDummy, v.promise.keep(errDummy))
default:
t.Fail()
}
c.brk = new(dropBreaker)
actual = c.Find(nil)
assert.Equal(t, rejectedQuery{}, actual)
}
func TestCollectionFindId(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
var query mgo.Query
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().FindId(gomock.Any()).Return(&query)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
actual := c.FindId(nil)
switch v := actual.(type) {
case promisedQuery:
assert.Equal(t, &query, v.Query)
assert.Equal(t, errDummy, v.promise.keep(errDummy))
default:
t.Fail()
}
c.brk = new(dropBreaker)
actual = c.FindId(nil)
assert.Equal(t, rejectedQuery{}, actual)
}
func TestCollectionInsert(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().Insert(nil, nil).Return(errDummy)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
err := c.Insert(nil, nil)
assert.Equal(t, errDummy, err)
c.brk = new(dropBreaker)
err = c.Insert(nil, nil)
assert.Equal(t, errDummy, err)
}
func TestCollectionPipe(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
var pipe mgo.Pipe
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().Pipe(gomock.Any()).Return(&pipe)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
actual := c.Pipe(nil)
switch v := actual.(type) {
case promisedPipe:
assert.Equal(t, &pipe, v.Pipe)
assert.Equal(t, errDummy, v.promise.keep(errDummy))
default:
t.Fail()
}
c.brk = new(dropBreaker)
actual = c.Pipe(nil)
assert.Equal(t, rejectedPipe{}, actual)
}
func TestCollectionRemove(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().Remove(gomock.Any()).Return(errDummy)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
err := c.Remove(nil)
assert.Equal(t, errDummy, err)
c.brk = new(dropBreaker)
err = c.Remove(nil)
assert.Equal(t, errDummy, err)
}
func TestCollectionRemoveAll(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().RemoveAll(gomock.Any()).Return(nil, errDummy)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
_, err := c.RemoveAll(nil)
assert.Equal(t, errDummy, err)
c.brk = new(dropBreaker)
_, err = c.RemoveAll(nil)
assert.Equal(t, errDummy, err)
}
func TestCollectionRemoveId(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().RemoveId(gomock.Any()).Return(errDummy)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
err := c.RemoveId(nil)
assert.Equal(t, errDummy, err)
c.brk = new(dropBreaker)
err = c.RemoveId(nil)
assert.Equal(t, errDummy, err)
}
func TestCollectionUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().Update(gomock.Any(), gomock.Any()).Return(errDummy)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
err := c.Update(nil, nil)
assert.Equal(t, errDummy, err)
c.brk = new(dropBreaker)
err = c.Update(nil, nil)
assert.Equal(t, errDummy, err)
}
func TestCollectionUpdateId(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().UpdateId(gomock.Any(), gomock.Any()).Return(errDummy)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
err := c.UpdateId(nil, nil)
assert.Equal(t, errDummy, err)
c.brk = new(dropBreaker)
err = c.UpdateId(nil, nil)
assert.Equal(t, errDummy, err)
}
func TestCollectionUpsert(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
col := internal.NewMockMgoCollection(ctrl)
col.EXPECT().Upsert(gomock.Any(), gomock.Any()).Return(nil, errDummy)
c := decoratedCollection{
collection: col,
brk: breaker.NewBreaker(),
}
_, err := c.Upsert(nil, nil)
assert.Equal(t, errDummy, err)
c.brk = new(dropBreaker)
_, err = c.Upsert(nil, nil)
assert.Equal(t, errDummy, err)
}
type mockPromise struct { type mockPromise struct {
accepted bool accepted bool
reason string reason string
@@ -68,3 +278,31 @@ func (p *mockPromise) Accept() {
func (p *mockPromise) Reject(reason string) { func (p *mockPromise) Reject(reason string) {
p.reason = reason p.reason = reason
} }
type dropBreaker struct {
}
func (d *dropBreaker) Name() string {
return "dummy"
}
func (d *dropBreaker) Allow() (breaker.Promise, error) {
return nil, errDummy
}
func (d *dropBreaker) Do(req func() error) error {
return nil
}
func (d *dropBreaker) DoWithAcceptable(req func() error, acceptable breaker.Acceptable) error {
return errDummy
}
func (d *dropBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
return nil
}
func (d *dropBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
acceptable breaker.Acceptable) error {
return nil
}

View File

@@ -0,0 +1,17 @@
//go:generate mockgen -package internal -destination collection_mock.go -source collection.go
package internal
import "github.com/globalsign/mgo"
type MgoCollection interface {
Find(query interface{}) *mgo.Query
FindId(id interface{}) *mgo.Query
Insert(docs ...interface{}) error
Pipe(pipeline interface{}) *mgo.Pipe
Remove(selector interface{}) error
RemoveAll(selector interface{}) (*mgo.ChangeInfo, error)
RemoveId(id interface{}) error
Update(selector, update interface{}) error
UpdateId(id, update interface{}) error
Upsert(selector, update interface{}) (*mgo.ChangeInfo, error)
}

View File

@@ -0,0 +1,180 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: collection.go
// Package internal is a generated GoMock package.
package internal
import (
mgo "github.com/globalsign/mgo"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockMgoCollection is a mock of MgoCollection interface
type MockMgoCollection struct {
ctrl *gomock.Controller
recorder *MockMgoCollectionMockRecorder
}
// MockMgoCollectionMockRecorder is the mock recorder for MockMgoCollection
type MockMgoCollectionMockRecorder struct {
mock *MockMgoCollection
}
// NewMockMgoCollection creates a new mock instance
func NewMockMgoCollection(ctrl *gomock.Controller) *MockMgoCollection {
mock := &MockMgoCollection{ctrl: ctrl}
mock.recorder = &MockMgoCollectionMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockMgoCollection) EXPECT() *MockMgoCollectionMockRecorder {
return m.recorder
}
// Find mocks base method
func (m *MockMgoCollection) Find(query interface{}) *mgo.Query {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Find", query)
ret0, _ := ret[0].(*mgo.Query)
return ret0
}
// Find indicates an expected call of Find
func (mr *MockMgoCollectionMockRecorder) Find(query interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Find", reflect.TypeOf((*MockMgoCollection)(nil).Find), query)
}
// FindId mocks base method
func (m *MockMgoCollection) FindId(id interface{}) *mgo.Query {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindId", id)
ret0, _ := ret[0].(*mgo.Query)
return ret0
}
// FindId indicates an expected call of FindId
func (mr *MockMgoCollectionMockRecorder) FindId(id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindId", reflect.TypeOf((*MockMgoCollection)(nil).FindId), id)
}
// Insert mocks base method
func (m *MockMgoCollection) Insert(docs ...interface{}) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range docs {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Insert", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert
func (mr *MockMgoCollectionMockRecorder) Insert(docs ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockMgoCollection)(nil).Insert), docs...)
}
// Pipe mocks base method
func (m *MockMgoCollection) Pipe(pipeline interface{}) *mgo.Pipe {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Pipe", pipeline)
ret0, _ := ret[0].(*mgo.Pipe)
return ret0
}
// Pipe indicates an expected call of Pipe
func (mr *MockMgoCollectionMockRecorder) Pipe(pipeline interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pipe", reflect.TypeOf((*MockMgoCollection)(nil).Pipe), pipeline)
}
// Remove mocks base method
func (m *MockMgoCollection) Remove(selector interface{}) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Remove", selector)
ret0, _ := ret[0].(error)
return ret0
}
// Remove indicates an expected call of Remove
func (mr *MockMgoCollectionMockRecorder) Remove(selector interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockMgoCollection)(nil).Remove), selector)
}
// RemoveAll mocks base method
func (m *MockMgoCollection) RemoveAll(selector interface{}) (*mgo.ChangeInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RemoveAll", selector)
ret0, _ := ret[0].(*mgo.ChangeInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RemoveAll indicates an expected call of RemoveAll
func (mr *MockMgoCollectionMockRecorder) RemoveAll(selector interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveAll", reflect.TypeOf((*MockMgoCollection)(nil).RemoveAll), selector)
}
// RemoveId mocks base method
func (m *MockMgoCollection) RemoveId(id interface{}) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RemoveId", id)
ret0, _ := ret[0].(error)
return ret0
}
// RemoveId indicates an expected call of RemoveId
func (mr *MockMgoCollectionMockRecorder) RemoveId(id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveId", reflect.TypeOf((*MockMgoCollection)(nil).RemoveId), id)
}
// Update mocks base method
func (m *MockMgoCollection) Update(selector, update interface{}) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", selector, update)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update
func (mr *MockMgoCollectionMockRecorder) Update(selector, update interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockMgoCollection)(nil).Update), selector, update)
}
// UpdateId mocks base method
func (m *MockMgoCollection) UpdateId(id, update interface{}) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateId", id, update)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateId indicates an expected call of UpdateId
func (mr *MockMgoCollectionMockRecorder) UpdateId(id, update interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateId", reflect.TypeOf((*MockMgoCollection)(nil).UpdateId), id, update)
}
// Upsert mocks base method
func (m *MockMgoCollection) Upsert(selector, update interface{}) (*mgo.ChangeInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Upsert", selector, update)
ret0, _ := ret[0].(*mgo.ChangeInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Upsert indicates an expected call of Upsert
func (mr *MockMgoCollectionMockRecorder) Upsert(selector, update interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockMgoCollection)(nil).Upsert), selector, update)
}

View File

@@ -11,7 +11,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis"
"github.com/globalsign/mgo" "github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson" "github.com/globalsign/mgo/bson"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -19,6 +18,7 @@ import (
"github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/mongo" "github.com/tal-tech/go-zero/core/stores/mongo"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
) )
func init() { func init() {
@@ -27,12 +27,10 @@ func init() {
func TestStat(t *testing.T) { func TestStat(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound) cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach) c := newCollection(dummyConn{}, cach)
@@ -73,12 +71,10 @@ func TestStatCacheFails(t *testing.T) {
func TestStatDbFails(t *testing.T) { func TestStatDbFails(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound) cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach) c := newCollection(dummyConn{}, cach)
@@ -97,12 +93,10 @@ func TestStatDbFails(t *testing.T) {
func TestStatFromMemory(t *testing.T) { func TestStatFromMemory(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound) cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach) c := newCollection(dummyConn{}, cach)

View File

@@ -5,8 +5,8 @@ import (
"github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/core/stores/sqlx"
) )
const postgreDriverName = "postgres" const postgresDriverName = "postgres"
func NewPostgre(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn { func NewPostgres(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
return sqlx.NewSqlConn(postgreDriverName, datasource, opts...) return sqlx.NewSqlConn(postgresDriverName, datasource, opts...)
} }

View File

@@ -0,0 +1,110 @@
package redis
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stringx"
)
func TestRedisConf(t *testing.T) {
tests := []struct {
name string
RedisConf
ok bool
}{
{
name: "missing host",
RedisConf: RedisConf{
Host: "",
Type: NodeType,
Pass: "",
},
ok: false,
},
{
name: "missing type",
RedisConf: RedisConf{
Host: "localhost:6379",
Type: "",
Pass: "",
},
ok: false,
},
{
name: "ok",
RedisConf: RedisConf{
Host: "localhost:6379",
Type: NodeType,
Pass: "",
},
ok: true,
},
}
for _, test := range tests {
t.Run(stringx.RandId(), func(t *testing.T) {
if test.ok {
assert.Nil(t, test.RedisConf.Validate())
assert.NotNil(t, test.RedisConf.NewRedis())
} else {
assert.NotNil(t, test.RedisConf.Validate())
}
})
}
}
func TestRedisKeyConf(t *testing.T) {
tests := []struct {
name string
RedisKeyConf
ok bool
}{
{
name: "missing host",
RedisKeyConf: RedisKeyConf{
RedisConf: RedisConf{
Host: "",
Type: NodeType,
Pass: "",
},
Key: "foo",
},
ok: false,
},
{
name: "missing key",
RedisKeyConf: RedisKeyConf{
RedisConf: RedisConf{
Host: "localhost:6379",
Type: NodeType,
Pass: "",
},
Key: "",
},
ok: false,
},
{
name: "ok",
RedisKeyConf: RedisKeyConf{
RedisConf: RedisConf{
Host: "localhost:6379",
Type: NodeType,
Pass: "",
},
Key: "foo",
},
ok: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.ok {
assert.Nil(t, test.RedisKeyConf.Validate())
} else {
assert.NotNil(t, test.RedisKeyConf.Validate())
}
})
}
}

View File

@@ -42,6 +42,12 @@ type (
red.Cmdable red.Cmdable
} }
// GeoLocation is used with GeoAdd to add geospatial location.
GeoLocation = red.GeoLocation
// GeoRadiusQuery is used with GeoRadius to query geospatial index.
GeoRadiusQuery = red.GeoRadiusQuery
GeoPos = red.GeoPos
Pipeliner = red.Pipeliner Pipeliner = red.Pipeliner
// Z represents sorted set member. // Z represents sorted set member.
@@ -173,6 +179,107 @@ func (s *Redis) Expireat(key string, expireTime int64) error {
}, acceptable) }, acceptable)
} }
func (s *Redis) GeoAdd(key string, geoLocation ...*GeoLocation) (val int64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoAdd(key, geoLocation...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoDist(key string, member1, member2, unit string) (val float64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoDist(key, member1, member2, unit).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoHash(key string, members ...string) (val []string, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoHash(key, members...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) (val []GeoLocation, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoRadius(key, longitude, latitude, query).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoRadiusByMember(key, member string, query *GeoRadiusQuery) (val []GeoLocation, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoRadiusByMember(key, member, query).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) GeoPos(key string, members ...string) (val []*GeoPos, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
if v, err := conn.GeoPos(key, members...).Result(); err != nil {
return err
} else {
val = v
return nil
}
}, acceptable)
return
}
func (s *Redis) Get(key string) (val string, err error) { func (s *Redis) Get(key string) (val string, err error) {
err = s.brk.DoWithAcceptable(func() error { err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s) conn, err := getRedis(s)
@@ -1273,6 +1380,20 @@ func (s *Redis) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64
return return
} }
func (s *Redis) Zrevrank(key string, field string) (val int64, err error) {
err = s.brk.DoWithAcceptable(func() error {
conn, err := getRedis(s)
if err != nil {
return err
}
val, err = conn.ZRevRank(key, field).Result()
return err
}, acceptable)
return
}
func (s *Redis) String() string { func (s *Redis) String() string {
return s.Addr return s.Addr
} }

View File

@@ -6,12 +6,15 @@ import (
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis" "github.com/alicebob/miniredis/v2"
red "github.com/go-redis/redis"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestRedis_Exists(t *testing.T) { func TestRedis_Exists(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := NewRedis(client.Addr, "").Exists("a")
assert.NotNil(t, err)
ok, err := client.Exists("a") ok, err := client.Exists("a")
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
@@ -24,7 +27,9 @@ func TestRedis_Exists(t *testing.T) {
func TestRedis_Eval(t *testing.T) { func TestRedis_Eval(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"}) _, err := NewRedis(client.Addr, "").Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
assert.NotNil(t, err)
_, err = client.Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
assert.Equal(t, Nil, err) assert.Equal(t, Nil, err)
err = client.Set("key1", "value1") err = client.Set("key1", "value1")
assert.Nil(t, err) assert.Nil(t, err)
@@ -40,6 +45,8 @@ func TestRedis_Hgetall(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
_, err := NewRedis(client.Addr, "").Hgetall("a")
assert.NotNil(t, err)
vals, err := client.Hgetall("a") vals, err := client.Hgetall("a")
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, map[string]string{ assert.EqualValues(t, map[string]string{
@@ -51,8 +58,11 @@ func TestRedis_Hgetall(t *testing.T) {
func TestRedis_Hvals(t *testing.T) { func TestRedis_Hvals(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.NotNil(t, NewRedis(client.Addr, "").Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
_, err := NewRedis(client.Addr, "").Hvals("a")
assert.NotNil(t, err)
vals, err := client.Hvals("a") vals, err := client.Hvals("a")
assert.Nil(t, err) assert.Nil(t, err)
assert.ElementsMatch(t, []string{"aaa", "bbb"}, vals) assert.ElementsMatch(t, []string{"aaa", "bbb"}, vals)
@@ -63,6 +73,8 @@ func TestRedis_Hsetnx(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
_, err := NewRedis(client.Addr, "").Hsetnx("a", "bb", "ccc")
assert.NotNil(t, err)
ok, err := client.Hsetnx("a", "bb", "ccc") ok, err := client.Hsetnx("a", "bb", "ccc")
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
@@ -79,6 +91,8 @@ func TestRedis_HdelHlen(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
_, err := NewRedis(client.Addr, "").Hlen("a")
assert.NotNil(t, err)
num, err := client.Hlen("a") num, err := client.Hlen("a")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, num) assert.Equal(t, 2, num)
@@ -93,6 +107,8 @@ func TestRedis_HdelHlen(t *testing.T) {
func TestRedis_HIncrBy(t *testing.T) { func TestRedis_HIncrBy(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := NewRedis(client.Addr, "").Hincrby("key", "field", 2)
assert.NotNil(t, err)
val, err := client.Hincrby("key", "field", 2) val, err := client.Hincrby("key", "field", 2)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, val) assert.Equal(t, 2, val)
@@ -106,6 +122,8 @@ func TestRedis_Hkeys(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
_, err := NewRedis(client.Addr, "").Hkeys("a")
assert.NotNil(t, err)
vals, err := client.Hkeys("a") vals, err := client.Hkeys("a")
assert.Nil(t, err) assert.Nil(t, err)
assert.ElementsMatch(t, []string{"aa", "bb"}, vals) assert.ElementsMatch(t, []string{"aa", "bb"}, vals)
@@ -116,6 +134,8 @@ func TestRedis_Hmget(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.Nil(t, client.Hset("a", "aa", "aaa")) assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb")) assert.Nil(t, client.Hset("a", "bb", "bbb"))
_, err := NewRedis(client.Addr, "").Hmget("a", "aa", "bb")
assert.NotNil(t, err)
vals, err := client.Hmget("a", "aa", "bb") vals, err := client.Hmget("a", "aa", "bb")
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"aaa", "bbb"}, vals) assert.EqualValues(t, []string{"aaa", "bbb"}, vals)
@@ -127,6 +147,7 @@ func TestRedis_Hmget(t *testing.T) {
func TestRedis_Hmset(t *testing.T) { func TestRedis_Hmset(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.NotNil(t, NewRedis(client.Addr, "").Hmset("a", nil))
assert.Nil(t, client.Hmset("a", map[string]string{ assert.Nil(t, client.Hmset("a", map[string]string{
"aa": "aaa", "aa": "aaa",
"bb": "bbb", "bb": "bbb",
@@ -139,6 +160,8 @@ func TestRedis_Hmset(t *testing.T) {
func TestRedis_Incr(t *testing.T) { func TestRedis_Incr(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := NewRedis(client.Addr, "").Incr("a")
assert.NotNil(t, err)
val, err := client.Incr("a") val, err := client.Incr("a")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1), val) assert.Equal(t, int64(1), val)
@@ -150,6 +173,8 @@ func TestRedis_Incr(t *testing.T) {
func TestRedis_IncrBy(t *testing.T) { func TestRedis_IncrBy(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := NewRedis(client.Addr, "").Incrby("a", 2)
assert.NotNil(t, err)
val, err := client.Incrby("a", 2) val, err := client.Incrby("a", 2)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(2), val) assert.Equal(t, int64(2), val)
@@ -165,26 +190,49 @@ func TestRedis_Keys(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
err = client.Set("key2", "value2") err = client.Set("key2", "value2")
assert.Nil(t, err) assert.Nil(t, err)
_, err = NewRedis(client.Addr, "").Keys("*")
assert.NotNil(t, err)
keys, err := client.Keys("*") keys, err := client.Keys("*")
assert.Nil(t, err) assert.Nil(t, err)
assert.ElementsMatch(t, []string{"key1", "key2"}, keys) assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
}) })
} }
func TestRedis_HyperLogLog(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
r := NewRedis(client.Addr, "")
_, err := r.Pfadd("key1")
assert.NotNil(t, err)
_, err = r.Pfcount("*")
assert.NotNil(t, err)
err = r.Pfmerge("*")
assert.NotNil(t, err)
})
}
func TestRedis_List(t *testing.T) { func TestRedis_List(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := NewRedis(client.Addr, "").Lpush("key", "value1", "value2")
assert.NotNil(t, err)
val, err := client.Lpush("key", "value1", "value2") val, err := client.Lpush("key", "value1", "value2")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, val) assert.Equal(t, 2, val)
_, err = NewRedis(client.Addr, "").Rpush("key", "value3", "value4")
assert.NotNil(t, err)
val, err = client.Rpush("key", "value3", "value4") val, err = client.Rpush("key", "value3", "value4")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, val) assert.Equal(t, 4, val)
_, err = NewRedis(client.Addr, "").Llen("key")
assert.NotNil(t, err)
val, err = client.Llen("key") val, err = client.Llen("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, val) assert.Equal(t, 4, val)
vals, err := client.Lrange("key", 0, 10) vals, err := client.Lrange("key", 0, 10)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"value2", "value1", "value3", "value4"}, vals) assert.EqualValues(t, []string{"value2", "value1", "value3", "value4"}, vals)
_, err = NewRedis(client.Addr, "").Lpop("key")
assert.NotNil(t, err)
v, err := client.Lpop("key") v, err := client.Lpop("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "value2", v) assert.Equal(t, "value2", v)
@@ -194,9 +242,13 @@ func TestRedis_List(t *testing.T) {
val, err = client.Rpush("key", "value3", "value3") val, err = client.Rpush("key", "value3", "value3")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 7, val) assert.Equal(t, 7, val)
_, err = NewRedis(client.Addr, "").Lrem("key", 2, "value1")
assert.NotNil(t, err)
n, err := client.Lrem("key", 2, "value1") n, err := client.Lrem("key", 2, "value1")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, n) assert.Equal(t, 2, n)
_, err = NewRedis(client.Addr, "").Lrange("key", 0, 10)
assert.NotNil(t, err)
vals, err = client.Lrange("key", 0, 10) vals, err = client.Lrange("key", 0, 10)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"value2", "value3", "value4", "value3", "value3"}, vals) assert.EqualValues(t, []string{"value2", "value3", "value4", "value3", "value3"}, vals)
@@ -215,6 +267,8 @@ func TestRedis_Mget(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
err = client.Set("key2", "value2") err = client.Set("key2", "value2")
assert.Nil(t, err) assert.Nil(t, err)
_, err = NewRedis(client.Addr, "").Mget("key1", "key0", "key2", "key3")
assert.NotNil(t, err)
vals, err := client.Mget("key1", "key0", "key2", "key3") vals, err := client.Mget("key1", "key0", "key2", "key3")
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"value1", "", "value2", ""}, vals) assert.EqualValues(t, []string{"value1", "", "value2", ""}, vals)
@@ -223,7 +277,9 @@ func TestRedis_Mget(t *testing.T) {
func TestRedis_SetBit(t *testing.T) { func TestRedis_SetBit(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
err := client.SetBit("key", 1, 1) err := NewRedis(client.Addr, "").SetBit("key", 1, 1)
assert.NotNil(t, err)
err = client.SetBit("key", 1, 1)
assert.Nil(t, err) assert.Nil(t, err)
}) })
} }
@@ -232,6 +288,8 @@ func TestRedis_GetBit(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
err := client.SetBit("key", 2, 1) err := client.SetBit("key", 2, 1)
assert.Nil(t, err) assert.Nil(t, err)
_, err = NewRedis(client.Addr, "").GetBit("key", 2)
assert.NotNil(t, err)
val, err := client.GetBit("key", 2) val, err := client.GetBit("key", 2)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, val) assert.Equal(t, 1, val)
@@ -240,6 +298,8 @@ func TestRedis_GetBit(t *testing.T) {
func TestRedis_Persist(t *testing.T) { func TestRedis_Persist(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := NewRedis(client.Addr, "").Persist("key")
assert.NotNil(t, err)
ok, err := client.Persist("key") ok, err := client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
@@ -248,11 +308,15 @@ func TestRedis_Persist(t *testing.T) {
ok, err = client.Persist("key") ok, err = client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
err = NewRedis(client.Addr, "").Expire("key", 5)
assert.NotNil(t, err)
err = client.Expire("key", 5) err = client.Expire("key", 5)
assert.Nil(t, err) assert.Nil(t, err)
ok, err = client.Persist("key") ok, err = client.Persist("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
err = NewRedis(client.Addr, "").Expireat("key", time.Now().Unix()+5)
assert.NotNil(t, err)
err = client.Expireat("key", time.Now().Unix()+5) err = client.Expireat("key", time.Now().Unix()+5)
assert.Nil(t, err) assert.Nil(t, err)
ok, err = client.Persist("key") ok, err = client.Persist("key")
@@ -274,6 +338,8 @@ func TestRedis_Scan(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
err = client.Set("key2", "value2") err = client.Set("key2", "value2")
assert.Nil(t, err) assert.Nil(t, err)
_, _, err = NewRedis(client.Addr, "").Scan(0, "*", 100)
assert.NotNil(t, err)
keys, _, err := client.Scan(0, "*", 100) keys, _, err := client.Scan(0, "*", 100)
assert.Nil(t, err) assert.Nil(t, err)
assert.ElementsMatch(t, []string{"key1", "key2"}, keys) assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
@@ -294,6 +360,8 @@ func TestRedis_Sscan(t *testing.T) {
var cursor uint64 = 0 var cursor uint64 = 0
sum := 0 sum := 0
for { for {
_, _, err := NewRedis(client.Addr, "").Sscan(key, cursor, "", 100)
assert.NotNil(t, err)
keys, next, err := client.Sscan(key, cursor, "", 100) keys, next, err := client.Sscan(key, cursor, "", 100)
assert.Nil(t, err) assert.Nil(t, err)
sum += len(keys) sum += len(keys)
@@ -304,6 +372,8 @@ func TestRedis_Sscan(t *testing.T) {
} }
assert.Equal(t, sum, 1550) assert.Equal(t, sum, 1550)
_, err = NewRedis(client.Addr, "").Del(key)
assert.NotNil(t, err)
_, err = client.Del(key) _, err = client.Del(key)
assert.Nil(t, err) assert.Nil(t, err)
}) })
@@ -311,46 +381,72 @@ func TestRedis_Sscan(t *testing.T) {
func TestRedis_Set(t *testing.T) { func TestRedis_Set(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
_, err := NewRedis(client.Addr, "").Sadd("key", 1, 2, 3, 4)
assert.NotNil(t, err)
num, err := client.Sadd("key", 1, 2, 3, 4) num, err := client.Sadd("key", 1, 2, 3, 4)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, num) assert.Equal(t, 4, num)
_, err = NewRedis(client.Addr, "").Scard("key")
assert.NotNil(t, err)
val, err := client.Scard("key") val, err := client.Scard("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(4), val) assert.Equal(t, int64(4), val)
_, err = NewRedis(client.Addr, "").Sismember("key", 2)
assert.NotNil(t, err)
ok, err := client.Sismember("key", 2) ok, err := client.Sismember("key", 2)
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
_, err = NewRedis(client.Addr, "").Srem("key", 3, 4)
assert.NotNil(t, err)
num, err = client.Srem("key", 3, 4) num, err = client.Srem("key", 3, 4)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, num) assert.Equal(t, 2, num)
_, err = NewRedis(client.Addr, "").Smembers("key")
assert.NotNil(t, err)
vals, err := client.Smembers("key") vals, err := client.Smembers("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.ElementsMatch(t, []string{"1", "2"}, vals) assert.ElementsMatch(t, []string{"1", "2"}, vals)
_, err = NewRedis(client.Addr, "").Srandmember("key", 1)
assert.NotNil(t, err)
members, err := client.Srandmember("key", 1) members, err := client.Srandmember("key", 1)
assert.Nil(t, err) assert.Nil(t, err)
assert.Len(t, members, 1) assert.Len(t, members, 1)
assert.Contains(t, []string{"1", "2"}, members[0]) assert.Contains(t, []string{"1", "2"}, members[0])
_, err = NewRedis(client.Addr, "").Spop("key")
assert.NotNil(t, err)
member, err := client.Spop("key") member, err := client.Spop("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.Contains(t, []string{"1", "2"}, member) assert.Contains(t, []string{"1", "2"}, member)
_, err = NewRedis(client.Addr, "").Smembers("key")
assert.NotNil(t, err)
vals, err = client.Smembers("key") vals, err = client.Smembers("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.NotContains(t, vals, member) assert.NotContains(t, vals, member)
_, err = NewRedis(client.Addr, "").Sadd("key1", 1, 2, 3, 4)
assert.NotNil(t, err)
num, err = client.Sadd("key1", 1, 2, 3, 4) num, err = client.Sadd("key1", 1, 2, 3, 4)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, num) assert.Equal(t, 4, num)
num, err = client.Sadd("key2", 2, 3, 4, 5) num, err = client.Sadd("key2", 2, 3, 4, 5)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 4, num) assert.Equal(t, 4, num)
_, err = NewRedis(client.Addr, "").Sunion("key1", "key2")
assert.NotNil(t, err)
vals, err = client.Sunion("key1", "key2") vals, err = client.Sunion("key1", "key2")
assert.Nil(t, err) assert.Nil(t, err)
assert.ElementsMatch(t, []string{"1", "2", "3", "4", "5"}, vals) assert.ElementsMatch(t, []string{"1", "2", "3", "4", "5"}, vals)
_, err = NewRedis(client.Addr, "").Sunionstore("key3", "key1", "key2")
assert.NotNil(t, err)
num, err = client.Sunionstore("key3", "key1", "key2") num, err = client.Sunionstore("key3", "key1", "key2")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 5, num) assert.Equal(t, 5, num)
_, err = NewRedis(client.Addr, "").Sdiff("key1", "key2")
assert.NotNil(t, err)
vals, err = client.Sdiff("key1", "key2") vals, err = client.Sdiff("key1", "key2")
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"1"}, vals) assert.EqualValues(t, []string{"1"}, vals)
_, err = NewRedis(client.Addr, "").Sdiffstore("key4", "key1", "key2")
assert.NotNil(t, err)
num, err = client.Sdiffstore("key4", "key1", "key2") num, err = client.Sdiffstore("key4", "key1", "key2")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 1, num) assert.Equal(t, 1, num)
@@ -359,8 +455,12 @@ func TestRedis_Set(t *testing.T) {
func TestRedis_SetGetDel(t *testing.T) { func TestRedis_SetGetDel(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
err := client.Set("hello", "world") err := NewRedis(client.Addr, "").Set("hello", "world")
assert.NotNil(t, err)
err = client.Set("hello", "world")
assert.Nil(t, err) assert.Nil(t, err)
_, err = NewRedis(client.Addr, "").Get("hello")
assert.NotNil(t, err)
val, err := client.Get("hello") val, err := client.Get("hello")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "world", val) assert.Equal(t, "world", val)
@@ -372,8 +472,12 @@ func TestRedis_SetGetDel(t *testing.T) {
func TestRedis_SetExNx(t *testing.T) { func TestRedis_SetExNx(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
err := client.Setex("hello", "world", 5) err := NewRedis(client.Addr, "").Setex("hello", "world", 5)
assert.NotNil(t, err)
err = client.Setex("hello", "world", 5)
assert.Nil(t, err) assert.Nil(t, err)
_, err = NewRedis(client.Addr, "").Setnx("hello", "newworld")
assert.NotNil(t, err)
ok, err := client.Setnx("hello", "newworld") ok, err := client.Setnx("hello", "newworld")
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
@@ -389,6 +493,8 @@ func TestRedis_SetExNx(t *testing.T) {
ttl, err := client.Ttl("hello") ttl, err := client.Ttl("hello")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ttl > 0) assert.True(t, ttl > 0)
_, err = NewRedis(client.Addr, "").SetnxEx("newhello", "newworld", 5)
assert.NotNil(t, err)
ok, err = client.SetnxEx("newhello", "newworld", 5) ok, err = client.SetnxEx("newhello", "newworld", 5)
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
@@ -408,12 +514,18 @@ func TestRedis_SetGetDelHashField(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
err := client.Hset("key", "field", "value") err := client.Hset("key", "field", "value")
assert.Nil(t, err) assert.Nil(t, err)
_, err = NewRedis(client.Addr, "").Hget("key", "field")
assert.NotNil(t, err)
val, err := client.Hget("key", "field") val, err := client.Hget("key", "field")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "value", val) assert.Equal(t, "value", val)
_, err = NewRedis(client.Addr, "").Hexists("key", "field")
assert.NotNil(t, err)
ok, err := client.Hexists("key", "field") ok, err := client.Hexists("key", "field")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
_, err = NewRedis(client.Addr, "").Hdel("key", "field")
assert.NotNil(t, err)
ret, err := client.Hdel("key", "field") ret, err := client.Hdel("key", "field")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ret) assert.True(t, ret)
@@ -434,23 +546,53 @@ func TestRedis_SortedSet(t *testing.T) {
val, err := client.Zscore("key", "value1") val, err := client.Zscore("key", "value1")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(2), val) assert.Equal(t, int64(2), val)
_, err = NewRedis(client.Addr, "").Zincrby("key", 3, "value1")
assert.NotNil(t, err)
val, err = client.Zincrby("key", 3, "value1") val, err = client.Zincrby("key", 3, "value1")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(5), val) assert.Equal(t, int64(5), val)
_, err = NewRedis(client.Addr, "").Zscore("key", "value1")
assert.NotNil(t, err)
val, err = client.Zscore("key", "value1") val, err = client.Zscore("key", "value1")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(5), val) assert.Equal(t, int64(5), val)
ok, err = client.Zadd("key", 6, "value2") _, err = NewRedis(client.Addr, "").Zadds("key")
assert.NotNil(t, err)
val, err = client.Zadds("key", Pair{
Key: "value2",
Score: 6,
}, Pair{
Key: "value3",
Score: 7,
})
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.Equal(t, int64(2), val)
ok, err = client.Zadd("key", 7, "value3") _, err = NewRedis(client.Addr, "").ZRevRangeWithScores("key", 1, 3)
assert.NotNil(t, err)
pairs, err := client.ZRevRangeWithScores("key", 1, 3)
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.EqualValues(t, []Pair{
{
Key: "value2",
Score: 6,
},
{
Key: "value1",
Score: 5,
},
}, pairs)
rank, err := client.Zrank("key", "value2") rank, err := client.Zrank("key", "value2")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, int64(1), rank) assert.Equal(t, int64(1), rank)
rank, err = client.Zrevrank("key", "value1")
assert.Nil(t, err)
assert.Equal(t, int64(2), rank)
_, err = NewRedis(client.Addr, "").Zrank("key", "value4")
assert.NotNil(t, err)
_, err = client.Zrank("key", "value4") _, err = client.Zrank("key", "value4")
assert.Equal(t, Nil, err) assert.Equal(t, Nil, err)
_, err = NewRedis(client.Addr, "").Zrem("key", "value2", "value3")
assert.NotNil(t, err)
num, err := client.Zrem("key", "value2", "value3") num, err := client.Zrem("key", "value2", "value3")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, num) assert.Equal(t, 2, num)
@@ -463,31 +605,47 @@ func TestRedis_SortedSet(t *testing.T) {
ok, err = client.Zadd("key", 8, "value4") ok, err = client.Zadd("key", 8, "value4")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
_, err = NewRedis(client.Addr, "").Zremrangebyscore("key", 6, 7)
assert.NotNil(t, err)
num, err = client.Zremrangebyscore("key", 6, 7) num, err = client.Zremrangebyscore("key", 6, 7)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, num) assert.Equal(t, 2, num)
ok, err = client.Zadd("key", 6, "value2") ok, err = client.Zadd("key", 6, "value2")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
_, err = NewRedis(client.Addr, "").Zadd("key", 7, "value3")
assert.NotNil(t, err)
ok, err = client.Zadd("key", 7, "value3") ok, err = client.Zadd("key", 7, "value3")
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, ok) assert.True(t, ok)
_, err = NewRedis(client.Addr, "").Zcount("key", 6, 7)
assert.NotNil(t, err)
num, err = client.Zcount("key", 6, 7) num, err = client.Zcount("key", 6, 7)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, num) assert.Equal(t, 2, num)
_, err = NewRedis(client.Addr, "").Zremrangebyrank("key", 1, 2)
assert.NotNil(t, err)
num, err = client.Zremrangebyrank("key", 1, 2) num, err = client.Zremrangebyrank("key", 1, 2)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, num) assert.Equal(t, 2, num)
_, err = NewRedis(client.Addr, "").Zcard("key")
assert.NotNil(t, err)
card, err := client.Zcard("key") card, err := client.Zcard("key")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, card) assert.Equal(t, 2, card)
_, err = NewRedis(client.Addr, "").Zrange("key", 0, -1)
assert.NotNil(t, err)
vals, err := client.Zrange("key", 0, -1) vals, err := client.Zrange("key", 0, -1)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"value1", "value4"}, vals) assert.EqualValues(t, []string{"value1", "value4"}, vals)
_, err = NewRedis(client.Addr, "").Zrevrange("key", 0, -1)
assert.NotNil(t, err)
vals, err = client.Zrevrange("key", 0, -1) vals, err = client.Zrevrange("key", 0, -1)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []string{"value4", "value1"}, vals) assert.EqualValues(t, []string{"value4", "value1"}, vals)
pairs, err := client.ZrangeWithScores("key", 0, -1) _, err = NewRedis(client.Addr, "").ZrangeWithScores("key", 0, -1)
assert.NotNil(t, err)
pairs, err = client.ZrangeWithScores("key", 0, -1)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []Pair{ assert.EqualValues(t, []Pair{
{ {
@@ -499,6 +657,8 @@ func TestRedis_SortedSet(t *testing.T) {
Score: 8, Score: 8,
}, },
}, pairs) }, pairs)
_, err = NewRedis(client.Addr, "").ZrangebyscoreWithScores("key", 5, 8)
assert.NotNil(t, err)
pairs, err = client.ZrangebyscoreWithScores("key", 5, 8) pairs, err = client.ZrangebyscoreWithScores("key", 5, 8)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []Pair{ assert.EqualValues(t, []Pair{
@@ -511,6 +671,9 @@ func TestRedis_SortedSet(t *testing.T) {
Score: 8, Score: 8,
}, },
}, pairs) }, pairs)
_, err = NewRedis(client.Addr, "").ZrangebyscoreWithScoresAndLimit(
"key", 5, 8, 1, 1)
assert.NotNil(t, err)
pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1) pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []Pair{ assert.EqualValues(t, []Pair{
@@ -519,6 +682,11 @@ func TestRedis_SortedSet(t *testing.T) {
Score: 8, Score: 8,
}, },
}, pairs) }, pairs)
pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
assert.Nil(t, err)
assert.Equal(t, 0, len(pairs))
_, err = NewRedis(client.Addr, "").ZrevrangebyscoreWithScores("key", 5, 8)
assert.NotNil(t, err)
pairs, err = client.ZrevrangebyscoreWithScores("key", 5, 8) pairs, err = client.ZrevrangebyscoreWithScores("key", 5, 8)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []Pair{ assert.EqualValues(t, []Pair{
@@ -531,6 +699,9 @@ func TestRedis_SortedSet(t *testing.T) {
Score: 5, Score: 5,
}, },
}, pairs) }, pairs)
_, err = NewRedis(client.Addr, "").ZrevrangebyscoreWithScoresAndLimit(
"key", 5, 8, 1, 1)
assert.NotNil(t, err)
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1) pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
assert.Nil(t, err) assert.Nil(t, err)
assert.EqualValues(t, []Pair{ assert.EqualValues(t, []Pair{
@@ -539,11 +710,19 @@ func TestRedis_SortedSet(t *testing.T) {
Score: 5, Score: 5,
}, },
}, pairs) }, pairs)
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
assert.Nil(t, err)
assert.Equal(t, 0, len(pairs))
_, err = NewRedis(client.Addr, "").Zrevrank("key", "value")
assert.NotNil(t, err)
}) })
} }
func TestRedis_Pipelined(t *testing.T) { func TestRedis_Pipelined(t *testing.T) {
runOnRedis(t, func(client *Redis) { runOnRedis(t, func(client *Redis) {
assert.NotNil(t, NewRedis(client.Addr, "").Pipelined(func(pipeliner Pipeliner) error {
return nil
}))
err := client.Pipelined( err := client.Pipelined(
func(pipe Pipeliner) error { func(pipe Pipeliner) error {
pipe.Incr("pipelined_counter") pipe.Incr("pipelined_counter")
@@ -553,6 +732,8 @@ func TestRedis_Pipelined(t *testing.T) {
}, },
) )
assert.Nil(t, err) assert.Nil(t, err)
_, err = NewRedis(client.Addr, "").Ttl("pipelined_counter")
assert.NotNil(t, err)
ttl, err := client.Ttl("pipelined_counter") ttl, err := client.Ttl("pipelined_counter")
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3600, ttl) assert.Equal(t, 3600, ttl)
@@ -565,6 +746,108 @@ func TestRedis_Pipelined(t *testing.T) {
}) })
} }
func TestRedisString(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
_, err := getRedis(NewRedis(client.Addr, ClusterType))
assert.Nil(t, err)
assert.Equal(t, client.Addr, client.String())
assert.NotNil(t, NewRedis(client.Addr, "").Ping())
})
}
func TestRedisScriptLoad(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
_, err := NewRedis(client.Addr, "").scriptLoad("foo")
assert.NotNil(t, err)
_, err = client.scriptLoad("foo")
assert.NotNil(t, err)
})
}
func TestRedisToPairs(t *testing.T) {
pairs := toPairs([]red.Z{
{
Member: 1,
Score: 1,
},
{
Member: 2,
Score: 2,
},
})
assert.EqualValues(t, []Pair{
{
Key: "1",
Score: 1,
},
{
Key: "2",
Score: 2,
},
}, pairs)
}
func TestRedisToStrings(t *testing.T) {
vals := toStrings([]interface{}{1, 2})
assert.EqualValues(t, []string{"1", "2"}, vals)
}
func TestRedisBlpop(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
var node mockedNode
_, err := client.Blpop(nil, "foo")
assert.NotNil(t, err)
_, err = client.Blpop(node, "foo")
assert.NotNil(t, err)
})
}
func TestRedisBlpopEx(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
var node mockedNode
_, _, err := client.BlpopEx(nil, "foo")
assert.NotNil(t, err)
_, _, err = client.BlpopEx(node, "foo")
assert.NotNil(t, err)
})
}
func TestRedisGeo(t *testing.T) {
runOnRedis(t, func(client *Redis) {
client.Ping()
var geoLocation = []*GeoLocation{{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, {Longitude: 15.087269, Latitude: 37.502669, Name: "Catania"}}
v, err := client.GeoAdd("sicily", geoLocation...)
assert.Nil(t, err)
assert.Equal(t, int64(2), v)
v2, err := client.GeoDist("sicily", "Palermo", "Catania", "m")
assert.Nil(t, err)
assert.Equal(t, 166274, int(v2))
// GeoHash not support
v3, err := client.GeoPos("sicily", "Palermo", "Catania")
assert.Nil(t, err)
assert.Equal(t, int64(v3[0].Longitude), int64(13))
assert.Equal(t, int64(v3[0].Latitude), int64(38))
assert.Equal(t, int64(v3[1].Longitude), int64(15))
assert.Equal(t, int64(v3[1].Latitude), int64(37))
v4, err := client.GeoRadius("sicily", 15, 37, &red.GeoRadiusQuery{WithDist: true, Unit: "km", Radius: 200})
assert.Nil(t, err)
assert.Equal(t, int64(v4[0].Dist), int64(190))
assert.Equal(t, int64(v4[1].Dist), int64(56))
var geoLocation2 = []*GeoLocation{{Longitude: 13.583333, Latitude: 37.316667, Name: "Agrigento"}}
v5, err := client.GeoAdd("sicily", geoLocation2...)
assert.Nil(t, err)
assert.Equal(t, int64(1), v5)
v6, err := client.GeoRadiusByMember("sicily", "Agrigento", &red.GeoRadiusQuery{Unit: "km", Radius: 100})
assert.Nil(t, err)
assert.Equal(t, v6[0].Name, "Agrigento")
assert.Equal(t, v6[1].Name, "Palermo")
})
}
func runOnRedis(t *testing.T, fn func(client *Redis)) { func runOnRedis(t *testing.T, fn func(client *Redis)) {
s, err := miniredis.Run() s, err := miniredis.Run()
assert.Nil(t, err) assert.Nil(t, err)
@@ -576,8 +859,18 @@ func runOnRedis(t *testing.T, fn func(client *Redis)) {
t.Error(err) t.Error(err)
} }
client.Close() if client != nil {
client.Close()
}
}() }()
fn(NewRedis(s.Addr(), NodeType)) fn(NewRedis(s.Addr(), NodeType))
} }
type mockedNode struct {
RedisNode
}
func (n mockedNode) BLPop(timeout time.Duration, keys ...string) *red.StringSliceCmd {
return red.NewStringSliceCmd("foo", "bar")
}

View File

@@ -0,0 +1,28 @@
package redistest
import (
"time"
"github.com/alicebob/miniredis/v2"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/stores/redis"
)
func CreateRedis() (r *redis.Redis, clean func(), err error) {
mr, err := miniredis.Run()
if err != nil {
return nil, nil, err
}
return redis.NewRedis(mr.Addr(), redis.NodeType), func() {
ch := make(chan lang.PlaceholderType)
go func() {
mr.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@@ -82,6 +82,7 @@ func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary
indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error { indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {
var primaryKey interface{} var primaryKey interface{}
var found bool var found bool
if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) { if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) {
primaryKey, err = indexQuery(cc.db, v) primaryKey, err = indexQuery(cc.db, v)
if err != nil { if err != nil {

View File

@@ -14,12 +14,14 @@ import (
"testing" "testing"
"time" "time"
"github.com/alicebob/miniredis" "github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/fx"
"github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/stat" "github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stores/cache" "github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/redis" "github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
"github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/core/stores/sqlx"
) )
@@ -30,17 +32,15 @@ func init() {
func TestCachedConn_GetCache(t *testing.T) { func TestCachedConn_GetCache(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
var value string var value string
err = c.GetCache("any", &value) err = c.GetCache("any", &value)
assert.Equal(t, ErrNotFound, err) assert.Equal(t, ErrNotFound, err)
s.Set("any", `"value"`) r.Set("any", `"value"`)
err = c.GetCache("any", &value) err = c.GetCache("any", &value)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, "value", value) assert.Equal(t, "value", value)
@@ -48,12 +48,10 @@ func TestCachedConn_GetCache(t *testing.T) {
func TestStat(t *testing.T) { func TestStat(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
@@ -73,15 +71,33 @@ func TestStat(t *testing.T) {
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) { func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType) c := NewConn(dummySqlConn{}, cache.CacheConf{
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) {
RedisConf: redis.RedisConf{
Host: r.Addr,
Type: redis.NodeType,
},
Weight: 100,
},
}, cache.WithExpiry(time.Second*10))
var str string var str string
err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
return fmt.Sprintf("%s/1234", s)
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
*v.(*string) = "zero"
return "primary", errors.New("foo")
}, func(conn sqlx.SqlConn, v, pri interface{}) error {
assert.Equal(t, "primary", pri)
*v.(*string) = "xin"
return nil
})
assert.NotNil(t, err)
err = c.QueryRowIndex(&str, "index", func(s interface{}) string { err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
return fmt.Sprintf("%s/1234", s) return fmt.Sprintf("%s/1234", s)
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) { }, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
@@ -104,12 +120,10 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) { func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10), c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second)) cache.WithNotFoundExpiry(time.Second))
@@ -135,6 +149,98 @@ func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
assert.Equal(t, `"xin"`, val) assert.Equal(t, `"xin"`, val)
} }
func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
const (
primaryInt8 int8 = 100
primaryInt16 int16 = 10000
primaryInt32 int32 = 10000000
primaryInt64 int64 = 10000000
primaryUint8 uint8 = 100
primaryUint16 uint16 = 10000
primaryUint32 uint32 = 10000000
primaryUint64 uint64 = 10000000
)
tests := []struct {
name string
primary interface{}
primaryCache string
}{
{
name: "int8 primary",
primary: primaryInt8,
primaryCache: fmt.Sprint(primaryInt8),
},
{
name: "int16 primary",
primary: primaryInt16,
primaryCache: fmt.Sprint(primaryInt16),
},
{
name: "int32 primary",
primary: primaryInt32,
primaryCache: fmt.Sprint(primaryInt32),
},
{
name: "int64 primary",
primary: primaryInt64,
primaryCache: fmt.Sprint(primaryInt64),
},
{
name: "uint8 primary",
primary: primaryUint8,
primaryCache: fmt.Sprint(primaryUint8),
},
{
name: "uint16 primary",
primary: primaryUint16,
primaryCache: fmt.Sprint(primaryUint16),
},
{
name: "uint32 primary",
primary: primaryUint32,
primaryCache: fmt.Sprint(primaryUint32),
},
{
name: "uint64 primary",
primary: primaryUint64,
primaryCache: fmt.Sprint(primaryUint64),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resetStats()
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second))
var str string
r.Set("index", test.primaryCache)
err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
return fmt.Sprintf("%v/1234", s)
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
assert.Fail(t, "should not go here")
return test.primary, nil
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
*v.(*string) = "xin"
assert.Equal(t, primary, primary)
return nil
})
assert.Nil(t, err)
assert.Equal(t, "xin", str)
val, err := r.Get("index")
assert.Nil(t, err)
assert.Equal(t, test.primaryCache, val)
val, err = r.Get(test.primaryCache + "/1234")
assert.Nil(t, err)
assert.Equal(t, `"xin"`, val)
})
}
}
func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) { func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
caches := map[string]string{ caches := map[string]string{
"index": "primary", "index": "primary",
@@ -144,12 +250,10 @@ func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
for k, v := range caches { for k, v := range caches {
t.Run(k+"/"+v, func(t *testing.T) { t.Run(k+"/"+v, func(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10), c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
cache.WithNotFoundExpiry(time.Second)) cache.WithNotFoundExpiry(time.Second))
@@ -201,12 +305,10 @@ func TestStatCacheFails(t *testing.T) {
func TestStatDbFails(t *testing.T) { func TestStatDbFails(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
@@ -224,12 +326,10 @@ func TestStatDbFails(t *testing.T) {
func TestStatFromMemory(t *testing.T) { func TestStatFromMemory(t *testing.T) {
resetStats() resetStats()
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10)) c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
var all sync.WaitGroup var all sync.WaitGroup
@@ -284,10 +384,9 @@ func TestStatFromMemory(t *testing.T) {
} }
func TestCachedConnQueryRow(t *testing.T) { func TestCachedConnQueryRow(t *testing.T) {
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
const ( const (
key = "user" key = "user"
@@ -296,7 +395,6 @@ func TestCachedConnQueryRow(t *testing.T) {
var conn trackedConn var conn trackedConn
var user string var user string
var ran bool var ran bool
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30)) c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error { err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
ran = true ran = true
@@ -304,7 +402,7 @@ func TestCachedConnQueryRow(t *testing.T) {
return nil return nil
}) })
assert.Nil(t, err) assert.Nil(t, err)
actualValue, err := s.Get(key) actualValue, err := r.Get(key)
assert.Nil(t, err) assert.Nil(t, err)
var actual string var actual string
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual)) assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
@@ -314,10 +412,9 @@ func TestCachedConnQueryRow(t *testing.T) {
} }
func TestCachedConnQueryRowFromCache(t *testing.T) { func TestCachedConnQueryRowFromCache(t *testing.T) {
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
const ( const (
key = "user" key = "user"
@@ -326,7 +423,6 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
var conn trackedConn var conn trackedConn
var user string var user string
var ran bool var ran bool
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30)) c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
assert.Nil(t, c.SetCache(key, value)) assert.Nil(t, c.SetCache(key, value))
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error { err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
@@ -335,7 +431,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
return nil return nil
}) })
assert.Nil(t, err) assert.Nil(t, err)
actualValue, err := s.Get(key) actualValue, err := r.Get(key)
assert.Nil(t, err) assert.Nil(t, err)
var actual string var actual string
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual)) assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
@@ -345,16 +441,14 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
} }
func TestQueryRowNotFound(t *testing.T) { func TestQueryRowNotFound(t *testing.T) {
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
const key = "user" const key = "user"
var conn trackedConn var conn trackedConn
var user string var user string
var ran int var ran int
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30)) c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error { err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
@@ -367,13 +461,11 @@ func TestQueryRowNotFound(t *testing.T) {
} }
func TestCachedConnExec(t *testing.T) { func TestCachedConnExec(t *testing.T) {
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
var conn trackedConn var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10)) c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
_, err = c.ExecNoCache("delete from user_table where id='kevin'") _, err = c.ExecNoCache("delete from user_table where id='kevin'")
assert.Nil(t, err) assert.Nil(t, err)
@@ -381,26 +473,31 @@ func TestCachedConnExec(t *testing.T) {
} }
func TestCachedConnExecDropCache(t *testing.T) { func TestCachedConnExecDropCache(t *testing.T) {
s, err := miniredis.Run() r, err := miniredis.Run()
if err != nil { assert.Nil(t, err)
t.Error(err) defer fx.DoWithTimeout(func() error {
} r.Close()
return nil
}, time.Second)
const ( const (
key = "user" key = "user"
value = "any" value = "any"
) )
var conn trackedConn var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType) c := NewNodeConn(&conn, redis.NewRedis(r.Addr(), redis.NodeType), cache.WithExpiry(time.Second*30))
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
assert.Nil(t, c.SetCache(key, value)) assert.Nil(t, c.SetCache(key, value))
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) { _, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
return conn.Exec("delete from user_table where id='kevin'") return conn.Exec("delete from user_table where id='kevin'")
}, key) }, key)
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, conn.execValue) assert.True(t, conn.execValue)
_, err = s.Get(key) _, err = r.Get(key)
assert.Exactly(t, miniredis.ErrKeyNotFound, err) assert.Exactly(t, miniredis.ErrKeyNotFound, err)
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
return nil, errors.New("foo")
}, key)
assert.NotNil(t, err)
} }
func TestCachedConnExecDropCacheFailed(t *testing.T) { func TestCachedConnExecDropCacheFailed(t *testing.T) {
@@ -416,13 +513,11 @@ func TestCachedConnExecDropCacheFailed(t *testing.T) {
} }
func TestCachedConnQueryRows(t *testing.T) { func TestCachedConnQueryRows(t *testing.T) {
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
var conn trackedConn var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10)) c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
var users []string var users []string
err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'") err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
@@ -431,13 +526,11 @@ func TestCachedConnQueryRows(t *testing.T) {
} }
func TestCachedConnTransact(t *testing.T) { func TestCachedConnTransact(t *testing.T) {
s, err := miniredis.Run() r, clean, err := redistest.CreateRedis()
if err != nil { assert.Nil(t, err)
t.Error(err) defer clean()
}
var conn trackedConn var conn trackedConn
r := redis.NewRedis(s.Addr(), redis.NodeType)
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10)) c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
err = c.Transact(func(session sqlx.Session) error { err = c.Transact(func(session sqlx.Session) error {
return nil return nil
@@ -446,6 +539,29 @@ func TestCachedConnTransact(t *testing.T) {
assert.True(t, conn.transactValue) assert.True(t, conn.transactValue)
} }
func TestQueryRowNoCache(t *testing.T) {
r, clean, err := redistest.CreateRedis()
assert.Nil(t, err)
defer clean()
const (
key = "user"
value = "any"
)
var user string
var ran bool
conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error {
user = value
ran = true
return nil
}}
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
err = c.QueryRowNoCache(&user, key)
assert.Nil(t, err)
assert.Equal(t, value, user)
assert.True(t, ran)
}
func resetStats() { func resetStats() {
atomic.StoreUint64(&stats.Total, 0) atomic.StoreUint64(&stats.Total, 0)
atomic.StoreUint64(&stats.Hit, 0) atomic.StoreUint64(&stats.Hit, 0)
@@ -454,6 +570,7 @@ func resetStats() {
} }
type dummySqlConn struct { type dummySqlConn struct {
queryRow func(interface{}, string, ...interface{}) error
} }
func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) { func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) {
@@ -465,6 +582,9 @@ func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) {
} }
func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error { func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error {
if d.queryRow != nil {
return d.queryRow(v, query, args...)
}
return nil return nil
} }

View File

@@ -2,6 +2,7 @@ package sqlx
import ( import (
"database/sql" "database/sql"
"errors"
"strconv" "strconv"
"testing" "testing"
@@ -11,14 +12,15 @@ import (
) )
type mockedConn struct { type mockedConn struct {
query string query string
args []interface{} args []interface{}
execErr error
} }
func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) { func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
c.query = query c.query = query
c.args = args c.args = args
return nil, nil return nil, c.execErr
} }
func (c *mockedConn) Prepare(query string) (StmtSession, error) { func (c *mockedConn) Prepare(query string) (StmtSession, error) {
@@ -68,9 +70,12 @@ func TestBulkInserterSuffix(t *testing.T) {
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES`+ inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES`+
`(?, ?, ?) ON DUPLICATE KEY UPDATE is_overtime=VALUES(is_overtime)`) `(?, ?, ?) ON DUPLICATE KEY UPDATE is_overtime=VALUES(is_overtime)`)
assert.Nil(t, err) assert.Nil(t, err)
assert.Nil(t, inserter.UpdateStmt(`INSERT INTO classroom_dau(classroom, user, count) VALUES`+
`(?, ?, ?) ON DUPLICATE KEY UPDATE is_overtime=VALUES(is_overtime)`))
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
assert.Nil(t, inserter.Insert("class_"+strconv.Itoa(i), "user_"+strconv.Itoa(i), i)) assert.Nil(t, inserter.Insert("class_"+strconv.Itoa(i), "user_"+strconv.Itoa(i), i))
} }
inserter.SetResultHandler(func(result sql.Result, err error) {})
inserter.Flush() inserter.Flush()
assert.Equal(t, `INSERT INTO classroom_dau(classroom, user, count) VALUES `+ assert.Equal(t, `INSERT INTO classroom_dau(classroom, user, count) VALUES `+
`('class_0', 'user_0', 0), ('class_1', 'user_1', 1), ('class_2', 'user_2', 2), `+ `('class_0', 'user_0', 0), ('class_1', 'user_1', 1), ('class_2', 'user_2', 2), `+
@@ -80,6 +85,33 @@ func TestBulkInserterSuffix(t *testing.T) {
}) })
} }
func TestBulkInserterBadStatement(t *testing.T) {
runSqlTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
var conn mockedConn
_, err := NewBulkInserter(&conn, "foo")
assert.NotNil(t, err)
})
}
func TestBulkInserter_Update(t *testing.T) {
conn := mockedConn{
execErr: errors.New("foo"),
}
_, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES()`)
assert.NotNil(t, err)
_, err = NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES(?)`)
assert.NotNil(t, err)
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES(?, ?, ?)`)
assert.Nil(t, err)
inserter.inserter.Execute([]string{"bar"})
inserter.SetResultHandler(func(result sql.Result, err error) {
})
inserter.UpdateOrDelete(func() {})
inserter.inserter.Execute([]string(nil))
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)) { func runSqlTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
logx.Disable() logx.Disable()

View File

@@ -2,6 +2,7 @@ package sqlx
import ( import (
"database/sql" "database/sql"
"errors"
"testing" "testing"
"github.com/DATA-DOG/go-sqlmock" "github.com/DATA-DOG/go-sqlmock"
@@ -22,6 +23,18 @@ func TestUnmarshalRowBool(t *testing.T) {
}) })
} }
func TestUnmarshalRowBoolNotSettable(t *testing.T) {
runOrmTest(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 bool
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select value from users where user=?", "anyone"))
})
}
func TestUnmarshalRowInt(t *testing.T) { func TestUnmarshalRowInt(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) { runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2") rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2")
@@ -228,6 +241,22 @@ func TestUnmarshalRowStructWithTags(t *testing.T) {
}) })
} }
func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
var 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"}).FromCSVString("liao")
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
return unmarshalRow(value, rows, true)
}, "select name, age from users where user=?", "anyone"))
})
}
func TestUnmarshalRowsBool(t *testing.T) { func TestUnmarshalRowsBool(t *testing.T) {
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) { runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
var expect = []bool{true, false} var expect = []bool{true, false}
@@ -955,6 +984,62 @@ func TestCommonSqlConn_QueryRowOptional(t *testing.T) {
}) })
} }
func TestUnmarshalRowError(t *testing.T) {
tests := []struct {
name string
colErr error
scanErr error
err error
next int
validate func(err error)
}{
{
name: "with error",
err: errors.New("foo"),
validate: func(err error) {
assert.NotNil(t, err)
},
},
{
name: "without next",
validate: func(err error) {
assert.Equal(t, ErrNotFound, err)
},
},
{
name: "with error",
scanErr: errors.New("foo"),
next: 1,
validate: func(err error) {
assert.Equal(t, ErrNotFound, err)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
runOrmTest(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)
var r struct {
User string `db:"user"`
Age int `db:"age"`
}
test.validate(query(db, func(rows *sql.Rows) error {
scanner := mockedScanner{
colErr: test.colErr,
scanErr: test.scanErr,
err: test.err,
}
return unmarshalRow(&r, &scanner, false)
}, "select age from users where user=?", "anyone"))
})
})
}
}
func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) { func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
logx.Disable() logx.Disable()
@@ -970,3 +1055,30 @@ func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
t.Errorf("there were unfulfilled expectations: %s", err) t.Errorf("there were unfulfilled expectations: %s", err)
} }
} }
type mockedScanner struct {
colErr error
scanErr error
err error
next int
}
func (m *mockedScanner) Columns() ([]string, error) {
return nil, m.colErr
}
func (m *mockedScanner) Err() error {
return m.err
}
func (m *mockedScanner) Next() bool {
if m.next > 0 {
m.next--
return true
}
return false
}
func (m *mockedScanner) Scan(v ...interface{}) error {
return m.scanErr
}

View File

@@ -1,6 +1,7 @@
package syncx package syncx
import ( import (
"sync"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -10,11 +11,15 @@ func TestBarrier_Guard(t *testing.T) {
const total = 10000 const total = 10000
var barrier Barrier var barrier Barrier
var count int var count int
var wg sync.WaitGroup
wg.Add(total)
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
barrier.Guard(func() { go barrier.Guard(func() {
count++ count++
wg.Done()
}) })
} }
wg.Wait()
assert.Equal(t, total, count) assert.Equal(t, total, count)
} }
@@ -22,10 +27,14 @@ func TestBarrierPtr_Guard(t *testing.T) {
const total = 10000 const total = 10000
barrier := new(Barrier) barrier := new(Barrier)
var count int var count int
wg := new(sync.WaitGroup)
wg.Add(total)
for i := 0; i < total; i++ { for i := 0; i < total; i++ {
barrier.Guard(func() { go barrier.Guard(func() {
count++ count++
wg.Done()
}) })
} }
wg.Wait()
assert.Equal(t, total, count) assert.Equal(t, total, count)
} }

View File

@@ -67,7 +67,3 @@ func TestSignalNoWait(t *testing.T) {
func sleep(millisecond int) { func sleep(millisecond int) {
time.Sleep(time.Duration(millisecond) * time.Millisecond) time.Sleep(time.Duration(millisecond) * time.Millisecond)
} }
func currentTimeMillis() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}

View File

@@ -6,18 +6,22 @@ import (
"github.com/tal-tech/go-zero/core/lang" "github.com/tal-tech/go-zero/core/lang"
) )
var ErrReturn = errors.New("discarding limited token, resource pool is full, someone returned multiple times") // ErrLimitReturn indicates that the more than borrowed elements were returned.
var ErrLimitReturn = errors.New("discarding limited token, resource pool is full, someone returned multiple times")
// Limit controls the concurrent requests.
type Limit struct { type Limit struct {
pool chan lang.PlaceholderType pool chan lang.PlaceholderType
} }
// NewLimit creates a Limit that can borrow n elements from it concurrently.
func NewLimit(n int) Limit { func NewLimit(n int) Limit {
return Limit{ return Limit{
pool: make(chan lang.PlaceholderType, n), pool: make(chan lang.PlaceholderType, n),
} }
} }
// Borrow borrows an element from Limit in blocking mode.
func (l Limit) Borrow() { func (l Limit) Borrow() {
l.pool <- lang.Placeholder l.pool <- lang.Placeholder
} }
@@ -28,10 +32,12 @@ func (l Limit) Return() error {
case <-l.pool: case <-l.pool:
return nil return nil
default: default:
return ErrReturn return ErrLimitReturn
} }
} }
// TryBorrow tries to borrow an element from Limit, in non-blocking mode.
// If success, true returned, false for otherwise.
func (l Limit) TryBorrow() bool { func (l Limit) TryBorrow() bool {
select { select {
case l.pool <- lang.Placeholder: case l.pool <- lang.Placeholder:

View File

@@ -13,5 +13,5 @@ func TestLimit(t *testing.T) {
assert.False(t, limit.TryBorrow()) assert.False(t, limit.TryBorrow())
assert.Nil(t, limit.Return()) assert.Nil(t, limit.Return())
assert.Nil(t, limit.Return()) assert.Nil(t, limit.Return())
assert.Equal(t, ErrReturn, limit.Return()) assert.Equal(t, ErrLimitReturn, limit.Return())
} }

View File

@@ -33,38 +33,43 @@ func NewSharedCalls() SharedCalls {
} }
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) { func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
g.lock.Lock() c, done := g.createCall(key)
if c, ok := g.calls[key]; ok { if done {
g.lock.Unlock()
c.wg.Wait()
return c.val, c.err return c.val, c.err
} }
c := g.makeCall(key, fn) g.makeCall(c, key, fn)
return c.val, c.err return c.val, c.err
} }
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) { func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
c, done := g.createCall(key)
if done {
return c.val, false, c.err
}
g.makeCall(c, key, fn)
return c.val, true, c.err
}
func (g *sharedGroup) createCall(key string) (c *call, done bool) {
g.lock.Lock() g.lock.Lock()
if c, ok := g.calls[key]; ok { if c, ok := g.calls[key]; ok {
g.lock.Unlock() g.lock.Unlock()
c.wg.Wait() c.wg.Wait()
return c.val, false, c.err return c, true
} }
c := g.makeCall(key, fn) c = new(call)
return c.val, true, c.err
}
func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call {
c := new(call)
c.wg.Add(1) c.wg.Add(1)
g.calls[key] = c g.calls[key] = c
g.lock.Unlock() g.lock.Unlock()
return c, false
}
func (g *sharedGroup) makeCall(c *call, key string, fn func() (interface{}, error)) {
defer func() { defer func() {
// delete key first, done later. can't reverse the order, because if reverse,
// another Do call might wg.Wait() without get notified with wg.Done()
g.lock.Lock() g.lock.Lock()
delete(g.calls, key) delete(g.calls, key)
g.lock.Unlock() g.lock.Unlock()
@@ -72,5 +77,4 @@ func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call
}() }()
c.val, c.err = fn() c.val, c.err = fn()
return c
} }

View File

@@ -95,7 +95,8 @@ func TestExclusiveCallDoDiffDupSuppress(t *testing.T) {
close(broadcast) close(broadcast)
wg.Wait() wg.Wait()
if got := atomic.LoadInt32(&calls); got != 5 { // five letters if got := atomic.LoadInt32(&calls); got != 5 {
// five letters
t.Errorf("number of calls = %d; want 5", got) t.Errorf("number of calls = %d; want 5", got)
} }
} }

View File

@@ -29,5 +29,5 @@ func TestTimeoutLimit(t *testing.T) {
assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100)) assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100))
assert.Nil(t, limit.Return()) assert.Nil(t, limit.Return())
assert.Nil(t, limit.Return()) assert.Nil(t, limit.Return())
assert.Equal(t, ErrReturn, limit.Return()) assert.Equal(t, ErrLimitReturn, limit.Return())
} }

View File

@@ -16,7 +16,7 @@ func TestRelativeTime(t *testing.T) {
} }
func TestRelativeTime_Time(t *testing.T) { func TestRelativeTime_Time(t *testing.T) {
diff := Time().Sub(time.Now()) diff := time.Until(Time())
if diff > 0 { if diff > 0 {
assert.True(t, diff < time.Second) assert.True(t, diff < time.Second)
} else { } else {

View File

@@ -27,12 +27,9 @@ func TestFakeTicker(t *testing.T) {
var count int32 var count int32
go func() { go func() {
for { for range ticker.Chan() {
select { if atomic.AddInt32(&count, 1) == total {
case <-ticker.Chan(): ticker.Done()
if atomic.AddInt32(&count, 1) == total {
ticker.Done()
}
} }
} }
}() }()

View File

@@ -18,7 +18,7 @@ func TestHttpPropagator_Extract(t *testing.T) {
assert.Equal(t, "trace", carrier.Get(traceIdKey)) assert.Equal(t, "trace", carrier.Get(traceIdKey))
assert.Equal(t, "span", carrier.Get(spanIdKey)) assert.Equal(t, "span", carrier.Get(spanIdKey))
carrier, err = Extract(HttpFormat, req) _, err = Extract(HttpFormat, req)
assert.Equal(t, ErrInvalidCarrier, err) assert.Equal(t, ErrInvalidCarrier, err)
} }
@@ -31,7 +31,7 @@ func TestHttpPropagator_Inject(t *testing.T) {
assert.Equal(t, "trace", carrier.Get(traceIdKey)) assert.Equal(t, "trace", carrier.Get(traceIdKey))
assert.Equal(t, "span", carrier.Get(spanIdKey)) assert.Equal(t, "span", carrier.Get(spanIdKey))
carrier, err = Inject(HttpFormat, req) _, err = Inject(HttpFormat, req)
assert.Equal(t, ErrInvalidCarrier, err) assert.Equal(t, ErrInvalidCarrier, err)
} }
@@ -45,9 +45,9 @@ func TestGrpcPropagator_Extract(t *testing.T) {
assert.Equal(t, "trace", carrier.Get(traceIdKey)) assert.Equal(t, "trace", carrier.Get(traceIdKey))
assert.Equal(t, "span", carrier.Get(spanIdKey)) assert.Equal(t, "span", carrier.Get(spanIdKey))
carrier, err = Extract(GrpcFormat, 1) _, err = Extract(GrpcFormat, 1)
assert.Equal(t, ErrInvalidCarrier, err) assert.Equal(t, ErrInvalidCarrier, err)
carrier, err = Extract(nil, 1) _, err = Extract(nil, 1)
assert.Equal(t, ErrInvalidCarrier, err) assert.Equal(t, ErrInvalidCarrier, err)
} }
@@ -61,8 +61,8 @@ func TestGrpcPropagator_Inject(t *testing.T) {
assert.Equal(t, "trace", carrier.Get(traceIdKey)) assert.Equal(t, "trace", carrier.Get(traceIdKey))
assert.Equal(t, "span", carrier.Get(spanIdKey)) assert.Equal(t, "span", carrier.Get(spanIdKey))
carrier, err = Inject(GrpcFormat, 1) _, err = Inject(GrpcFormat, 1)
assert.Equal(t, ErrInvalidCarrier, err) assert.Equal(t, ErrInvalidCarrier, err)
carrier, err = Inject(nil, 1) _, err = Inject(nil, 1)
assert.Equal(t, ErrInvalidCarrier, err) assert.Equal(t, ErrInvalidCarrier, err)
} }

View File

@@ -17,7 +17,6 @@ const (
clientFlag = "client" clientFlag = "client"
serverFlag = "server" serverFlag = "server"
spanSepRune = '.' spanSepRune = '.'
timeFormat = "2006-01-02 15:04:05.000"
) )
var spanSep = string([]byte{spanSepRune}) var spanSep = string([]byte{spanSepRune})
@@ -37,9 +36,7 @@ func newServerSpan(carrier Carrier, serviceName, operationName string) tracespec
return carrier.Get(traceIdKey) return carrier.Get(traceIdKey)
} }
return "" return ""
}, func() string { }, stringx.RandId)
return stringx.RandId()
})
spanId := stringx.TakeWithPriority(func() string { spanId := stringx.TakeWithPriority(func() string {
if carrier != nil { if carrier != nil {
return carrier.Get(spanIdKey) return carrier.Get(spanIdKey)

View File

@@ -30,6 +30,7 @@ func TestCompareVersions(t *testing.T) {
} }
for _, each := range cases { for _, each := range cases {
each := each
t.Run(each.ver1, func(t *testing.T) { t.Run(each.ver1, func(t *testing.T) {
actual := CompareVersions(each.ver1, each.operator, each.ver2) actual := CompareVersions(each.ver1, each.operator, each.ver2)
assert.Equal(t, each.out, actual, fmt.Sprintf("%s vs %s", each.ver1, each.ver2)) assert.Equal(t, each.out, actual, fmt.Sprintf("%s vs %s", each.ver1, each.ver2))

View File

@@ -1,622 +0,0 @@
# Rapid development of microservices - multiple RPCs
English | [简体中文](bookstore.md)
## 0. Why building microservices are so difficult
To build a well working microservice, we need lots of knowledges from different aspects.
* basic functionalities
1. concurrency control and rate limit, to avoid being brought down by unexpected inbound
2. service discovery, make sure new or terminated nodes are detected asap
3. load balancing, balance the traffic base on the throughput of nodes
4. timeout control, avoid the nodes continue to process the timed out requests
5. circuit breaker, load shedding, fail fast, protects the failure nodes to recover asap
* advanced functionalities
1. authorization, make sure users can only access their own data
2. tracing, to understand the whole system and locate the specific problem quickly
3. logging, collects data and helps to backtrace problems
4. observability, no metrics, no optimization
For any point listed above, we need a long article to describe the theory and the implementation. But for us, the developers, its very difficult to understand all the concepts and make it happen in our systems. Although, we can use the frameworks that have been well served busy sites. [go-zero](https://github.com/tal-tech/go-zero) is born for this purpose, especially for cloud-native microservice systems.
As well, we always adhere to the idea that **prefer tools over conventions and documents**. We hope to reduce the boilerplate code as much as possible, and let developers focus on developing the business related code. For this purpose, we developed the tool `goctl`.
Lets take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, youll find that its so easy to write microservices!
## 1. What is a bookstore service
For simplicity, the bookstore service only contains two functionalities, adding books and quering prices.
Writting this bookstore service is to demonstrate the complete flow of creating a microservice by using go-zero. But algorithms and detail implementations are quite simplified, and this bookstore service is not suitable for production use.
## 2. Architecture of shorturl microservice
<img src="images/bookstore-arch.png" alt="architecture" width="800" />
## 3. goctl generated code overview
All modules with green background are generated, and will be enabled when necessary. The modules with red background are handwritten code, which is typically business logic code.
* API Gateway
<img src="images/api-gen.png" alt="api" width="800" />
* RPC
<img src="images/rpc-gen.png" alt="rpc" width="800" />
* model
<img src="images/model-gen.png" alt="model" width="800" />
And now, lets walk through the complete flow of quickly create a microservice with go-zero.
## 4. Get started
* install etcd, mysql, redis
* install protoc-gen-go
```shell
go get -u github.com/golang/protobuf/protoc-gen-go
```
* install goctl
```shell
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
```
* create the working dir bookstore
* in `bookstore` dir, execute `go mod init bookstore` to initialize `go.mod``
## 5. Write code for API Gateway
* use goctl to generate `api/bookstore.api`
```Plain Text
goctl api -o bookstore.api
```
for simplicity, the leading `info` block is removed, and the code looks like:
```go
type (
addReq struct {
book string `form:"book"`
price int64 `form:"price"`
}
addResp struct {
ok bool `json:"ok"`
}
)
type (
checkReq struct {
book string `form:"book"`
}
checkResp struct {
found bool `json:"found"`
price int64 `json:"price"`
}
)
service bookstore-api {
@server(
handler: AddHandler
)
get /add(addReq) returns(addResp)
@server(
handler: CheckHandler
)
get /check(checkReq) returns(checkResp)
}
```
the usage of `type` keyword is the same as that in go, service is used to define get/post/head/delete api requests, described below:
* `service bookstore-api { defines the service name
* `@server` defines the properties that used in server side
* `handler` defines the handler name
* `get /add(addReq) returns(addResp)` defines this is a GET request, the request parameters, and the response parameters
* generate the code for API Gateway by using goctl
```shell
goctl api go -api bookstore.api -dir .
```
the generated file structure looks like:
```Plain Text
api
├── bookstore.api // api definition
├── bookstore.go // main entrance
├── etc
│ └── bookstore-api.yaml // configuration file
└── internal
├── config
│ └── config.go // configuration definition
├── handler
│ ├── addhandler.go // implements addHandler
│ ├── checkhandler.go // implements checkHandler
│ └── routes.go // routes definition
├── logic
│ ├── addlogic.go // implements AddLogic
│ └── checklogic.go // implements CheckLogic
├── svc
│ └── servicecontext.go // defines ServiceContext
└── types
└── types.go // defines request/response
```
* start API Gateway service, listens on port 8888 by default
```shell
go run bookstore.go -f etc/bookstore-api.yaml
```
* test API Gateway service
```shell
curl -i "http://localhost:8888/check?book=go-zero"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 06:46:18 GMT
Content-Length: 25
{"found":false,"price":0}
```
You can see that the API Gateway service did nothing except returned a zero value. And lets implement the business logic in rpc service.
* you can modify `internal/svc/servicecontext.go` to pass dependencies if needed
* implement logic in package `internal/logic`
* you can use goctl to generate code for clients base on the .api file
* till now, the client engineer can work with the api, dont need to wait for the implementation of server side
## 6. Write code for add rpc service
* under directory `rpc/add` create `add.proto` file
```shell
goctl rpc template -o add.proto
```
edit the file and make the code looks like:
```protobuf
syntax = "proto3";
package add;
message addReq {
string book = 1;
int64 price = 2;
}
message addResp {
bool ok = 1;
}
service adder {
rpc add(addReq) returns(addResp);
}
```
* use goctl to generate the rpc code, execute the following command in `rpc/add`
```shell
goctl rpc proto -src add.proto
```
the generated file structure looks like:
```Plain Text
rpc/add
├── add.go // rpc main entrance
├── add.proto // rpc definition
├── adder
│ ├── adder.go // defines how rpc clients call this service
│ ├── adder_mock.go // mock file, for test purpose
│ └── types.go // request/response definition
├── etc
│ └── add.yaml // configuration file
├── internal
│ ├── config
│ │ └── config.go // configuration definition
│ ├── logic
│ │ └── addlogic.go // add logic here
│ ├── server
│ │ └── adderserver.go // rpc handler
│ └── svc
│ └── servicecontext.go // defines service context, like dependencies
└── pb
└── add.pb.go
```
just run it, looks like:
```shell
$ go run add.go -f etc/add.yaml
Starting rpc server at 127.0.0.1:8080...
```
you can change the listening port in file `etc/add.yaml`.
## 7. Write code for check rpc service
* under directory `rpc/check` create `check.proto` file
```shell
goctl rpc template -o check.proto
```
edit the file and make the code looks like:
```protobuf
syntax = "proto3";
package check;
message checkReq {
string book = 1;
}
message checkResp {
bool found = 1;
int64 price = 2;
}
service checker {
rpc check(checkReq) returns(checkResp);
}
```
* use goctl to generate the rpc code, execute the following command in `rpc/check`
```shell
goctl rpc proto -src check.proto
```
the generated file structure looks like:
```Plain Text
rpc/check
├── check.go // rpc main entrance
├── check.proto // rpc definition
├── checker
│ ├── checker.go // defines how rpc clients call this service
│ ├── checker_mock.go // mock file, for test purpose
│ └── types.go // request/response definition
├── etc
│ └── check.yaml // configuration file
├── internal
│ ├── config
│ │ └── config.go // configuration definition
│ ├── logic
│ │ └── checklogic.go // check logic here
│ ├── server
│ │ └── checkerserver.go // rpc handler
│ └── svc
│ └── servicecontext.go // defines service context, like dependencies
└── pb
└── check.pb.go
```
you can change the listening port in `etc/check.yaml`.
we need to change the port in `etc/check.yaml` to `8081`, because `8080` is used by `add` service.
just run it, looks like:
```shell
$ go run check.go -f etc/check.yaml
Starting rpc server at 127.0.0.1:8081...
```
## 8. Modify API Gateway to call add/check rpc service
* modify the configuration file `bookstore-api.yaml`, add the following:
```yaml
Add:
Etcd:
Hosts:
- localhost:2379
Key: add.rpc
Check:
Etcd:
Hosts:
- localhost:2379
Key: check.rpc
```
automatically discover the add/check service by using etcd.
* modify the file `internal/config/config.go`, add dependency on add/check service:
```go
type Config struct {
rest.RestConf
Add zrpc.RpcClientConf // manual code
Check zrpc.RpcClientConf // manual code
}
```
* modify the file `internal/svc/servicecontext.go`, like below:
```go
type ServiceContext struct {
Config config.Config
Adder adder.Adder // manual code
Checker checker.Checker // manual code
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Adder: adder.NewAdder(zrpc.MustNewClient(c.Add)), // manual code
Checker: checker.NewChecker(zrpc.MustNewClient(c.Check)), // manual code
}
}
```
passing the dependencies among services within ServiceContext.
* modify the method `Add` in the file `internal/logic/addlogic.go`, looks like:
```go
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
// manual code start
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
Book: req.Book,
Price: req.Price,
})
if err != nil {
return nil, err
}
return &types.AddResp{
Ok: resp.Ok,
}, nil
// manual code stop
}
```
by calling the method `Add` of `adder` to add books into bookstore.
* modify the file `internal/logic/checklogic.go`, looks like:
```go
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
// manual code start
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
Book: req.Book,
})
if err != nil {
return nil, err
}
return &types.CheckResp{
Found: resp.Found,
Price: resp.Price,
}, nil
// manual code stop
}
```
by calling the method `Check` of `checker` to check the prices from the bookstore.
Till now, weve done the modification of API Gateway. All the manually added code are marked.
## 9. Define the database schema, generate the code for CRUD+cache
* under bookstore, create the directory `rpc/model`: `mkdir -p rpc/model`
* under the directory rpc/model create the file called `book.sql`, contents as below:
```sql
CREATE TABLE `book`
(
`book` varchar(255) NOT NULL COMMENT 'book name',
`price` int NOT NULL COMMENT 'book price',
PRIMARY KEY(`book`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
* create DB and table
```sql
create database gozero;
```
```sql
source book.sql;
```
* under the directory `rpc/model` execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
```shell
goctl model mysql ddl -c -src book.sql -dir .
```
you can also generate the code from the database url by using `datasource` subcommand instead of `ddl`
the generated file structure looks like:
```Plain Text
rpc/model
├── bookstore.sql
├── bookstoremodel.go // CRUD+cache code
└── vars.go // const and var definition
```
## 10. Modify add/check rpc to call crud+cache
* modify `rpc/add/etc/add.yaml`, add the following:
```yaml
DataSource: root:@tcp(localhost:3306)/gozero
Table: book
Cache:
- Host: localhost:6379
```
you can use multiple redis as cache. redis node and cluster are both supported.
* modify `rpc/add/internal/config.go`, like below:
```go
type Config struct {
zrpc.RpcServerConf
DataSource string // manual code
Table string // manual code
Cache cache.CacheConf // manual code
}
```
added the configuration for mysql and redis cache.
* modify `rpc/add/internal/svc/servicecontext.go` and `rpc/check/internal/svc/servicecontext.go`, like below:
```go
type ServiceContext struct {
c config.Config
Model *model.BookModel // manual code
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
c: c,
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // manual code
}
}
```
* modify `rpc/add/internal/logic/addlogic.go`, like below:
```go
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
// manual code start
_, err := l.svcCtx.Model.Insert(model.Book{
Book: in.Book,
Price: in.Price,
})
if err != nil {
return nil, err
}
return &add.AddResp{
Ok: true,
}, nil
// manual code stop
}
```
* modify `rpc/check/internal/logic/checklogic.go`, like below:
```go
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
// manual code start
resp, err := l.svcCtx.Model.FindOne(in.Book)
if err != nil {
return nil, err
}
return &check.CheckResp{
Found: true,
Price: resp.Price,
}, nil
// manual code stop
}
```
till now, we finished modifing the code, all the modified code is marked.
## 11. Call shorten and expand services
* call add api
```shell
curl -i "http://localhost:8888/add?book=go-zero&price=10"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 09:42:13 GMT
Content-Length: 11
{"ok":true}
```
* call check api
```shell
curl -i "http://localhost:8888/check?book=go-zero"
```
response like:
```http
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 03 Sep 2020 09:47:34 GMT
Content-Length: 25
{"found":true,"price":10}
```
## 12. Benchmark
Because benchmarking the write requests depends on the write throughput of mysql, we only benchmarked the check api. We read the data from mysql and cache it in redis. For simplicity, I only check one book, because of cache, the effect is the same for multiple books.
Before benchmark, we need to change the max open files:
```shel
ulimit -n 20000
```
And change the log level to error, to avoid too many logs affect the benchmark. Add the following in every yaml file:
```yaml
Log:
Level: error
```
![Benchmark](images/bookstore-benchmark.png)
as shown above, in my MacBook Pro, the QPS is like 30K+.
## 13. Full code
[https://github.com/tal-tech/go-zero/tree/master/example/bookstore](https://github.com/tal-tech/go-zero/tree/master/example/bookstore)
## 14. Conclusion
We always adhere to **prefer tools over conventions and documents**.
go-zero is not only a framework, but also a tool to simplify and standardize the building of micoservice systems.
We not only keep the framework simple, but also encapsulate the complexity into the framework. And the developers are free from building the difficult and boilerplate code. Then we get the rapid development and less failure.
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And its easy to deal with the busy sites.
If you have any ideas that can help us to improve the productivity, tell me any time! 👏

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