Compare commits

...

260 Commits

Author SHA1 Message Date
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
kevin
93f430a449 update shorturl doc 2020-09-29 17:36:00 +08:00
kevin
d1b303fe7e export cache package, add client interceptor customization 2020-09-29 17:25:49 +08:00
kevin
dbca20e3df add zrpc client interceptor 2020-09-29 16:09:11 +08:00
boob
b3ead4d76c doc: update sharedcalls.md layout (#107) 2020-09-29 14:32:17 +08:00
kevin
33a9db85c8 add unit test, fix interceptor bug 2020-09-29 14:30:22 +08:00
kingxt
e7d46aa6e2 refactor and rename folder to group (#106)
* 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 ()

* support return ()

* revert

* format api

* refactor and rename folder to group

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-29 11:14:52 +08:00
kevin
b282304054 add api doc link 2020-09-28 16:58:29 +08:00
bittoy
0a36031d48 use default mongo db (#103) 2020-09-28 16:35:07 +08:00
kevin
e5d7c3ab04 unmarshal should be struct 2020-09-28 15:19:30 +08:00
kevin
12c08bfd39 Revert "goreportcard not working, remove it temporarily"
This reverts commit 8f465fa439.
2020-09-28 11:41:23 +08:00
kevin
8f465fa439 goreportcard not working, remove it temporarily 2020-09-28 00:31:24 +08:00
kingxt
8a470bb6ee support return () syntax (#101)
* 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 ()

* support return ()

* remove pwd for windows not support

* revert

* remove no need

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-27 17:23:15 +08:00
kevin
9277ad77f7 fix typo of prometheus 2020-09-27 17:15:15 +08:00
kevin
a958400595 rename prommetric to prometheous, add unit tests 2020-09-27 16:14:16 +08:00
kevin
015716d1b5 update wechat and etcd yaml 2020-09-27 14:15:33 +08:00
kevin
54e9d01312 update example 2020-09-27 11:10:21 +08:00
kevin
bc831b75dd export AddOptions, AddStreamInterceptors, AddUnaryInterceptors 2020-09-26 22:05:57 +08:00
kevin
ff112fdaee query from cache first when do cache.Take 2020-09-26 21:58:46 +08:00
kingxt
8d0f7dbb27 rename (#98)
* 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

* rename

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-24 10:31:49 +08:00
Keson
a5ce2c448e fix bug: module parse error (#97) 2020-09-23 22:10:25 +08:00
kevin
0dd8e27557 add more clear error when rpc service is not started 2020-09-23 22:07:26 +08:00
Zhang Hao
17a0908a84 add test (#95) 2020-09-22 19:15:30 +08:00
Keson
9f9c24cce9 fix bug: release empty struct limit (#96) 2020-09-22 19:13:46 +08:00
kingxt
b628bc0086 goctl support import api file (#94)
* 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

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-22 18:32:26 +08:00
kevin
be9c48da7f add tracing logs in server side and client side 2020-09-22 17:34:39 +08:00
kevin
797a90ae7d remove unnecessary tag 2020-09-21 22:41:14 +08:00
kevin
92e60a5777 use options instead of opts in error message 2020-09-21 22:37:07 +08:00
miaogaolin
46995a4d7d 修改不能编辑代码注释 (#92)
* rename file and function name

* update comments of "code generate"
2020-09-21 18:27:35 +08:00
kingxt
5e6dcac734 feature: goctl jwt (#91)
* 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 add jwt support

* goctl add jwt support

* goctl add jwt support

* goctl add jwt support

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-21 16:38:23 +08:00
dylanNew
3e7e466526 fix redis error (#88)
Co-authored-by: dylan <wangdi@xiaoheiban.cn>
2020-09-21 16:37:40 +08:00
kingxt
b6b8941a18 update doc (#90)
* 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

* update jwt doc

* update jwt doc

* update jwt doc

* update jwt doc

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-21 16:09:02 +08:00
kingxt
878fd14739 remove no need (#87)
* 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

* add jwt doc

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-21 14:29:12 +08:00
kevin
5e99f2b85d add trace/span in http logs 2020-09-20 22:02:45 +08:00
Howie
9c23399c33 chore: fix typos (#85)
* chore: fix typos

Signed-off-by: lihaowei <haoweili35@gmail.com>

* chore: fix 2 typos
2020-09-20 14:00:31 +08:00
kevin
86d3de4c89 use package level defined contextKey as context key 2020-09-20 12:46:35 +08:00
kevin
dc17855367 printing context key friendly 2020-09-20 12:08:30 +08:00
kevin
1606a92c6e use contextType as string type 2020-09-20 12:04:49 +08:00
mlboy
029fd3ea35 fix: golint: context.WithValue should should not use basic type as key (#83)
* fix: golint: context.WithValue should should not use basic type as key

* optimiz
2020-09-20 12:01:43 +08:00
kevin
57299a7597 rename ngin to rest in goctl 2020-09-20 09:15:19 +08:00
Changkun Ou
762af9dda2 optimize AtomicError (#82)
This commit optimize AtomicError using atomic.Value. Benchmarks:

name               old time/op  new time/op  delta
AtomicError/Load-6   305ns ±11%    12ns ± 6%  -96.18%  (p=0.000 n=10+10)
AtomicError/Set-6   314ns ±16%    14ns ± 2%  -95.61%  (p=0.000 n=10+9)
2020-09-18 22:45:01 +08:00
kevin
eccfaba614 update doc 2020-09-18 22:33:40 +08:00
kevin
974c19d6d3 update rpc example 2020-09-18 18:15:39 +08:00
Zhang Hao
0f8140031a fix rpc client examle (#81) 2020-09-18 18:07:08 +08:00
kevin
0b1ee79d3a rename rpcx to zrpc 2020-09-18 11:41:52 +08:00
Zhang Hao
26e16107ce fix example tracing edge config (#76) 2020-09-18 08:53:06 +08:00
kevin
1e5e9d63bd update wechat qrcode 2020-09-17 10:28:33 +08:00
kevin
f994e1df1a add more tests 2020-09-16 20:03:30 +08:00
kevin
b5dcadda78 remove markdown linter temporarily 2020-09-16 16:53:38 +08:00
kevin
df37597ac3 simplify mapreduce code 2020-09-16 16:48:59 +08:00
miaogaolin
68335ada54 rename file and function name (#74) 2020-09-16 13:30:47 +08:00
bittoy
ecdae2477e add mapping readme (#75) 2020-09-16 13:30:13 +08:00
kevin
a561884fcf print message when starting api server 2020-09-16 13:27:16 +08:00
kevin
a50bcb90a6 rename function 2020-09-14 21:13:19 +08:00
Keson
e6f8e0e8c3 optimize: api generating for idea plugin (#68)
* add flag: force to generate api

* add flag: force to generate api

* format api template

* Revert "format api template"
2020-09-14 17:12:31 +08:00
kingxt
598ff6d0fc api support empty request or empty response (#72)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* api support empty request or empty response

* update readme

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-14 17:10:45 +08:00
miaogaolin
9a57993e83 fix goctl api (#71) 2020-09-14 14:51:22 +08:00
kingxt
ee45b0a459 optimize route parse (#70)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* optimized route parser

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-14 11:44:53 +08:00
masonchen2014
2896ef1a49 Sharedcalls.md (#69)
* sharedcalls.md

* markdownlint

Co-authored-by: chenmusheng <chenmusheng@laoyuegou.com>
2020-09-14 10:13:33 +08:00
kingxt
05df86436f optimized api new with absolute path like: goctl api new $PWD/xxxx (#67)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* remove update

* optimized api new with absolute path like: goctl api new $PWD/xxxx

* optimized api new with absolute path like: goctl api new $PWD/xxxx

* optimized api new with absolute path like: goctl api new $PWD/xxxx

* optimized api new with absolute path like: goctl api new $PWD/xxxx

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-13 16:17:21 +08:00
kevin
fb22589cf5 update doc, add metric link 2020-09-12 22:50:37 +08:00
kevin
a8fb010333 drain pipe if reducer not drained 2020-09-12 17:13:32 +08:00
zhoushuguang
8cc09244a0 Metric (#65)
* doc for service metric

* doc for service metric

* doc for service metric

* doc for service metric

Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-09-12 13:08:10 +08:00
Sergey Cheung
21e811887c Markdown lint (#58)
* markdown linter

* format markdown docs

* format exiting markdown docs
2020-09-11 19:42:58 +08:00
kevin
7f0ec14704 update goctl makefile 2020-09-11 18:17:07 +08:00
Keson
d12e9fa2d7 add model&rpc doc (#62) 2020-09-11 16:47:21 +08:00
miaogaolin
ce5961a7d0 fix goctl model (#61) 2020-09-11 16:46:45 +08:00
kingxt
e1d942a799 update doc (#64)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* update doc

* remove update

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-11 16:16:30 +08:00
kingxt
754e631dc4 update quick start (#63)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* update doc

* update readme.md

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-11 16:06:04 +08:00
kevin
72aeac3fa9 add in-process cache doc 2020-09-11 15:30:20 +08:00
kingxt
1c3c8f4bbc add fast create api demo service (#59)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* add fast create api demo: goctl api new

* refactor

* refactor

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-11 15:27:35 +08:00
Keson
17e6cfb7a9 quickly generating rpc demo service (#60)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* add README.md

* format error

* reactor templatex.go

* update project.go & README.md

* add: quickly generate rpc service
2020-09-11 15:26:55 +08:00
kevin
0d151c17f8 update wechat image 2020-09-10 18:05:04 +08:00
miaogaolin
52990550fb fix GOMOD env fetch bug (#55) 2020-09-09 11:43:47 +08:00
zhoushuguang
3a9b9ceace add (#54)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-09-08 21:20:44 +08:00
bittoy
3128d63134 fix goctl model path (#53) 2020-09-08 17:05:22 +08:00
kingxt
4408767981 fix command run path bug (#52)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* optimized go path logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-08 12:19:34 +08:00
kevin
ff7c14c6b6 make chinese readme as default 2020-09-08 09:24:12 +08:00
kevin
520f4d7c1b update readme to add mapreduce link 2020-09-07 23:35:57 +08:00
zhoushuguang
0e674933f3 add mr tool doc (#50)
Co-authored-by: zhoushuguang <zhoushuguang@xiaoheiban.cn>
2020-09-07 22:40:29 +08:00
kingxt
1d12f20ff6 refactor (#49)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* refactor gomod module logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-07 18:47:03 +08:00
kingxt
2b815162f6 refactor (#48)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* refactor gomod module logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-07 18:12:03 +08:00
kingxt
1602f6ce81 refactor gomod logic (#47)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* refactor gomod module logic

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-07 18:04:59 +08:00
kevin
c5cd0d32d1 fix typoin doc 2020-09-07 17:08:06 +08:00
kevin
1cb17311dd add unit test for mapreduce 2020-09-06 18:19:19 +08:00
kevin
e987eb60d3 fix mapreduce problem when reducer doesn't write 2020-09-06 18:13:42 +08:00
kevin
99a863e8be add language link 2020-09-06 16:37:14 +08:00
kevin
5333fb93e5 add bookstore english tutorial 2020-09-06 16:36:18 +08:00
kevin
cb13556461 add language link 2020-09-06 15:38:33 +08:00
kevin
561370d5c9 add shorturl english tutorial 2020-09-06 15:36:12 +08:00
kevin
7c779d0433 fix readme typo 2020-09-05 19:18:15 +08:00
kevin
6814c86fcd add english readme 2020-09-05 19:09:18 +08:00
windk
a1d2ea9d85 fix typo (#38)
fix typo: *shorten.ShortenReq -> *transform.ShortenReq
2020-09-04 17:38:20 +08:00
kevin
4dfbd66323 add goctl description 2020-09-04 15:40:12 +08:00
kevin
dbf556e7d2 update readme 2020-09-04 08:16:17 +08:00
kevin
c0d0e00803 update example doc 2020-09-04 08:13:22 +08:00
kevin
b4aa89fc25 add wechat qrcode 2020-09-04 08:00:10 +08:00
kevin
11dd3d75ec add bookstore example 2020-09-03 23:26:04 +08:00
wwek
167422ac4f fix LF (#37)
* fix LF

* fix  remove export
2020-09-03 22:37:52 +08:00
kevin
a74d73fb2e fix bookstore example 2020-09-03 20:23:27 +08:00
kevin
81a9ada2d9 add bookstore example 2020-09-03 18:09:12 +08:00
kevin
55c9c3f3dd replace clickhouse driver to the official one 2020-09-03 16:58:31 +08:00
kevin
8dd93d59a0 refactor code 2020-09-03 14:00:09 +08:00
Keson
3a4e1cbb33 fix bug: miss time import (#36)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* optimize gomod cache

* add README.md

* format error

* reactor templatex.go

* update project.go & README.md

* fix bug: miss time import
2020-09-03 13:57:28 +08:00
kevin
d1129e3974 refactor 2020-09-03 10:15:14 +08:00
Leonard Wang
1e85f74fd8 fix shorturl example code (#35) 2020-09-03 08:34:11 +08:00
kingxt
33eb2936e8 fix: root path on windows bug (#34)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* fix bug on windows

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-02 15:14:27 +08:00
kevin
b7a018b33a add shorturl example code 2020-09-01 16:04:39 +08:00
kevin
ea1c9aa250 support go 1.13 2020-09-01 15:04:01 +08:00
kevin
fbad810cd1 update shorturl doc 2020-09-01 13:46:05 +08:00
kevin
6b15475ccd update shorturl doc 2020-09-01 13:38:16 +08:00
kingxt
5c0c3ea467 trim space (#31)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* trim space

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-01 11:46:10 +08:00
kingxt
89f3712347 remove no need empty line (#29)
* rebase upstream

* rebase

* trim no need line

* trim no need line

* trim no need line

* trim no need line

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-09-01 11:08:19 +08:00
kevin
af7acdd843 fix doc errors 2020-09-01 10:26:16 +08:00
kevin
7ffa3349a9 update readme 2020-08-31 22:45:55 +08:00
kevin
f03862c378 update docs 2020-08-31 22:37:43 +08:00
kevin
fe3e70a60f update shorturl doc 2020-08-31 20:52:29 +08:00
kevin
36174ba5cc make svcCtx as a member for better code generation 2020-08-31 12:32:13 +08:00
kevin
7b17b3604a fix dockerfile generation 2020-08-31 12:27:38 +08:00
kevin
eb40c2731d remove files 2020-08-30 23:56:51 +08:00
kevin
618bec5075 remove makefile generation 2020-08-30 23:52:51 +08:00
kevin
5821b7324e update readme 2020-08-30 23:34:57 +08:00
kevin
befdaab542 update goctl makefile 2020-08-30 18:22:46 +08:00
kevin
431be8ed9d make goctl work on linux 2020-08-30 16:37:34 +08:00
kevin
3c688c319e update shorturl doc 2020-08-29 23:42:42 +08:00
kevin
59ffa75c00 fix typo in doc 2020-08-29 23:33:34 +08:00
kevin
09340e82a7 fix doc error 2020-08-29 22:51:48 +08:00
kevin
6c4a4be5d2 update shorturl doc 2020-08-29 20:27:52 +08:00
kevin
6e3d99e869 reorg imports 2020-08-29 14:31:51 +08:00
Keson
0f97b2019a rpc generation support windows (#28)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* update usage

* fixed env prepare for project in go path

* optimize gomod cache

* add README.md

* format error

* reactor templatex.go

* remove waste code

* update project.go & README.md

* update project.go & README.md

* rpc generation supports windows
2020-08-29 14:30:17 +08:00
kevin
0cf4ed46a1 update shorturl doc 2020-08-29 00:36:36 +08:00
kevin
3affe62ae4 update shorturl doc 2020-08-29 00:28:57 +08:00
Keson
0734bbcab3 update handler generation (#27)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* update usage

* fixed env prepare for project in go path

* optimize gomod cache

* add README.md

* format error

* reactor templatex.go

* remove waste code

* update project.go & README.md

* update project.go & README.md
2020-08-29 00:15:15 +08:00
kevin
f411178a4f refine rpc generator 2020-08-28 22:44:41 +08:00
kevin
72132ce399 refine goctl rpc generator 2020-08-28 21:22:35 +08:00
Keson
db16115037 rpc service generation (#26)
* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* update usage

* fixed env prepare for project in go path

* optimize gomod cache

* add README.md

* format error

* reactor templatex.go

* remove waste code
2020-08-28 19:24:58 +08:00
kevin
71bbf91a63 update shorturl doc 2020-08-27 23:29:56 +08:00
kevin
69ccc61cfe update shorturl doc 2020-08-27 23:16:07 +08:00
kevin
a94cf653f0 better image rendering 2020-08-27 23:00:40 +08:00
kevin
77e23ad65d add quick example 2020-08-27 22:54:18 +08:00
kingxt
38806e7237 fix config yaml gen (#25)
* optimized

* format

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-08-27 15:23:19 +08:00
kevin
a987d12237 sort imports on api generation 2020-08-27 14:40:05 +08:00
kevin
33208e6ef6 return zero value instead of nil on generated logic 2020-08-27 13:49:31 +08:00
kevin
5d8a3c07cd disable cpu stat in wsl linux 2020-08-27 13:22:44 +08:00
kevin
1c24e71568 use yaml, and detect go.mod in current dir 2020-08-27 11:44:35 +08:00
kevin
229544f3ca move test code into internal package 2020-08-26 15:18:45 +08:00
kevin
c575fa7f95 fix ci script 2020-08-26 14:59:04 +08:00
kevin
fe2252184a update ci configuration 2020-08-26 14:53:12 +08:00
kevin
1a8014c704 add more tests 2020-08-26 14:32:35 +08:00
kevin
30e52707ae add more tests 2020-08-26 14:19:16 +08:00
kingxt
73b61e09ed fix format (#23)
* fir format

* fix bug

Co-authored-by: kingxt <dream4kingxt@163.com>
2020-08-26 11:32:55 +08:00
kevin
9b8595a85e add more tests 2020-08-25 22:42:42 +08:00
kevin
015e284515 add more tests 2020-08-25 20:21:59 +08:00
474 changed files with 14880 additions and 3650 deletions

View File

@@ -1,4 +1,4 @@
ignore:
- "doc"
- "example"
- "tools"
- "tools"

View File

@@ -7,7 +7,6 @@ on:
branches: [ master ]
jobs:
build:
name: Build
runs-on: ubuntu-latest

View File

@@ -1,36 +0,0 @@
run:
# concurrency: 6
timeout: 5m
skip-dirs:
- core
- doc
- example
- rest
- rpcx
- tools
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
# - dupl
linters-settings:
issues:
exclude-rules:
- linters:
- staticcheck
text: 'SA1019: (baseresponse.BoolResponse|oldresponse.FormatBadRequestResponse|oldresponse.FormatResponse)|SA5008: unknown JSON option ("optional"|"default=|"range=|"options=)'

View File

@@ -13,15 +13,13 @@ const (
// maps as k in the error rate table
maps = 14
setScript = `
local key = KEYS[1]
for _, offset in ipairs(ARGV) do
redis.call("setbit", key, offset, 1)
redis.call("setbit", KEYS[1], offset, 1)
end
`
testScript = `
local key = KEYS[1]
for _, offset in ipairs(ARGV) do
if tonumber(redis.call("getbit", key, offset)) == 0 then
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
return false
end
end
@@ -39,7 +37,6 @@ type (
BloomFilter struct {
bits uint
maps uint
bitSet BitSetProvider
}
)

View File

@@ -2,18 +2,18 @@ package bloom
import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/lang"
"github.com/tal-tech/go-zero/core/stores/redis"
)
func TestRedisBitSet_New_Set_Test(t *testing.T) {
s, err := miniredis.Run()
if err != nil {
t.Error("Miniredis could not start")
}
defer s.Close()
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
store := redis.NewRedis(s.Addr(), redis.NodeType)
bitSet := newRedisBitSet(store, "test_key", 1024)
@@ -46,11 +46,9 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
}
func TestRedisBitSet_Add(t *testing.T) {
s, err := miniredis.Run()
if err != nil {
t.Error("Miniredis could not start")
}
defer s.Close()
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
store := redis.NewRedis(s.Addr(), redis.NodeType)
filter := New(store, "test_key", 64)
@@ -60,3 +58,22 @@ func TestRedisBitSet_Add(t *testing.T) {
assert.Nil(t, err)
assert.True(t, ok)
}
func createMiniRedis() (r *miniredis.Miniredis, clean func(), err error) {
r, err = miniredis.Run()
if err != nil {
return nil, nil, err
}
return r, func() {
ch := make(chan lang.PlaceholderType)
go func() {
r.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@@ -5,17 +5,12 @@ import (
"fmt"
"strings"
"sync"
"time"
"github.com/tal-tech/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/proc"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stringx"
)
const (
StateClosed State = iota
StateOpen
"github.com/tal-tech/go-zero/core/timex"
)
const (
@@ -27,11 +22,10 @@ const (
var ErrServiceUnavailable = errors.New("circuit breaker is open")
type (
State = int32
Acceptable func(err error) bool
Breaker interface {
// Name returns the name of the netflixBreaker.
// Name returns the name of the Breaker.
Name() string
// Allow checks if the request is allowed.
@@ -40,34 +34,34 @@ type (
// If not allow, ErrServiceUnavailable will be returned.
Allow() (Promise, error)
// Do runs the given request if the netflixBreaker accepts it.
// Do returns an error instantly if the netflixBreaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error
// Do runs the given request if the Breaker accepts it.
// Do returns an error instantly if the Breaker rejects the request.
// If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again.
Do(req func() error) error
// DoWithAcceptable runs the given request if the netflixBreaker accepts it.
// Do returns an error instantly if the netflixBreaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error
// DoWithAcceptable runs the given request if the Breaker accepts it.
// DoWithAcceptable returns an error instantly if the Breaker rejects the request.
// If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again.
// acceptable checks if it's a successful call, even if the err is not nil.
DoWithAcceptable(req func() error, acceptable Acceptable) error
// DoWithFallback runs the given request if the netflixBreaker accepts it.
// DoWithFallback runs the fallback if the netflixBreaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error
// DoWithFallback runs the given request if the Breaker accepts it.
// DoWithFallback runs the fallback if the Breaker rejects the request.
// If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again.
DoWithFallback(req func() error, fallback func(err error) error) error
// DoWithFallbackAcceptable runs the given request if the netflixBreaker accepts it.
// DoWithFallback runs the fallback if the netflixBreaker rejects the request.
// If a panic occurs in the request, the netflixBreaker handles it as an error
// DoWithFallbackAcceptable runs the given request if the Breaker accepts it.
// DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request.
// If a panic occurs in the request, the Breaker handles it as an error
// and causes the same panic again.
// 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
}
BreakerOption func(breaker *circuitBreaker)
Option func(breaker *circuitBreaker)
Promise interface {
Accept()
@@ -95,7 +89,7 @@ type (
}
)
func NewBreaker(opts ...BreakerOption) Breaker {
func NewBreaker(opts ...Option) Breaker {
var b circuitBreaker
for _, opt := range opts {
opt(&b)
@@ -133,7 +127,7 @@ func (cb *circuitBreaker) Name() string {
return cb.name
}
func WithName(name string) BreakerOption {
func WithName(name string) Option {
return func(b *circuitBreaker) {
b.name = name
}
@@ -195,23 +189,23 @@ type errorWindow struct {
func (ew *errorWindow) add(reason string) {
ew.lock.Lock()
ew.reasons[ew.index] = fmt.Sprintf("%s %s", time.Now().Format(timeFormat), reason)
ew.reasons[ew.index] = fmt.Sprintf("%s %s", timex.Time().Format(timeFormat), reason)
ew.index = (ew.index + 1) % numHistoryReasons
ew.count = mathx.MinInt(ew.count+1, numHistoryReasons)
ew.lock.Unlock()
}
func (ew *errorWindow) String() string {
var builder strings.Builder
var reasons []string
ew.lock.Lock()
for i := ew.index + ew.count - 1; i >= ew.index; i-- {
builder.WriteString(ew.reasons[i%numHistoryReasons])
builder.WriteByte('\n')
// reverse order
for i := ew.index - 1; i >= ew.index-ew.count; i-- {
reasons = append(reasons, ew.reasons[(i+numHistoryReasons)%numHistoryReasons])
}
ew.lock.Unlock()
return builder.String()
return strings.Join(reasons, "\n")
}
type promiseWithReason struct {

View File

@@ -2,7 +2,9 @@ package breaker
import (
"errors"
"fmt"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -33,6 +35,84 @@ func TestLogReason(t *testing.T) {
assert.Equal(t, numHistoryReasons, errs.count)
}
func TestErrorWindow(t *testing.T) {
tests := []struct {
name string
reasons []string
}{
{
name: "no error",
},
{
name: "one error",
reasons: []string{"foo"},
},
{
name: "two errors",
reasons: []string{"foo", "bar"},
},
{
name: "five errors",
reasons: []string{"first", "second", "third", "fourth", "fifth"},
},
{
name: "six errors",
reasons: []string{"first", "second", "third", "fourth", "fifth", "sixth"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var ew errorWindow
for _, reason := range test.reasons {
ew.add(reason)
}
var reasons []string
if len(test.reasons) > numHistoryReasons {
reasons = test.reasons[len(test.reasons)-numHistoryReasons:]
} else {
reasons = test.reasons
}
for _, reason := range reasons {
assert.True(t, strings.Contains(ew.String(), reason), fmt.Sprintf("actual: %s", ew.String()))
}
})
}
}
func TestPromiseWithReason(t *testing.T) {
tests := []struct {
name string
reason string
expect string
}{
{
name: "success",
},
{
name: "success",
reason: "fail",
expect: "fail",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
promise := promiseWithReason{
promise: new(mockedPromise),
errWin: new(errorWindow),
}
if len(test.reason) == 0 {
promise.Accept()
} else {
promise.Reject(test.reason)
}
assert.True(t, strings.Contains(promise.errWin.String(), test.expect))
})
}
}
func BenchmarkGoogleBreaker(b *testing.B) {
br := NewBreaker()
for i := 0; i < b.N; i++ {
@@ -41,3 +121,12 @@ func BenchmarkGoogleBreaker(b *testing.B) {
})
}
}
type mockedPromise struct {
}
func (m *mockedPromise) Accept() {
}
func (m *mockedPromise) Reject() {
}

View File

@@ -41,10 +41,13 @@ func GetBreaker(name string) Breaker {
}
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
}
@@ -55,20 +58,5 @@ func NoBreakFor(name string) {
}
func do(name string, execute func(b Breaker) error) error {
lock.RLock()
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)
return execute(GetBreaker(name))
}

View File

@@ -2,7 +2,6 @@ package breaker
import (
"math"
"sync/atomic"
"time"
"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/
type googleBreaker struct {
k float64
state int32
stat *collection.RollingWindow
proba *mathx.Proba
}
@@ -32,7 +30,6 @@ func newGoogleBreaker() *googleBreaker {
return &googleBreaker{
stat: st,
k: k,
state: StateClosed,
proba: mathx.NewProba(),
}
}
@@ -43,15 +40,9 @@ func (b *googleBreaker) accept() error {
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
if dropRatio <= 0 {
if atomic.LoadInt32(&b.state) == StateOpen {
atomic.CompareAndSwapInt32(&b.state, StateOpen, StateClosed)
}
return nil
}
if atomic.LoadInt32(&b.state) == StateClosed {
atomic.CompareAndSwapInt32(&b.state, StateClosed, StateOpen)
}
if b.proba.TrueOnProba(dropRatio) {
return ErrServiceUnavailable
}

View File

@@ -2,7 +2,6 @@ package breaker
import (
"errors"
"math"
"math/rand"
"testing"
"time"
@@ -27,7 +26,6 @@ func getGoogleBreaker() *googleBreaker {
return &googleBreaker{
stat: st,
k: 5,
state: StateClosed,
proba: mathx.NewProba(),
}
}
@@ -158,7 +156,7 @@ func TestGoogleBreakerSelfProtection(t *testing.T) {
t.Run("total request > 100, total < 2 * success", func(t *testing.T) {
b := getGoogleBreaker()
size := rand.Intn(10000)
accepts := int(math.Ceil(float64(size))) + 1
accepts := size + 1
markSuccess(b, accepts)
markFailed(b, size-accepts)
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.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"
)
const unzipLimit = 100 * 1024 * 1024 // 100MB
func Gzip(bs []byte) []byte {
var b bytes.Buffer
@@ -24,8 +26,7 @@ func Gunzip(bs []byte) ([]byte, error) {
defer r.Close()
var c bytes.Buffer
_, err = io.Copy(&c, r)
if err != nil {
if _, err = io.Copy(&c, io.LimitReader(r, unzipLimit)); err != nil {
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
lock sync.Mutex
data map[string]interface{}
evicts *list.List
expire time.Duration
timingWheel *TimingWheel
lruCache lru
@@ -82,12 +81,7 @@ func (c *Cache) Del(key string) {
}
func (c *Cache) Get(key string) (interface{}, bool) {
c.lock.Lock()
value, ok := c.data[key]
if ok {
c.lruCache.add(key)
}
c.lock.Unlock()
value, ok := c.doGet(key)
if ok {
c.stats.IncrementHit()
} else {
@@ -113,12 +107,25 @@ func (c *Cache) Set(key string, value interface{}) {
}
func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}, error) {
val, fresh, err := c.barrier.DoEx(key, func() (interface{}, error) {
if val, ok := c.doGet(key); ok {
c.stats.IncrementHit()
return val, nil
}
var fresh bool
val, err := c.barrier.Do(key, func() (interface{}, error) {
// because O(1) on map search in memory, and fetch is an IO query
// so we do double check, cache might be taken by another call
if val, ok := c.doGet(key); ok {
return val, nil
}
v, e := fetch()
if e != nil {
return nil, e
}
fresh = true
c.Set(key, v)
return v, nil
})
@@ -137,6 +144,18 @@ func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}
return val, nil
}
func (c *Cache) doGet(key string) (interface{}, bool) {
c.lock.Lock()
defer c.lock.Unlock()
value, ok := c.data[key]
if ok {
c.lruCache.add(key)
}
return value, ok
}
func (c *Cache) onEvict(key string) {
// already locked
delete(c.data, key)
@@ -258,18 +277,15 @@ func (cs *cacheStat) statLoop() {
ticker := time.NewTicker(statInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
hit := atomic.SwapUint64(&cs.hit, 0)
miss := atomic.SwapUint64(&cs.miss, 0)
total := hit + miss
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)
for range ticker.C {
hit := atomic.SwapUint64(&cs.hit, 0)
miss := atomic.SwapUint64(&cs.miss, 0)
total := hit + miss
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)
}
}

View File

@@ -1,6 +1,7 @@
package collection
import (
"errors"
"strconv"
"sync"
"sync/atomic"
@@ -10,6 +11,8 @@ import (
"github.com/stretchr/testify/assert"
)
var errDummy = errors.New("dummy")
func TestCacheSet(t *testing.T) {
cache, err := NewCache(time.Second*2, WithName("any"))
assert.Nil(t, err)
@@ -63,6 +66,54 @@ func TestCacheTake(t *testing.T) {
assert.Equal(t, int32(1), atomic.LoadInt32(&count))
}
func TestCacheTakeExists(t *testing.T) {
cache, err := NewCache(time.Second * 2)
assert.Nil(t, err)
var count int32
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
cache.Set("first", "first element")
cache.Take("first", func() (interface{}, error) {
atomic.AddInt32(&count, 1)
time.Sleep(time.Millisecond * 100)
return "first element", nil
})
wg.Done()
}()
}
wg.Wait()
assert.Equal(t, 1, cache.size())
assert.Equal(t, int32(0), atomic.LoadInt32(&count))
}
func TestCacheTakeError(t *testing.T) {
cache, err := NewCache(time.Second * 2)
assert.Nil(t, err)
var count int32
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
_, err := cache.Take("first", func() (interface{}, error) {
atomic.AddInt32(&count, 1)
time.Sleep(time.Millisecond * 100)
return "", errDummy
})
assert.Equal(t, errDummy, err)
wg.Done()
}()
}
wg.Wait()
assert.Equal(t, 0, cache.size())
assert.Equal(t, int32(1), atomic.LoadInt32(&count))
}
func TestCacheWithLruEvicts(t *testing.T) {
cache, err := NewCache(time.Minute, WithLimit(3))
assert.Nil(t, err)

View File

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

View File

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

View File

@@ -22,6 +22,10 @@ type (
)
func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow {
if size < 1 {
panic("size must be greater than 0")
}
w := &RollingWindow{
size: size,
win: newWindow(size),

View File

@@ -11,6 +11,13 @@ import (
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) {
const size = 3
r := NewRollingWindow(size, duration)
@@ -81,7 +88,7 @@ func TestRollingWindowReduce(t *testing.T) {
for _, test := range tests {
t.Run(stringx.Rand(), func(t *testing.T) {
r := test.win
for x := 0; x < size; x = x + 1 {
for x := 0; x < size; x++ {
for i := 0; i <= x; i++ {
r.Add(float64(i))
}

View File

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

View File

@@ -5,8 +5,13 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
)
func init() {
logx.Disable()
}
func BenchmarkRawSet(b *testing.B) {
m := make(map[interface{}]struct{})
for i := 0; i < b.N; i++ {
@@ -147,3 +152,51 @@ func TestCount(t *testing.T) {
// then
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)
}

58
core/conf/config_test.go Normal file
View File

@@ -0,0 +1,58 @@
package conf
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/hash"
)
func TestConfigJson(t *testing.T) {
tests := []string{
".json",
".yaml",
".yml",
}
text := `{
"a": "foo",
"b": 1
}`
for _, test := range tests {
test := test
t.Run(test, func(t *testing.T) {
t.Parallel()
tmpfile, err := createTempFile(test, text)
assert.Nil(t, err)
defer os.Remove(tmpfile)
var val struct {
A string `json:"a"`
B int `json:"b"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
})
}
}
func createTempFile(ext, text string) (string, error) {
tmpfile, err := ioutil.TempFile(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
if err != nil {
return "", err
}
if err := ioutil.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
return "", err
}
filename := tmpfile.Name()
if err = tmpfile.Close(); err != nil {
return "", err
}
return filename, nil
}

View File

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

View File

@@ -41,3 +41,8 @@ func TestSetInt(t *testing.T) {
props.SetInt(key, value)
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) {
deadline := time.Now().Add(time.Second)
ctx, _ := context.WithDeadline(context.Background(), deadline)
ctx, _ = ShrinkDeadline(ctx, time.Minute)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
ctx, cancel = ShrinkDeadline(ctx, time.Minute)
defer cancel()
dl, ok := ctx.Deadline()
assert.True(t, ok)
assert.Equal(t, deadline, dl)
@@ -19,8 +21,10 @@ func TestShrinkDeadlineLess(t *testing.T) {
func TestShrinkDeadlineMore(t *testing.T) {
deadline := time.Now().Add(time.Minute)
ctx, _ := context.WithDeadline(context.Background(), deadline)
ctx, _ = ShrinkDeadline(ctx, time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
ctx, cancel = ShrinkDeadline(ctx, time.Second)
defer cancel()
dl, ok := ctx.Deadline()
assert.True(t, ok)
assert.True(t, dl.Before(deadline))

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
//go:generate mockgen -package internal -destination listener_mock.go -source listener.go Listener
package internal
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

@@ -40,6 +40,7 @@ spec:
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
- --auto-compaction-retention=1
image: quay.io/coreos/etcd:latest
name: etcd0
ports:
@@ -111,6 +112,7 @@ spec:
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
- --auto-compaction-retention=1
image: quay.io/coreos/etcd:latest
name: etcd1
ports:
@@ -182,6 +184,7 @@ spec:
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
- --auto-compaction-retention=1
image: quay.io/coreos/etcd:latest
name: etcd2
ports:
@@ -253,6 +256,7 @@ spec:
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
- --auto-compaction-retention=1
image: quay.io/coreos/etcd:latest
name: etcd3
ports:
@@ -324,6 +328,7 @@ spec:
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
- --initial-cluster-state
- new
- --auto-compaction-retention=1
image: quay.io/coreos/etcd:latest
name: etcd4
ports:

View File

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

View File

@@ -1,21 +1,18 @@
package errorx
import "sync"
import "sync/atomic"
type AtomicError struct {
err error
lock sync.Mutex
err atomic.Value // error
}
func (ae *AtomicError) Set(err error) {
ae.lock.Lock()
ae.err = err
ae.lock.Unlock()
ae.err.Store(err)
}
func (ae *AtomicError) Load() error {
ae.lock.Lock()
err := ae.err
ae.lock.Unlock()
return err
if v := ae.err.Load(); v != nil {
return v.(error)
}
return nil
}

View File

@@ -2,6 +2,8 @@ package errorx
import (
"errors"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
@@ -19,3 +21,53 @@ func TestAtomicErrorNil(t *testing.T) {
var err AtomicError
assert.Nil(t, err.Load())
}
func BenchmarkAtomicError(b *testing.B) {
var aerr AtomicError
wg := sync.WaitGroup{}
b.Run("Load", func(b *testing.B) {
var done uint32
go func() {
for {
if atomic.LoadUint32(&done) != 0 {
break
}
wg.Add(1)
go func() {
aerr.Set(errDummy)
wg.Done()
}()
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = aerr.Load()
}
b.StopTimer()
atomic.StoreUint32(&done, 1)
wg.Wait()
})
b.Run("Set", func(b *testing.B) {
var done uint32
go func() {
for {
if atomic.LoadUint32(&done) != 0 {
break
}
wg.Add(1)
go func() {
_ = aerr.Load()
wg.Done()
}()
}
}()
b.ResetTimer()
for i := 0; i < b.N; i++ {
aerr.Set(errDummy)
}
b.StopTimer()
atomic.StoreUint32(&done, 1)
wg.Wait()
})
}

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)
lock.Lock()
defer lock.Unlock()
for _, i := range tasks {
result = append(result, i)
}
result = append(result, tasks...)
}, WithBulkTasks(1000))
for i := 0; i < total; i++ {
assert.Nil(t, exec.Add(i))

View File

@@ -68,6 +68,7 @@ func Range(source <-chan interface{}) Stream {
}
// 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 {
if n < 0 {
n = 0
@@ -84,6 +85,14 @@ func (p Stream) Buffer(n int) Stream {
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.
func (p Stream) Distinct(fn KeyFunc) Stream {
source := make(chan interface{})
@@ -151,6 +160,10 @@ func (p Stream) Group(fn KeyFunc) Stream {
}
func (p Stream) Head(n int64) Stream {
if n < 1 {
panic("n must be greater than 0")
}
source := make(chan interface{})
go func() {
@@ -235,7 +248,37 @@ func (p Stream) Sort(less LessFunc) Stream {
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 {
if n < 1 {
panic("n should be greater than 0")
}
source := make(chan interface{})
go func() {

View File

@@ -49,6 +49,36 @@ func TestBufferNegative(t *testing.T) {
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) {
var count int32
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)
}
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) {
var result int
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) {
var result int
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)
}
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) {
var result int
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

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

View File

@@ -135,6 +135,7 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
passCounter: passCounter,
rtCounter: rtCounter,
windows: buckets,
dropTime: syncx.NewAtomicDuration(),
droppedRecently: syncx.NewAtomicBool(),
}
// cpu >= 800, inflight < maxPass
@@ -160,6 +161,40 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
}
shedder.avgFlying = 80
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) {

View File

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

View File

@@ -8,55 +8,60 @@ import (
"github.com/tal-tech/go-zero/core/timex"
)
const customCallerDepth = 3
const durationCallerDepth = 3
type customLog logEntry
type durationLogger logEntry
func WithDuration(d time.Duration) Logger {
return customLog{
return &durationLogger{
Duration: timex.ReprOfDuration(d),
}
}
func (l customLog) Error(v ...interface{}) {
func (l *durationLogger) Error(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
}
}
func (l customLog) Errorf(format string, v ...interface{}) {
func (l *durationLogger) Errorf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
}
}
func (l customLog) Info(v ...interface{}) {
func (l *durationLogger) Info(v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprint(v...))
}
}
func (l customLog) Infof(format string, v ...interface{}) {
func (l *durationLogger) Infof(format string, v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
}
}
func (l customLog) Slow(v ...interface{}) {
func (l *durationLogger) Slow(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprint(v...))
}
}
func (l customLog) Slowf(format string, v ...interface{}) {
func (l *durationLogger) Slowf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
}
}
func (l customLog) write(writer io.Writer, level, content string) {
func (l *durationLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *durationLogger) write(writer io.Writer, level, content string) {
l.Timestamp = getTimestamp()
l.Level = level
l.Content = content
outputJson(writer, logEntry(l))
outputJson(writer, logEntry(*l))
}

View File

@@ -0,0 +1,52 @@
package logx
import (
"log"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestWithDurationError(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Error("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationErrorf(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Errorf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationInfo(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Info("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationInfof(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Infof("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationSlow(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).Slow("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}
func TestWithDurationSlowf(t *testing.T) {
var builder strings.Builder
log.SetOutput(&builder)
WithDuration(time.Second).WithDuration(time.Hour).Slowf("foo")
assert.True(t, strings.Contains(builder.String(), "duration"), builder.String())
}

View File

@@ -15,6 +15,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"
"github.com/tal-tech/go-zero/core/iox"
"github.com/tal-tech/go-zero/core/sysx"
@@ -42,6 +43,7 @@ const (
consoleMode = "console"
volumeMode = "volume"
levelAlert = "alert"
levelInfo = "info"
levelError = "error"
levelSevere = "severe"
@@ -96,6 +98,7 @@ type (
Infof(string, ...interface{})
Slow(...interface{})
Slowf(string, ...interface{})
WithDuration(time.Duration) Logger
}
)
@@ -119,6 +122,10 @@ func SetUp(c LogConf) error {
}
}
func Alert(v string) {
output(errorLog, levelAlert, v)
}
func Close() error {
if writeConsole {
return nil

View File

@@ -6,8 +6,10 @@ import (
"io"
"io/ioutil"
"log"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
@@ -21,10 +23,13 @@ var (
)
type mockWriter struct {
lock sync.Mutex
builder strings.Builder
}
func (mw *mockWriter) Write(data []byte) (int, error) {
mw.lock.Lock()
defer mw.lock.Unlock()
return mw.builder.Write(data)
}
@@ -32,12 +37,22 @@ func (mw *mockWriter) Close() error {
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() {
mw.lock.Lock()
defer mw.lock.Unlock()
mw.builder.Reset()
}
func (mw *mockWriter) Contains(text string) bool {
return strings.Contains(mw.builder.String(), text)
func (mw *mockWriter) String() string {
mw.lock.Lock()
defer mw.lock.Unlock()
return mw.builder.String()
}
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)))
}
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) {
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
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) {
const message = "hello there"
writer := new(mockWriter)
@@ -135,6 +198,68 @@ func TestMustNil(t *testing.T) {
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) {
for i := 0; i < b.N; i++ {
var buf []byte
@@ -232,7 +357,7 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
t.Error(err)
}
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) {
@@ -252,4 +377,10 @@ func testSetLevelTwiceWithMode(t *testing.T, mode string) {
atomic.StoreUint32(&initialized, 1)
Info(message)
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) {
if l.compress {
defer func() {
if r := recover(); r != nil {
ErrorStack(r)
}
}()
compressLogFile(file)
if !l.compress {
return
}
defer func() {
if r := recover(); r != nil {
ErrorStack(r)
}
}()
compressLogFile(file)
}
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) {
CollectSysLog()
content := getContent(captureOutput(func() {
log.Printf(testlog)
log.Print(testlog)
}))
assert.True(t, strings.Contains(content, testlog))
}
@@ -33,10 +33,10 @@ func captureOutput(f func()) string {
writer := new(mockWriter)
infoLog = writer
prevLevel := logLevel
logLevel = InfoLevel
prevLevel := atomic.LoadUint32(&logLevel)
SetLevel(InfoLevel)
f()
logLevel = prevLevel
SetLevel(prevLevel)
return writer.builder.String()
}

View File

@@ -1,49 +0,0 @@
package logx
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/trace/tracespec"
)
const (
mockTraceId = "mock-trace-id"
mockSpanId = "mock-span-id"
)
var mock tracespec.Trace = new(mockTrace)
func TestTraceLog(t *testing.T) {
var buf strings.Builder
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
WithContext(ctx).(tracingEntry).write(&buf, levelInfo, testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
assert.True(t, strings.Contains(buf.String(), mockSpanId))
}
type mockTrace struct{}
func (t mockTrace) TraceId() string {
return mockTraceId
}
func (t mockTrace) SpanId() string {
return mockSpanId
}
func (t mockTrace) Finish() {
}
func (t mockTrace) Fork(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) {
return nil, nil
}
func (t mockTrace) Follow(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) {
return nil, nil
}
func (t mockTrace) Visit(fn func(key string, val string) bool) {
}

View File

@@ -4,54 +4,61 @@ import (
"context"
"fmt"
"io"
"time"
"github.com/tal-tech/go-zero/core/timex"
"github.com/tal-tech/go-zero/core/trace/tracespec"
)
type tracingEntry struct {
type traceLogger struct {
logEntry
Trace string `json:"trace,omitempty"`
Span string `json:"span,omitempty"`
ctx context.Context `json:"-"`
Trace string `json:"trace,omitempty"`
Span string `json:"span,omitempty"`
ctx context.Context
}
func (l tracingEntry) Error(v ...interface{}) {
func (l *traceLogger) Error(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprint(v...), durationCallerDepth))
}
}
func (l tracingEntry) Errorf(format string, v ...interface{}) {
func (l *traceLogger) Errorf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), customCallerDepth))
l.write(errorLog, levelError, formatWithCaller(fmt.Sprintf(format, v...), durationCallerDepth))
}
}
func (l tracingEntry) Info(v ...interface{}) {
func (l *traceLogger) Info(v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprint(v...))
}
}
func (l tracingEntry) Infof(format string, v ...interface{}) {
func (l *traceLogger) Infof(format string, v ...interface{}) {
if shouldLog(InfoLevel) {
l.write(infoLog, levelInfo, fmt.Sprintf(format, v...))
}
}
func (l tracingEntry) Slow(v ...interface{}) {
func (l *traceLogger) Slow(v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprint(v...))
}
}
func (l tracingEntry) Slowf(format string, v ...interface{}) {
func (l *traceLogger) Slowf(format string, v ...interface{}) {
if shouldLog(ErrorLevel) {
l.write(slowLog, levelSlow, fmt.Sprintf(format, v...))
}
}
func (l tracingEntry) write(writer io.Writer, level, content string) {
func (l *traceLogger) WithDuration(duration time.Duration) Logger {
l.Duration = timex.ReprOfDuration(duration)
return l
}
func (l *traceLogger) write(writer io.Writer, level, content string) {
l.Timestamp = getTimestamp()
l.Level = level
l.Content = content
@@ -61,7 +68,7 @@ func (l tracingEntry) write(writer io.Writer, level, content string) {
}
func WithContext(ctx context.Context) Logger {
return tracingEntry{
return &traceLogger{
ctx: ctx,
}
}

View File

@@ -0,0 +1,115 @@
package logx
import (
"context"
"log"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/trace/tracespec"
)
const (
mockTraceId = "mock-trace-id"
mockSpanId = "mock-span-id"
)
var mock tracespec.Trace = new(mockTrace)
func TestTraceLog(t *testing.T) {
var buf mockWriter
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
assert.True(t, strings.Contains(buf.String(), mockTraceId))
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{}
func (t mockTrace) TraceId() string {
return mockTraceId
}
func (t mockTrace) SpanId() string {
return mockSpanId
}
func (t mockTrace) Finish() {
}
func (t mockTrace) Fork(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) {
return nil, nil
}
func (t mockTrace) Follow(ctx context.Context, serviceName, operationName string) (context.Context, tracespec.Trace) {
return nil, nil
}
func (t mockTrace) Visit(fn func(key string, val string) bool) {
}

View File

@@ -0,0 +1,31 @@
package mapping
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
type Bar struct {
Val string `json:"val"`
}
func TestFieldOptionOptionalDep(t *testing.T) {
var bar Bar
rt := reflect.TypeOf(bar)
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
val, opt, err := parseKeyAndOptions(jsonTagKey, field)
assert.Equal(t, "val", val)
assert.Nil(t, opt)
assert.Nil(t, err)
}
// check nil working
var o *fieldOptions
check := func(o *fieldOptions) {
assert.Equal(t, 0, len(o.optionalDep()))
}
check(o)
}

View File

@@ -23,6 +23,7 @@ const (
var (
errTypeMismatch = errors.New("type mismatch")
errValueNotSettable = errors.New("value is not settable")
errValueNotStruct = errors.New("value type is not struct")
keyUnmarshaler = NewUnmarshaler(defaultKeyName)
cacheKeys atomic.Value
cacheKeysLock sync.Mutex
@@ -80,6 +81,10 @@ func (u *Unmarshaler) unmarshalWithFullName(m Valuer, v interface{}, fullName st
}
rte := reflect.TypeOf(v).Elem()
if rte.Kind() != reflect.Struct {
return errValueNotStruct
}
rve := rv.Elem()
numFields := rte.NumField()
for i := 0; i < numFields; i++ {
@@ -345,7 +350,7 @@ func (u *Unmarshaler) processNamedFieldWithValue(field reflect.StructField, valu
options := opts.options()
if len(options) > 0 {
if !stringx.Contains(options, mapValue.(string)) {
return fmt.Errorf(`error: value "%s" for field "%s" is not defined in opts "%v"`,
return fmt.Errorf(`error: value "%s" for field "%s" is not defined in options "%v"`,
mapValue, key, options)
}
}

View File

@@ -14,6 +14,13 @@ import (
// so we only can test to 62 bits.
const maxUintBitsToTest = 62
func TestUnmarshalWithFullNameNotStruct(t *testing.T) {
var s map[string]interface{}
content := []byte(`{"name":"xiaoming"}`)
err := UnmarshalJsonBytes(content, &s)
assert.Equal(t, errValueNotStruct, err)
}
func TestUnmarshalWithoutTagName(t *testing.T) {
type inner struct {
Optional bool `key:",optional"`
@@ -2380,6 +2387,13 @@ func TestUnmarshalNestedMapSimpleTypeMatch(t *testing.T) {
assert.Equal(t, "1", c.Anything["id"])
}
func TestUnmarshalValuer(t *testing.T) {
unmarshaler := NewUnmarshaler(jsonTagKey)
var foo string
err := unmarshaler.UnmarshalValuer(nil, foo)
assert.NotNil(t, err)
}
func BenchmarkUnmarshalString(b *testing.B) {
type inner struct {
Value string `key:"value"`

View File

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

View File

@@ -16,7 +16,10 @@ const (
minWorkers = 1
)
var ErrCancelWithNil = errors.New("mapreduce cancelled with nil")
var (
ErrCancelWithNil = errors.New("mapreduce cancelled with nil")
ErrReduceNoOutput = errors.New("reduce not writing value")
)
type (
GenerateFunc func(source chan<- interface{})
@@ -76,7 +79,7 @@ func Map(generate GenerateFunc, mapper MapFunc, opts ...Option) chan interface{}
collector := make(chan interface{}, options.workers)
done := syncx.NewDoneChan()
go mapDispatcher(mapper, source, collector, done.Done(), options.workers)
go executeMappers(mapper, source, collector, done.Done(), options.workers)
return collector
}
@@ -93,7 +96,14 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
collector := make(chan interface{}, options.workers)
done := syncx.NewDoneChan()
writer := newGuardedWriter(output, done.Done())
var closeOnce sync.Once
var retErr errorx.AtomicError
finish := func() {
closeOnce.Do(func() {
done.Close()
close(output)
})
}
cancel := once(func(err error) {
if err != nil {
retErr.Set(err)
@@ -102,19 +112,24 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
}
drain(source)
done.Close()
close(output)
finish()
})
go func() {
defer func() {
if r := recover(); r != nil {
cancel(fmt.Errorf("%v", r))
} else {
finish()
}
}()
reducer(collector, writer, cancel)
drain(collector)
}()
go mapperDispatcher(mapper, source, collector, done.Done(), cancel, options.workers)
go executeMappers(func(item interface{}, w Writer) {
mapper(item, w, cancel)
}, source, collector, done.Done(), options.workers)
value, ok := <-output
if err := retErr.Load(); err != nil {
@@ -122,13 +137,14 @@ func MapReduceWithSource(source <-chan interface{}, mapper MapperFunc, reducer R
} else if ok {
return value, nil
} else {
return nil, nil
return nil, ErrReduceNoOutput
}
}
func MapReduceVoid(generator GenerateFunc, mapper MapperFunc, reducer VoidReducerFunc, opts ...Option) error {
_, err := MapReduce(generator, mapper, func(input <-chan interface{}, writer Writer, cancel func(error)) {
reducer(input, cancel)
drain(input)
// We need to write a placeholder to let MapReduce to continue on reducer done,
// otherwise, all goroutines are waiting. The placeholder will be discarded by MapReduce.
writer.Write(lang.Placeholder)
@@ -213,20 +229,6 @@ func executeMappers(mapper MapFunc, input <-chan interface{}, collector chan<- i
}
}
func mapDispatcher(mapper MapFunc, input <-chan interface{}, collector chan<- interface{},
done <-chan lang.PlaceholderType, workers int) {
executeMappers(func(item interface{}, writer Writer) {
mapper(item, writer)
}, input, collector, done, workers)
}
func mapperDispatcher(mapper MapperFunc, input <-chan interface{}, collector chan<- interface{},
done <-chan lang.PlaceholderType, cancel func(error), workers int) {
executeMappers(func(item interface{}, writer Writer) {
mapper(item, writer, cancel)
}, input, collector, done, workers)
}
func newOptions() *mapReduceOptions {
return &mapReduceOptions{
workers: defaultWorkers,

View File

@@ -378,6 +378,22 @@ func TestMapReduceVoidCancelWithRemains(t *testing.T) {
assert.True(t, done.True())
}
func TestMapReduceWithoutReducerWrite(t *testing.T) {
uids := []int{1, 2, 3}
res, err := MapReduce(func(source chan<- interface{}) {
for _, uid := range uids {
source <- uid
}
}, func(item interface{}, writer Writer, cancel func(error)) {
writer.Write(item)
}, func(pipe <-chan interface{}, writer Writer, cancel func(error)) {
drain(pipe)
// not calling writer.Write(...), should not panic
})
assert.Equal(t, ErrReduceNoOutput, err)
assert.Nil(t, res)
}
func BenchmarkMapReduce(b *testing.B) {
b.ReportAllocs()

View File

@@ -0,0 +1,16 @@
package proc
import (
"log"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDumpGoroutines(t *testing.T) {
var buf strings.Builder
log.SetOutput(&buf)
dumpGoroutines()
assert.True(t, strings.Contains(buf.String(), ".dump"))
}

View File

@@ -26,10 +26,6 @@ var started uint32
// Profile represents an active profiling session.
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 []func()

21
core/proc/profile_test.go Normal file
View File

@@ -0,0 +1,21 @@
package proc
import (
"log"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestProfile(t *testing.T) {
var buf strings.Builder
log.SetOutput(&buf)
profiler := StartProfile()
// start again should not work
assert.NotNil(t, StartProfile())
profiler.Stop()
// stop twice
profiler.Stop()
assert.True(t, strings.Contains(buf.String(), ".pprof"))
}

View File

@@ -32,7 +32,7 @@ func AddWrapUpListener(fn func()) (waitForCalled func()) {
return wrapUpListeners.addListener(fn)
}
func SetTimeoutToForceQuit(duration time.Duration) {
func SetTimeToForceQuit(duration time.Duration) {
delayTimeBeforeForceQuit = duration
}

View File

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

View File

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

@@ -22,19 +22,30 @@ var (
cores uint64
)
// if /proc not present, ignore the cpu calcuation, like wsl linux
func init() {
cpus, err := perCpuUsage()
logx.Must(err)
cores = uint64(len(cpus))
if err != nil {
logx.Error(err)
return
}
cores = uint64(len(cpus))
sets, err := cpuSets()
logx.Must(err)
if err != nil {
logx.Error(err)
return
}
quota = float64(len(sets))
cq, err := cpuQuota()
if err == nil {
if cq != -1 {
period, err := cpuPeriod()
logx.Must(err)
if err != nil {
logx.Error(err)
return
}
limit := float64(cq) / float64(period)
if limit < quota {
@@ -44,10 +55,16 @@ func init() {
}
preSystem, err = systemCpuUsage()
logx.Must(err)
if err != nil {
logx.Error(err)
return
}
preTotal, err = totalCpuUsage()
logx.Must(err)
if err != nil {
logx.Error(err)
return
}
}
func RefreshCpu() uint64 {

View File

@@ -1,6 +1,14 @@
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) {
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

@@ -1,4 +1,4 @@
package internal
package cache
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package internal
package cache
import (
"encoding/json"
@@ -8,7 +8,6 @@ import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/errorx"
"github.com/tal-tech/go-zero/core/hash"
@@ -76,12 +75,12 @@ func (mc *mockedNode) TakeWithExpire(v interface{}, key string, query func(v int
func TestCache_SetDel(t *testing.T) {
const total = 1000
r1 := miniredis.NewMiniRedis()
assert.Nil(t, r1.Start())
defer r1.Close()
r2 := miniredis.NewMiniRedis()
assert.Nil(t, r2.Start())
defer r2.Close()
r1, clean1, err := createMiniRedis()
assert.Nil(t, err)
defer clean1()
r2, clean2, err := createMiniRedis()
assert.Nil(t, err)
defer clean2()
conf := ClusterConf{
{
RedisConf: redis.RedisConf{
@@ -111,6 +110,45 @@ func TestCache_SetDel(t *testing.T) {
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++ {
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 := createMiniRedis()
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++ {
assert.Nil(t, c.DelCache(fmt.Sprintf("key/%d", i)))
}
@@ -188,6 +226,25 @@ func TestCache_Balance(t *testing.T) {
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 {
var entropy float64

View File

@@ -1,5 +1,3 @@
package cache
import "github.com/tal-tech/go-zero/core/stores/internal"
type CacheConf = internal.ClusterConf
type CacheConf = ClusterConf

View File

@@ -1,13 +1,13 @@
package internal
package cache
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"sync"
"time"
"github.com/tal-tech/go-zero/core/jsonx"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx"
"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 {
data, err := json.Marshal(v)
data, err := jsonx.Marshal(v)
if err != nil {
return err
}
@@ -168,7 +168,7 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
}
}
return json.Marshal(v)
return jsonx.Marshal(v)
})
if err != nil {
return err
@@ -181,11 +181,11 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
c.stat.IncrementHit()
}
return json.Unmarshal(val.([]byte), v)
return jsonx.Unmarshal(val.([]byte), v)
}
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 {
return nil
}

206
core/stores/cache/cachenode_test.go vendored Normal file
View File

@@ -0,0 +1,206 @@
package cache
import (
"errors"
"fmt"
"math/rand"
"strconv"
"sync"
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stores/redis"
"github.com/tal-tech/go-zero/core/syncx"
)
var errTestNotFound = errors.New("not found")
func init() {
logx.Disable()
stat.SetReporter(nil)
}
func TestCacheNode_DelCache(t *testing.T) {
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
r: rand.New(rand.NewSource(time.Now().UnixNano())),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errTestNotFound,
}
assert.Nil(t, cn.DelCache())
assert.Nil(t, cn.DelCache([]string{}...))
assert.Nil(t, cn.DelCache(make([]string, 0)...))
cn.SetCache("first", "one")
assert.Nil(t, cn.DelCache("first"))
cn.SetCache("first", "one")
cn.SetCache("second", "two")
assert.Nil(t, cn.DelCache("first", "second"))
}
func TestCacheNode_InvalidCache(t *testing.T) {
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
r: rand.New(rand.NewSource(time.Now().UnixNano())),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errTestNotFound,
}
s.Set("any", "value")
var str string
assert.NotNil(t, cn.GetCache("any", &str))
assert.Equal(t, "", str)
_, err = s.Get("any")
assert.Equal(t, miniredis.ErrKeyNotFound, err)
}
func TestCacheNode_Take(t *testing.T) {
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
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 := s.Get("any")
assert.Nil(t, err)
assert.Equal(t, `"value"`, val)
}
func TestCacheNode_TakeNotFound(t *testing.T) {
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
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 := s.Get("any")
assert.Nil(t, err)
assert.Equal(t, `*`, val)
s.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))
s.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) {
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
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 := s.Get("any")
assert.Nil(t, err)
assert.Equal(t, `"value"`, val)
}
func TestCacheNode_String(t *testing.T) {
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
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, s.Addr(), cn.String())
}
func TestCacheValueWithBigInt(t *testing.T) {
s, clean, err := createMiniRedis()
assert.Nil(t, err)
defer clean()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
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

@@ -1,21 +1,45 @@
package cache
import (
"time"
import "time"
"github.com/tal-tech/go-zero/core/stores/internal"
const (
defaultExpiry = time.Hour * 24 * 7
defaultNotFoundExpiry = time.Minute
)
type Option = internal.Option
type (
Options struct {
Expiry time.Duration
NotFoundExpiry time.Duration
}
Option func(o *Options)
)
func newOptions(opts ...Option) Options {
var o Options
for _, opt := range opts {
opt(&o)
}
if o.Expiry <= 0 {
o.Expiry = defaultExpiry
}
if o.NotFoundExpiry <= 0 {
o.NotFoundExpiry = defaultNotFoundExpiry
}
return o
}
func WithExpiry(expiry time.Duration) Option {
return func(o *internal.Options) {
return func(o *Options) {
o.Expiry = expiry
}
}
func WithNotFoundExpiry(expiry time.Duration) Option {
return func(o *internal.Options) {
return func(o *Options) {
o.NotFoundExpiry = expiry
}
}

View File

@@ -1,4 +1,4 @@
package internal
package cache
import (
"sync/atomic"
@@ -48,20 +48,17 @@ func (cs *CacheStat) statLoop() {
ticker := time.NewTicker(statInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
total := atomic.SwapUint64(&cs.Total, 0)
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)
for range ticker.C {
total := atomic.SwapUint64(&cs.Total, 0)
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)
}
}

View File

@@ -1,4 +1,4 @@
package internal
package cache
import (
"fmt"

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

View File

@@ -1,4 +1,4 @@
package internal
package cache
import "github.com/tal-tech/go-zero/core/stores/redis"

View File

@@ -1,4 +1,4 @@
package internal
package cache
import "strings"

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

@@ -0,0 +1,48 @@
package cache
import (
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/lang"
)
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)
}
func createMiniRedis() (r *miniredis.Miniredis, clean func(), err error) {
r, err = miniredis.Run()
if err != nil {
return nil, nil, err
}
return r, func() {
ch := make(chan lang.PlaceholderType)
go func() {
r.Close()
close(ch)
}()
select {
case <-ch:
case <-time.After(time.Second):
}
}, nil
}

View File

@@ -1,7 +1,7 @@
package clickhouse
import (
_ "github.com/kshvakov/clickhouse"
_ "github.com/ClickHouse/clickhouse-go"
"github.com/tal-tech/go-zero/core/stores/sqlx"
)

View File

@@ -1,65 +0,0 @@
package internal
import (
"errors"
"math/rand"
"sync"
"testing"
"time"
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/logx"
"github.com/tal-tech/go-zero/core/mathx"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stores/redis"
)
func init() {
logx.Disable()
stat.SetReporter(nil)
}
func TestCacheNode_DelCache(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)
defer s.Close()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
r: rand.New(rand.NewSource(time.Now().UnixNano())),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errors.New("any"),
}
assert.Nil(t, cn.DelCache())
assert.Nil(t, cn.DelCache([]string{}...))
assert.Nil(t, cn.DelCache(make([]string, 0)...))
cn.SetCache("first", "one")
assert.Nil(t, cn.DelCache("first"))
cn.SetCache("first", "one")
cn.SetCache("second", "two")
assert.Nil(t, cn.DelCache("first", "second"))
}
func TestCacheNode_InvalidCache(t *testing.T) {
s, err := miniredis.Run()
assert.Nil(t, err)
defer s.Close()
cn := cacheNode{
rds: redis.NewRedis(s.Addr(), redis.NodeType),
r: rand.New(rand.NewSource(time.Now().UnixNano())),
lock: new(sync.Mutex),
unstableExpiry: mathx.NewUnstable(expiryDeviation),
stat: NewCacheStat("any"),
errNotFound: errors.New("any"),
}
s.Set("any", "value")
var str string
assert.NotNil(t, cn.GetCache("any", &str))
assert.Equal(t, "", str)
_, err = s.Get("any")
assert.Equal(t, miniredis.ErrKeyNotFound, err)
}

View File

@@ -1,33 +0,0 @@
package internal
import "time"
const (
defaultExpiry = time.Hour * 24 * 7
defaultNotFoundExpiry = time.Minute
)
type (
Options struct {
Expiry time.Duration
NotFoundExpiry time.Duration
}
Option func(o *Options)
)
func newOptions(opts ...Option) Options {
var o Options
for _, opt := range opts {
opt(&o)
}
if o.Expiry <= 0 {
o.Expiry = defaultExpiry
}
if o.NotFoundExpiry <= 0 {
o.NotFoundExpiry = defaultNotFoundExpiry
}
return o
}

View File

@@ -1,5 +1,7 @@
package kv
import "github.com/tal-tech/go-zero/core/stores/internal"
import (
"github.com/tal-tech/go-zero/core/stores/cache"
)
type KvConf = internal.ClusterConf
type KvConf = cache.ClusterConf

View File

@@ -6,7 +6,7 @@ import (
"github.com/tal-tech/go-zero/core/errorx"
"github.com/tal-tech/go-zero/core/hash"
"github.com/tal-tech/go-zero/core/stores/internal"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/redis"
)
@@ -73,6 +73,7 @@ type (
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
Zscore(key string, value string) (int64, error)
Zrevrank(key, field string) (int64, error)
}
clusterStore struct {
@@ -81,7 +82,7 @@ type (
)
func NewStore(c KvConf) Store {
if len(c) == 0 || internal.TotalWeights(c) <= 0 {
if len(c) == 0 || cache.TotalWeights(c) <= 0 {
log.Fatal("no cache nodes")
}
@@ -635,6 +636,15 @@ func (cs clusterStore) ZrevrangebyscoreWithScoresAndLimit(key string, start, sto
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) {
node, err := cs.getRedis(key)
if err != nil {

View File

@@ -6,7 +6,8 @@ import (
"github.com/alicebob/miniredis"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stores/internal"
"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/redis"
"github.com/tal-tech/go-zero/core/stringx"
)
@@ -15,6 +16,10 @@ var s1, _ = miniredis.Run()
var s2, _ = miniredis.Run()
func TestRedis_Exists(t *testing.T) {
store := clusterStore{dispatcher: hash.NewConsistentHash()}
_, err := store.Exists("foo")
assert.NotNil(t, err)
runOnCluster(t, func(client Store) {
ok, err := client.Exists("a")
assert.Nil(t, err)
@@ -27,6 +32,10 @@ func TestRedis_Exists(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) {
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, "notexist")
assert.Equal(t, redis.Nil, err)
@@ -41,6 +50,12 @@ func TestRedis_Eval(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) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -54,6 +69,10 @@ func TestRedis_Hgetall(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) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -64,6 +83,10 @@ func TestRedis_Hvals(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) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -80,6 +103,12 @@ func TestRedis_Hsetnx(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) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -96,6 +125,10 @@ func TestRedis_HdelHlen(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) {
val, err := client.Hincrby("key", "field", 2)
assert.Nil(t, err)
@@ -107,6 +140,10 @@ func TestRedis_HIncrBy(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) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -117,6 +154,10 @@ func TestRedis_Hkeys(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) {
assert.Nil(t, client.Hset("a", "aa", "aaa"))
assert.Nil(t, client.Hset("a", "bb", "bbb"))
@@ -130,6 +171,12 @@ func TestRedis_Hmget(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) {
assert.Nil(t, client.Hmset("a", map[string]string{
"aa": "aaa",
@@ -142,6 +189,10 @@ func TestRedis_Hmset(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) {
val, err := client.Incr("a")
assert.Nil(t, err)
@@ -153,6 +204,10 @@ func TestRedis_Incr(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) {
val, err := client.Incrby("a", 2)
assert.Nil(t, err)
@@ -164,6 +219,20 @@ func TestRedis_IncrBy(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) {
val, err := client.Lpush("key", "value1", "value2")
assert.Nil(t, err)
@@ -202,6 +271,14 @@ func TestRedis_List(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) {
ok, err := client.Persist("key")
assert.Nil(t, err)
@@ -225,8 +302,16 @@ func TestRedis_Persist(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) {
key := "list"
var list []string
for i := 0; i < 1550; i++ {
list = append(list, stringx.Randn(i))
@@ -254,6 +339,20 @@ func TestRedis_Sscan(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) {
num, err := client.Sadd("key", 1, 2, 3, 4)
assert.Nil(t, err)
@@ -290,6 +389,14 @@ func TestRedis_Set(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) {
err := client.Set("hello", "world")
assert.Nil(t, err)
@@ -303,6 +410,16 @@ func TestRedis_SetGetDel(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) {
err := client.Setex("hello", "world", 5)
assert.Nil(t, err)
@@ -337,6 +454,16 @@ func TestRedis_SetExNx(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) {
err := client.Hset("key", "field", "value")
assert.Nil(t, err)
@@ -356,6 +483,50 @@ func TestRedis_SetGetDelHashField(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) {
ok, err := client.Zadd("key", 1, "value1")
assert.Nil(t, err)
@@ -471,6 +642,33 @@ func TestRedis_SortedSet(t *testing.T) {
Score: 5,
},
}, 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)
})
}
@@ -478,7 +676,7 @@ func runOnCluster(t *testing.T, fn func(cluster Store)) {
s1.FlushAll()
s2.FlushAll()
store := NewStore([]internal.NodeConf{
store := NewStore([]cache.NodeConf{
{
RedisConf: redis.RedisConf{
Host: s1.Addr(),

View File

@@ -7,6 +7,7 @@ import (
"github.com/globalsign/mgo"
"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/timex"
)
@@ -29,8 +30,9 @@ type (
}
decoratedCollection struct {
*mgo.Collection
brk breaker.Breaker
name string
collection internal.MgoCollection
brk breaker.Breaker
}
keepablePromise struct {
@@ -41,7 +43,8 @@ type (
func newCollection(collection *mgo.Collection) Collection {
return &decoratedCollection{
Collection: collection,
name: collection.FullName,
collection: collection,
brk: breaker.NewBreaker(),
}
}
@@ -54,7 +57,7 @@ func (c *decoratedCollection) Find(query interface{}) Query {
startTime := timex.Now()
return promisedQuery{
Query: c.Collection.Find(query),
Query: c.collection.Find(query),
promise: keepablePromise{
promise: promise,
log: func(err error) {
@@ -73,7 +76,7 @@ func (c *decoratedCollection) FindId(id interface{}) Query {
startTime := timex.Now()
return promisedQuery{
Query: c.Collection.FindId(id),
Query: c.collection.FindId(id),
promise: keepablePromise{
promise: promise,
log: func(err error) {
@@ -92,7 +95,7 @@ func (c *decoratedCollection) Insert(docs ...interface{}) (err error) {
c.logDuration("insert", duration, err, docs...)
}()
return c.Collection.Insert(docs...)
return c.collection.Insert(docs...)
}, acceptable)
}
@@ -104,7 +107,7 @@ func (c *decoratedCollection) Pipe(pipeline interface{}) Pipe {
startTime := timex.Now()
return promisedPipe{
Pipe: c.Collection.Pipe(pipeline),
Pipe: c.collection.Pipe(pipeline),
promise: keepablePromise{
promise: promise,
log: func(err error) {
@@ -123,7 +126,7 @@ func (c *decoratedCollection) Remove(selector interface{}) (err error) {
c.logDuration("remove", duration, err, selector)
}()
return c.Collection.Remove(selector)
return c.collection.Remove(selector)
}, acceptable)
}
@@ -135,7 +138,7 @@ func (c *decoratedCollection) RemoveAll(selector interface{}) (info *mgo.ChangeI
c.logDuration("removeAll", duration, err, selector)
}()
info, err = c.Collection.RemoveAll(selector)
info, err = c.collection.RemoveAll(selector)
return err
}, acceptable)
@@ -150,7 +153,7 @@ func (c *decoratedCollection) RemoveId(id interface{}) (err error) {
c.logDuration("removeId", duration, err, id)
}()
return c.Collection.RemoveId(id)
return c.collection.RemoveId(id)
}, acceptable)
}
@@ -162,7 +165,7 @@ func (c *decoratedCollection) Update(selector, update interface{}) (err error) {
c.logDuration("update", duration, err, selector, update)
}()
return c.Collection.Update(selector, update)
return c.collection.Update(selector, update)
}, acceptable)
}
@@ -174,7 +177,7 @@ func (c *decoratedCollection) UpdateId(id, update interface{}) (err error) {
c.logDuration("updateId", duration, err, id, update)
}()
return c.Collection.UpdateId(id, update)
return c.collection.UpdateId(id, update)
}, acceptable)
}
@@ -186,7 +189,7 @@ func (c *decoratedCollection) Upsert(selector, update interface{}) (info *mgo.Ch
c.logDuration("upsert", duration, err, selector, update)
}()
info, err = c.Collection.Upsert(selector, update)
info, err = c.collection.Upsert(selector, update)
return err
}, acceptable)
@@ -200,17 +203,17 @@ func (c *decoratedCollection) logDuration(method string, duration time.Duration,
} else if err != nil {
if duration > slowThreshold {
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 {
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 {
if duration > slowThreshold {
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
c.FullName, method, string(content))
c.name, method, string(content))
} 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"
"github.com/globalsign/mgo"
"github.com/golang/mock/gomock"
"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"
)
var errDummy = errors.New("dummy")
func init() {
logx.Disable()
}
func TestKeepPromise_accept(t *testing.T) {
p := new(mockPromise)
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 {
accepted bool
reason string
@@ -68,3 +278,31 @@ func (p *mockPromise) Accept() {
func (p *mockPromise) Reject(reason string) {
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

@@ -22,8 +22,8 @@ type (
}
)
func MustNewModel(url, database, collection string, opts ...Option) *Model {
model, err := NewModel(url, database, collection, opts...)
func MustNewModel(url, collection string, opts ...Option) *Model {
model, err := NewModel(url, collection, opts...)
if err != nil {
log.Fatal(err)
}
@@ -31,15 +31,16 @@ func MustNewModel(url, database, collection string, opts ...Option) *Model {
return model
}
func NewModel(url, database, collection string, opts ...Option) (*Model, error) {
func NewModel(url, collection string, opts ...Option) (*Model, error) {
session, err := getConcurrentSession(url)
if err != nil {
return nil, err
}
return &Model{
session: session,
db: session.DB(database),
session: session,
// If name is empty, the database name provided in the dialed URL is used instead
db: session.DB(""),
collection: collection,
opts: opts,
}, nil

View File

@@ -2,7 +2,7 @@ package mongoc
import (
"github.com/globalsign/mgo"
"github.com/tal-tech/go-zero/core/stores/internal"
"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/syncx"
)
@@ -12,7 +12,7 @@ var (
// can't use one SharedCalls per conn, because multiple conns may share the same cache key.
sharedCalls = syncx.NewSharedCalls()
stats = internal.NewCacheStat("mongoc")
stats = cache.NewCacheStat("mongoc")
)
type (
@@ -20,11 +20,11 @@ type (
cachedCollection struct {
collection mongo.Collection
cache internal.Cache
cache cache.Cache
}
)
func newCollection(collection mongo.Collection, c internal.Cache) *cachedCollection {
func newCollection(collection mongo.Collection, c cache.Cache) *cachedCollection {
return &cachedCollection{
collection: collection,
cache: c,

View File

@@ -16,7 +16,7 @@ import (
"github.com/globalsign/mgo/bson"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/core/stat"
"github.com/tal-tech/go-zero/core/stores/internal"
"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/redis"
)
@@ -33,7 +33,7 @@ func TestStat(t *testing.T) {
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
for i := 0; i < 10; i++ {
@@ -56,7 +56,7 @@ func TestStatCacheFails(t *testing.T) {
defer log.SetOutput(os.Stdout)
r := redis.NewRedis("localhost:59999", redis.NodeType)
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
for i := 0; i < 20; i++ {
@@ -79,7 +79,7 @@ func TestStatDbFails(t *testing.T) {
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
for i := 0; i < 20; i++ {
@@ -103,7 +103,7 @@ func TestStatFromMemory(t *testing.T) {
}
r := redis.NewRedis(s.Addr(), redis.NodeType)
cach := internal.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
c := newCollection(dummyConn{}, cach)
var all sync.WaitGroup

View File

@@ -5,19 +5,18 @@ import (
"github.com/globalsign/mgo"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/internal"
"github.com/tal-tech/go-zero/core/stores/mongo"
"github.com/tal-tech/go-zero/core/stores/redis"
)
type Model struct {
*mongo.Model
cache internal.Cache
cache cache.Cache
generateCollection func(*mgo.Session) *cachedCollection
}
func MustNewNodeModel(url, database, collection string, rds *redis.Redis, opts ...cache.Option) *Model {
model, err := NewNodeModel(url, database, collection, rds, opts...)
func MustNewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) *Model {
model, err := NewNodeModel(url, collection, rds, opts...)
if err != nil {
log.Fatal(err)
}
@@ -25,8 +24,8 @@ func MustNewNodeModel(url, database, collection string, rds *redis.Redis, opts .
return model
}
func MustNewModel(url, database, collection string, c cache.CacheConf, opts ...cache.Option) *Model {
model, err := NewModel(url, database, collection, c, opts...)
func MustNewModel(url, collection string, c cache.CacheConf, opts ...cache.Option) *Model {
model, err := NewModel(url, collection, c, opts...)
if err != nil {
log.Fatal(err)
}
@@ -34,16 +33,16 @@ func MustNewModel(url, database, collection string, c cache.CacheConf, opts ...c
return model
}
func NewNodeModel(url, database, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
c := internal.NewCacheNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
return createModel(url, database, collection, c, func(collection mongo.Collection) *cachedCollection {
func NewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
c := cache.NewCacheNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
return createModel(url, collection, c, func(collection mongo.Collection) *cachedCollection {
return newCollection(collection, c)
})
}
func NewModel(url, database, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
c := internal.NewCache(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
return createModel(url, database, collection, c, func(collection mongo.Collection) *cachedCollection {
func NewModel(url, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
c := cache.NewCache(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
return createModel(url, collection, c, func(collection mongo.Collection) *cachedCollection {
return newCollection(collection, c)
})
}
@@ -224,9 +223,9 @@ func (mm *Model) pipe(fn func(c *cachedCollection) mongo.Pipe) (mongo.Pipe, erro
return fn(mm.GetCollection(session)), nil
}
func createModel(url, database, collection string, c internal.Cache,
func createModel(url, collection string, c cache.Cache,
create func(mongo.Collection) *cachedCollection) (*Model, error) {
model, err := mongo.NewModel(url, database, collection)
model, err := mongo.NewModel(url, collection)
if err != nil {
return nil, err
}

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

@@ -1273,6 +1273,20 @@ func (s *Redis) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64
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 {
return s.Addr
}

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