mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-27 08:35:30 +08:00
Compare commits
244 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57b73d8b49 | ||
|
|
a79cee12ee | ||
|
|
7a921f66e6 | ||
|
|
12e235efb0 | ||
|
|
01060cf16d | ||
|
|
0786862a35 | ||
|
|
efa43483b2 | ||
|
|
771371e051 | ||
|
|
2ee95f8981 | ||
|
|
5bc01e4bfd | ||
|
|
510e966982 | ||
|
|
10e3b8ac80 | ||
|
|
04059bbf5a | ||
|
|
d643007c79 | ||
|
|
fc43876cc5 | ||
|
|
a926cb514f | ||
|
|
25cab2f273 | ||
|
|
8d2e2753a2 | ||
|
|
cc4c50e3eb | ||
|
|
751072bdb0 | ||
|
|
e97e1f10db | ||
|
|
0bd2a0656c | ||
|
|
71a2b20301 | ||
|
|
8df7de94e3 | ||
|
|
bf21203297 | ||
|
|
ae98375194 | ||
|
|
82d1ccf376 | ||
|
|
bb6d49c17e | ||
|
|
ed735ec47c | ||
|
|
ba4bac3a03 | ||
|
|
08433d7e04 | ||
|
|
a3b525b50d | ||
|
|
097f6886f2 | ||
|
|
07a1549634 | ||
|
|
befca26c58 | ||
|
|
3556a2eef4 | ||
|
|
807765f77e | ||
|
|
e44584e549 | ||
|
|
acd48f0abb | ||
|
|
f919bc6713 | ||
|
|
a0030b8f45 | ||
|
|
a5f0cce1b1 | ||
|
|
4d13dda605 | ||
|
|
b56cc8e459 | ||
|
|
c435811479 | ||
|
|
c686c93fb5 | ||
|
|
da8f76e6bd | ||
|
|
99596a4149 | ||
|
|
ec2a9f2c57 | ||
|
|
fd73ced6dc | ||
|
|
5071736ab4 | ||
|
|
0d7f1d23b4 | ||
|
|
84ab11ac09 | ||
|
|
67804a6bb2 | ||
|
|
65ee877236 | ||
|
|
b060867009 | ||
|
|
4d53045c6b | ||
|
|
cecd4b1b75 | ||
|
|
7cd0463953 | ||
|
|
7a82cf80ce | ||
|
|
f997aee3ba | ||
|
|
88ec89bdbd | ||
|
|
7d1b43780a | ||
|
|
4b5c2de376 | ||
|
|
e5c560e8ba | ||
|
|
bed494d904 | ||
|
|
2dfecda465 | ||
|
|
3ebb1e0221 | ||
|
|
348184904c | ||
|
|
7a27fa50a1 | ||
|
|
8d4951c990 | ||
|
|
6e57f6c527 | ||
|
|
b9ac51b6c3 | ||
|
|
702e8d79ce | ||
|
|
95a9dabf8b | ||
|
|
bae66c49c2 | ||
|
|
e0afe0b4bb | ||
|
|
24fb29a356 | ||
|
|
71083b5e64 | ||
|
|
1174f17bd9 | ||
|
|
d6d8fc21d8 | ||
|
|
9592639cb4 | ||
|
|
abcb28e506 | ||
|
|
a92f65580c | ||
|
|
3819f67cf4 | ||
|
|
295c8d2934 | ||
|
|
88da8685dd | ||
|
|
c7831ac96d | ||
|
|
e898761762 | ||
|
|
13d1c5cd00 | ||
|
|
16bfb1b7be | ||
|
|
ef4d4968d6 | ||
|
|
7b4a5e3ec6 | ||
|
|
e6df21e0d2 | ||
|
|
0a2c2d1eca | ||
|
|
a5fb29a6f0 | ||
|
|
f8da301e57 | ||
|
|
cb9075b737 | ||
|
|
3f389a55c2 | ||
|
|
afbd565d87 | ||
|
|
d629acc2b7 | ||
|
|
f32c6a9b28 | ||
|
|
95aa65efb9 | ||
|
|
3806e66cf1 | ||
|
|
bd430baf52 | ||
|
|
48f4154ea8 | ||
|
|
2599e0d28d | ||
|
|
12327fa07d | ||
|
|
57079bf4a4 | ||
|
|
7f6eceb5a3 | ||
|
|
7d7cb836af | ||
|
|
f87d9d1dda | ||
|
|
856b5aadb1 | ||
|
|
f7d778e0ed | ||
|
|
88333ee77f | ||
|
|
e76f44a35b | ||
|
|
c9ec22d5f4 | ||
|
|
afffc1048b | ||
|
|
d0b76b1d9a | ||
|
|
b004b070d7 | ||
|
|
677d581bd1 | ||
|
|
b776468e69 | ||
|
|
4c9315e984 | ||
|
|
668a7011c4 | ||
|
|
cc07a1d69b | ||
|
|
7f99a3baa8 | ||
|
|
9504418462 | ||
|
|
b144a2335c | ||
|
|
7b9ed7a313 | ||
|
|
3d2e9fcb84 | ||
|
|
2b993424c1 | ||
|
|
5e87b33b23 | ||
|
|
9b7cc43dcb | ||
|
|
000b28cf84 | ||
|
|
9fd16cd278 | ||
|
|
b71429e16b | ||
|
|
a13b48c33e | ||
|
|
033525fea8 | ||
|
|
607fc3297a | ||
|
|
4287877b74 | ||
|
|
2b7545ce11 | ||
|
|
60925c1164 | ||
|
|
1c9e81aa28 | ||
|
|
db7dcaa120 | ||
|
|
099d44054d | ||
|
|
f5f873c6bd | ||
|
|
6dbd3eada9 | ||
|
|
cf2d20a211 | ||
|
|
91bfc093f4 | ||
|
|
cf33aae91d | ||
|
|
c9494c8bc7 | ||
|
|
1fd2ef9347 | ||
|
|
efffb40fa3 | ||
|
|
9c8f31cf83 | ||
|
|
96cb7af728 | ||
|
|
41964f9d52 | ||
|
|
fe0d0687f5 | ||
|
|
1c1e4bca86 | ||
|
|
1abe21aa2a | ||
|
|
cee170f3e9 | ||
|
|
907efd92c9 | ||
|
|
737cd4751a | ||
|
|
dfe6e88529 | ||
|
|
85a815bea0 | ||
|
|
aa3c391919 | ||
|
|
c9b0ac1ee4 | ||
|
|
33faab61a3 | ||
|
|
81bf122fa4 | ||
|
|
a14bd309a9 | ||
|
|
ea7e410145 | ||
|
|
e81358e7fa | ||
|
|
695ea69bfc | ||
|
|
d2ed14002c | ||
|
|
1d9c4a4c4b | ||
|
|
7e83895c6e | ||
|
|
dc0534573c | ||
|
|
fe3739b7f3 | ||
|
|
94645481b1 | ||
|
|
338caf9927 | ||
|
|
9cc979960f | ||
|
|
f904710811 | ||
|
|
8291eabc2c | ||
|
|
901fadb5d3 | ||
|
|
c824e9e118 | ||
|
|
6f49639f80 | ||
|
|
7d4a548d29 | ||
|
|
936dd67008 | ||
|
|
84cc41df42 | ||
|
|
da1a93e932 | ||
|
|
7e61555d42 | ||
|
|
7a134ec64d | ||
|
|
d123b00e73 | ||
|
|
20d53add46 | ||
|
|
a1b141d31a | ||
|
|
0a9c427443 | ||
|
|
c32759d735 | ||
|
|
fe855c52f1 | ||
|
|
3f8b080882 | ||
|
|
adc275872d | ||
|
|
be39133dba | ||
|
|
15a9ab1d18 | ||
|
|
7c354dcc38 | ||
|
|
3733b06f1b | ||
|
|
8115a0932e | ||
|
|
4df5eb760c | ||
|
|
4a639b853c | ||
|
|
1023425c1d | ||
|
|
360fbfd0fa | ||
|
|
09b7625f06 | ||
|
|
6db294b5cc | ||
|
|
305b6749fd | ||
|
|
10b855713d | ||
|
|
1cc0f071d9 | ||
|
|
02ce8f82c8 | ||
|
|
8a585afbf0 | ||
|
|
e356025cef | ||
|
|
14dee114dd | ||
|
|
637a94a189 | ||
|
|
173b347c90 | ||
|
|
6749c5b94a | ||
|
|
e66cca3710 | ||
|
|
f90c0aa98e | ||
|
|
f00b5416a3 | ||
|
|
f49694d6b6 | ||
|
|
d809bf2dca | ||
|
|
44ae5463bc | ||
|
|
40dbd722d7 | ||
|
|
709574133b | ||
|
|
cb1c593108 | ||
|
|
6ecf575c00 | ||
|
|
b8fcdd5460 | ||
|
|
ce42281568 | ||
|
|
40230d79e7 | ||
|
|
ba7851795b | ||
|
|
096fe3bc47 | ||
|
|
e37858295a | ||
|
|
5a4afb1518 | ||
|
|
63f1f39c40 | ||
|
|
481895d1e4 | ||
|
|
9e9ce3bf48 | ||
|
|
0ce654968d | ||
|
|
2703493541 | ||
|
|
d4240cd4b0 | ||
|
|
a22bcc84a3 |
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '18 19 * * 6'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,6 +4,7 @@
|
|||||||
# Unignore all with extensions
|
# Unignore all with extensions
|
||||||
!*.*
|
!*.*
|
||||||
!**/Dockerfile
|
!**/Dockerfile
|
||||||
|
!**/Makefile
|
||||||
|
|
||||||
# Unignore all dirs
|
# Unignore all dirs
|
||||||
!*/
|
!*/
|
||||||
@@ -12,7 +13,6 @@
|
|||||||
.idea
|
.idea
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/logs
|
**/logs
|
||||||
!Makefile
|
|
||||||
|
|
||||||
# gitlab ci
|
# gitlab ci
|
||||||
.cache
|
.cache
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"MD010": false,
|
|
||||||
"MD013": false,
|
|
||||||
"MD033": false,
|
|
||||||
"MD034": false
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,6 @@ type (
|
|||||||
|
|
||||||
BloomFilter struct {
|
BloomFilter struct {
|
||||||
bits uint
|
bits uint
|
||||||
maps uint
|
|
||||||
bitSet BitSetProvider
|
bitSet BitSetProvider
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,19 +3,15 @@ package bloom
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error("Miniredis could not start")
|
defer clean()
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
store := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
bitSet := newRedisBitSet(store, "test_key", 1024)
|
bitSet := newRedisBitSet(store, "test_key", 1024)
|
||||||
isSetBefore, err := bitSet.check([]uint{0})
|
isSetBefore, err := bitSet.check([]uint{0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -46,13 +42,10 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedisBitSet_Add(t *testing.T) {
|
func TestRedisBitSet_Add(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error("Miniredis could not start")
|
defer clean()
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
|
|
||||||
store := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
filter := New(store, "test_key", 64)
|
filter := New(store, "test_key", 64)
|
||||||
assert.Nil(t, filter.Add([]byte("hello")))
|
assert.Nil(t, filter.Add([]byte("hello")))
|
||||||
assert.Nil(t, filter.Add([]byte("world")))
|
assert.Nil(t, filter.Add([]byte("world")))
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/timex"
|
"github.com/tal-tech/go-zero/core/timex"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
StateClosed State = iota
|
|
||||||
StateOpen
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
numHistoryReasons = 5
|
numHistoryReasons = 5
|
||||||
timeFormat = "15:04:05"
|
timeFormat = "15:04:05"
|
||||||
@@ -27,11 +22,10 @@ const (
|
|||||||
var ErrServiceUnavailable = errors.New("circuit breaker is open")
|
var ErrServiceUnavailable = errors.New("circuit breaker is open")
|
||||||
|
|
||||||
type (
|
type (
|
||||||
State = int32
|
|
||||||
Acceptable func(err error) bool
|
Acceptable func(err error) bool
|
||||||
|
|
||||||
Breaker interface {
|
Breaker interface {
|
||||||
// Name returns the name of the netflixBreaker.
|
// Name returns the name of the Breaker.
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
// Allow checks if the request is allowed.
|
// Allow checks if the request is allowed.
|
||||||
@@ -40,34 +34,34 @@ type (
|
|||||||
// If not allow, ErrServiceUnavailable will be returned.
|
// If not allow, ErrServiceUnavailable will be returned.
|
||||||
Allow() (Promise, error)
|
Allow() (Promise, error)
|
||||||
|
|
||||||
// Do runs the given request if the netflixBreaker accepts it.
|
// Do runs the given request if the Breaker accepts it.
|
||||||
// Do returns an error instantly if the netflixBreaker rejects the request.
|
// Do returns an error instantly if the Breaker rejects the request.
|
||||||
// If a panic occurs in the request, the netflixBreaker handles it as an error
|
// If a panic occurs in the request, the Breaker handles it as an error
|
||||||
// and causes the same panic again.
|
// and causes the same panic again.
|
||||||
Do(req func() error) error
|
Do(req func() error) error
|
||||||
|
|
||||||
// DoWithAcceptable runs the given request if the netflixBreaker accepts it.
|
// DoWithAcceptable runs the given request if the Breaker accepts it.
|
||||||
// Do returns an error instantly if the netflixBreaker rejects the request.
|
// DoWithAcceptable returns an error instantly if the Breaker rejects the request.
|
||||||
// If a panic occurs in the request, the netflixBreaker handles it as an error
|
// If a panic occurs in the request, the Breaker handles it as an error
|
||||||
// and causes the same panic again.
|
// and causes the same panic again.
|
||||||
// acceptable checks if it's a successful call, even if the err is not nil.
|
// acceptable checks if it's a successful call, even if the err is not nil.
|
||||||
DoWithAcceptable(req func() error, acceptable Acceptable) error
|
DoWithAcceptable(req func() error, acceptable Acceptable) error
|
||||||
|
|
||||||
// DoWithFallback runs the given request if the netflixBreaker accepts it.
|
// DoWithFallback runs the given request if the Breaker accepts it.
|
||||||
// DoWithFallback runs the fallback if the netflixBreaker rejects the request.
|
// DoWithFallback runs the fallback if the Breaker rejects the request.
|
||||||
// If a panic occurs in the request, the netflixBreaker handles it as an error
|
// If a panic occurs in the request, the Breaker handles it as an error
|
||||||
// and causes the same panic again.
|
// and causes the same panic again.
|
||||||
DoWithFallback(req func() error, fallback func(err error) error) error
|
DoWithFallback(req func() error, fallback func(err error) error) error
|
||||||
|
|
||||||
// DoWithFallbackAcceptable runs the given request if the netflixBreaker accepts it.
|
// DoWithFallbackAcceptable runs the given request if the Breaker accepts it.
|
||||||
// DoWithFallback runs the fallback if the netflixBreaker rejects the request.
|
// DoWithFallbackAcceptable runs the fallback if the Breaker rejects the request.
|
||||||
// If a panic occurs in the request, the netflixBreaker handles it as an error
|
// If a panic occurs in the request, the Breaker handles it as an error
|
||||||
// and causes the same panic again.
|
// and causes the same panic again.
|
||||||
// acceptable checks if it's a successful call, even if the err is not nil.
|
// acceptable checks if it's a successful call, even if the err is not nil.
|
||||||
DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
|
DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error
|
||||||
}
|
}
|
||||||
|
|
||||||
BreakerOption func(breaker *circuitBreaker)
|
Option func(breaker *circuitBreaker)
|
||||||
|
|
||||||
Promise interface {
|
Promise interface {
|
||||||
Accept()
|
Accept()
|
||||||
@@ -95,7 +89,7 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBreaker(opts ...BreakerOption) Breaker {
|
func NewBreaker(opts ...Option) Breaker {
|
||||||
var b circuitBreaker
|
var b circuitBreaker
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&b)
|
opt(&b)
|
||||||
@@ -133,7 +127,7 @@ func (cb *circuitBreaker) Name() string {
|
|||||||
return cb.name
|
return cb.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithName(name string) BreakerOption {
|
func WithName(name string) Option {
|
||||||
return func(b *circuitBreaker) {
|
return func(b *circuitBreaker) {
|
||||||
b.name = name
|
b.name = name
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,13 @@ func GetBreaker(name string) Breaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
b, ok = breakers[name]
|
||||||
|
if !ok {
|
||||||
|
b = NewBreaker(WithName(name))
|
||||||
|
breakers[name] = b
|
||||||
|
}
|
||||||
|
lock.Unlock()
|
||||||
|
|
||||||
b = NewBreaker()
|
|
||||||
breakers[name] = b
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,20 +58,5 @@ func NoBreakFor(name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func do(name string, execute func(b Breaker) error) error {
|
func do(name string, execute func(b Breaker) error) error {
|
||||||
lock.RLock()
|
return execute(GetBreaker(name))
|
||||||
b, ok := breakers[name]
|
|
||||||
lock.RUnlock()
|
|
||||||
if ok {
|
|
||||||
return execute(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.Lock()
|
|
||||||
b, ok = breakers[name]
|
|
||||||
if !ok {
|
|
||||||
b = NewBreaker(WithName(name))
|
|
||||||
breakers[name] = b
|
|
||||||
}
|
|
||||||
lock.Unlock()
|
|
||||||
|
|
||||||
return execute(b)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package breaker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/collection"
|
"github.com/tal-tech/go-zero/core/collection"
|
||||||
@@ -21,7 +20,6 @@ const (
|
|||||||
// see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/
|
// see Client-Side Throttling section in https://landing.google.com/sre/sre-book/chapters/handling-overload/
|
||||||
type googleBreaker struct {
|
type googleBreaker struct {
|
||||||
k float64
|
k float64
|
||||||
state int32
|
|
||||||
stat *collection.RollingWindow
|
stat *collection.RollingWindow
|
||||||
proba *mathx.Proba
|
proba *mathx.Proba
|
||||||
}
|
}
|
||||||
@@ -32,7 +30,6 @@ func newGoogleBreaker() *googleBreaker {
|
|||||||
return &googleBreaker{
|
return &googleBreaker{
|
||||||
stat: st,
|
stat: st,
|
||||||
k: k,
|
k: k,
|
||||||
state: StateClosed,
|
|
||||||
proba: mathx.NewProba(),
|
proba: mathx.NewProba(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,15 +40,9 @@ func (b *googleBreaker) accept() error {
|
|||||||
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
|
// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101
|
||||||
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
|
dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
|
||||||
if dropRatio <= 0 {
|
if dropRatio <= 0 {
|
||||||
if atomic.LoadInt32(&b.state) == StateOpen {
|
|
||||||
atomic.CompareAndSwapInt32(&b.state, StateOpen, StateClosed)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if atomic.LoadInt32(&b.state) == StateClosed {
|
|
||||||
atomic.CompareAndSwapInt32(&b.state, StateClosed, StateOpen)
|
|
||||||
}
|
|
||||||
if b.proba.TrueOnProba(dropRatio) {
|
if b.proba.TrueOnProba(dropRatio) {
|
||||||
return ErrServiceUnavailable
|
return ErrServiceUnavailable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package breaker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -27,7 +26,6 @@ func getGoogleBreaker() *googleBreaker {
|
|||||||
return &googleBreaker{
|
return &googleBreaker{
|
||||||
stat: st,
|
stat: st,
|
||||||
k: 5,
|
k: 5,
|
||||||
state: StateClosed,
|
|
||||||
proba: mathx.NewProba(),
|
proba: mathx.NewProba(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +156,7 @@ func TestGoogleBreakerSelfProtection(t *testing.T) {
|
|||||||
t.Run("total request > 100, total < 2 * success", func(t *testing.T) {
|
t.Run("total request > 100, total < 2 * success", func(t *testing.T) {
|
||||||
b := getGoogleBreaker()
|
b := getGoogleBreaker()
|
||||||
size := rand.Intn(10000)
|
size := rand.Intn(10000)
|
||||||
accepts := int(math.Ceil(float64(size))) + 1
|
accepts := size + 1
|
||||||
markSuccess(b, accepts)
|
markSuccess(b, accepts)
|
||||||
markFailed(b, size-accepts)
|
markFailed(b, size-accepts)
|
||||||
assert.Nil(t, b.accept())
|
assert.Nil(t, b.accept())
|
||||||
|
|||||||
80
core/cmdline/input_test.go
Normal file
80
core/cmdline/input_test.go
Normal 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:
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,3 +71,12 @@ func TestDiffieHellmanMiddleManAttack(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, string(src), string(decryptedSrc))
|
assert.Equal(t, string(src), string(decryptedSrc))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeyBytes(t *testing.T) {
|
||||||
|
var empty DhKey
|
||||||
|
assert.Equal(t, 0, len(empty.Bytes()))
|
||||||
|
|
||||||
|
key, err := GenerateKey()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, len(key.Bytes()) > 0)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const unzipLimit = 100 * 1024 * 1024 // 100MB
|
||||||
|
|
||||||
func Gzip(bs []byte) []byte {
|
func Gzip(bs []byte) []byte {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
|
||||||
@@ -24,8 +26,7 @@ func Gunzip(bs []byte) ([]byte, error) {
|
|||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
var c bytes.Buffer
|
var c bytes.Buffer
|
||||||
_, err = io.Copy(&c, r)
|
if _, err = io.Copy(&c, io.LimitReader(r, unzipLimit)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
core/codec/hmac_test.go
Normal file
19
core/codec/hmac_test.go
Normal 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
58
core/codec/rsa_test.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -29,7 +29,6 @@ type (
|
|||||||
name string
|
name string
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
data map[string]interface{}
|
data map[string]interface{}
|
||||||
evicts *list.List
|
|
||||||
expire time.Duration
|
expire time.Duration
|
||||||
timingWheel *TimingWheel
|
timingWheel *TimingWheel
|
||||||
lruCache lru
|
lruCache lru
|
||||||
@@ -278,18 +277,15 @@ func (cs *cacheStat) statLoop() {
|
|||||||
ticker := time.NewTicker(statInterval)
|
ticker := time.NewTicker(statInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for range ticker.C {
|
||||||
select {
|
hit := atomic.SwapUint64(&cs.hit, 0)
|
||||||
case <-ticker.C:
|
miss := atomic.SwapUint64(&cs.miss, 0)
|
||||||
hit := atomic.SwapUint64(&cs.hit, 0)
|
total := hit + miss
|
||||||
miss := atomic.SwapUint64(&cs.miss, 0)
|
if total == 0 {
|
||||||
total := hit + miss
|
continue
|
||||||
if total == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
percent := 100 * float32(hit) / float32(total)
|
|
||||||
logx.Statf("cache(%s) - qpm: %d, hit_ratio: %.1f%%, elements: %d, hit: %d, miss: %d",
|
|
||||||
cs.name, total, percent, cs.sizeCallback(), hit, miss)
|
|
||||||
}
|
}
|
||||||
|
percent := 100 * float32(hit) / float32(total)
|
||||||
|
logx.Statf("cache(%s) - qpm: %d, hit_ratio: %.1f%%, elements: %d, hit: %d, miss: %d",
|
||||||
|
cs.name, total, percent, cs.sizeCallback(), hit, miss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ type Ring struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRing(n int) *Ring {
|
func NewRing(n int) *Ring {
|
||||||
|
if n < 1 {
|
||||||
|
panic("n should be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
return &Ring{
|
return &Ring{
|
||||||
elements: make([]interface{}, n),
|
elements: make([]interface{}, n),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNewRing(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
NewRing(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRingLess(t *testing.T) {
|
func TestRingLess(t *testing.T) {
|
||||||
ring := NewRing(5)
|
ring := NewRing(5)
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
// RollingWindowOption let callers customize the RollingWindow.
|
||||||
RollingWindowOption func(rollingWindow *RollingWindow)
|
RollingWindowOption func(rollingWindow *RollingWindow)
|
||||||
|
|
||||||
|
// RollingWindow defines a rolling window to calculate the events in buckets with time interval.
|
||||||
RollingWindow struct {
|
RollingWindow struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
size int
|
size int
|
||||||
@@ -17,11 +19,17 @@ type (
|
|||||||
interval time.Duration
|
interval time.Duration
|
||||||
offset int
|
offset int
|
||||||
ignoreCurrent bool
|
ignoreCurrent bool
|
||||||
lastTime time.Duration
|
lastTime time.Duration // start time of the last bucket
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewRollingWindow returns a RollingWindow that with size buckets and time interval,
|
||||||
|
// use opts to customize the RollingWindow.
|
||||||
func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow {
|
func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOption) *RollingWindow {
|
||||||
|
if size < 1 {
|
||||||
|
panic("size must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
w := &RollingWindow{
|
w := &RollingWindow{
|
||||||
size: size,
|
size: size,
|
||||||
win: newWindow(size),
|
win: newWindow(size),
|
||||||
@@ -34,6 +42,7 @@ func NewRollingWindow(size int, interval time.Duration, opts ...RollingWindowOpt
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add adds value to current bucket.
|
||||||
func (rw *RollingWindow) Add(v float64) {
|
func (rw *RollingWindow) Add(v float64) {
|
||||||
rw.lock.Lock()
|
rw.lock.Lock()
|
||||||
defer rw.lock.Unlock()
|
defer rw.lock.Unlock()
|
||||||
@@ -41,6 +50,7 @@ func (rw *RollingWindow) Add(v float64) {
|
|||||||
rw.win.add(rw.offset, v)
|
rw.win.add(rw.offset, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reduce runs fn on all buckets, ignore current bucket if ignoreCurrent was set.
|
||||||
func (rw *RollingWindow) Reduce(fn func(b *Bucket)) {
|
func (rw *RollingWindow) Reduce(fn func(b *Bucket)) {
|
||||||
rw.lock.RLock()
|
rw.lock.RLock()
|
||||||
defer rw.lock.RUnlock()
|
defer rw.lock.RUnlock()
|
||||||
@@ -70,29 +80,23 @@ func (rw *RollingWindow) span() int {
|
|||||||
|
|
||||||
func (rw *RollingWindow) updateOffset() {
|
func (rw *RollingWindow) updateOffset() {
|
||||||
span := rw.span()
|
span := rw.span()
|
||||||
if span > 0 {
|
if span <= 0 {
|
||||||
offset := rw.offset
|
return
|
||||||
// reset expired buckets
|
|
||||||
start := offset + 1
|
|
||||||
steps := start + span
|
|
||||||
var remainder int
|
|
||||||
if steps > rw.size {
|
|
||||||
remainder = steps - rw.size
|
|
||||||
steps = rw.size
|
|
||||||
}
|
|
||||||
for i := start; i < steps; i++ {
|
|
||||||
rw.win.resetBucket(i)
|
|
||||||
offset = i
|
|
||||||
}
|
|
||||||
for i := 0; i < remainder; i++ {
|
|
||||||
rw.win.resetBucket(i)
|
|
||||||
offset = i
|
|
||||||
}
|
|
||||||
rw.offset = offset
|
|
||||||
rw.lastTime = timex.Now()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset := rw.offset
|
||||||
|
// reset expired buckets
|
||||||
|
for i := 0; i < span; i++ {
|
||||||
|
rw.win.resetBucket((offset + i + 1) % rw.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.offset = (offset + span) % rw.size
|
||||||
|
now := timex.Now()
|
||||||
|
// align to interval time boundary
|
||||||
|
rw.lastTime = now - (now-rw.lastTime)%rw.interval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bucket defines the bucket that holds sum and num of additions.
|
||||||
type Bucket struct {
|
type Bucket struct {
|
||||||
Sum float64
|
Sum float64
|
||||||
Count int64
|
Count int64
|
||||||
@@ -114,9 +118,9 @@ type window struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newWindow(size int) *window {
|
func newWindow(size int) *window {
|
||||||
var buckets []*Bucket
|
buckets := make([]*Bucket, size)
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
buckets = append(buckets, new(Bucket))
|
buckets[i] = new(Bucket)
|
||||||
}
|
}
|
||||||
return &window{
|
return &window{
|
||||||
buckets: buckets,
|
buckets: buckets,
|
||||||
@@ -130,14 +134,15 @@ func (w *window) add(offset int, v float64) {
|
|||||||
|
|
||||||
func (w *window) reduce(start, count int, fn func(b *Bucket)) {
|
func (w *window) reduce(start, count int, fn func(b *Bucket)) {
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
fn(w.buckets[(start+i)%len(w.buckets)])
|
fn(w.buckets[(start+i)%w.size])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *window) resetBucket(offset int) {
|
func (w *window) resetBucket(offset int) {
|
||||||
w.buckets[offset].reset()
|
w.buckets[offset%w.size].reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IgnoreCurrentBucket lets the Reduce call ignore current bucket.
|
||||||
func IgnoreCurrentBucket() RollingWindowOption {
|
func IgnoreCurrentBucket() RollingWindowOption {
|
||||||
return func(w *RollingWindow) {
|
return func(w *RollingWindow) {
|
||||||
w.ignoreCurrent = true
|
w.ignoreCurrent = true
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ import (
|
|||||||
|
|
||||||
const duration = time.Millisecond * 50
|
const duration = time.Millisecond * 50
|
||||||
|
|
||||||
|
func TestNewRollingWindow(t *testing.T) {
|
||||||
|
assert.NotNil(t, NewRollingWindow(10, time.Second))
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
NewRollingWindow(0, time.Second)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRollingWindowAdd(t *testing.T) {
|
func TestRollingWindowAdd(t *testing.T) {
|
||||||
const size = 3
|
const size = 3
|
||||||
r := NewRollingWindow(size, duration)
|
r := NewRollingWindow(size, duration)
|
||||||
@@ -81,7 +88,7 @@ func TestRollingWindowReduce(t *testing.T) {
|
|||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(stringx.Rand(), func(t *testing.T) {
|
t.Run(stringx.Rand(), func(t *testing.T) {
|
||||||
r := test.win
|
r := test.win
|
||||||
for x := 0; x < size; x = x + 1 {
|
for x := 0; x < size; x++ {
|
||||||
for i := 0; i <= x; i++ {
|
for i := 0; i <= x; i++ {
|
||||||
r.Add(float64(i))
|
r.Add(float64(i))
|
||||||
}
|
}
|
||||||
@@ -98,6 +105,37 @@ func TestRollingWindowReduce(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRollingWindowBucketTimeBoundary(t *testing.T) {
|
||||||
|
const size = 3
|
||||||
|
interval := time.Millisecond * 30
|
||||||
|
r := NewRollingWindow(size, interval)
|
||||||
|
listBuckets := func() []float64 {
|
||||||
|
var buckets []float64
|
||||||
|
r.Reduce(func(b *Bucket) {
|
||||||
|
buckets = append(buckets, b.Sum)
|
||||||
|
})
|
||||||
|
return buckets
|
||||||
|
}
|
||||||
|
assert.Equal(t, []float64{0, 0, 0}, listBuckets())
|
||||||
|
r.Add(1)
|
||||||
|
assert.Equal(t, []float64{0, 0, 1}, listBuckets())
|
||||||
|
time.Sleep(time.Millisecond * 45)
|
||||||
|
r.Add(2)
|
||||||
|
r.Add(3)
|
||||||
|
assert.Equal(t, []float64{0, 1, 5}, listBuckets())
|
||||||
|
// sleep time should be less than interval, and make the bucket change happen
|
||||||
|
time.Sleep(time.Millisecond * 20)
|
||||||
|
r.Add(4)
|
||||||
|
r.Add(5)
|
||||||
|
r.Add(6)
|
||||||
|
assert.Equal(t, []float64{1, 5, 15}, listBuckets())
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
r.Add(7)
|
||||||
|
r.Add(8)
|
||||||
|
r.Add(9)
|
||||||
|
assert.Equal(t, []float64{0, 0, 24}, listBuckets())
|
||||||
|
}
|
||||||
|
|
||||||
func TestRollingWindowDataRace(t *testing.T) {
|
func TestRollingWindowDataRace(t *testing.T) {
|
||||||
const size = 3
|
const size = 3
|
||||||
r := NewRollingWindow(size, duration)
|
r := NewRollingWindow(size, duration)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const (
|
|||||||
stringType
|
stringType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Set is not thread-safe, for concurrent use, make sure to use it with synchronization.
|
||||||
type Set struct {
|
type Set struct {
|
||||||
data map[interface{}]lang.PlaceholderType
|
data map[interface{}]lang.PlaceholderType
|
||||||
tp int
|
tp int
|
||||||
@@ -182,10 +183,7 @@ func (s *Set) add(i interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) setType(i interface{}) {
|
func (s *Set) setType(i interface{}) {
|
||||||
if s.tp != untyped {
|
// s.tp can only be untyped here
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch i.(type) {
|
switch i.(type) {
|
||||||
case int:
|
case int:
|
||||||
s.tp = intType
|
s.tp = intType
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logx.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRawSet(b *testing.B) {
|
func BenchmarkRawSet(b *testing.B) {
|
||||||
m := make(map[interface{}]struct{})
|
m := make(map[interface{}]struct{})
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@@ -147,3 +152,51 @@ func TestCount(t *testing.T) {
|
|||||||
// then
|
// then
|
||||||
assert.Equal(t, set.Count(), 3)
|
assert.Equal(t, set.Count(), 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKeysIntMismatch(t *testing.T) {
|
||||||
|
set := NewSet()
|
||||||
|
set.add(int64(1))
|
||||||
|
set.add(2)
|
||||||
|
vals := set.KeysInt()
|
||||||
|
assert.EqualValues(t, []int{2}, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeysInt64Mismatch(t *testing.T) {
|
||||||
|
set := NewSet()
|
||||||
|
set.add(1)
|
||||||
|
set.add(int64(2))
|
||||||
|
vals := set.KeysInt64()
|
||||||
|
assert.EqualValues(t, []int64{2}, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeysUintMismatch(t *testing.T) {
|
||||||
|
set := NewSet()
|
||||||
|
set.add(1)
|
||||||
|
set.add(uint(2))
|
||||||
|
vals := set.KeysUint()
|
||||||
|
assert.EqualValues(t, []uint{2}, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeysUint64Mismatch(t *testing.T) {
|
||||||
|
set := NewSet()
|
||||||
|
set.add(1)
|
||||||
|
set.add(uint64(2))
|
||||||
|
vals := set.KeysUint64()
|
||||||
|
assert.EqualValues(t, []uint64{2}, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeysStrMismatch(t *testing.T) {
|
||||||
|
set := NewSet()
|
||||||
|
set.add(1)
|
||||||
|
set.add("2")
|
||||||
|
vals := set.KeysStr()
|
||||||
|
assert.EqualValues(t, []string{"2"}, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetType(t *testing.T) {
|
||||||
|
set := NewUnmanagedSet()
|
||||||
|
set.add(1)
|
||||||
|
set.add("2")
|
||||||
|
vals := set.Keys()
|
||||||
|
assert.ElementsMatch(t, []interface{}{1, "2"}, vals)
|
||||||
|
}
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ func (tw *TimingWheel) removeTask(key interface{}) {
|
|||||||
|
|
||||||
timer := val.(*positionEntry)
|
timer := val.(*positionEntry)
|
||||||
timer.item.removed = true
|
timer.item.removed = true
|
||||||
|
tw.timers.Del(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tw *TimingWheel) run() {
|
func (tw *TimingWheel) run() {
|
||||||
@@ -248,7 +249,6 @@ func (tw *TimingWheel) scanAndRunTasks(l *list.List) {
|
|||||||
if task.removed {
|
if task.removed {
|
||||||
next := e.Next()
|
next := e.Next()
|
||||||
l.Remove(e)
|
l.Remove(e)
|
||||||
tw.timers.Del(task.key)
|
|
||||||
e = next
|
e = next
|
||||||
continue
|
continue
|
||||||
} else if task.circle > 0 {
|
} else if task.circle > 0 {
|
||||||
@@ -301,6 +301,7 @@ func (tw *TimingWheel) setTask(task *timingEntry) {
|
|||||||
func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) {
|
func (tw *TimingWheel) setTimerPosition(pos int, task *timingEntry) {
|
||||||
if val, ok := tw.timers.Get(task.key); ok {
|
if val, ok := tw.timers.Get(task.key); ok {
|
||||||
timer := val.(*positionEntry)
|
timer := val.(*positionEntry)
|
||||||
|
timer.item = task
|
||||||
timer.pos = pos
|
timer.pos = pos
|
||||||
} else {
|
} else {
|
||||||
tw.timers.Set(task.key, &positionEntry{
|
tw.timers.Set(task.key, &positionEntry{
|
||||||
|
|||||||
@@ -594,6 +594,31 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMoveAndRemoveTask(t *testing.T) {
|
||||||
|
ticker := timex.NewFakeTicker()
|
||||||
|
tick := func(v int) {
|
||||||
|
for i := 0; i < v; i++ {
|
||||||
|
ticker.Tick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var keys []int
|
||||||
|
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||||
|
assert.Equal(t, "any", k)
|
||||||
|
assert.Equal(t, 3, v.(int))
|
||||||
|
keys = append(keys, v.(int))
|
||||||
|
ticker.Done()
|
||||||
|
}, ticker)
|
||||||
|
defer tw.Stop()
|
||||||
|
tw.SetTimer("any", 3, testStep*8)
|
||||||
|
tick(6)
|
||||||
|
tw.MoveTimer("any", testStep*7)
|
||||||
|
tick(3)
|
||||||
|
tw.RemoveTimer("any")
|
||||||
|
tick(30)
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
assert.Equal(t, 0, len(keys))
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkTimingWheel(b *testing.B) {
|
func BenchmarkTimingWheel(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/mapping"
|
"github.com/tal-tech/go-zero/core/mapping"
|
||||||
@@ -19,7 +20,7 @@ func LoadConfig(file string, v interface{}) error {
|
|||||||
if content, err := ioutil.ReadFile(file); err != nil {
|
if content, err := ioutil.ReadFile(file); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if loader, ok := loaders[path.Ext(file)]; ok {
|
} else if loader, ok := loaders[path.Ext(file)]; ok {
|
||||||
return loader(content, v)
|
return loader([]byte(os.ExpandEnv(string(content))), v)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("unrecoginized file type: %s", file)
|
return fmt.Errorf("unrecoginized file type: %s", file)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ func TestConfigJson(t *testing.T) {
|
|||||||
}
|
}
|
||||||
text := `{
|
text := `{
|
||||||
"a": "foo",
|
"a": "foo",
|
||||||
"b": 1
|
"b": 1,
|
||||||
|
"c": "${FOO}"
|
||||||
}`
|
}`
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test
|
test := test
|
||||||
t.Run(test, func(t *testing.T) {
|
t.Run(test, func(t *testing.T) {
|
||||||
t.Parallel()
|
os.Setenv("FOO", "2")
|
||||||
|
defer os.Unsetenv("FOO")
|
||||||
tmpfile, err := createTempFile(test, text)
|
tmpfile, err := createTempFile(test, text)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer os.Remove(tmpfile)
|
defer os.Remove(tmpfile)
|
||||||
@@ -31,11 +32,12 @@ func TestConfigJson(t *testing.T) {
|
|||||||
var val struct {
|
var val struct {
|
||||||
A string `json:"a"`
|
A string `json:"a"`
|
||||||
B int `json:"b"`
|
B int `json:"b"`
|
||||||
|
C string `json:"c"`
|
||||||
}
|
}
|
||||||
err = LoadConfig(tmpfile, &val)
|
MustLoad(tmpfile, &val)
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "foo", val.A)
|
assert.Equal(t, "foo", val.A)
|
||||||
assert.Equal(t, 1, val.B)
|
assert.Equal(t, 1, val.B)
|
||||||
|
assert.Equal(t, "2", val.C)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ type mapBasedProperties struct {
|
|||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads the properties into a properties configuration instance. May return the
|
// Loads the properties into a properties configuration instance.
|
||||||
// configuration itself along with an error that indicates if there was a problem loading the configuration.
|
// Returns an error that indicates if there was a problem loading the configuration.
|
||||||
func LoadProperties(filename string) (Properties, error) {
|
func LoadProperties(filename string) (Properties, error) {
|
||||||
lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#"))
|
lines, err := iox.ReadTextLines(filename, iox.WithoutBlank(), iox.OmitWithPrefix("#"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := make(map[string]string)
|
raw := make(map[string]string)
|
||||||
|
|||||||
@@ -41,3 +41,8 @@ func TestSetInt(t *testing.T) {
|
|||||||
props.SetInt(key, value)
|
props.SetInt(key, value)
|
||||||
assert.Equal(t, value, props.GetInt(key))
|
assert.Equal(t, value, props.GetInt(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadBadFile(t *testing.T) {
|
||||||
|
_, err := LoadProperties("nosuchfile")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import (
|
|||||||
|
|
||||||
func TestShrinkDeadlineLess(t *testing.T) {
|
func TestShrinkDeadlineLess(t *testing.T) {
|
||||||
deadline := time.Now().Add(time.Second)
|
deadline := time.Now().Add(time.Second)
|
||||||
ctx, _ := context.WithDeadline(context.Background(), deadline)
|
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||||
ctx, _ = ShrinkDeadline(ctx, time.Minute)
|
defer cancel()
|
||||||
|
ctx, cancel = ShrinkDeadline(ctx, time.Minute)
|
||||||
|
defer cancel()
|
||||||
dl, ok := ctx.Deadline()
|
dl, ok := ctx.Deadline()
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, deadline, dl)
|
assert.Equal(t, deadline, dl)
|
||||||
@@ -19,8 +21,10 @@ func TestShrinkDeadlineLess(t *testing.T) {
|
|||||||
|
|
||||||
func TestShrinkDeadlineMore(t *testing.T) {
|
func TestShrinkDeadlineMore(t *testing.T) {
|
||||||
deadline := time.Now().Add(time.Minute)
|
deadline := time.Now().Add(time.Minute)
|
||||||
ctx, _ := context.WithDeadline(context.Background(), deadline)
|
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||||
ctx, _ = ShrinkDeadline(ctx, time.Second)
|
defer cancel()
|
||||||
|
ctx, cancel = ShrinkDeadline(ctx, time.Second)
|
||||||
|
defer cancel()
|
||||||
dl, ok := ctx.Deadline()
|
dl, ok := ctx.Deadline()
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.True(t, dl.Before(deadline))
|
assert.True(t, dl.Before(deadline))
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ func TestContextCancel(t *testing.T) {
|
|||||||
c := context.WithValue(context.Background(), "key", "value")
|
c := context.WithValue(context.Background(), "key", "value")
|
||||||
c1, cancel := context.WithCancel(c)
|
c1, cancel := context.WithCancel(c)
|
||||||
o := ValueOnlyFrom(c1)
|
o := ValueOnlyFrom(c1)
|
||||||
c2, _ := context.WithCancel(o)
|
c2, cancel2 := context.WithCancel(o)
|
||||||
|
defer cancel2()
|
||||||
contexts := []context.Context{c1, c2}
|
contexts := []context.Context{c1, c2}
|
||||||
|
|
||||||
for _, c := range contexts {
|
for _, c := range contexts {
|
||||||
@@ -35,7 +36,8 @@ func TestContextCancel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContextDeadline(t *testing.T) {
|
func TestContextDeadline(t *testing.T) {
|
||||||
c, _ := context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond))
|
c, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond))
|
||||||
|
cancel()
|
||||||
o := ValueOnlyFrom(c)
|
o := ValueOnlyFrom(c)
|
||||||
select {
|
select {
|
||||||
case <-time.After(100 * time.Millisecond):
|
case <-time.After(100 * time.Millisecond):
|
||||||
@@ -43,9 +45,11 @@ func TestContextDeadline(t *testing.T) {
|
|||||||
t.Fatal("ValueOnlyContext: context should not have timed out")
|
t.Fatal("ValueOnlyContext: context should not have timed out")
|
||||||
}
|
}
|
||||||
|
|
||||||
c, _ = context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond))
|
c, cancel = context.WithDeadline(context.Background(), time.Now().Add(10*time.Millisecond))
|
||||||
|
cancel()
|
||||||
o = ValueOnlyFrom(c)
|
o = ValueOnlyFrom(c)
|
||||||
c, _ = context.WithDeadline(o, time.Now().Add(20*time.Millisecond))
|
c, cancel = context.WithDeadline(o, time.Now().Add(20*time.Millisecond))
|
||||||
|
defer cancel()
|
||||||
select {
|
select {
|
||||||
case <-time.After(100 * time.Millisecond):
|
case <-time.After(100 * time.Millisecond):
|
||||||
t.Fatal("ValueOnlyContext+Deadline: context should have timed out")
|
t.Fatal("ValueOnlyContext+Deadline: context should have timed out")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
indexOfKey = iota
|
_ = iota
|
||||||
indexOfId
|
indexOfId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
//go:generate mockgen -package internal -destination listener_mock.go -source listener.go Listener
|
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
type Listener interface {
|
type Listener interface {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd0:2379
|
- http://etcd0.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -107,7 +107,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd1:2379
|
- http://etcd1.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -179,7 +179,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd2:2379
|
- http://etcd2.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -251,7 +251,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd3:2379
|
- http://etcd3.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
@@ -323,7 +323,7 @@ spec:
|
|||||||
- --listen-client-urls
|
- --listen-client-urls
|
||||||
- http://0.0.0.0:2379
|
- http://0.0.0.0:2379
|
||||||
- --advertise-client-urls
|
- --advertise-client-urls
|
||||||
- http://etcd4:2379
|
- http://etcd4.discov:2379
|
||||||
- --initial-cluster
|
- --initial-cluster
|
||||||
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
- etcd0=http://etcd0:2380,etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380,etcd4=http://etcd4:2380
|
||||||
- --initial-cluster-state
|
- --initial-cluster-state
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ func TestPublisher_keepAliveAsyncQuit(t *testing.T) {
|
|||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
const id clientv3.LeaseID = 1
|
const id clientv3.LeaseID = 1
|
||||||
cli := internal.NewMockEtcdClient(ctrl)
|
cli := internal.NewMockEtcdClient(ctrl)
|
||||||
|
cli.EXPECT().ActiveConnection()
|
||||||
|
cli.EXPECT().Close()
|
||||||
|
defer cli.Close()
|
||||||
|
cli.ActiveConnection()
|
||||||
restore := setMockClient(cli)
|
restore := setMockClient(cli)
|
||||||
defer restore()
|
defer restore()
|
||||||
cli.EXPECT().Ctx().AnyTimes()
|
cli.EXPECT().Ctx().AnyTimes()
|
||||||
|
|||||||
11
core/errorx/callchain.go
Normal file
11
core/errorx/callchain.go
Normal 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
|
||||||
|
}
|
||||||
27
core/errorx/callchain_test.go
Normal file
27
core/errorx/callchain_test.go
Normal 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
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -86,9 +86,7 @@ func TestBuldExecutorFlushSlowTasks(t *testing.T) {
|
|||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
for _, i := range tasks {
|
result = append(result, tasks...)
|
||||||
result = append(result, i)
|
|
||||||
}
|
|
||||||
}, WithBulkTasks(1000))
|
}, WithBulkTasks(1000))
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
assert.Nil(t, exec.Add(i))
|
assert.Nil(t, exec.Add(i))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package executors
|
|||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/lang"
|
"github.com/tal-tech/go-zero/core/lang"
|
||||||
@@ -35,6 +36,7 @@ type (
|
|||||||
// avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
|
// avoid race condition on waitGroup when calling wg.Add/Done/Wait(...)
|
||||||
wgBarrier syncx.Barrier
|
wgBarrier syncx.Barrier
|
||||||
confirmChan chan lang.PlaceholderType
|
confirmChan chan lang.PlaceholderType
|
||||||
|
inflight int32
|
||||||
guarded bool
|
guarded bool
|
||||||
newTicker func(duration time.Duration) timex.Ticker
|
newTicker func(duration time.Duration) timex.Ticker
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
@@ -82,6 +84,7 @@ func (pe *PeriodicalExecutor) Sync(fn func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pe *PeriodicalExecutor) Wait() {
|
func (pe *PeriodicalExecutor) Wait() {
|
||||||
|
pe.Flush()
|
||||||
pe.wgBarrier.Guard(func() {
|
pe.wgBarrier.Guard(func() {
|
||||||
pe.waitGroup.Wait()
|
pe.waitGroup.Wait()
|
||||||
})
|
})
|
||||||
@@ -90,18 +93,16 @@ func (pe *PeriodicalExecutor) Wait() {
|
|||||||
func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) {
|
func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) {
|
||||||
pe.lock.Lock()
|
pe.lock.Lock()
|
||||||
defer func() {
|
defer func() {
|
||||||
var start bool
|
|
||||||
if !pe.guarded {
|
if !pe.guarded {
|
||||||
pe.guarded = true
|
pe.guarded = true
|
||||||
start = true
|
// defer to unlock quickly
|
||||||
|
defer pe.backgroundFlush()
|
||||||
}
|
}
|
||||||
pe.lock.Unlock()
|
pe.lock.Unlock()
|
||||||
if start {
|
|
||||||
pe.backgroundFlush()
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if pe.container.AddTask(task) {
|
if pe.container.AddTask(task) {
|
||||||
|
atomic.AddInt32(&pe.inflight, 1)
|
||||||
return pe.container.RemoveAll(), true
|
return pe.container.RemoveAll(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +111,9 @@ func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool)
|
|||||||
|
|
||||||
func (pe *PeriodicalExecutor) backgroundFlush() {
|
func (pe *PeriodicalExecutor) backgroundFlush() {
|
||||||
threading.GoSafe(func() {
|
threading.GoSafe(func() {
|
||||||
|
// flush before quit goroutine to avoid missing tasks
|
||||||
|
defer pe.Flush()
|
||||||
|
|
||||||
ticker := pe.newTicker(pe.interval)
|
ticker := pe.newTicker(pe.interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -119,6 +123,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
|
|||||||
select {
|
select {
|
||||||
case vals := <-pe.commander:
|
case vals := <-pe.commander:
|
||||||
commanded = true
|
commanded = true
|
||||||
|
atomic.AddInt32(&pe.inflight, -1)
|
||||||
pe.enterExecution()
|
pe.enterExecution()
|
||||||
pe.confirmChan <- lang.Placeholder
|
pe.confirmChan <- lang.Placeholder
|
||||||
pe.executeTasks(vals)
|
pe.executeTasks(vals)
|
||||||
@@ -128,13 +133,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
|
|||||||
commanded = false
|
commanded = false
|
||||||
} else if pe.Flush() {
|
} else if pe.Flush() {
|
||||||
last = timex.Now()
|
last = timex.Now()
|
||||||
} else if timex.Since(last) > pe.interval*idleRound {
|
} else if pe.shallQuit(last) {
|
||||||
pe.lock.Lock()
|
|
||||||
pe.guarded = false
|
|
||||||
pe.lock.Unlock()
|
|
||||||
|
|
||||||
// flush again to avoid missing tasks
|
|
||||||
pe.Flush()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,3 +176,19 @@ func (pe *PeriodicalExecutor) hasTasks(tasks interface{}) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pe *PeriodicalExecutor) shallQuit(last time.Duration) (stop bool) {
|
||||||
|
if timex.Since(last) <= pe.interval*idleRound {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking pe.inflight and setting pe.guarded should be locked together
|
||||||
|
pe.lock.Lock()
|
||||||
|
if atomic.LoadInt32(&pe.inflight) == 0 {
|
||||||
|
pe.guarded = false
|
||||||
|
stop = true
|
||||||
|
}
|
||||||
|
pe.lock.Unlock()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -140,6 +140,26 @@ func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
|||||||
assert.Equal(t, total, cnt)
|
assert.Equal(t, total, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPeriodicalExecutor_Deadlock(t *testing.T) {
|
||||||
|
executor := NewBulkExecutor(func(tasks []interface{}) {
|
||||||
|
}, WithBulkTasks(1), WithBulkInterval(time.Millisecond))
|
||||||
|
for i := 0; i < 1e5; i++ {
|
||||||
|
executor.Add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
|
||||||
|
ticker := timex.NewFakeTicker()
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
|
||||||
|
exec.newTicker = func(d time.Duration) timex.Ticker {
|
||||||
|
return ticker
|
||||||
|
}
|
||||||
|
assert.False(t, exec.hasTasks(nil))
|
||||||
|
assert.True(t, exec.hasTasks(1))
|
||||||
|
}
|
||||||
|
|
||||||
// go test -benchtime 10s -bench .
|
// go test -benchtime 10s -bench .
|
||||||
func BenchmarkExecutor(b *testing.B) {
|
func BenchmarkExecutor(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ func Range(source <-chan interface{}) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Buffer buffers the items into a queue with size n.
|
// Buffer buffers the items into a queue with size n.
|
||||||
|
// It can balance the producer and the consumer if their processing throughput don't match.
|
||||||
func (p Stream) Buffer(n int) Stream {
|
func (p Stream) Buffer(n int) Stream {
|
||||||
if n < 0 {
|
if n < 0 {
|
||||||
n = 0
|
n = 0
|
||||||
@@ -84,6 +85,14 @@ func (p Stream) Buffer(n int) Stream {
|
|||||||
return Range(source)
|
return Range(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count counts the number of elements in the result.
|
||||||
|
func (p Stream) Count() (count int) {
|
||||||
|
for range p.source {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Distinct removes the duplicated items base on the given KeyFunc.
|
// Distinct removes the duplicated items base on the given KeyFunc.
|
||||||
func (p Stream) Distinct(fn KeyFunc) Stream {
|
func (p Stream) Distinct(fn KeyFunc) Stream {
|
||||||
source := make(chan interface{})
|
source := make(chan interface{})
|
||||||
@@ -151,6 +160,10 @@ func (p Stream) Group(fn KeyFunc) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p Stream) Head(n int64) Stream {
|
func (p Stream) Head(n int64) Stream {
|
||||||
|
if n < 1 {
|
||||||
|
panic("n must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
source := make(chan interface{})
|
source := make(chan interface{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -235,7 +248,37 @@ func (p Stream) Sort(less LessFunc) Stream {
|
|||||||
return Just(items...)
|
return Just(items...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split splits the elements into chunk with size up to n,
|
||||||
|
// might be less than n on tailing elements.
|
||||||
|
func (p Stream) Split(n int) Stream {
|
||||||
|
if n < 1 {
|
||||||
|
panic("n should be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
source := make(chan interface{})
|
||||||
|
go func() {
|
||||||
|
var chunk []interface{}
|
||||||
|
for item := range p.source {
|
||||||
|
chunk = append(chunk, item)
|
||||||
|
if len(chunk) == n {
|
||||||
|
source <- chunk
|
||||||
|
chunk = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chunk != nil {
|
||||||
|
source <- chunk
|
||||||
|
}
|
||||||
|
close(source)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return Range(source)
|
||||||
|
}
|
||||||
|
|
||||||
func (p Stream) Tail(n int64) Stream {
|
func (p Stream) Tail(n int64) Stream {
|
||||||
|
if n < 1 {
|
||||||
|
panic("n should be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
source := make(chan interface{})
|
source := make(chan interface{})
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -49,6 +49,36 @@ func TestBufferNegative(t *testing.T) {
|
|||||||
assert.Equal(t, 10, result)
|
assert.Equal(t, 10, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCount(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
elements []interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no elements with nil",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no elements",
|
||||||
|
elements: []interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1 element",
|
||||||
|
elements: []interface{}{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple elements",
|
||||||
|
elements: []interface{}{1, 2, 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
val := Just(test.elements...).Count()
|
||||||
|
assert.Equal(t, len(test.elements), val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDone(t *testing.T) {
|
func TestDone(t *testing.T) {
|
||||||
var count int32
|
var count int32
|
||||||
Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) {
|
Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||||
@@ -139,6 +169,14 @@ func TestHead(t *testing.T) {
|
|||||||
assert.Equal(t, 3, result)
|
assert.Equal(t, 3, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHeadZero(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
Just(1, 2, 3, 4).Head(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestHeadMore(t *testing.T) {
|
func TestHeadMore(t *testing.T) {
|
||||||
var result int
|
var result int
|
||||||
Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||||
@@ -245,6 +283,22 @@ func TestSort(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSplit(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(0).Done()
|
||||||
|
})
|
||||||
|
var chunks [][]interface{}
|
||||||
|
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(4).ForEach(func(item interface{}) {
|
||||||
|
chunk := item.([]interface{})
|
||||||
|
chunks = append(chunks, chunk)
|
||||||
|
})
|
||||||
|
assert.EqualValues(t, [][]interface{}{
|
||||||
|
{1, 2, 3, 4},
|
||||||
|
{5, 6, 7, 8},
|
||||||
|
{9, 10},
|
||||||
|
}, chunks)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTail(t *testing.T) {
|
func TestTail(t *testing.T) {
|
||||||
var result int
|
var result int
|
||||||
Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||||
@@ -256,6 +310,14 @@ func TestTail(t *testing.T) {
|
|||||||
assert.Equal(t, 7, result)
|
assert.Equal(t, 7, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTailZero(t *testing.T) {
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
Just(1, 2, 3, 4).Tail(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestWalk(t *testing.T) {
|
func TestWalk(t *testing.T) {
|
||||||
var result int
|
var result int
|
||||||
Just(1, 2, 3, 4, 5).Walk(func(item interface{}, pipe chan<- interface{}) {
|
Just(1, 2, 3, 4, 5).Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||||
|
|||||||
23
core/iox/pipe.go
Normal file
23
core/iox/pipe.go
Normal 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
13
core/iox/pipe_test.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -3,9 +3,10 @@ package limit
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPeriodLimit_Take(t *testing.T) {
|
func TestPeriodLimit_Take(t *testing.T) {
|
||||||
@@ -33,16 +34,16 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPeriodLimit(t *testing.T, opts ...LimitOption) {
|
func testPeriodLimit(t *testing.T, opts ...LimitOption) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer clean()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
seconds = 1
|
seconds = 1
|
||||||
total = 100
|
total = 100
|
||||||
quota = 5
|
quota = 5
|
||||||
)
|
)
|
||||||
l := NewPeriodLimit(seconds, quota, redis.NewRedis(s.Addr(), redis.NodeType), "periodlimit", opts...)
|
l := NewPeriodLimit(seconds, quota, store, "periodlimit", opts...)
|
||||||
var allowed, hitQuota, overQuota int
|
var allowed, hitQuota, overQuota int
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
val, err := l.Take("first")
|
val, err := l.Take("first")
|
||||||
|
|||||||
@@ -153,13 +153,10 @@ func (lim *TokenLimiter) waitForRedis() {
|
|||||||
lim.rescueLock.Unlock()
|
lim.rescueLock.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for range ticker.C {
|
||||||
select {
|
if lim.store.Ping() {
|
||||||
case <-ticker.C:
|
atomic.StoreUint32(&lim.redisAlive, 1)
|
||||||
if lim.store.Ping() {
|
return
|
||||||
atomic.StoreUint32(&lim.redisAlive, 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -44,16 +45,16 @@ func TestTokenLimit_Rescue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_Take(t *testing.T) {
|
func TestTokenLimit_Take(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer clean()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
rate = 5
|
rate = 5
|
||||||
burst = 10
|
burst = 10
|
||||||
)
|
)
|
||||||
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
|
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
|
||||||
var allowed int
|
var allowed int
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
time.Sleep(time.Second / time.Duration(total))
|
time.Sleep(time.Second / time.Duration(total))
|
||||||
@@ -66,16 +67,16 @@ func TestTokenLimit_Take(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_TakeBurst(t *testing.T) {
|
func TestTokenLimit_TakeBurst(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer clean()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
rate = 5
|
rate = 5
|
||||||
burst = 10
|
burst = 10
|
||||||
)
|
)
|
||||||
l := NewTokenLimiter(rate, burst, redis.NewRedis(s.Addr(), redis.NodeType), "tokenlimit")
|
l := NewTokenLimiter(rate, burst, store, "tokenlimit")
|
||||||
var allowed int
|
var allowed int
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
if l.Allow() {
|
if l.Allow() {
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
|
|||||||
passCounter: passCounter,
|
passCounter: passCounter,
|
||||||
rtCounter: rtCounter,
|
rtCounter: rtCounter,
|
||||||
windows: buckets,
|
windows: buckets,
|
||||||
|
dropTime: syncx.NewAtomicDuration(),
|
||||||
droppedRecently: syncx.NewAtomicBool(),
|
droppedRecently: syncx.NewAtomicBool(),
|
||||||
}
|
}
|
||||||
// cpu >= 800, inflight < maxPass
|
// cpu >= 800, inflight < maxPass
|
||||||
@@ -160,6 +161,40 @@ func TestAdaptiveShedderShouldDrop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
shedder.avgFlying = 80
|
shedder.avgFlying = 80
|
||||||
assert.False(t, shedder.shouldDrop())
|
assert.False(t, shedder.shouldDrop())
|
||||||
|
|
||||||
|
// cpu >= 800, inflight < maxPass
|
||||||
|
systemOverloadChecker = func(int64) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
shedder.avgFlying = 80
|
||||||
|
shedder.flying = 80
|
||||||
|
_, err := shedder.Allow()
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdaptiveShedderStillHot(t *testing.T) {
|
||||||
|
logx.Disable()
|
||||||
|
passCounter := newRollingWindow()
|
||||||
|
rtCounter := newRollingWindow()
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
time.Sleep(bucketDuration)
|
||||||
|
}
|
||||||
|
passCounter.Add(float64((i + 1) * 100))
|
||||||
|
for j := i*10 + 1; j <= i*10+10; j++ {
|
||||||
|
rtCounter.Add(float64(j))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shedder := &adaptiveShedder{
|
||||||
|
passCounter: passCounter,
|
||||||
|
rtCounter: rtCounter,
|
||||||
|
windows: buckets,
|
||||||
|
dropTime: syncx.NewAtomicDuration(),
|
||||||
|
droppedRecently: syncx.ForAtomicBool(true),
|
||||||
|
}
|
||||||
|
assert.False(t, shedder.stillHot())
|
||||||
|
shedder.dropTime.Set(-coolOffDuration * 2)
|
||||||
|
assert.False(t, shedder.stillHot())
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAdaptiveShedder_Allow(b *testing.B) {
|
func BenchmarkAdaptiveShedder_Allow(b *testing.B) {
|
||||||
|
|||||||
@@ -13,3 +13,8 @@ func TestGroup(t *testing.T) {
|
|||||||
assert.NotNil(t, limiter)
|
assert.NotNil(t, limiter)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShedderClose(t *testing.T) {
|
||||||
|
var nop nopCloser
|
||||||
|
assert.Nil(t, nop.Close())
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const (
|
|||||||
consoleMode = "console"
|
consoleMode = "console"
|
||||||
volumeMode = "volume"
|
volumeMode = "volume"
|
||||||
|
|
||||||
|
levelAlert = "alert"
|
||||||
levelInfo = "info"
|
levelInfo = "info"
|
||||||
levelError = "error"
|
levelError = "error"
|
||||||
levelSevere = "severe"
|
levelSevere = "severe"
|
||||||
@@ -121,6 +122,10 @@ func SetUp(c LogConf) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Alert(v string) {
|
||||||
|
output(errorLog, levelAlert, v)
|
||||||
|
}
|
||||||
|
|
||||||
func Close() error {
|
func Close() error {
|
||||||
if writeConsole {
|
if writeConsole {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -21,10 +23,13 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type mockWriter struct {
|
type mockWriter struct {
|
||||||
|
lock sync.Mutex
|
||||||
builder strings.Builder
|
builder strings.Builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Write(data []byte) (int, error) {
|
func (mw *mockWriter) Write(data []byte) (int, error) {
|
||||||
|
mw.lock.Lock()
|
||||||
|
defer mw.lock.Unlock()
|
||||||
return mw.builder.Write(data)
|
return mw.builder.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,12 +37,22 @@ func (mw *mockWriter) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mw *mockWriter) Contains(text string) bool {
|
||||||
|
mw.lock.Lock()
|
||||||
|
defer mw.lock.Unlock()
|
||||||
|
return strings.Contains(mw.builder.String(), text)
|
||||||
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Reset() {
|
func (mw *mockWriter) Reset() {
|
||||||
|
mw.lock.Lock()
|
||||||
|
defer mw.lock.Unlock()
|
||||||
mw.builder.Reset()
|
mw.builder.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Contains(text string) bool {
|
func (mw *mockWriter) String() string {
|
||||||
return strings.Contains(mw.builder.String(), text)
|
mw.lock.Lock()
|
||||||
|
defer mw.lock.Unlock()
|
||||||
|
return mw.builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileLineFileMode(t *testing.T) {
|
func TestFileLineFileMode(t *testing.T) {
|
||||||
@@ -69,6 +84,14 @@ func TestFileLineConsoleMode(t *testing.T) {
|
|||||||
assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, writer.Contains(fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStructedLogAlert(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelAlert, func(writer io.WriteCloser) {
|
||||||
|
errorLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Alert(fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestStructedLogInfo(t *testing.T) {
|
func TestStructedLogInfo(t *testing.T) {
|
||||||
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
doTestStructedLog(t, levelInfo, func(writer io.WriteCloser) {
|
||||||
infoLog = writer
|
infoLog = writer
|
||||||
@@ -85,6 +108,46 @@ func TestStructedLogSlow(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStructedLogSlowf(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelSlow, func(writer io.WriteCloser) {
|
||||||
|
slowLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Slowf(fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructedLogStat(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||||
|
statLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Stat(v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructedLogStatf(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelStat, func(writer io.WriteCloser) {
|
||||||
|
statLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Statf(fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructedLogSevere(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
|
||||||
|
severeLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Severe(v...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructedLogSeveref(t *testing.T) {
|
||||||
|
doTestStructedLog(t, levelSevere, func(writer io.WriteCloser) {
|
||||||
|
severeLog = writer
|
||||||
|
}, func(v ...interface{}) {
|
||||||
|
Severef(fmt.Sprint(v...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestStructedLogWithDuration(t *testing.T) {
|
func TestStructedLogWithDuration(t *testing.T) {
|
||||||
const message = "hello there"
|
const message = "hello there"
|
||||||
writer := new(mockWriter)
|
writer := new(mockWriter)
|
||||||
@@ -135,6 +198,68 @@ func TestMustNil(t *testing.T) {
|
|||||||
Must(nil)
|
Must(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetup(t *testing.T) {
|
||||||
|
MustSetup(LogConf{
|
||||||
|
ServiceName: "any",
|
||||||
|
Mode: "console",
|
||||||
|
})
|
||||||
|
MustSetup(LogConf{
|
||||||
|
ServiceName: "any",
|
||||||
|
Mode: "file",
|
||||||
|
Path: os.TempDir(),
|
||||||
|
})
|
||||||
|
MustSetup(LogConf{
|
||||||
|
ServiceName: "any",
|
||||||
|
Mode: "volume",
|
||||||
|
Path: os.TempDir(),
|
||||||
|
})
|
||||||
|
assert.NotNil(t, setupWithVolume(LogConf{}))
|
||||||
|
assert.NotNil(t, setupWithFiles(LogConf{}))
|
||||||
|
assert.Nil(t, setupWithFiles(LogConf{
|
||||||
|
ServiceName: "any",
|
||||||
|
Path: os.TempDir(),
|
||||||
|
Compress: true,
|
||||||
|
KeepDays: 1,
|
||||||
|
}))
|
||||||
|
setupLogLevel(LogConf{
|
||||||
|
Level: levelInfo,
|
||||||
|
})
|
||||||
|
setupLogLevel(LogConf{
|
||||||
|
Level: levelError,
|
||||||
|
})
|
||||||
|
setupLogLevel(LogConf{
|
||||||
|
Level: levelSevere,
|
||||||
|
})
|
||||||
|
_, err := createOutput("")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
Disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisable(t *testing.T) {
|
||||||
|
Disable()
|
||||||
|
|
||||||
|
var opt logOptions
|
||||||
|
WithKeepDays(1)(&opt)
|
||||||
|
WithGzip()(&opt)
|
||||||
|
assert.Nil(t, Close())
|
||||||
|
writeConsole = false
|
||||||
|
assert.Nil(t, Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithGzip(t *testing.T) {
|
||||||
|
fn := WithGzip()
|
||||||
|
var opt logOptions
|
||||||
|
fn(&opt)
|
||||||
|
assert.True(t, opt.gzipEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithKeepDays(t *testing.T) {
|
||||||
|
fn := WithKeepDays(1)
|
||||||
|
var opt logOptions
|
||||||
|
fn(&opt)
|
||||||
|
assert.Equal(t, 1, opt.keepDays)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkCopyByteSliceAppend(b *testing.B) {
|
func BenchmarkCopyByteSliceAppend(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
var buf []byte
|
var buf []byte
|
||||||
@@ -232,7 +357,7 @@ func doTestStructedLog(t *testing.T, level string, setup func(writer io.WriteClo
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, level, entry.Level)
|
assert.Equal(t, level, entry.Level)
|
||||||
assert.Equal(t, message, entry.Content)
|
assert.True(t, strings.Contains(entry.Content, message))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
||||||
@@ -252,4 +377,10 @@ func testSetLevelTwiceWithMode(t *testing.T, mode string) {
|
|||||||
atomic.StoreUint32(&initialized, 1)
|
atomic.StoreUint32(&initialized, 1)
|
||||||
Info(message)
|
Info(message)
|
||||||
assert.Equal(t, 0, writer.builder.Len())
|
assert.Equal(t, 0, writer.builder.Len())
|
||||||
|
Infof(message)
|
||||||
|
assert.Equal(t, 0, writer.builder.Len())
|
||||||
|
ErrorStack(message)
|
||||||
|
assert.Equal(t, 0, writer.builder.Len())
|
||||||
|
ErrorStackf(message)
|
||||||
|
assert.Equal(t, 0, writer.builder.Len())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,14 +192,16 @@ func (l *RotateLogger) init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *RotateLogger) maybeCompressFile(file string) {
|
func (l *RotateLogger) maybeCompressFile(file string) {
|
||||||
if l.compress {
|
if !l.compress {
|
||||||
defer func() {
|
return
|
||||||
if r := recover(); r != nil {
|
|
||||||
ErrorStack(r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
compressLogFile(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ErrorStack(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
compressLogFile(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RotateLogger) maybeDeleteOutdatedFiles() {
|
func (l *RotateLogger) maybeDeleteOutdatedFiles() {
|
||||||
|
|||||||
119
core/logx/rotatelogger_test.go
Normal file
119
core/logx/rotatelogger_test.go
Normal 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`))
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ const testlog = "Stay hungry, stay foolish."
|
|||||||
func TestCollectSysLog(t *testing.T) {
|
func TestCollectSysLog(t *testing.T) {
|
||||||
CollectSysLog()
|
CollectSysLog()
|
||||||
content := getContent(captureOutput(func() {
|
content := getContent(captureOutput(func() {
|
||||||
log.Printf(testlog)
|
log.Print(testlog)
|
||||||
}))
|
}))
|
||||||
assert.True(t, strings.Contains(content, testlog))
|
assert.True(t, strings.Contains(content, testlog))
|
||||||
}
|
}
|
||||||
@@ -33,10 +33,10 @@ func captureOutput(f func()) string {
|
|||||||
writer := new(mockWriter)
|
writer := new(mockWriter)
|
||||||
infoLog = writer
|
infoLog = writer
|
||||||
|
|
||||||
prevLevel := logLevel
|
prevLevel := atomic.LoadUint32(&logLevel)
|
||||||
logLevel = InfoLevel
|
SetLevel(InfoLevel)
|
||||||
f()
|
f()
|
||||||
logLevel = prevLevel
|
SetLevel(prevLevel)
|
||||||
|
|
||||||
return writer.builder.String()
|
return writer.builder.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package logx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/trace/tracespec"
|
"github.com/tal-tech/go-zero/core/trace/tracespec"
|
||||||
@@ -17,13 +20,77 @@ const (
|
|||||||
var mock tracespec.Trace = new(mockTrace)
|
var mock tracespec.Trace = new(mockTrace)
|
||||||
|
|
||||||
func TestTraceLog(t *testing.T) {
|
func TestTraceLog(t *testing.T) {
|
||||||
var buf strings.Builder
|
var buf mockWriter
|
||||||
|
atomic.StoreUint32(&initialized, 1)
|
||||||
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
||||||
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
|
WithContext(ctx).(*traceLogger).write(&buf, levelInfo, testlog)
|
||||||
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTraceError(t *testing.T) {
|
||||||
|
var buf mockWriter
|
||||||
|
atomic.StoreUint32(&initialized, 1)
|
||||||
|
errorLog = newLogWriter(log.New(&buf, "", flags))
|
||||||
|
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
||||||
|
l := WithContext(ctx).(*traceLogger)
|
||||||
|
SetLevel(InfoLevel)
|
||||||
|
l.WithDuration(time.Second).Error(testlog)
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
buf.Reset()
|
||||||
|
l.WithDuration(time.Second).Errorf(testlog)
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraceInfo(t *testing.T) {
|
||||||
|
var buf mockWriter
|
||||||
|
atomic.StoreUint32(&initialized, 1)
|
||||||
|
infoLog = newLogWriter(log.New(&buf, "", flags))
|
||||||
|
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
||||||
|
l := WithContext(ctx).(*traceLogger)
|
||||||
|
SetLevel(InfoLevel)
|
||||||
|
l.WithDuration(time.Second).Info(testlog)
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
buf.Reset()
|
||||||
|
l.WithDuration(time.Second).Infof(testlog)
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraceSlow(t *testing.T) {
|
||||||
|
var buf mockWriter
|
||||||
|
atomic.StoreUint32(&initialized, 1)
|
||||||
|
slowLog = newLogWriter(log.New(&buf, "", flags))
|
||||||
|
ctx := context.WithValue(context.Background(), tracespec.TracingKey, mock)
|
||||||
|
l := WithContext(ctx).(*traceLogger)
|
||||||
|
SetLevel(InfoLevel)
|
||||||
|
l.WithDuration(time.Second).Slow(testlog)
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
buf.Reset()
|
||||||
|
l.WithDuration(time.Second).Slowf(testlog)
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.True(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraceWithoutContext(t *testing.T) {
|
||||||
|
var buf mockWriter
|
||||||
|
atomic.StoreUint32(&initialized, 1)
|
||||||
|
infoLog = newLogWriter(log.New(&buf, "", flags))
|
||||||
|
l := WithContext(context.Background()).(*traceLogger)
|
||||||
|
SetLevel(InfoLevel)
|
||||||
|
l.WithDuration(time.Second).Info(testlog)
|
||||||
|
assert.False(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.False(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
buf.Reset()
|
||||||
|
l.WithDuration(time.Second).Infof(testlog)
|
||||||
|
assert.False(t, strings.Contains(buf.String(), mockTraceId))
|
||||||
|
assert.False(t, strings.Contains(buf.String(), mockSpanId))
|
||||||
|
}
|
||||||
|
|
||||||
type mockTrace struct{}
|
type mockTrace struct{}
|
||||||
|
|
||||||
func (t mockTrace) TraceId() string {
|
func (t mockTrace) TraceId() string {
|
||||||
|
|||||||
@@ -153,58 +153,57 @@ func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fie
|
|||||||
key := strings.TrimSpace(segments[0])
|
key := strings.TrimSpace(segments[0])
|
||||||
options := segments[1:]
|
options := segments[1:]
|
||||||
|
|
||||||
if len(options) > 0 {
|
if len(options) == 0 {
|
||||||
var fieldOpts fieldOptions
|
return key, nil, nil
|
||||||
|
|
||||||
for _, segment := range options {
|
|
||||||
option := strings.TrimSpace(segment)
|
|
||||||
switch {
|
|
||||||
case option == stringOption:
|
|
||||||
fieldOpts.FromString = true
|
|
||||||
case strings.HasPrefix(option, optionalOption):
|
|
||||||
segs := strings.Split(option, equalToken)
|
|
||||||
switch len(segs) {
|
|
||||||
case 1:
|
|
||||||
fieldOpts.Optional = true
|
|
||||||
case 2:
|
|
||||||
fieldOpts.Optional = true
|
|
||||||
fieldOpts.OptionalDep = segs[1]
|
|
||||||
default:
|
|
||||||
return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
|
|
||||||
}
|
|
||||||
case option == optionalOption:
|
|
||||||
fieldOpts.Optional = true
|
|
||||||
case strings.HasPrefix(option, optionsOption):
|
|
||||||
segs := strings.Split(option, equalToken)
|
|
||||||
if len(segs) != 2 {
|
|
||||||
return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
|
|
||||||
} else {
|
|
||||||
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
|
|
||||||
}
|
|
||||||
case strings.HasPrefix(option, defaultOption):
|
|
||||||
segs := strings.Split(option, equalToken)
|
|
||||||
if len(segs) != 2 {
|
|
||||||
return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
|
|
||||||
} else {
|
|
||||||
fieldOpts.Default = strings.TrimSpace(segs[1])
|
|
||||||
}
|
|
||||||
case strings.HasPrefix(option, rangeOption):
|
|
||||||
segs := strings.Split(option, equalToken)
|
|
||||||
if len(segs) != 2 {
|
|
||||||
return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
|
|
||||||
}
|
|
||||||
if nr, err := parseNumberRange(segs[1]); err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
} else {
|
|
||||||
fieldOpts.Range = nr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, &fieldOpts, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return key, nil, nil
|
var fieldOpts fieldOptions
|
||||||
|
for _, segment := range options {
|
||||||
|
option := strings.TrimSpace(segment)
|
||||||
|
switch {
|
||||||
|
case option == stringOption:
|
||||||
|
fieldOpts.FromString = true
|
||||||
|
case strings.HasPrefix(option, optionalOption):
|
||||||
|
segs := strings.Split(option, equalToken)
|
||||||
|
switch len(segs) {
|
||||||
|
case 1:
|
||||||
|
fieldOpts.Optional = true
|
||||||
|
case 2:
|
||||||
|
fieldOpts.Optional = true
|
||||||
|
fieldOpts.OptionalDep = segs[1]
|
||||||
|
default:
|
||||||
|
return "", nil, fmt.Errorf("field %s has wrong optional", field.Name)
|
||||||
|
}
|
||||||
|
case option == optionalOption:
|
||||||
|
fieldOpts.Optional = true
|
||||||
|
case strings.HasPrefix(option, optionsOption):
|
||||||
|
segs := strings.Split(option, equalToken)
|
||||||
|
if len(segs) != 2 {
|
||||||
|
return "", nil, fmt.Errorf("field %s has wrong options", field.Name)
|
||||||
|
} else {
|
||||||
|
fieldOpts.Options = strings.Split(segs[1], optionSeparator)
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(option, defaultOption):
|
||||||
|
segs := strings.Split(option, equalToken)
|
||||||
|
if len(segs) != 2 {
|
||||||
|
return "", nil, fmt.Errorf("field %s has wrong default option", field.Name)
|
||||||
|
} else {
|
||||||
|
fieldOpts.Default = strings.TrimSpace(segs[1])
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(option, rangeOption):
|
||||||
|
segs := strings.Split(option, equalToken)
|
||||||
|
if len(segs) != 2 {
|
||||||
|
return "", nil, fmt.Errorf("field %s has wrong range", field.Name)
|
||||||
|
}
|
||||||
|
if nr, err := parseNumberRange(segs[1]); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
} else {
|
||||||
|
fieldOpts.Range = nr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, &fieldOpts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
|
func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func TestMaxInt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, each := range cases {
|
for _, each := range cases {
|
||||||
|
each := each
|
||||||
t.Run(stringx.Rand(), func(t *testing.T) {
|
t.Run(stringx.Rand(), func(t *testing.T) {
|
||||||
actual := MaxInt(each.a, each.b)
|
actual := MaxInt(each.a, each.b)
|
||||||
assert.Equal(t, each.expect, actual)
|
assert.Equal(t, each.expect, actual)
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ var started uint32
|
|||||||
|
|
||||||
// Profile represents an active profiling session.
|
// Profile represents an active profiling session.
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
// path holds the base path where various profiling files are written.
|
|
||||||
// If blank, the base path will be generated by ioutil.TempDir.
|
|
||||||
path string
|
|
||||||
|
|
||||||
// closers holds cleanup functions that run after each profile
|
// closers holds cleanup functions that run after each profile
|
||||||
closers []func()
|
closers []func()
|
||||||
|
|
||||||
|
|||||||
16
core/prof/profilecenter_test.go
Normal file
16
core/prof/profilecenter_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package prof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReport(t *testing.T) {
|
||||||
|
once.Do(func() {})
|
||||||
|
assert.NotContains(t, generateReport(), "foo")
|
||||||
|
report("foo", time.Second)
|
||||||
|
assert.Contains(t, generateReport(), "foo")
|
||||||
|
report("foo", time.Second)
|
||||||
|
}
|
||||||
23
core/prof/profiler_test.go
Normal file
23
core/prof/profiler_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package prof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/core/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProfiler(t *testing.T) {
|
||||||
|
EnableProfiling()
|
||||||
|
Start()
|
||||||
|
Report("foo", ProfilePoint{
|
||||||
|
ElapsedTimer: utils.NewElapsedTimer(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNullProfiler(t *testing.T) {
|
||||||
|
p := newNullProfiler()
|
||||||
|
p.Start()
|
||||||
|
p.Report("foo", ProfilePoint{
|
||||||
|
ElapsedTimer: utils.NewElapsedTimer(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ func newMockedService(multiplier int) *mockedService {
|
|||||||
|
|
||||||
func (s *mockedService) Start() {
|
func (s *mockedService) Start() {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
number = number * s.multiplier
|
number *= s.multiplier
|
||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
<-s.quit
|
<-s.quit
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tal-tech/go-zero/core/executors"
|
"github.com/tal-tech/go-zero/core/executors"
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/proc"
|
"github.com/tal-tech/go-zero/core/proc"
|
||||||
"github.com/tal-tech/go-zero/core/sysx"
|
"github.com/tal-tech/go-zero/core/sysx"
|
||||||
"github.com/tal-tech/go-zero/core/timex"
|
"github.com/tal-tech/go-zero/core/timex"
|
||||||
@@ -23,7 +24,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
reporter func(string)
|
reporter = logx.Alert
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
lessExecutor = executors.NewLessExecutor(time.Minute * 5)
|
lessExecutor = executors.NewLessExecutor(time.Minute * 5)
|
||||||
dropped int32
|
dropped int32
|
||||||
|
|||||||
22
core/stat/alert_test.go
Normal file
22
core/stat/alert_test.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRefreshCpu(t *testing.T) {
|
||||||
|
assert.True(t, RefreshCpu() >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRefreshCpu(b *testing.B) {
|
func BenchmarkRefreshCpu(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
37
core/stat/metrics_test.go
Normal file
37
core/stat/metrics_test.go
Normal 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
|
||||||
|
}
|
||||||
30
core/stat/remotewriter_test.go
Normal file
30
core/stat/remotewriter_test.go
Normal 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)
|
||||||
|
}
|
||||||
76
core/stores/cache/cache_test.go
vendored
76
core/stores/cache/cache_test.go
vendored
@@ -8,11 +8,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/errorx"
|
"github.com/tal-tech/go-zero/core/errorx"
|
||||||
"github.com/tal-tech/go-zero/core/hash"
|
"github.com/tal-tech/go-zero/core/hash"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
"github.com/tal-tech/go-zero/core/syncx"
|
"github.com/tal-tech/go-zero/core/syncx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,23 +76,23 @@ func (mc *mockedNode) TakeWithExpire(v interface{}, key string, query func(v int
|
|||||||
|
|
||||||
func TestCache_SetDel(t *testing.T) {
|
func TestCache_SetDel(t *testing.T) {
|
||||||
const total = 1000
|
const total = 1000
|
||||||
r1 := miniredis.NewMiniRedis()
|
r1, clean1, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, r1.Start())
|
assert.Nil(t, err)
|
||||||
defer r1.Close()
|
defer clean1()
|
||||||
r2 := miniredis.NewMiniRedis()
|
r2, clean2, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, r2.Start())
|
assert.Nil(t, err)
|
||||||
defer r2.Close()
|
defer clean2()
|
||||||
conf := ClusterConf{
|
conf := ClusterConf{
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
Host: r1.Addr(),
|
Host: r1.Addr,
|
||||||
Type: redis.NodeType,
|
Type: redis.NodeType,
|
||||||
},
|
},
|
||||||
Weight: 100,
|
Weight: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
RedisConf: redis.RedisConf{
|
RedisConf: redis.RedisConf{
|
||||||
Host: r2.Addr(),
|
Host: r2.Addr,
|
||||||
Type: redis.NodeType,
|
Type: redis.NodeType,
|
||||||
},
|
},
|
||||||
Weight: 100,
|
Weight: 100,
|
||||||
@@ -111,6 +111,45 @@ func TestCache_SetDel(t *testing.T) {
|
|||||||
assert.Nil(t, c.GetCache(fmt.Sprintf("key/%d", i), &v))
|
assert.Nil(t, c.GetCache(fmt.Sprintf("key/%d", i), &v))
|
||||||
assert.Equal(t, i, v)
|
assert.Equal(t, i, v)
|
||||||
}
|
}
|
||||||
|
assert.Nil(t, c.DelCache())
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
assert.Nil(t, c.DelCache(fmt.Sprintf("key/%d", i)))
|
||||||
|
}
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
var v int
|
||||||
|
assert.Equal(t, errPlaceholder, c.GetCache(fmt.Sprintf("key/%d", i), &v))
|
||||||
|
assert.Equal(t, 0, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache_OneNode(t *testing.T) {
|
||||||
|
const total = 1000
|
||||||
|
r, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
conf := ClusterConf{
|
||||||
|
{
|
||||||
|
RedisConf: redis.RedisConf{
|
||||||
|
Host: r.Addr,
|
||||||
|
Type: redis.NodeType,
|
||||||
|
},
|
||||||
|
Weight: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := NewCache(conf, syncx.NewSharedCalls(), NewCacheStat("mock"), errPlaceholder)
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
assert.Nil(t, c.SetCache(fmt.Sprintf("key/%d", i), i))
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, c.SetCacheWithExpire(fmt.Sprintf("key/%d", i), i, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
var v int
|
||||||
|
assert.Nil(t, c.GetCache(fmt.Sprintf("key/%d", i), &v))
|
||||||
|
assert.Equal(t, i, v)
|
||||||
|
}
|
||||||
|
assert.Nil(t, c.DelCache())
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
assert.Nil(t, c.DelCache(fmt.Sprintf("key/%d", i)))
|
assert.Nil(t, c.DelCache(fmt.Sprintf("key/%d", i)))
|
||||||
}
|
}
|
||||||
@@ -188,6 +227,25 @@ func TestCache_Balance(t *testing.T) {
|
|||||||
assert.Equal(t, total/10, count)
|
assert.Equal(t, total/10, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCacheNoNode(t *testing.T) {
|
||||||
|
dispatcher := hash.NewConsistentHash()
|
||||||
|
c := cacheCluster{
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
errNotFound: errPlaceholder,
|
||||||
|
}
|
||||||
|
assert.NotNil(t, c.DelCache("foo"))
|
||||||
|
assert.NotNil(t, c.DelCache("foo", "bar", "any"))
|
||||||
|
assert.NotNil(t, c.GetCache("foo", nil))
|
||||||
|
assert.NotNil(t, c.SetCache("foo", nil))
|
||||||
|
assert.NotNil(t, c.SetCacheWithExpire("foo", nil, time.Second))
|
||||||
|
assert.NotNil(t, c.Take(nil, "foo", func(v interface{}) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
assert.NotNil(t, c.TakeWithExpire(nil, "foo", func(v interface{}, duration time.Duration) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func calcEntropy(m map[int]int, total int) float64 {
|
func calcEntropy(m map[int]int, total int) float64 {
|
||||||
var entropy float64
|
var entropy float64
|
||||||
|
|
||||||
|
|||||||
18
core/stores/cache/cachenode.go
vendored
18
core/stores/cache/cachenode.go
vendored
@@ -1,13 +1,13 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/tal-tech/go-zero/core/jsonx"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/mathx"
|
"github.com/tal-tech/go-zero/core/mathx"
|
||||||
"github.com/tal-tech/go-zero/core/stat"
|
"github.com/tal-tech/go-zero/core/stat"
|
||||||
@@ -79,7 +79,7 @@ func (c cacheNode) SetCache(key string, v interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c cacheNode) SetCacheWithExpire(key string, v interface{}, expire time.Duration) error {
|
func (c cacheNode) SetCacheWithExpire(key string, v interface{}, expire time.Duration) error {
|
||||||
data, err := json.Marshal(v)
|
data, err := jsonx.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -168,24 +168,24 @@ func (c cacheNode) doTake(v interface{}, key string, query func(v interface{}) e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Marshal(v)
|
return jsonx.Marshal(v)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if fresh {
|
if fresh {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
// got the result from previous ongoing query
|
|
||||||
c.stat.IncrementTotal()
|
|
||||||
c.stat.IncrementHit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.Unmarshal(val.([]byte), v)
|
// got the result from previous ongoing query
|
||||||
|
c.stat.IncrementTotal()
|
||||||
|
c.stat.IncrementHit()
|
||||||
|
|
||||||
|
return jsonx.Unmarshal(val.([]byte), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c cacheNode) processCache(key string, data string, v interface{}) error {
|
func (c cacheNode) processCache(key string, data string, v interface{}) error {
|
||||||
err := json.Unmarshal([]byte(data), v)
|
err := jsonx.Unmarshal([]byte(data), v)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
154
core/stores/cache/cachenode_test.go
vendored
154
core/stores/cache/cachenode_test.go
vendored
@@ -2,36 +2,42 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/mathx"
|
"github.com/tal-tech/go-zero/core/mathx"
|
||||||
"github.com/tal-tech/go-zero/core/stat"
|
"github.com/tal-tech/go-zero/core/stat"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
|
"github.com/tal-tech/go-zero/core/syncx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errTestNotFound = errors.New("not found")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logx.Disable()
|
logx.Disable()
|
||||||
stat.SetReporter(nil)
|
stat.SetReporter(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheNode_DelCache(t *testing.T) {
|
func TestCacheNode_DelCache(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
store, clean, err := redistest.CreateRedis()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer s.Close()
|
defer clean()
|
||||||
|
|
||||||
cn := cacheNode{
|
cn := cacheNode{
|
||||||
rds: redis.NewRedis(s.Addr(), redis.NodeType),
|
rds: store,
|
||||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
stat: NewCacheStat("any"),
|
stat: NewCacheStat("any"),
|
||||||
errNotFound: errors.New("any"),
|
errNotFound: errTestNotFound,
|
||||||
}
|
}
|
||||||
assert.Nil(t, cn.DelCache())
|
assert.Nil(t, cn.DelCache())
|
||||||
assert.Nil(t, cn.DelCache([]string{}...))
|
assert.Nil(t, cn.DelCache([]string{}...))
|
||||||
@@ -54,7 +60,7 @@ func TestCacheNode_InvalidCache(t *testing.T) {
|
|||||||
lock: new(sync.Mutex),
|
lock: new(sync.Mutex),
|
||||||
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
stat: NewCacheStat("any"),
|
stat: NewCacheStat("any"),
|
||||||
errNotFound: errors.New("any"),
|
errNotFound: errTestNotFound,
|
||||||
}
|
}
|
||||||
s.Set("any", "value")
|
s.Set("any", "value")
|
||||||
var str string
|
var str string
|
||||||
@@ -63,3 +69,139 @@ func TestCacheNode_InvalidCache(t *testing.T) {
|
|||||||
_, err = s.Get("any")
|
_, err = s.Get("any")
|
||||||
assert.Equal(t, miniredis.ErrKeyNotFound, err)
|
assert.Equal(t, miniredis.ErrKeyNotFound, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCacheNode_Take(t *testing.T) {
|
||||||
|
store, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
cn := cacheNode{
|
||||||
|
rds: store,
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
barrier: syncx.NewSharedCalls(),
|
||||||
|
lock: new(sync.Mutex),
|
||||||
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
|
stat: NewCacheStat("any"),
|
||||||
|
errNotFound: errTestNotFound,
|
||||||
|
}
|
||||||
|
var str string
|
||||||
|
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||||
|
*v.(*string) = "value"
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "value", str)
|
||||||
|
assert.Nil(t, cn.GetCache("any", &str))
|
||||||
|
val, err := store.Get("any")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, `"value"`, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheNode_TakeNotFound(t *testing.T) {
|
||||||
|
store, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
cn := cacheNode{
|
||||||
|
rds: store,
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
barrier: syncx.NewSharedCalls(),
|
||||||
|
lock: new(sync.Mutex),
|
||||||
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
|
stat: NewCacheStat("any"),
|
||||||
|
errNotFound: errTestNotFound,
|
||||||
|
}
|
||||||
|
var str string
|
||||||
|
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||||
|
return errTestNotFound
|
||||||
|
})
|
||||||
|
assert.Equal(t, errTestNotFound, err)
|
||||||
|
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
|
||||||
|
val, err := store.Get("any")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, `*`, val)
|
||||||
|
|
||||||
|
store.Set("any", "*")
|
||||||
|
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.Equal(t, errTestNotFound, err)
|
||||||
|
assert.Equal(t, errTestNotFound, cn.GetCache("any", &str))
|
||||||
|
|
||||||
|
store.Del("any")
|
||||||
|
var errDummy = errors.New("dummy")
|
||||||
|
err = cn.Take(&str, "any", func(v interface{}) error {
|
||||||
|
return errDummy
|
||||||
|
})
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheNode_TakeWithExpire(t *testing.T) {
|
||||||
|
store, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
cn := cacheNode{
|
||||||
|
rds: store,
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
barrier: syncx.NewSharedCalls(),
|
||||||
|
lock: new(sync.Mutex),
|
||||||
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
|
stat: NewCacheStat("any"),
|
||||||
|
errNotFound: errors.New("any"),
|
||||||
|
}
|
||||||
|
var str string
|
||||||
|
err = cn.TakeWithExpire(&str, "any", func(v interface{}, expire time.Duration) error {
|
||||||
|
*v.(*string) = "value"
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "value", str)
|
||||||
|
assert.Nil(t, cn.GetCache("any", &str))
|
||||||
|
val, err := store.Get("any")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, `"value"`, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheNode_String(t *testing.T) {
|
||||||
|
store, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
cn := cacheNode{
|
||||||
|
rds: store,
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
barrier: syncx.NewSharedCalls(),
|
||||||
|
lock: new(sync.Mutex),
|
||||||
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
|
stat: NewCacheStat("any"),
|
||||||
|
errNotFound: errors.New("any"),
|
||||||
|
}
|
||||||
|
assert.Equal(t, store.Addr, cn.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheValueWithBigInt(t *testing.T) {
|
||||||
|
store, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
cn := cacheNode{
|
||||||
|
rds: store,
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
barrier: syncx.NewSharedCalls(),
|
||||||
|
lock: new(sync.Mutex),
|
||||||
|
unstableExpiry: mathx.NewUnstable(expiryDeviation),
|
||||||
|
stat: NewCacheStat("any"),
|
||||||
|
errNotFound: errors.New("any"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
key = "key"
|
||||||
|
value int64 = 323427211229009810
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Nil(t, cn.SetCache(key, value))
|
||||||
|
var val interface{}
|
||||||
|
assert.Nil(t, cn.GetCache(key, &val))
|
||||||
|
assert.Equal(t, strconv.FormatInt(value, 10), fmt.Sprintf("%v", val))
|
||||||
|
}
|
||||||
|
|||||||
25
core/stores/cache/cachestat.go
vendored
25
core/stores/cache/cachestat.go
vendored
@@ -48,20 +48,17 @@ func (cs *CacheStat) statLoop() {
|
|||||||
ticker := time.NewTicker(statInterval)
|
ticker := time.NewTicker(statInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for range ticker.C {
|
||||||
select {
|
total := atomic.SwapUint64(&cs.Total, 0)
|
||||||
case <-ticker.C:
|
if total == 0 {
|
||||||
total := atomic.SwapUint64(&cs.Total, 0)
|
continue
|
||||||
if total == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
hit := atomic.SwapUint64(&cs.Hit, 0)
|
|
||||||
percent := 100 * float32(hit) / float32(total)
|
|
||||||
miss := atomic.SwapUint64(&cs.Miss, 0)
|
|
||||||
dbf := atomic.SwapUint64(&cs.DbFails, 0)
|
|
||||||
logx.Statf("dbcache(%s) - qpm: %d, hit_ratio: %.1f%%, hit: %d, miss: %d, db_fails: %d",
|
|
||||||
cs.name, total, percent, hit, miss, dbf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hit := atomic.SwapUint64(&cs.Hit, 0)
|
||||||
|
percent := 100 * float32(hit) / float32(total)
|
||||||
|
miss := atomic.SwapUint64(&cs.Miss, 0)
|
||||||
|
dbf := atomic.SwapUint64(&cs.DbFails, 0)
|
||||||
|
logx.Statf("dbcache(%s) - qpm: %d, hit_ratio: %.1f%%, hit: %d, miss: %d, db_fails: %d",
|
||||||
|
cs.name, total, percent, hit, miss, dbf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
core/stores/cache/cleaner_test.go
vendored
Normal file
56
core/stores/cache/cleaner_test.go
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNextDelay(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input time.Duration
|
||||||
|
output time.Duration
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "second",
|
||||||
|
input: time.Second,
|
||||||
|
output: time.Second * 5,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "5 seconds",
|
||||||
|
input: time.Second * 5,
|
||||||
|
output: time.Minute,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "minute",
|
||||||
|
input: time.Minute,
|
||||||
|
output: time.Minute * 5,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "5 minutes",
|
||||||
|
input: time.Minute * 5,
|
||||||
|
output: time.Hour,
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hour",
|
||||||
|
input: time.Hour,
|
||||||
|
output: 0,
|
||||||
|
ok: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
next, ok := nextDelay(test.input)
|
||||||
|
assert.Equal(t, test.ok, ok)
|
||||||
|
assert.Equal(t, test.output, next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
26
core/stores/cache/util_test.go
vendored
Normal file
26
core/stores/cache/util_test.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatKeys(t *testing.T) {
|
||||||
|
assert.Equal(t, "a,b", formatKeys([]string{"a", "b"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTotalWeights(t *testing.T) {
|
||||||
|
val := TotalWeights([]NodeConf{
|
||||||
|
{
|
||||||
|
Weight: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Weight: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, 1, val)
|
||||||
|
}
|
||||||
@@ -73,6 +73,7 @@ type (
|
|||||||
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
|
ZrevrangebyscoreWithScores(key string, start, stop int64) ([]redis.Pair, error)
|
||||||
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
|
ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64, page, size int) ([]redis.Pair, error)
|
||||||
Zscore(key string, value string) (int64, error)
|
Zscore(key string, value string) (int64, error)
|
||||||
|
Zrevrank(key, field string) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
clusterStore struct {
|
clusterStore struct {
|
||||||
@@ -635,6 +636,15 @@ func (cs clusterStore) ZrevrangebyscoreWithScoresAndLimit(key string, start, sto
|
|||||||
return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
|
return node.ZrevrangebyscoreWithScoresAndLimit(key, start, stop, page, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cs clusterStore) Zrevrank(key, field string) (int64, error) {
|
||||||
|
node, err := cs.getRedis(key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.Zrevrank(key, field)
|
||||||
|
}
|
||||||
|
|
||||||
func (cs clusterStore) Zscore(key string, value string) (int64, error) {
|
func (cs clusterStore) Zscore(key string, value string) (int64, error) {
|
||||||
node, err := cs.getRedis(key)
|
node, err := cs.getRedis(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/hash"
|
||||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
"github.com/tal-tech/go-zero/core/stringx"
|
"github.com/tal-tech/go-zero/core/stringx"
|
||||||
@@ -15,6 +16,10 @@ var s1, _ = miniredis.Run()
|
|||||||
var s2, _ = miniredis.Run()
|
var s2, _ = miniredis.Run()
|
||||||
|
|
||||||
func TestRedis_Exists(t *testing.T) {
|
func TestRedis_Exists(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Exists("foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
ok, err := client.Exists("a")
|
ok, err := client.Exists("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -27,6 +32,10 @@ func TestRedis_Exists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Eval(t *testing.T) {
|
func TestRedis_Eval(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Eval(`redis.call("EXISTS", KEYS[1])`, "key1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, "notexist")
|
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, "notexist")
|
||||||
assert.Equal(t, redis.Nil, err)
|
assert.Equal(t, redis.Nil, err)
|
||||||
@@ -41,6 +50,12 @@ func TestRedis_Eval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Hgetall(t *testing.T) {
|
func TestRedis_Hgetall(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
err := store.Hset("a", "aa", "aaa")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Hgetall("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
@@ -54,6 +69,10 @@ func TestRedis_Hgetall(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Hvals(t *testing.T) {
|
func TestRedis_Hvals(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Hvals("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
@@ -64,6 +83,10 @@ func TestRedis_Hvals(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Hsetnx(t *testing.T) {
|
func TestRedis_Hsetnx(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Hsetnx("a", "dd", "ddd")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
@@ -80,6 +103,12 @@ func TestRedis_Hsetnx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_HdelHlen(t *testing.T) {
|
func TestRedis_HdelHlen(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Hdel("a", "aa")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Hlen("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
@@ -96,6 +125,10 @@ func TestRedis_HdelHlen(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_HIncrBy(t *testing.T) {
|
func TestRedis_HIncrBy(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Hincrby("key", "field", 3)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
val, err := client.Hincrby("key", "field", 2)
|
val, err := client.Hincrby("key", "field", 2)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -107,6 +140,10 @@ func TestRedis_HIncrBy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Hkeys(t *testing.T) {
|
func TestRedis_Hkeys(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Hkeys("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
@@ -117,6 +154,10 @@ func TestRedis_Hkeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Hmget(t *testing.T) {
|
func TestRedis_Hmget(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Hmget("a", "aa", "bb")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
@@ -130,6 +171,12 @@ func TestRedis_Hmget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Hmset(t *testing.T) {
|
func TestRedis_Hmset(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
err := store.Hmset("a", map[string]string{
|
||||||
|
"aa": "aaa",
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
assert.Nil(t, client.Hmset("a", map[string]string{
|
assert.Nil(t, client.Hmset("a", map[string]string{
|
||||||
"aa": "aaa",
|
"aa": "aaa",
|
||||||
@@ -142,6 +189,10 @@ func TestRedis_Hmset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Incr(t *testing.T) {
|
func TestRedis_Incr(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Incr("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
val, err := client.Incr("a")
|
val, err := client.Incr("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -153,6 +204,10 @@ func TestRedis_Incr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_IncrBy(t *testing.T) {
|
func TestRedis_IncrBy(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Incrby("a", 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
val, err := client.Incrby("a", 2)
|
val, err := client.Incrby("a", 2)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -164,6 +219,20 @@ func TestRedis_IncrBy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_List(t *testing.T) {
|
func TestRedis_List(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Lpush("key", "value1", "value2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Rpush("key", "value3", "value4")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Llen("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Lrange("key", 0, 10)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Lpop("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Lrem("key", 0, "val")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
val, err := client.Lpush("key", "value1", "value2")
|
val, err := client.Lpush("key", "value1", "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -202,6 +271,14 @@ func TestRedis_List(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Persist(t *testing.T) {
|
func TestRedis_Persist(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Persist("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
err = store.Expire("key", 5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
err = store.Expireat("key", time.Now().Unix()+5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
ok, err := client.Persist("key")
|
ok, err := client.Persist("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -225,8 +302,16 @@ func TestRedis_Persist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Sscan(t *testing.T) {
|
func TestRedis_Sscan(t *testing.T) {
|
||||||
|
key := "list"
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Sadd(key, nil)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, _, err = store.Sscan(key, 0, "", 100)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Del(key)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
key := "list"
|
|
||||||
var list []string
|
var list []string
|
||||||
for i := 0; i < 1550; i++ {
|
for i := 0; i < 1550; i++ {
|
||||||
list = append(list, stringx.Randn(i))
|
list = append(list, stringx.Randn(i))
|
||||||
@@ -254,6 +339,20 @@ func TestRedis_Sscan(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Set(t *testing.T) {
|
func TestRedis_Set(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Scard("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Sismember("key", 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Srem("key", 3, 4)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Smembers("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Srandmember("key", 1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Spop("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
num, err := client.Sadd("key", 1, 2, 3, 4)
|
num, err := client.Sadd("key", 1, 2, 3, 4)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -290,6 +389,14 @@ func TestRedis_Set(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_SetGetDel(t *testing.T) {
|
func TestRedis_SetGetDel(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
err := store.Set("hello", "world")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Get("hello")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Del("hello")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
err := client.Set("hello", "world")
|
err := client.Set("hello", "world")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -303,6 +410,16 @@ func TestRedis_SetGetDel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_SetExNx(t *testing.T) {
|
func TestRedis_SetExNx(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
err := store.Setex("hello", "world", 5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Setnx("newhello", "newworld")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Ttl("hello")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.SetnxEx("newhello", "newworld", 5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
err := client.Setex("hello", "world", 5)
|
err := client.Setex("hello", "world", 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -337,6 +454,16 @@ func TestRedis_SetExNx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_SetGetDelHashField(t *testing.T) {
|
func TestRedis_SetGetDelHashField(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
err := store.Hset("key", "field", "value")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Hget("key", "field")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Hexists("key", "field")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Hdel("key", "field")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
err := client.Hset("key", "field", "value")
|
err := client.Hset("key", "field", "value")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -356,6 +483,50 @@ func TestRedis_SetGetDelHashField(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_SortedSet(t *testing.T) {
|
func TestRedis_SortedSet(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Zadd("key", 1, "value1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zscore("key", "value1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zcount("key", 6, 7)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zincrby("key", 3, "value1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zrank("key", "value2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zrem("key", "value2", "value3")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zremrangebyscore("key", 6, 7)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zremrangebyrank("key", 1, 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zcard("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zrange("key", 0, -1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zrevrange("key", 0, -1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.ZrangeWithScores("key", 0, -1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.ZrangebyscoreWithScores("key", 5, 8)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.ZrevrangebyscoreWithScores("key", 5, 8)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zrevrank("key", "value")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Zadds("key", redis.Pair{
|
||||||
|
Key: "value2",
|
||||||
|
Score: 6,
|
||||||
|
}, redis.Pair{
|
||||||
|
Key: "value3",
|
||||||
|
Score: 7,
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
runOnCluster(t, func(client Store) {
|
runOnCluster(t, func(client Store) {
|
||||||
ok, err := client.Zadd("key", 1, "value1")
|
ok, err := client.Zadd("key", 1, "value1")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -471,6 +642,33 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
Score: 5,
|
Score: 5,
|
||||||
},
|
},
|
||||||
}, pairs)
|
}, pairs)
|
||||||
|
rank, err = client.Zrevrank("key", "value1")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(1), rank)
|
||||||
|
val, err = client.Zadds("key", redis.Pair{
|
||||||
|
Key: "value2",
|
||||||
|
Score: 6,
|
||||||
|
}, redis.Pair{
|
||||||
|
Key: "value3",
|
||||||
|
Score: 7,
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(2), val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedis_HyperLogLog(t *testing.T) {
|
||||||
|
store := clusterStore{dispatcher: hash.NewConsistentHash()}
|
||||||
|
_, err := store.Pfadd("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = store.Pfcount("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
runOnCluster(t, func(cluster Store) {
|
||||||
|
_, err := cluster.Pfadd("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = cluster.Pfcount("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/globalsign/mgo"
|
"github.com/globalsign/mgo"
|
||||||
"github.com/tal-tech/go-zero/core/breaker"
|
"github.com/tal-tech/go-zero/core/breaker"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/mongo/internal"
|
||||||
"github.com/tal-tech/go-zero/core/timex"
|
"github.com/tal-tech/go-zero/core/timex"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,8 +30,9 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
decoratedCollection struct {
|
decoratedCollection struct {
|
||||||
*mgo.Collection
|
name string
|
||||||
brk breaker.Breaker
|
collection internal.MgoCollection
|
||||||
|
brk breaker.Breaker
|
||||||
}
|
}
|
||||||
|
|
||||||
keepablePromise struct {
|
keepablePromise struct {
|
||||||
@@ -41,7 +43,8 @@ type (
|
|||||||
|
|
||||||
func newCollection(collection *mgo.Collection) Collection {
|
func newCollection(collection *mgo.Collection) Collection {
|
||||||
return &decoratedCollection{
|
return &decoratedCollection{
|
||||||
Collection: collection,
|
name: collection.FullName,
|
||||||
|
collection: collection,
|
||||||
brk: breaker.NewBreaker(),
|
brk: breaker.NewBreaker(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,7 @@ func (c *decoratedCollection) Find(query interface{}) Query {
|
|||||||
|
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
return promisedQuery{
|
return promisedQuery{
|
||||||
Query: c.Collection.Find(query),
|
Query: c.collection.Find(query),
|
||||||
promise: keepablePromise{
|
promise: keepablePromise{
|
||||||
promise: promise,
|
promise: promise,
|
||||||
log: func(err error) {
|
log: func(err error) {
|
||||||
@@ -73,7 +76,7 @@ func (c *decoratedCollection) FindId(id interface{}) Query {
|
|||||||
|
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
return promisedQuery{
|
return promisedQuery{
|
||||||
Query: c.Collection.FindId(id),
|
Query: c.collection.FindId(id),
|
||||||
promise: keepablePromise{
|
promise: keepablePromise{
|
||||||
promise: promise,
|
promise: promise,
|
||||||
log: func(err error) {
|
log: func(err error) {
|
||||||
@@ -92,7 +95,7 @@ func (c *decoratedCollection) Insert(docs ...interface{}) (err error) {
|
|||||||
c.logDuration("insert", duration, err, docs...)
|
c.logDuration("insert", duration, err, docs...)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return c.Collection.Insert(docs...)
|
return c.collection.Insert(docs...)
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +107,7 @@ func (c *decoratedCollection) Pipe(pipeline interface{}) Pipe {
|
|||||||
|
|
||||||
startTime := timex.Now()
|
startTime := timex.Now()
|
||||||
return promisedPipe{
|
return promisedPipe{
|
||||||
Pipe: c.Collection.Pipe(pipeline),
|
Pipe: c.collection.Pipe(pipeline),
|
||||||
promise: keepablePromise{
|
promise: keepablePromise{
|
||||||
promise: promise,
|
promise: promise,
|
||||||
log: func(err error) {
|
log: func(err error) {
|
||||||
@@ -123,7 +126,7 @@ func (c *decoratedCollection) Remove(selector interface{}) (err error) {
|
|||||||
c.logDuration("remove", duration, err, selector)
|
c.logDuration("remove", duration, err, selector)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return c.Collection.Remove(selector)
|
return c.collection.Remove(selector)
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +138,7 @@ func (c *decoratedCollection) RemoveAll(selector interface{}) (info *mgo.ChangeI
|
|||||||
c.logDuration("removeAll", duration, err, selector)
|
c.logDuration("removeAll", duration, err, selector)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
info, err = c.Collection.RemoveAll(selector)
|
info, err = c.collection.RemoveAll(selector)
|
||||||
return err
|
return err
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
@@ -150,7 +153,7 @@ func (c *decoratedCollection) RemoveId(id interface{}) (err error) {
|
|||||||
c.logDuration("removeId", duration, err, id)
|
c.logDuration("removeId", duration, err, id)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return c.Collection.RemoveId(id)
|
return c.collection.RemoveId(id)
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +165,7 @@ func (c *decoratedCollection) Update(selector, update interface{}) (err error) {
|
|||||||
c.logDuration("update", duration, err, selector, update)
|
c.logDuration("update", duration, err, selector, update)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return c.Collection.Update(selector, update)
|
return c.collection.Update(selector, update)
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +177,7 @@ func (c *decoratedCollection) UpdateId(id, update interface{}) (err error) {
|
|||||||
c.logDuration("updateId", duration, err, id, update)
|
c.logDuration("updateId", duration, err, id, update)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return c.Collection.UpdateId(id, update)
|
return c.collection.UpdateId(id, update)
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +189,7 @@ func (c *decoratedCollection) Upsert(selector, update interface{}) (info *mgo.Ch
|
|||||||
c.logDuration("upsert", duration, err, selector, update)
|
c.logDuration("upsert", duration, err, selector, update)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
info, err = c.Collection.Upsert(selector, update)
|
info, err = c.collection.Upsert(selector, update)
|
||||||
return err
|
return err
|
||||||
}, acceptable)
|
}, acceptable)
|
||||||
|
|
||||||
@@ -200,17 +203,17 @@ func (c *decoratedCollection) logDuration(method string, duration time.Duration,
|
|||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
if duration > slowThreshold {
|
if duration > slowThreshold {
|
||||||
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
|
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - fail(%s) - %s",
|
||||||
c.FullName, method, err.Error(), string(content))
|
c.name, method, err.Error(), string(content))
|
||||||
} else {
|
} else {
|
||||||
logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s) - %s",
|
logx.WithDuration(duration).Infof("mongo(%s) - %s - fail(%s) - %s",
|
||||||
c.FullName, method, err.Error(), string(content))
|
c.name, method, err.Error(), string(content))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if duration > slowThreshold {
|
if duration > slowThreshold {
|
||||||
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
|
logx.WithDuration(duration).Slowf("[MONGO] mongo(%s) - slowcall - %s - ok - %s",
|
||||||
c.FullName, method, string(content))
|
c.name, method, string(content))
|
||||||
} else {
|
} else {
|
||||||
logx.WithDuration(duration).Infof("mongo(%s) - %s - ok - %s", c.FullName, method, string(content))
|
logx.WithDuration(duration).Infof("mongo(%s) - %s - ok - %s", c.name, method, string(content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,20 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/globalsign/mgo"
|
"github.com/globalsign/mgo"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/breaker"
|
||||||
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/mongo/internal"
|
||||||
"github.com/tal-tech/go-zero/core/stringx"
|
"github.com/tal-tech/go-zero/core/stringx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errDummy = errors.New("dummy")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logx.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
func TestKeepPromise_accept(t *testing.T) {
|
func TestKeepPromise_accept(t *testing.T) {
|
||||||
p := new(mockPromise)
|
p := new(mockPromise)
|
||||||
kp := keepablePromise{
|
kp := keepablePromise{
|
||||||
@@ -56,6 +66,206 @@ func TestKeepPromise_keep(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewCollection(t *testing.T) {
|
||||||
|
col := newCollection(&mgo.Collection{
|
||||||
|
Database: nil,
|
||||||
|
Name: "foo",
|
||||||
|
FullName: "bar",
|
||||||
|
})
|
||||||
|
assert.Equal(t, "bar", col.(*decoratedCollection).name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionFind(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
var query mgo.Query
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().Find(gomock.Any()).Return(&query)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
actual := c.Find(nil)
|
||||||
|
switch v := actual.(type) {
|
||||||
|
case promisedQuery:
|
||||||
|
assert.Equal(t, &query, v.Query)
|
||||||
|
assert.Equal(t, errDummy, v.promise.keep(errDummy))
|
||||||
|
default:
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
actual = c.Find(nil)
|
||||||
|
assert.Equal(t, rejectedQuery{}, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionFindId(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
var query mgo.Query
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().FindId(gomock.Any()).Return(&query)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
actual := c.FindId(nil)
|
||||||
|
switch v := actual.(type) {
|
||||||
|
case promisedQuery:
|
||||||
|
assert.Equal(t, &query, v.Query)
|
||||||
|
assert.Equal(t, errDummy, v.promise.keep(errDummy))
|
||||||
|
default:
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
actual = c.FindId(nil)
|
||||||
|
assert.Equal(t, rejectedQuery{}, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionInsert(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().Insert(nil, nil).Return(errDummy)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
err := c.Insert(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
err = c.Insert(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionPipe(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
var pipe mgo.Pipe
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().Pipe(gomock.Any()).Return(&pipe)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
actual := c.Pipe(nil)
|
||||||
|
switch v := actual.(type) {
|
||||||
|
case promisedPipe:
|
||||||
|
assert.Equal(t, &pipe, v.Pipe)
|
||||||
|
assert.Equal(t, errDummy, v.promise.keep(errDummy))
|
||||||
|
default:
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
actual = c.Pipe(nil)
|
||||||
|
assert.Equal(t, rejectedPipe{}, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionRemove(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().Remove(gomock.Any()).Return(errDummy)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
err := c.Remove(nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
err = c.Remove(nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionRemoveAll(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().RemoveAll(gomock.Any()).Return(nil, errDummy)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
_, err := c.RemoveAll(nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
_, err = c.RemoveAll(nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionRemoveId(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().RemoveId(gomock.Any()).Return(errDummy)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
err := c.RemoveId(nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
err = c.RemoveId(nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionUpdate(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().Update(gomock.Any(), gomock.Any()).Return(errDummy)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
err := c.Update(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
err = c.Update(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionUpdateId(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().UpdateId(gomock.Any(), gomock.Any()).Return(errDummy)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
err := c.UpdateId(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
err = c.UpdateId(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectionUpsert(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
|
||||||
|
col := internal.NewMockMgoCollection(ctrl)
|
||||||
|
col.EXPECT().Upsert(gomock.Any(), gomock.Any()).Return(nil, errDummy)
|
||||||
|
c := decoratedCollection{
|
||||||
|
collection: col,
|
||||||
|
brk: breaker.NewBreaker(),
|
||||||
|
}
|
||||||
|
_, err := c.Upsert(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
c.brk = new(dropBreaker)
|
||||||
|
_, err = c.Upsert(nil, nil)
|
||||||
|
assert.Equal(t, errDummy, err)
|
||||||
|
}
|
||||||
|
|
||||||
type mockPromise struct {
|
type mockPromise struct {
|
||||||
accepted bool
|
accepted bool
|
||||||
reason string
|
reason string
|
||||||
@@ -68,3 +278,31 @@ func (p *mockPromise) Accept() {
|
|||||||
func (p *mockPromise) Reject(reason string) {
|
func (p *mockPromise) Reject(reason string) {
|
||||||
p.reason = reason
|
p.reason = reason
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dropBreaker struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dropBreaker) Name() string {
|
||||||
|
return "dummy"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dropBreaker) Allow() (breaker.Promise, error) {
|
||||||
|
return nil, errDummy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dropBreaker) Do(req func() error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dropBreaker) DoWithAcceptable(req func() error, acceptable breaker.Acceptable) error {
|
||||||
|
return errDummy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dropBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dropBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,
|
||||||
|
acceptable breaker.Acceptable) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
17
core/stores/mongo/internal/collection.go
Normal file
17
core/stores/mongo/internal/collection.go
Normal 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)
|
||||||
|
}
|
||||||
180
core/stores/mongo/internal/collection_mock.go
Normal file
180
core/stores/mongo/internal/collection_mock.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
|
||||||
"github.com/globalsign/mgo"
|
"github.com/globalsign/mgo"
|
||||||
"github.com/globalsign/mgo/bson"
|
"github.com/globalsign/mgo/bson"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -19,6 +18,7 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||||
"github.com/tal-tech/go-zero/core/stores/mongo"
|
"github.com/tal-tech/go-zero/core/stores/mongo"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -27,12 +27,10 @@ func init() {
|
|||||||
|
|
||||||
func TestStat(t *testing.T) {
|
func TestStat(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||||
c := newCollection(dummyConn{}, cach)
|
c := newCollection(dummyConn{}, cach)
|
||||||
|
|
||||||
@@ -73,12 +71,10 @@ func TestStatCacheFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatDbFails(t *testing.T) {
|
func TestStatDbFails(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||||
c := newCollection(dummyConn{}, cach)
|
c := newCollection(dummyConn{}, cach)
|
||||||
|
|
||||||
@@ -97,12 +93,10 @@ func TestStatDbFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatFromMemory(t *testing.T) {
|
func TestStatFromMemory(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
cach := cache.NewCacheNode(r, sharedCalls, stats, mgo.ErrNotFound)
|
||||||
c := newCollection(dummyConn{}, cach)
|
c := newCollection(dummyConn{}, cach)
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const postgreDriverName = "postgres"
|
const postgresDriverName = "postgres"
|
||||||
|
|
||||||
func NewPostgre(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
|
func NewPostgres(datasource string, opts ...sqlx.SqlOption) sqlx.SqlConn {
|
||||||
return sqlx.NewSqlConn(postgreDriverName, datasource, opts...)
|
return sqlx.NewSqlConn(postgresDriverName, datasource, opts...)
|
||||||
}
|
}
|
||||||
|
|||||||
110
core/stores/redis/conf_test.go
Normal file
110
core/stores/redis/conf_test.go
Normal 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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,12 @@ type (
|
|||||||
red.Cmdable
|
red.Cmdable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GeoLocation is used with GeoAdd to add geospatial location.
|
||||||
|
GeoLocation = red.GeoLocation
|
||||||
|
// GeoRadiusQuery is used with GeoRadius to query geospatial index.
|
||||||
|
GeoRadiusQuery = red.GeoRadiusQuery
|
||||||
|
GeoPos = red.GeoPos
|
||||||
|
|
||||||
Pipeliner = red.Pipeliner
|
Pipeliner = red.Pipeliner
|
||||||
|
|
||||||
// Z represents sorted set member.
|
// Z represents sorted set member.
|
||||||
@@ -173,6 +179,107 @@ func (s *Redis) Expireat(key string, expireTime int64) error {
|
|||||||
}, acceptable)
|
}, acceptable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Redis) GeoAdd(key string, geoLocation ...*GeoLocation) (val int64, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := conn.GeoAdd(key, geoLocation...).Result(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
val = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, acceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Redis) GeoDist(key string, member1, member2, unit string) (val float64, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := conn.GeoDist(key, member1, member2, unit).Result(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
val = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, acceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Redis) GeoHash(key string, members ...string) (val []string, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := conn.GeoHash(key, members...).Result(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
val = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, acceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Redis) GeoRadius(key string, longitude, latitude float64, query *GeoRadiusQuery) (val []GeoLocation, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := conn.GeoRadius(key, longitude, latitude, query).Result(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
val = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, acceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (s *Redis) GeoRadiusByMember(key, member string, query *GeoRadiusQuery) (val []GeoLocation, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := conn.GeoRadiusByMember(key, member, query).Result(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
val = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, acceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Redis) GeoPos(key string, members ...string) (val []*GeoPos, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := conn.GeoPos(key, members...).Result(); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
val = v
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}, acceptable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Redis) Get(key string) (val string, err error) {
|
func (s *Redis) Get(key string) (val string, err error) {
|
||||||
err = s.brk.DoWithAcceptable(func() error {
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
conn, err := getRedis(s)
|
conn, err := getRedis(s)
|
||||||
@@ -1273,6 +1380,20 @@ func (s *Redis) ZrevrangebyscoreWithScoresAndLimit(key string, start, stop int64
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Redis) Zrevrank(key string, field string) (val int64, err error) {
|
||||||
|
err = s.brk.DoWithAcceptable(func() error {
|
||||||
|
conn, err := getRedis(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err = conn.ZRevRank(key, field).Result()
|
||||||
|
return err
|
||||||
|
}, acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Redis) String() string {
|
func (s *Redis) String() string {
|
||||||
return s.Addr
|
return s.Addr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
red "github.com/go-redis/redis"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedis_Exists(t *testing.T) {
|
func TestRedis_Exists(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
_, err := NewRedis(client.Addr, "").Exists("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err := client.Exists("a")
|
ok, err := client.Exists("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
@@ -24,7 +27,9 @@ func TestRedis_Exists(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_Eval(t *testing.T) {
|
func TestRedis_Eval(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
_, err := client.Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
|
_, err := NewRedis(client.Addr, "").Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = client.Eval(`redis.call("EXISTS", KEYS[1])`, []string{"notexist"})
|
||||||
assert.Equal(t, Nil, err)
|
assert.Equal(t, Nil, err)
|
||||||
err = client.Set("key1", "value1")
|
err = client.Set("key1", "value1")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -40,6 +45,8 @@ func TestRedis_Hgetall(t *testing.T) {
|
|||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
|
_, err := NewRedis(client.Addr, "").Hgetall("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err := client.Hgetall("a")
|
vals, err := client.Hgetall("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, map[string]string{
|
assert.EqualValues(t, map[string]string{
|
||||||
@@ -51,8 +58,11 @@ func TestRedis_Hgetall(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_Hvals(t *testing.T) {
|
func TestRedis_Hvals(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
assert.NotNil(t, NewRedis(client.Addr, "").Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
|
_, err := NewRedis(client.Addr, "").Hvals("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err := client.Hvals("a")
|
vals, err := client.Hvals("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(t, []string{"aaa", "bbb"}, vals)
|
assert.ElementsMatch(t, []string{"aaa", "bbb"}, vals)
|
||||||
@@ -63,6 +73,8 @@ func TestRedis_Hsetnx(t *testing.T) {
|
|||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
|
_, err := NewRedis(client.Addr, "").Hsetnx("a", "bb", "ccc")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err := client.Hsetnx("a", "bb", "ccc")
|
ok, err := client.Hsetnx("a", "bb", "ccc")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
@@ -79,6 +91,8 @@ func TestRedis_HdelHlen(t *testing.T) {
|
|||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
|
_, err := NewRedis(client.Addr, "").Hlen("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err := client.Hlen("a")
|
num, err := client.Hlen("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, num)
|
assert.Equal(t, 2, num)
|
||||||
@@ -93,6 +107,8 @@ func TestRedis_HdelHlen(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_HIncrBy(t *testing.T) {
|
func TestRedis_HIncrBy(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
_, err := NewRedis(client.Addr, "").Hincrby("key", "field", 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.Hincrby("key", "field", 2)
|
val, err := client.Hincrby("key", "field", 2)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, val)
|
assert.Equal(t, 2, val)
|
||||||
@@ -106,6 +122,8 @@ func TestRedis_Hkeys(t *testing.T) {
|
|||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
|
_, err := NewRedis(client.Addr, "").Hkeys("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err := client.Hkeys("a")
|
vals, err := client.Hkeys("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(t, []string{"aa", "bb"}, vals)
|
assert.ElementsMatch(t, []string{"aa", "bb"}, vals)
|
||||||
@@ -116,6 +134,8 @@ func TestRedis_Hmget(t *testing.T) {
|
|||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
assert.Nil(t, client.Hset("a", "aa", "aaa"))
|
||||||
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
assert.Nil(t, client.Hset("a", "bb", "bbb"))
|
||||||
|
_, err := NewRedis(client.Addr, "").Hmget("a", "aa", "bb")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err := client.Hmget("a", "aa", "bb")
|
vals, err := client.Hmget("a", "aa", "bb")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"aaa", "bbb"}, vals)
|
assert.EqualValues(t, []string{"aaa", "bbb"}, vals)
|
||||||
@@ -127,6 +147,7 @@ func TestRedis_Hmget(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_Hmset(t *testing.T) {
|
func TestRedis_Hmset(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
assert.NotNil(t, NewRedis(client.Addr, "").Hmset("a", nil))
|
||||||
assert.Nil(t, client.Hmset("a", map[string]string{
|
assert.Nil(t, client.Hmset("a", map[string]string{
|
||||||
"aa": "aaa",
|
"aa": "aaa",
|
||||||
"bb": "bbb",
|
"bb": "bbb",
|
||||||
@@ -139,6 +160,8 @@ func TestRedis_Hmset(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_Incr(t *testing.T) {
|
func TestRedis_Incr(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
_, err := NewRedis(client.Addr, "").Incr("a")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.Incr("a")
|
val, err := client.Incr("a")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(1), val)
|
assert.Equal(t, int64(1), val)
|
||||||
@@ -150,6 +173,8 @@ func TestRedis_Incr(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_IncrBy(t *testing.T) {
|
func TestRedis_IncrBy(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
_, err := NewRedis(client.Addr, "").Incrby("a", 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.Incrby("a", 2)
|
val, err := client.Incrby("a", 2)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(2), val)
|
assert.Equal(t, int64(2), val)
|
||||||
@@ -165,26 +190,49 @@ func TestRedis_Keys(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = client.Set("key2", "value2")
|
err = client.Set("key2", "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").Keys("*")
|
||||||
|
assert.NotNil(t, err)
|
||||||
keys, err := client.Keys("*")
|
keys, err := client.Keys("*")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
|
assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRedis_HyperLogLog(t *testing.T) {
|
||||||
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
client.Ping()
|
||||||
|
r := NewRedis(client.Addr, "")
|
||||||
|
_, err := r.Pfadd("key1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = r.Pfcount("*")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
err = r.Pfmerge("*")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestRedis_List(t *testing.T) {
|
func TestRedis_List(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
_, err := NewRedis(client.Addr, "").Lpush("key", "value1", "value2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.Lpush("key", "value1", "value2")
|
val, err := client.Lpush("key", "value1", "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, val)
|
assert.Equal(t, 2, val)
|
||||||
|
_, err = NewRedis(client.Addr, "").Rpush("key", "value3", "value4")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err = client.Rpush("key", "value3", "value4")
|
val, err = client.Rpush("key", "value3", "value4")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 4, val)
|
assert.Equal(t, 4, val)
|
||||||
|
_, err = NewRedis(client.Addr, "").Llen("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err = client.Llen("key")
|
val, err = client.Llen("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 4, val)
|
assert.Equal(t, 4, val)
|
||||||
vals, err := client.Lrange("key", 0, 10)
|
vals, err := client.Lrange("key", 0, 10)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"value2", "value1", "value3", "value4"}, vals)
|
assert.EqualValues(t, []string{"value2", "value1", "value3", "value4"}, vals)
|
||||||
|
_, err = NewRedis(client.Addr, "").Lpop("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
v, err := client.Lpop("key")
|
v, err := client.Lpop("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "value2", v)
|
assert.Equal(t, "value2", v)
|
||||||
@@ -194,9 +242,13 @@ func TestRedis_List(t *testing.T) {
|
|||||||
val, err = client.Rpush("key", "value3", "value3")
|
val, err = client.Rpush("key", "value3", "value3")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 7, val)
|
assert.Equal(t, 7, val)
|
||||||
|
_, err = NewRedis(client.Addr, "").Lrem("key", 2, "value1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
n, err := client.Lrem("key", 2, "value1")
|
n, err := client.Lrem("key", 2, "value1")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, n)
|
assert.Equal(t, 2, n)
|
||||||
|
_, err = NewRedis(client.Addr, "").Lrange("key", 0, 10)
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err = client.Lrange("key", 0, 10)
|
vals, err = client.Lrange("key", 0, 10)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"value2", "value3", "value4", "value3", "value3"}, vals)
|
assert.EqualValues(t, []string{"value2", "value3", "value4", "value3", "value3"}, vals)
|
||||||
@@ -215,6 +267,8 @@ func TestRedis_Mget(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = client.Set("key2", "value2")
|
err = client.Set("key2", "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").Mget("key1", "key0", "key2", "key3")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err := client.Mget("key1", "key0", "key2", "key3")
|
vals, err := client.Mget("key1", "key0", "key2", "key3")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"value1", "", "value2", ""}, vals)
|
assert.EqualValues(t, []string{"value1", "", "value2", ""}, vals)
|
||||||
@@ -223,7 +277,9 @@ func TestRedis_Mget(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_SetBit(t *testing.T) {
|
func TestRedis_SetBit(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
err := client.SetBit("key", 1, 1)
|
err := NewRedis(client.Addr, "").SetBit("key", 1, 1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
err = client.SetBit("key", 1, 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -232,6 +288,8 @@ func TestRedis_GetBit(t *testing.T) {
|
|||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
err := client.SetBit("key", 2, 1)
|
err := client.SetBit("key", 2, 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").GetBit("key", 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.GetBit("key", 2)
|
val, err := client.GetBit("key", 2)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, val)
|
assert.Equal(t, 1, val)
|
||||||
@@ -240,6 +298,8 @@ func TestRedis_GetBit(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_Persist(t *testing.T) {
|
func TestRedis_Persist(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
_, err := NewRedis(client.Addr, "").Persist("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err := client.Persist("key")
|
ok, err := client.Persist("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
@@ -248,11 +308,15 @@ func TestRedis_Persist(t *testing.T) {
|
|||||||
ok, err = client.Persist("key")
|
ok, err = client.Persist("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
err = NewRedis(client.Addr, "").Expire("key", 5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
err = client.Expire("key", 5)
|
err = client.Expire("key", 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
ok, err = client.Persist("key")
|
ok, err = client.Persist("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
err = NewRedis(client.Addr, "").Expireat("key", time.Now().Unix()+5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
err = client.Expireat("key", time.Now().Unix()+5)
|
err = client.Expireat("key", time.Now().Unix()+5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
ok, err = client.Persist("key")
|
ok, err = client.Persist("key")
|
||||||
@@ -274,6 +338,8 @@ func TestRedis_Scan(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = client.Set("key2", "value2")
|
err = client.Set("key2", "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, _, err = NewRedis(client.Addr, "").Scan(0, "*", 100)
|
||||||
|
assert.NotNil(t, err)
|
||||||
keys, _, err := client.Scan(0, "*", 100)
|
keys, _, err := client.Scan(0, "*", 100)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
|
assert.ElementsMatch(t, []string{"key1", "key2"}, keys)
|
||||||
@@ -294,6 +360,8 @@ func TestRedis_Sscan(t *testing.T) {
|
|||||||
var cursor uint64 = 0
|
var cursor uint64 = 0
|
||||||
sum := 0
|
sum := 0
|
||||||
for {
|
for {
|
||||||
|
_, _, err := NewRedis(client.Addr, "").Sscan(key, cursor, "", 100)
|
||||||
|
assert.NotNil(t, err)
|
||||||
keys, next, err := client.Sscan(key, cursor, "", 100)
|
keys, next, err := client.Sscan(key, cursor, "", 100)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
sum += len(keys)
|
sum += len(keys)
|
||||||
@@ -304,6 +372,8 @@ func TestRedis_Sscan(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, sum, 1550)
|
assert.Equal(t, sum, 1550)
|
||||||
|
_, err = NewRedis(client.Addr, "").Del(key)
|
||||||
|
assert.NotNil(t, err)
|
||||||
_, err = client.Del(key)
|
_, err = client.Del(key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
})
|
})
|
||||||
@@ -311,46 +381,72 @@ func TestRedis_Sscan(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_Set(t *testing.T) {
|
func TestRedis_Set(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
_, err := NewRedis(client.Addr, "").Sadd("key", 1, 2, 3, 4)
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err := client.Sadd("key", 1, 2, 3, 4)
|
num, err := client.Sadd("key", 1, 2, 3, 4)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 4, num)
|
assert.Equal(t, 4, num)
|
||||||
|
_, err = NewRedis(client.Addr, "").Scard("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.Scard("key")
|
val, err := client.Scard("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(4), val)
|
assert.Equal(t, int64(4), val)
|
||||||
|
_, err = NewRedis(client.Addr, "").Sismember("key", 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err := client.Sismember("key", 2)
|
ok, err := client.Sismember("key", 2)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
_, err = NewRedis(client.Addr, "").Srem("key", 3, 4)
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err = client.Srem("key", 3, 4)
|
num, err = client.Srem("key", 3, 4)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, num)
|
assert.Equal(t, 2, num)
|
||||||
|
_, err = NewRedis(client.Addr, "").Smembers("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err := client.Smembers("key")
|
vals, err := client.Smembers("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(t, []string{"1", "2"}, vals)
|
assert.ElementsMatch(t, []string{"1", "2"}, vals)
|
||||||
|
_, err = NewRedis(client.Addr, "").Srandmember("key", 1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
members, err := client.Srandmember("key", 1)
|
members, err := client.Srandmember("key", 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Len(t, members, 1)
|
assert.Len(t, members, 1)
|
||||||
assert.Contains(t, []string{"1", "2"}, members[0])
|
assert.Contains(t, []string{"1", "2"}, members[0])
|
||||||
|
_, err = NewRedis(client.Addr, "").Spop("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
member, err := client.Spop("key")
|
member, err := client.Spop("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Contains(t, []string{"1", "2"}, member)
|
assert.Contains(t, []string{"1", "2"}, member)
|
||||||
|
_, err = NewRedis(client.Addr, "").Smembers("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err = client.Smembers("key")
|
vals, err = client.Smembers("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.NotContains(t, vals, member)
|
assert.NotContains(t, vals, member)
|
||||||
|
_, err = NewRedis(client.Addr, "").Sadd("key1", 1, 2, 3, 4)
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err = client.Sadd("key1", 1, 2, 3, 4)
|
num, err = client.Sadd("key1", 1, 2, 3, 4)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 4, num)
|
assert.Equal(t, 4, num)
|
||||||
num, err = client.Sadd("key2", 2, 3, 4, 5)
|
num, err = client.Sadd("key2", 2, 3, 4, 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 4, num)
|
assert.Equal(t, 4, num)
|
||||||
|
_, err = NewRedis(client.Addr, "").Sunion("key1", "key2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err = client.Sunion("key1", "key2")
|
vals, err = client.Sunion("key1", "key2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.ElementsMatch(t, []string{"1", "2", "3", "4", "5"}, vals)
|
assert.ElementsMatch(t, []string{"1", "2", "3", "4", "5"}, vals)
|
||||||
|
_, err = NewRedis(client.Addr, "").Sunionstore("key3", "key1", "key2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err = client.Sunionstore("key3", "key1", "key2")
|
num, err = client.Sunionstore("key3", "key1", "key2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 5, num)
|
assert.Equal(t, 5, num)
|
||||||
|
_, err = NewRedis(client.Addr, "").Sdiff("key1", "key2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err = client.Sdiff("key1", "key2")
|
vals, err = client.Sdiff("key1", "key2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"1"}, vals)
|
assert.EqualValues(t, []string{"1"}, vals)
|
||||||
|
_, err = NewRedis(client.Addr, "").Sdiffstore("key4", "key1", "key2")
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err = client.Sdiffstore("key4", "key1", "key2")
|
num, err = client.Sdiffstore("key4", "key1", "key2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, num)
|
assert.Equal(t, 1, num)
|
||||||
@@ -359,8 +455,12 @@ func TestRedis_Set(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_SetGetDel(t *testing.T) {
|
func TestRedis_SetGetDel(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
err := client.Set("hello", "world")
|
err := NewRedis(client.Addr, "").Set("hello", "world")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
err = client.Set("hello", "world")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").Get("hello")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.Get("hello")
|
val, err := client.Get("hello")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "world", val)
|
assert.Equal(t, "world", val)
|
||||||
@@ -372,8 +472,12 @@ func TestRedis_SetGetDel(t *testing.T) {
|
|||||||
|
|
||||||
func TestRedis_SetExNx(t *testing.T) {
|
func TestRedis_SetExNx(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
err := client.Setex("hello", "world", 5)
|
err := NewRedis(client.Addr, "").Setex("hello", "world", 5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
err = client.Setex("hello", "world", 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").Setnx("hello", "newworld")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err := client.Setnx("hello", "newworld")
|
ok, err := client.Setnx("hello", "newworld")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
@@ -389,6 +493,8 @@ func TestRedis_SetExNx(t *testing.T) {
|
|||||||
ttl, err := client.Ttl("hello")
|
ttl, err := client.Ttl("hello")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ttl > 0)
|
assert.True(t, ttl > 0)
|
||||||
|
_, err = NewRedis(client.Addr, "").SetnxEx("newhello", "newworld", 5)
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err = client.SetnxEx("newhello", "newworld", 5)
|
ok, err = client.SetnxEx("newhello", "newworld", 5)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
@@ -408,12 +514,18 @@ func TestRedis_SetGetDelHashField(t *testing.T) {
|
|||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
err := client.Hset("key", "field", "value")
|
err := client.Hset("key", "field", "value")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").Hget("key", "field")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err := client.Hget("key", "field")
|
val, err := client.Hget("key", "field")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "value", val)
|
assert.Equal(t, "value", val)
|
||||||
|
_, err = NewRedis(client.Addr, "").Hexists("key", "field")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err := client.Hexists("key", "field")
|
ok, err := client.Hexists("key", "field")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
_, err = NewRedis(client.Addr, "").Hdel("key", "field")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ret, err := client.Hdel("key", "field")
|
ret, err := client.Hdel("key", "field")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ret)
|
assert.True(t, ret)
|
||||||
@@ -434,23 +546,53 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
val, err := client.Zscore("key", "value1")
|
val, err := client.Zscore("key", "value1")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(2), val)
|
assert.Equal(t, int64(2), val)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zincrby("key", 3, "value1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err = client.Zincrby("key", 3, "value1")
|
val, err = client.Zincrby("key", 3, "value1")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(5), val)
|
assert.Equal(t, int64(5), val)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zscore("key", "value1")
|
||||||
|
assert.NotNil(t, err)
|
||||||
val, err = client.Zscore("key", "value1")
|
val, err = client.Zscore("key", "value1")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(5), val)
|
assert.Equal(t, int64(5), val)
|
||||||
ok, err = client.Zadd("key", 6, "value2")
|
_, err = NewRedis(client.Addr, "").Zadds("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
val, err = client.Zadds("key", Pair{
|
||||||
|
Key: "value2",
|
||||||
|
Score: 6,
|
||||||
|
}, Pair{
|
||||||
|
Key: "value3",
|
||||||
|
Score: 7,
|
||||||
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.Equal(t, int64(2), val)
|
||||||
ok, err = client.Zadd("key", 7, "value3")
|
_, err = NewRedis(client.Addr, "").ZRevRangeWithScores("key", 1, 3)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
pairs, err := client.ZRevRangeWithScores("key", 1, 3)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.EqualValues(t, []Pair{
|
||||||
|
{
|
||||||
|
Key: "value2",
|
||||||
|
Score: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "value1",
|
||||||
|
Score: 5,
|
||||||
|
},
|
||||||
|
}, pairs)
|
||||||
rank, err := client.Zrank("key", "value2")
|
rank, err := client.Zrank("key", "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, int64(1), rank)
|
assert.Equal(t, int64(1), rank)
|
||||||
|
rank, err = client.Zrevrank("key", "value1")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(2), rank)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zrank("key", "value4")
|
||||||
|
assert.NotNil(t, err)
|
||||||
_, err = client.Zrank("key", "value4")
|
_, err = client.Zrank("key", "value4")
|
||||||
assert.Equal(t, Nil, err)
|
assert.Equal(t, Nil, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zrem("key", "value2", "value3")
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err := client.Zrem("key", "value2", "value3")
|
num, err := client.Zrem("key", "value2", "value3")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, num)
|
assert.Equal(t, 2, num)
|
||||||
@@ -463,31 +605,47 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
ok, err = client.Zadd("key", 8, "value4")
|
ok, err = client.Zadd("key", 8, "value4")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zremrangebyscore("key", 6, 7)
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err = client.Zremrangebyscore("key", 6, 7)
|
num, err = client.Zremrangebyscore("key", 6, 7)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, num)
|
assert.Equal(t, 2, num)
|
||||||
ok, err = client.Zadd("key", 6, "value2")
|
ok, err = client.Zadd("key", 6, "value2")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zadd("key", 7, "value3")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ok, err = client.Zadd("key", 7, "value3")
|
ok, err = client.Zadd("key", 7, "value3")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zcount("key", 6, 7)
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err = client.Zcount("key", 6, 7)
|
num, err = client.Zcount("key", 6, 7)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, num)
|
assert.Equal(t, 2, num)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zremrangebyrank("key", 1, 2)
|
||||||
|
assert.NotNil(t, err)
|
||||||
num, err = client.Zremrangebyrank("key", 1, 2)
|
num, err = client.Zremrangebyrank("key", 1, 2)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, num)
|
assert.Equal(t, 2, num)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zcard("key")
|
||||||
|
assert.NotNil(t, err)
|
||||||
card, err := client.Zcard("key")
|
card, err := client.Zcard("key")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, card)
|
assert.Equal(t, 2, card)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zrange("key", 0, -1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err := client.Zrange("key", 0, -1)
|
vals, err := client.Zrange("key", 0, -1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"value1", "value4"}, vals)
|
assert.EqualValues(t, []string{"value1", "value4"}, vals)
|
||||||
|
_, err = NewRedis(client.Addr, "").Zrevrange("key", 0, -1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
vals, err = client.Zrevrange("key", 0, -1)
|
vals, err = client.Zrevrange("key", 0, -1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []string{"value4", "value1"}, vals)
|
assert.EqualValues(t, []string{"value4", "value1"}, vals)
|
||||||
pairs, err := client.ZrangeWithScores("key", 0, -1)
|
_, err = NewRedis(client.Addr, "").ZrangeWithScores("key", 0, -1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
pairs, err = client.ZrangeWithScores("key", 0, -1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []Pair{
|
assert.EqualValues(t, []Pair{
|
||||||
{
|
{
|
||||||
@@ -499,6 +657,8 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
Score: 8,
|
Score: 8,
|
||||||
},
|
},
|
||||||
}, pairs)
|
}, pairs)
|
||||||
|
_, err = NewRedis(client.Addr, "").ZrangebyscoreWithScores("key", 5, 8)
|
||||||
|
assert.NotNil(t, err)
|
||||||
pairs, err = client.ZrangebyscoreWithScores("key", 5, 8)
|
pairs, err = client.ZrangebyscoreWithScores("key", 5, 8)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []Pair{
|
assert.EqualValues(t, []Pair{
|
||||||
@@ -511,6 +671,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
Score: 8,
|
Score: 8,
|
||||||
},
|
},
|
||||||
}, pairs)
|
}, pairs)
|
||||||
|
_, err = NewRedis(client.Addr, "").ZrangebyscoreWithScoresAndLimit(
|
||||||
|
"key", 5, 8, 1, 1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []Pair{
|
assert.EqualValues(t, []Pair{
|
||||||
@@ -519,6 +682,11 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
Score: 8,
|
Score: 8,
|
||||||
},
|
},
|
||||||
}, pairs)
|
}, pairs)
|
||||||
|
pairs, err = client.ZrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 0, len(pairs))
|
||||||
|
_, err = NewRedis(client.Addr, "").ZrevrangebyscoreWithScores("key", 5, 8)
|
||||||
|
assert.NotNil(t, err)
|
||||||
pairs, err = client.ZrevrangebyscoreWithScores("key", 5, 8)
|
pairs, err = client.ZrevrangebyscoreWithScores("key", 5, 8)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []Pair{
|
assert.EqualValues(t, []Pair{
|
||||||
@@ -531,6 +699,9 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
Score: 5,
|
Score: 5,
|
||||||
},
|
},
|
||||||
}, pairs)
|
}, pairs)
|
||||||
|
_, err = NewRedis(client.Addr, "").ZrevrangebyscoreWithScoresAndLimit(
|
||||||
|
"key", 5, 8, 1, 1)
|
||||||
|
assert.NotNil(t, err)
|
||||||
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 1)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.EqualValues(t, []Pair{
|
assert.EqualValues(t, []Pair{
|
||||||
@@ -539,11 +710,19 @@ func TestRedis_SortedSet(t *testing.T) {
|
|||||||
Score: 5,
|
Score: 5,
|
||||||
},
|
},
|
||||||
}, pairs)
|
}, pairs)
|
||||||
|
pairs, err = client.ZrevrangebyscoreWithScoresAndLimit("key", 5, 8, 1, 0)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 0, len(pairs))
|
||||||
|
_, err = NewRedis(client.Addr, "").Zrevrank("key", "value")
|
||||||
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRedis_Pipelined(t *testing.T) {
|
func TestRedis_Pipelined(t *testing.T) {
|
||||||
runOnRedis(t, func(client *Redis) {
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
assert.NotNil(t, NewRedis(client.Addr, "").Pipelined(func(pipeliner Pipeliner) error {
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
err := client.Pipelined(
|
err := client.Pipelined(
|
||||||
func(pipe Pipeliner) error {
|
func(pipe Pipeliner) error {
|
||||||
pipe.Incr("pipelined_counter")
|
pipe.Incr("pipelined_counter")
|
||||||
@@ -553,6 +732,8 @@ func TestRedis_Pipelined(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
_, err = NewRedis(client.Addr, "").Ttl("pipelined_counter")
|
||||||
|
assert.NotNil(t, err)
|
||||||
ttl, err := client.Ttl("pipelined_counter")
|
ttl, err := client.Ttl("pipelined_counter")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 3600, ttl)
|
assert.Equal(t, 3600, ttl)
|
||||||
@@ -565,6 +746,108 @@ func TestRedis_Pipelined(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRedisString(t *testing.T) {
|
||||||
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
client.Ping()
|
||||||
|
_, err := getRedis(NewRedis(client.Addr, ClusterType))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, client.Addr, client.String())
|
||||||
|
assert.NotNil(t, NewRedis(client.Addr, "").Ping())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisScriptLoad(t *testing.T) {
|
||||||
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
client.Ping()
|
||||||
|
_, err := NewRedis(client.Addr, "").scriptLoad("foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = client.scriptLoad("foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisToPairs(t *testing.T) {
|
||||||
|
pairs := toPairs([]red.Z{
|
||||||
|
{
|
||||||
|
Member: 1,
|
||||||
|
Score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Member: 2,
|
||||||
|
Score: 2,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.EqualValues(t, []Pair{
|
||||||
|
{
|
||||||
|
Key: "1",
|
||||||
|
Score: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "2",
|
||||||
|
Score: 2,
|
||||||
|
},
|
||||||
|
}, pairs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisToStrings(t *testing.T) {
|
||||||
|
vals := toStrings([]interface{}{1, 2})
|
||||||
|
assert.EqualValues(t, []string{"1", "2"}, vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisBlpop(t *testing.T) {
|
||||||
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
client.Ping()
|
||||||
|
var node mockedNode
|
||||||
|
_, err := client.Blpop(nil, "foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = client.Blpop(node, "foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisBlpopEx(t *testing.T) {
|
||||||
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
client.Ping()
|
||||||
|
var node mockedNode
|
||||||
|
_, _, err := client.BlpopEx(nil, "foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, _, err = client.BlpopEx(node, "foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisGeo(t *testing.T) {
|
||||||
|
runOnRedis(t, func(client *Redis) {
|
||||||
|
client.Ping()
|
||||||
|
var geoLocation = []*GeoLocation{{Longitude: 13.361389, Latitude: 38.115556, Name: "Palermo"}, {Longitude: 15.087269, Latitude: 37.502669, Name: "Catania"}}
|
||||||
|
v, err := client.GeoAdd("sicily", geoLocation...)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(2), v)
|
||||||
|
v2, err := client.GeoDist("sicily", "Palermo", "Catania", "m")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 166274, int(v2))
|
||||||
|
// GeoHash not support
|
||||||
|
v3, err := client.GeoPos("sicily", "Palermo", "Catania")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(v3[0].Longitude), int64(13))
|
||||||
|
assert.Equal(t, int64(v3[0].Latitude), int64(38))
|
||||||
|
assert.Equal(t, int64(v3[1].Longitude), int64(15))
|
||||||
|
assert.Equal(t, int64(v3[1].Latitude), int64(37))
|
||||||
|
v4, err := client.GeoRadius("sicily", 15, 37, &red.GeoRadiusQuery{WithDist: true, Unit: "km", Radius: 200})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(v4[0].Dist), int64(190))
|
||||||
|
assert.Equal(t, int64(v4[1].Dist), int64(56))
|
||||||
|
var geoLocation2 = []*GeoLocation{{Longitude: 13.583333, Latitude: 37.316667, Name: "Agrigento"}}
|
||||||
|
v5, err := client.GeoAdd("sicily", geoLocation2...)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, int64(1), v5)
|
||||||
|
v6, err := client.GeoRadiusByMember("sicily", "Agrigento", &red.GeoRadiusQuery{Unit: "km", Radius: 100})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, v6[0].Name, "Agrigento")
|
||||||
|
assert.Equal(t, v6[1].Name, "Palermo")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func runOnRedis(t *testing.T, fn func(client *Redis)) {
|
func runOnRedis(t *testing.T, fn func(client *Redis)) {
|
||||||
s, err := miniredis.Run()
|
s, err := miniredis.Run()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -576,8 +859,18 @@ func runOnRedis(t *testing.T, fn func(client *Redis)) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Close()
|
if client != nil {
|
||||||
|
client.Close()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fn(NewRedis(s.Addr(), NodeType))
|
fn(NewRedis(s.Addr(), NodeType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockedNode struct {
|
||||||
|
RedisNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n mockedNode) BLPop(timeout time.Duration, keys ...string) *red.StringSliceCmd {
|
||||||
|
return red.NewStringSliceCmd("foo", "bar")
|
||||||
|
}
|
||||||
|
|||||||
28
core/stores/redis/redistest/redistest.go
Normal file
28
core/stores/redis/redistest/redistest.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package redistest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
"github.com/tal-tech/go-zero/core/lang"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateRedis() (r *redis.Redis, clean func(), err error) {
|
||||||
|
mr, err := miniredis.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return redis.NewRedis(mr.Addr(), redis.NodeType), func() {
|
||||||
|
ch := make(chan lang.PlaceholderType)
|
||||||
|
go func() {
|
||||||
|
mr.Close()
|
||||||
|
close(ch)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -82,6 +82,7 @@ func (cc CachedConn) QueryRowIndex(v interface{}, key string, keyer func(primary
|
|||||||
indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {
|
indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error {
|
||||||
var primaryKey interface{}
|
var primaryKey interface{}
|
||||||
var found bool
|
var found bool
|
||||||
|
|
||||||
if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) {
|
if err := cc.cache.TakeWithExpire(&primaryKey, key, func(val interface{}, expire time.Duration) (err error) {
|
||||||
primaryKey, err = indexQuery(cc.db, v)
|
primaryKey, err = indexQuery(cc.db, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis"
|
"github.com/alicebob/miniredis/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/tal-tech/go-zero/core/fx"
|
||||||
"github.com/tal-tech/go-zero/core/logx"
|
"github.com/tal-tech/go-zero/core/logx"
|
||||||
"github.com/tal-tech/go-zero/core/stat"
|
"github.com/tal-tech/go-zero/core/stat"
|
||||||
"github.com/tal-tech/go-zero/core/stores/cache"
|
"github.com/tal-tech/go-zero/core/stores/cache"
|
||||||
"github.com/tal-tech/go-zero/core/stores/redis"
|
"github.com/tal-tech/go-zero/core/stores/redis"
|
||||||
|
"github.com/tal-tech/go-zero/core/stores/redis/redistest"
|
||||||
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
"github.com/tal-tech/go-zero/core/stores/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -30,17 +32,15 @@ func init() {
|
|||||||
|
|
||||||
func TestCachedConn_GetCache(t *testing.T) {
|
func TestCachedConn_GetCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
var value string
|
var value string
|
||||||
err = c.GetCache("any", &value)
|
err = c.GetCache("any", &value)
|
||||||
assert.Equal(t, ErrNotFound, err)
|
assert.Equal(t, ErrNotFound, err)
|
||||||
s.Set("any", `"value"`)
|
r.Set("any", `"value"`)
|
||||||
err = c.GetCache("any", &value)
|
err = c.GetCache("any", &value)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "value", value)
|
assert.Equal(t, "value", value)
|
||||||
@@ -48,12 +48,10 @@ func TestCachedConn_GetCache(t *testing.T) {
|
|||||||
|
|
||||||
func TestStat(t *testing.T) {
|
func TestStat(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
@@ -73,15 +71,33 @@ func TestStat(t *testing.T) {
|
|||||||
|
|
||||||
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
c := NewConn(dummySqlConn{}, cache.CacheConf{
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
{
|
||||||
|
RedisConf: redis.RedisConf{
|
||||||
|
Host: r.Addr,
|
||||||
|
Type: redis.NodeType,
|
||||||
|
},
|
||||||
|
Weight: 100,
|
||||||
|
},
|
||||||
|
}, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
var str string
|
var str string
|
||||||
|
err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
|
||||||
|
return fmt.Sprintf("%s/1234", s)
|
||||||
|
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
|
||||||
|
*v.(*string) = "zero"
|
||||||
|
return "primary", errors.New("foo")
|
||||||
|
}, func(conn sqlx.SqlConn, v, pri interface{}) error {
|
||||||
|
assert.Equal(t, "primary", pri)
|
||||||
|
*v.(*string) = "xin"
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
|
err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
|
||||||
return fmt.Sprintf("%s/1234", s)
|
return fmt.Sprintf("%s/1234", s)
|
||||||
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
|
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
|
||||||
@@ -104,12 +120,10 @@ func TestCachedConn_QueryRowIndex_NoCache(t *testing.T) {
|
|||||||
|
|
||||||
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||||
cache.WithNotFoundExpiry(time.Second))
|
cache.WithNotFoundExpiry(time.Second))
|
||||||
|
|
||||||
@@ -135,6 +149,98 @@ func TestCachedConn_QueryRowIndex_HasCache(t *testing.T) {
|
|||||||
assert.Equal(t, `"xin"`, val)
|
assert.Equal(t, `"xin"`, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCachedConn_QueryRowIndex_HasCache_IntPrimary(t *testing.T) {
|
||||||
|
const (
|
||||||
|
primaryInt8 int8 = 100
|
||||||
|
primaryInt16 int16 = 10000
|
||||||
|
primaryInt32 int32 = 10000000
|
||||||
|
primaryInt64 int64 = 10000000
|
||||||
|
primaryUint8 uint8 = 100
|
||||||
|
primaryUint16 uint16 = 10000
|
||||||
|
primaryUint32 uint32 = 10000000
|
||||||
|
primaryUint64 uint64 = 10000000
|
||||||
|
)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
primary interface{}
|
||||||
|
primaryCache string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "int8 primary",
|
||||||
|
primary: primaryInt8,
|
||||||
|
primaryCache: fmt.Sprint(primaryInt8),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int16 primary",
|
||||||
|
primary: primaryInt16,
|
||||||
|
primaryCache: fmt.Sprint(primaryInt16),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int32 primary",
|
||||||
|
primary: primaryInt32,
|
||||||
|
primaryCache: fmt.Sprint(primaryInt32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "int64 primary",
|
||||||
|
primary: primaryInt64,
|
||||||
|
primaryCache: fmt.Sprint(primaryInt64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint8 primary",
|
||||||
|
primary: primaryUint8,
|
||||||
|
primaryCache: fmt.Sprint(primaryUint8),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint16 primary",
|
||||||
|
primary: primaryUint16,
|
||||||
|
primaryCache: fmt.Sprint(primaryUint16),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint32 primary",
|
||||||
|
primary: primaryUint32,
|
||||||
|
primaryCache: fmt.Sprint(primaryUint32),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uint64 primary",
|
||||||
|
primary: primaryUint64,
|
||||||
|
primaryCache: fmt.Sprint(primaryUint64),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
resetStats()
|
||||||
|
r, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||||
|
cache.WithNotFoundExpiry(time.Second))
|
||||||
|
|
||||||
|
var str string
|
||||||
|
r.Set("index", test.primaryCache)
|
||||||
|
err = c.QueryRowIndex(&str, "index", func(s interface{}) string {
|
||||||
|
return fmt.Sprintf("%v/1234", s)
|
||||||
|
}, func(conn sqlx.SqlConn, v interface{}) (interface{}, error) {
|
||||||
|
assert.Fail(t, "should not go here")
|
||||||
|
return test.primary, nil
|
||||||
|
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
|
||||||
|
*v.(*string) = "xin"
|
||||||
|
assert.Equal(t, primary, primary)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, "xin", str)
|
||||||
|
val, err := r.Get("index")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, test.primaryCache, val)
|
||||||
|
val, err = r.Get(test.primaryCache + "/1234")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, `"xin"`, val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
|
func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
|
||||||
caches := map[string]string{
|
caches := map[string]string{
|
||||||
"index": "primary",
|
"index": "primary",
|
||||||
@@ -144,12 +250,10 @@ func TestCachedConn_QueryRowIndex_HasWrongCache(t *testing.T) {
|
|||||||
for k, v := range caches {
|
for k, v := range caches {
|
||||||
t.Run(k+"/"+v, func(t *testing.T) {
|
t.Run(k+"/"+v, func(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10),
|
||||||
cache.WithNotFoundExpiry(time.Second))
|
cache.WithNotFoundExpiry(time.Second))
|
||||||
|
|
||||||
@@ -201,12 +305,10 @@ func TestStatCacheFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatDbFails(t *testing.T) {
|
func TestStatDbFails(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
@@ -224,12 +326,10 @@ func TestStatDbFails(t *testing.T) {
|
|||||||
|
|
||||||
func TestStatFromMemory(t *testing.T) {
|
func TestStatFromMemory(t *testing.T) {
|
||||||
resetStats()
|
resetStats()
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(dummySqlConn{}, r, cache.WithExpiry(time.Second*10))
|
||||||
|
|
||||||
var all sync.WaitGroup
|
var all sync.WaitGroup
|
||||||
@@ -284,10 +384,9 @@ func TestStatFromMemory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRow(t *testing.T) {
|
func TestCachedConnQueryRow(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -296,7 +395,6 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
var user string
|
var user string
|
||||||
var ran bool
|
var ran bool
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
||||||
ran = true
|
ran = true
|
||||||
@@ -304,7 +402,7 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
actualValue, err := s.Get(key)
|
actualValue, err := r.Get(key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
var actual string
|
var actual string
|
||||||
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
||||||
@@ -314,10 +412,9 @@ func TestCachedConnQueryRow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRowFromCache(t *testing.T) {
|
func TestCachedConnQueryRowFromCache(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
@@ -326,7 +423,6 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
var user string
|
var user string
|
||||||
var ran bool
|
var ran bool
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
assert.Nil(t, c.SetCache(key, value))
|
assert.Nil(t, c.SetCache(key, value))
|
||||||
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
||||||
@@ -335,7 +431,7 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
actualValue, err := s.Get(key)
|
actualValue, err := r.Get(key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
var actual string
|
var actual string
|
||||||
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
assert.Nil(t, json.Unmarshal([]byte(actualValue), &actual))
|
||||||
@@ -345,16 +441,14 @@ func TestCachedConnQueryRowFromCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestQueryRowNotFound(t *testing.T) {
|
func TestQueryRowNotFound(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
const key = "user"
|
const key = "user"
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
var user string
|
var user string
|
||||||
var ran int
|
var ran int
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
err = c.QueryRow(&user, key, func(conn sqlx.SqlConn, v interface{}) error {
|
||||||
@@ -367,13 +461,11 @@ func TestQueryRowNotFound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnExec(t *testing.T) {
|
func TestCachedConnExec(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
||||||
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
|
_, err = c.ExecNoCache("delete from user_table where id='kevin'")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -381,26 +473,31 @@ func TestCachedConnExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnExecDropCache(t *testing.T) {
|
func TestCachedConnExecDropCache(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, err := miniredis.Run()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer fx.DoWithTimeout(func() error {
|
||||||
}
|
r.Close()
|
||||||
|
return nil
|
||||||
|
}, time.Second)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
key = "user"
|
key = "user"
|
||||||
value = "any"
|
value = "any"
|
||||||
)
|
)
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
c := NewNodeConn(&conn, redis.NewRedis(r.Addr(), redis.NodeType), cache.WithExpiry(time.Second*30))
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
|
||||||
assert.Nil(t, c.SetCache(key, value))
|
assert.Nil(t, c.SetCache(key, value))
|
||||||
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
return conn.Exec("delete from user_table where id='kevin'")
|
return conn.Exec("delete from user_table where id='kevin'")
|
||||||
}, key)
|
}, key)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, conn.execValue)
|
assert.True(t, conn.execValue)
|
||||||
_, err = s.Get(key)
|
_, err = r.Get(key)
|
||||||
assert.Exactly(t, miniredis.ErrKeyNotFound, err)
|
assert.Exactly(t, miniredis.ErrKeyNotFound, err)
|
||||||
|
_, err = c.Exec(func(conn sqlx.SqlConn) (result sql.Result, e error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
}, key)
|
||||||
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
||||||
@@ -416,13 +513,11 @@ func TestCachedConnExecDropCacheFailed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnQueryRows(t *testing.T) {
|
func TestCachedConnQueryRows(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
||||||
var users []string
|
var users []string
|
||||||
err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
|
err = c.QueryRowsNoCache(&users, "select user from user_table where id='kevin'")
|
||||||
@@ -431,13 +526,11 @@ func TestCachedConnQueryRows(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCachedConnTransact(t *testing.T) {
|
func TestCachedConnTransact(t *testing.T) {
|
||||||
s, err := miniredis.Run()
|
r, clean, err := redistest.CreateRedis()
|
||||||
if err != nil {
|
assert.Nil(t, err)
|
||||||
t.Error(err)
|
defer clean()
|
||||||
}
|
|
||||||
|
|
||||||
var conn trackedConn
|
var conn trackedConn
|
||||||
r := redis.NewRedis(s.Addr(), redis.NodeType)
|
|
||||||
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*10))
|
||||||
err = c.Transact(func(session sqlx.Session) error {
|
err = c.Transact(func(session sqlx.Session) error {
|
||||||
return nil
|
return nil
|
||||||
@@ -446,6 +539,29 @@ func TestCachedConnTransact(t *testing.T) {
|
|||||||
assert.True(t, conn.transactValue)
|
assert.True(t, conn.transactValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQueryRowNoCache(t *testing.T) {
|
||||||
|
r, clean, err := redistest.CreateRedis()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
const (
|
||||||
|
key = "user"
|
||||||
|
value = "any"
|
||||||
|
)
|
||||||
|
var user string
|
||||||
|
var ran bool
|
||||||
|
conn := dummySqlConn{queryRow: func(v interface{}, q string, args ...interface{}) error {
|
||||||
|
user = value
|
||||||
|
ran = true
|
||||||
|
return nil
|
||||||
|
}}
|
||||||
|
c := NewNodeConn(&conn, r, cache.WithExpiry(time.Second*30))
|
||||||
|
err = c.QueryRowNoCache(&user, key)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, value, user)
|
||||||
|
assert.True(t, ran)
|
||||||
|
}
|
||||||
|
|
||||||
func resetStats() {
|
func resetStats() {
|
||||||
atomic.StoreUint64(&stats.Total, 0)
|
atomic.StoreUint64(&stats.Total, 0)
|
||||||
atomic.StoreUint64(&stats.Hit, 0)
|
atomic.StoreUint64(&stats.Hit, 0)
|
||||||
@@ -454,6 +570,7 @@ func resetStats() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type dummySqlConn struct {
|
type dummySqlConn struct {
|
||||||
|
queryRow func(interface{}, string, ...interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) {
|
func (d dummySqlConn) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
@@ -465,6 +582,9 @@ func (d dummySqlConn) Prepare(query string) (sqlx.StmtSession, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error {
|
func (d dummySqlConn) QueryRow(v interface{}, query string, args ...interface{}) error {
|
||||||
|
if d.queryRow != nil {
|
||||||
|
return d.queryRow(v, query, args...)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sqlx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -11,14 +12,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type mockedConn struct {
|
type mockedConn struct {
|
||||||
query string
|
query string
|
||||||
args []interface{}
|
args []interface{}
|
||||||
|
execErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
|
func (c *mockedConn) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||||
c.query = query
|
c.query = query
|
||||||
c.args = args
|
c.args = args
|
||||||
return nil, nil
|
return nil, c.execErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mockedConn) Prepare(query string) (StmtSession, error) {
|
func (c *mockedConn) Prepare(query string) (StmtSession, error) {
|
||||||
@@ -68,9 +70,12 @@ func TestBulkInserterSuffix(t *testing.T) {
|
|||||||
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES`+
|
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES`+
|
||||||
`(?, ?, ?) ON DUPLICATE KEY UPDATE is_overtime=VALUES(is_overtime)`)
|
`(?, ?, ?) ON DUPLICATE KEY UPDATE is_overtime=VALUES(is_overtime)`)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
assert.Nil(t, inserter.UpdateStmt(`INSERT INTO classroom_dau(classroom, user, count) VALUES`+
|
||||||
|
`(?, ?, ?) ON DUPLICATE KEY UPDATE is_overtime=VALUES(is_overtime)`))
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
assert.Nil(t, inserter.Insert("class_"+strconv.Itoa(i), "user_"+strconv.Itoa(i), i))
|
assert.Nil(t, inserter.Insert("class_"+strconv.Itoa(i), "user_"+strconv.Itoa(i), i))
|
||||||
}
|
}
|
||||||
|
inserter.SetResultHandler(func(result sql.Result, err error) {})
|
||||||
inserter.Flush()
|
inserter.Flush()
|
||||||
assert.Equal(t, `INSERT INTO classroom_dau(classroom, user, count) VALUES `+
|
assert.Equal(t, `INSERT INTO classroom_dau(classroom, user, count) VALUES `+
|
||||||
`('class_0', 'user_0', 0), ('class_1', 'user_1', 1), ('class_2', 'user_2', 2), `+
|
`('class_0', 'user_0', 0), ('class_1', 'user_1', 1), ('class_2', 'user_2', 2), `+
|
||||||
@@ -80,6 +85,33 @@ func TestBulkInserterSuffix(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBulkInserterBadStatement(t *testing.T) {
|
||||||
|
runSqlTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
var conn mockedConn
|
||||||
|
_, err := NewBulkInserter(&conn, "foo")
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBulkInserter_Update(t *testing.T) {
|
||||||
|
conn := mockedConn{
|
||||||
|
execErr: errors.New("foo"),
|
||||||
|
}
|
||||||
|
_, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES()`)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
_, err = NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES(?)`)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
inserter, err := NewBulkInserter(&conn, `INSERT INTO classroom_dau(classroom, user, count) VALUES(?, ?, ?)`)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
inserter.inserter.Execute([]string{"bar"})
|
||||||
|
inserter.SetResultHandler(func(result sql.Result, err error) {
|
||||||
|
})
|
||||||
|
inserter.UpdateOrDelete(func() {})
|
||||||
|
inserter.inserter.Execute([]string(nil))
|
||||||
|
assert.NotNil(t, inserter.UpdateStmt("foo"))
|
||||||
|
assert.NotNil(t, inserter.Insert("foo", "bar"))
|
||||||
|
}
|
||||||
|
|
||||||
func runSqlTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
func runSqlTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
||||||
logx.Disable()
|
logx.Disable()
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sqlx
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/DATA-DOG/go-sqlmock"
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
@@ -22,6 +23,18 @@ func TestUnmarshalRowBool(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowBoolNotSettable(t *testing.T) {
|
||||||
|
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("1")
|
||||||
|
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||||
|
|
||||||
|
var value bool
|
||||||
|
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRow(value, rows, true)
|
||||||
|
}, "select value from users where user=?", "anyone"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalRowInt(t *testing.T) {
|
func TestUnmarshalRowInt(t *testing.T) {
|
||||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2")
|
rs := sqlmock.NewRows([]string{"value"}).FromCSVString("2")
|
||||||
@@ -228,6 +241,22 @@ func TestUnmarshalRowStructWithTags(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowStructWithTagsWrongColumns(t *testing.T) {
|
||||||
|
var value = new(struct {
|
||||||
|
Age *int `db:"age"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
})
|
||||||
|
|
||||||
|
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"name"}).FromCSVString("liao")
|
||||||
|
mock.ExpectQuery("select (.+) from users where user=?").WithArgs("anyone").WillReturnRows(rs)
|
||||||
|
|
||||||
|
assert.NotNil(t, query(db, func(rows *sql.Rows) error {
|
||||||
|
return unmarshalRow(value, rows, true)
|
||||||
|
}, "select name, age from users where user=?", "anyone"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnmarshalRowsBool(t *testing.T) {
|
func TestUnmarshalRowsBool(t *testing.T) {
|
||||||
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
var expect = []bool{true, false}
|
var expect = []bool{true, false}
|
||||||
@@ -955,6 +984,62 @@ func TestCommonSqlConn_QueryRowOptional(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalRowError(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
colErr error
|
||||||
|
scanErr error
|
||||||
|
err error
|
||||||
|
next int
|
||||||
|
validate func(err error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with error",
|
||||||
|
err: errors.New("foo"),
|
||||||
|
validate: func(err error) {
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without next",
|
||||||
|
validate: func(err error) {
|
||||||
|
assert.Equal(t, ErrNotFound, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with error",
|
||||||
|
scanErr: errors.New("foo"),
|
||||||
|
next: 1,
|
||||||
|
validate: func(err error) {
|
||||||
|
assert.Equal(t, ErrNotFound, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
runOrmTest(t, func(db *sql.DB, mock sqlmock.Sqlmock) {
|
||||||
|
rs := sqlmock.NewRows([]string{"age"}).FromCSVString("5")
|
||||||
|
mock.ExpectQuery("select (.+) from users where user=?").WithArgs(
|
||||||
|
"anyone").WillReturnRows(rs)
|
||||||
|
|
||||||
|
var r struct {
|
||||||
|
User string `db:"user"`
|
||||||
|
Age int `db:"age"`
|
||||||
|
}
|
||||||
|
test.validate(query(db, func(rows *sql.Rows) error {
|
||||||
|
scanner := mockedScanner{
|
||||||
|
colErr: test.colErr,
|
||||||
|
scanErr: test.scanErr,
|
||||||
|
err: test.err,
|
||||||
|
}
|
||||||
|
return unmarshalRow(&r, &scanner, false)
|
||||||
|
}, "select age from users where user=?", "anyone"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
||||||
logx.Disable()
|
logx.Disable()
|
||||||
|
|
||||||
@@ -970,3 +1055,30 @@ func runOrmTest(t *testing.T, fn func(db *sql.DB, mock sqlmock.Sqlmock)) {
|
|||||||
t.Errorf("there were unfulfilled expectations: %s", err)
|
t.Errorf("there were unfulfilled expectations: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockedScanner struct {
|
||||||
|
colErr error
|
||||||
|
scanErr error
|
||||||
|
err error
|
||||||
|
next int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedScanner) Columns() ([]string, error) {
|
||||||
|
return nil, m.colErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedScanner) Err() error {
|
||||||
|
return m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedScanner) Next() bool {
|
||||||
|
if m.next > 0 {
|
||||||
|
m.next--
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockedScanner) Scan(v ...interface{}) error {
|
||||||
|
return m.scanErr
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package syncx
|
package syncx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -10,11 +11,15 @@ func TestBarrier_Guard(t *testing.T) {
|
|||||||
const total = 10000
|
const total = 10000
|
||||||
var barrier Barrier
|
var barrier Barrier
|
||||||
var count int
|
var count int
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(total)
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
barrier.Guard(func() {
|
go barrier.Guard(func() {
|
||||||
count++
|
count++
|
||||||
|
wg.Done()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
assert.Equal(t, total, count)
|
assert.Equal(t, total, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,10 +27,14 @@ func TestBarrierPtr_Guard(t *testing.T) {
|
|||||||
const total = 10000
|
const total = 10000
|
||||||
barrier := new(Barrier)
|
barrier := new(Barrier)
|
||||||
var count int
|
var count int
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
wg.Add(total)
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
barrier.Guard(func() {
|
go barrier.Guard(func() {
|
||||||
count++
|
count++
|
||||||
|
wg.Done()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
assert.Equal(t, total, count)
|
assert.Equal(t, total, count)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,3 @@ func TestSignalNoWait(t *testing.T) {
|
|||||||
func sleep(millisecond int) {
|
func sleep(millisecond int) {
|
||||||
time.Sleep(time.Duration(millisecond) * time.Millisecond)
|
time.Sleep(time.Duration(millisecond) * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func currentTimeMillis() int64 {
|
|
||||||
return time.Now().UnixNano() / int64(time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,18 +6,22 @@ import (
|
|||||||
"github.com/tal-tech/go-zero/core/lang"
|
"github.com/tal-tech/go-zero/core/lang"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrReturn = errors.New("discarding limited token, resource pool is full, someone returned multiple times")
|
// ErrLimitReturn indicates that the more than borrowed elements were returned.
|
||||||
|
var ErrLimitReturn = errors.New("discarding limited token, resource pool is full, someone returned multiple times")
|
||||||
|
|
||||||
|
// Limit controls the concurrent requests.
|
||||||
type Limit struct {
|
type Limit struct {
|
||||||
pool chan lang.PlaceholderType
|
pool chan lang.PlaceholderType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewLimit creates a Limit that can borrow n elements from it concurrently.
|
||||||
func NewLimit(n int) Limit {
|
func NewLimit(n int) Limit {
|
||||||
return Limit{
|
return Limit{
|
||||||
pool: make(chan lang.PlaceholderType, n),
|
pool: make(chan lang.PlaceholderType, n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Borrow borrows an element from Limit in blocking mode.
|
||||||
func (l Limit) Borrow() {
|
func (l Limit) Borrow() {
|
||||||
l.pool <- lang.Placeholder
|
l.pool <- lang.Placeholder
|
||||||
}
|
}
|
||||||
@@ -28,10 +32,12 @@ func (l Limit) Return() error {
|
|||||||
case <-l.pool:
|
case <-l.pool:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return ErrReturn
|
return ErrLimitReturn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TryBorrow tries to borrow an element from Limit, in non-blocking mode.
|
||||||
|
// If success, true returned, false for otherwise.
|
||||||
func (l Limit) TryBorrow() bool {
|
func (l Limit) TryBorrow() bool {
|
||||||
select {
|
select {
|
||||||
case l.pool <- lang.Placeholder:
|
case l.pool <- lang.Placeholder:
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ func TestLimit(t *testing.T) {
|
|||||||
assert.False(t, limit.TryBorrow())
|
assert.False(t, limit.TryBorrow())
|
||||||
assert.Nil(t, limit.Return())
|
assert.Nil(t, limit.Return())
|
||||||
assert.Nil(t, limit.Return())
|
assert.Nil(t, limit.Return())
|
||||||
assert.Equal(t, ErrReturn, limit.Return())
|
assert.Equal(t, ErrLimitReturn, limit.Return())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,38 +33,43 @@ func NewSharedCalls() SharedCalls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
|
func (g *sharedGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
|
||||||
g.lock.Lock()
|
c, done := g.createCall(key)
|
||||||
if c, ok := g.calls[key]; ok {
|
if done {
|
||||||
g.lock.Unlock()
|
|
||||||
c.wg.Wait()
|
|
||||||
return c.val, c.err
|
return c.val, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
c := g.makeCall(key, fn)
|
g.makeCall(c, key, fn)
|
||||||
return c.val, c.err
|
return c.val, c.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
|
func (g *sharedGroup) DoEx(key string, fn func() (interface{}, error)) (val interface{}, fresh bool, err error) {
|
||||||
|
c, done := g.createCall(key)
|
||||||
|
if done {
|
||||||
|
return c.val, false, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.makeCall(c, key, fn)
|
||||||
|
return c.val, true, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *sharedGroup) createCall(key string) (c *call, done bool) {
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
if c, ok := g.calls[key]; ok {
|
if c, ok := g.calls[key]; ok {
|
||||||
g.lock.Unlock()
|
g.lock.Unlock()
|
||||||
c.wg.Wait()
|
c.wg.Wait()
|
||||||
return c.val, false, c.err
|
return c, true
|
||||||
}
|
}
|
||||||
|
|
||||||
c := g.makeCall(key, fn)
|
c = new(call)
|
||||||
return c.val, true, c.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call {
|
|
||||||
c := new(call)
|
|
||||||
c.wg.Add(1)
|
c.wg.Add(1)
|
||||||
g.calls[key] = c
|
g.calls[key] = c
|
||||||
g.lock.Unlock()
|
g.lock.Unlock()
|
||||||
|
|
||||||
|
return c, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *sharedGroup) makeCall(c *call, key string, fn func() (interface{}, error)) {
|
||||||
defer func() {
|
defer func() {
|
||||||
// delete key first, done later. can't reverse the order, because if reverse,
|
|
||||||
// another Do call might wg.Wait() without get notified with wg.Done()
|
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
delete(g.calls, key)
|
delete(g.calls, key)
|
||||||
g.lock.Unlock()
|
g.lock.Unlock()
|
||||||
@@ -72,5 +77,4 @@ func (g *sharedGroup) makeCall(key string, fn func() (interface{}, error)) *call
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
c.val, c.err = fn()
|
c.val, c.err = fn()
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ func TestExclusiveCallDoDiffDupSuppress(t *testing.T) {
|
|||||||
close(broadcast)
|
close(broadcast)
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
if got := atomic.LoadInt32(&calls); got != 5 { // five letters
|
if got := atomic.LoadInt32(&calls); got != 5 {
|
||||||
|
// five letters
|
||||||
t.Errorf("number of calls = %d; want 5", got)
|
t.Errorf("number of calls = %d; want 5", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ func TestTimeoutLimit(t *testing.T) {
|
|||||||
assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100))
|
assert.Equal(t, ErrTimeout, limit.Borrow(time.Millisecond*100))
|
||||||
assert.Nil(t, limit.Return())
|
assert.Nil(t, limit.Return())
|
||||||
assert.Nil(t, limit.Return())
|
assert.Nil(t, limit.Return())
|
||||||
assert.Equal(t, ErrReturn, limit.Return())
|
assert.Equal(t, ErrLimitReturn, limit.Return())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestRelativeTime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRelativeTime_Time(t *testing.T) {
|
func TestRelativeTime_Time(t *testing.T) {
|
||||||
diff := Time().Sub(time.Now())
|
diff := time.Until(Time())
|
||||||
if diff > 0 {
|
if diff > 0 {
|
||||||
assert.True(t, diff < time.Second)
|
assert.True(t, diff < time.Second)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -27,12 +27,9 @@ func TestFakeTicker(t *testing.T) {
|
|||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for range ticker.Chan() {
|
||||||
select {
|
if atomic.AddInt32(&count, 1) == total {
|
||||||
case <-ticker.Chan():
|
ticker.Done()
|
||||||
if atomic.AddInt32(&count, 1) == total {
|
|
||||||
ticker.Done()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func TestHttpPropagator_Extract(t *testing.T) {
|
|||||||
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
||||||
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
||||||
|
|
||||||
carrier, err = Extract(HttpFormat, req)
|
_, err = Extract(HttpFormat, req)
|
||||||
assert.Equal(t, ErrInvalidCarrier, err)
|
assert.Equal(t, ErrInvalidCarrier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ func TestHttpPropagator_Inject(t *testing.T) {
|
|||||||
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
||||||
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
||||||
|
|
||||||
carrier, err = Inject(HttpFormat, req)
|
_, err = Inject(HttpFormat, req)
|
||||||
assert.Equal(t, ErrInvalidCarrier, err)
|
assert.Equal(t, ErrInvalidCarrier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +45,9 @@ func TestGrpcPropagator_Extract(t *testing.T) {
|
|||||||
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
||||||
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
||||||
|
|
||||||
carrier, err = Extract(GrpcFormat, 1)
|
_, err = Extract(GrpcFormat, 1)
|
||||||
assert.Equal(t, ErrInvalidCarrier, err)
|
assert.Equal(t, ErrInvalidCarrier, err)
|
||||||
carrier, err = Extract(nil, 1)
|
_, err = Extract(nil, 1)
|
||||||
assert.Equal(t, ErrInvalidCarrier, err)
|
assert.Equal(t, ErrInvalidCarrier, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,8 +61,8 @@ func TestGrpcPropagator_Inject(t *testing.T) {
|
|||||||
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
assert.Equal(t, "trace", carrier.Get(traceIdKey))
|
||||||
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
assert.Equal(t, "span", carrier.Get(spanIdKey))
|
||||||
|
|
||||||
carrier, err = Inject(GrpcFormat, 1)
|
_, err = Inject(GrpcFormat, 1)
|
||||||
assert.Equal(t, ErrInvalidCarrier, err)
|
assert.Equal(t, ErrInvalidCarrier, err)
|
||||||
carrier, err = Inject(nil, 1)
|
_, err = Inject(nil, 1)
|
||||||
assert.Equal(t, ErrInvalidCarrier, err)
|
assert.Equal(t, ErrInvalidCarrier, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ const (
|
|||||||
clientFlag = "client"
|
clientFlag = "client"
|
||||||
serverFlag = "server"
|
serverFlag = "server"
|
||||||
spanSepRune = '.'
|
spanSepRune = '.'
|
||||||
timeFormat = "2006-01-02 15:04:05.000"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var spanSep = string([]byte{spanSepRune})
|
var spanSep = string([]byte{spanSepRune})
|
||||||
@@ -37,9 +36,7 @@ func newServerSpan(carrier Carrier, serviceName, operationName string) tracespec
|
|||||||
return carrier.Get(traceIdKey)
|
return carrier.Get(traceIdKey)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}, func() string {
|
}, stringx.RandId)
|
||||||
return stringx.RandId()
|
|
||||||
})
|
|
||||||
spanId := stringx.TakeWithPriority(func() string {
|
spanId := stringx.TakeWithPriority(func() string {
|
||||||
if carrier != nil {
|
if carrier != nil {
|
||||||
return carrier.Get(spanIdKey)
|
return carrier.Get(spanIdKey)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ func TestCompareVersions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, each := range cases {
|
for _, each := range cases {
|
||||||
|
each := each
|
||||||
t.Run(each.ver1, func(t *testing.T) {
|
t.Run(each.ver1, func(t *testing.T) {
|
||||||
actual := CompareVersions(each.ver1, each.operator, each.ver2)
|
actual := CompareVersions(each.ver1, each.operator, each.ver2)
|
||||||
assert.Equal(t, each.out, actual, fmt.Sprintf("%s vs %s", each.ver1, each.ver2))
|
assert.Equal(t, each.out, actual, fmt.Sprintf("%s vs %s", each.ver1, each.ver2))
|
||||||
|
|||||||
@@ -1,622 +0,0 @@
|
|||||||
# Rapid development of microservices - multiple RPCs
|
|
||||||
|
|
||||||
English | [简体中文](bookstore.md)
|
|
||||||
|
|
||||||
## 0. Why building microservices are so difficult
|
|
||||||
|
|
||||||
To build a well working microservice, we need lots of knowledges from different aspects.
|
|
||||||
|
|
||||||
* basic functionalities
|
|
||||||
1. concurrency control and rate limit, to avoid being brought down by unexpected inbound
|
|
||||||
2. service discovery, make sure new or terminated nodes are detected asap
|
|
||||||
3. load balancing, balance the traffic base on the throughput of nodes
|
|
||||||
4. timeout control, avoid the nodes continue to process the timed out requests
|
|
||||||
5. circuit breaker, load shedding, fail fast, protects the failure nodes to recover asap
|
|
||||||
|
|
||||||
* advanced functionalities
|
|
||||||
1. authorization, make sure users can only access their own data
|
|
||||||
2. tracing, to understand the whole system and locate the specific problem quickly
|
|
||||||
3. logging, collects data and helps to backtrace problems
|
|
||||||
4. observability, no metrics, no optimization
|
|
||||||
|
|
||||||
For any point listed above, we need a long article to describe the theory and the implementation. But for us, the developers, it’s very difficult to understand all the concepts and make it happen in our systems. Although, we can use the frameworks that have been well served busy sites. [go-zero](https://github.com/tal-tech/go-zero) is born for this purpose, especially for cloud-native microservice systems.
|
|
||||||
|
|
||||||
As well, we always adhere to the idea that **prefer tools over conventions and documents**. We hope to reduce the boilerplate code as much as possible, and let developers focus on developing the business related code. For this purpose, we developed the tool `goctl`.
|
|
||||||
|
|
||||||
Let’s take the shorturl microservice as a quick example to demonstrate how to quickly create microservices by using [go-zero](https://github.com/tal-tech/go-zero). After finishing this tutorial, you’ll find that it’s so easy to write microservices!
|
|
||||||
|
|
||||||
## 1. What is a bookstore service
|
|
||||||
|
|
||||||
For simplicity, the bookstore service only contains two functionalities, adding books and quering prices.
|
|
||||||
|
|
||||||
Writting this bookstore service is to demonstrate the complete flow of creating a microservice by using go-zero. But algorithms and detail implementations are quite simplified, and this bookstore service is not suitable for production use.
|
|
||||||
|
|
||||||
## 2. Architecture of shorturl microservice
|
|
||||||
|
|
||||||
<img src="images/bookstore-arch.png" alt="architecture" width="800" />
|
|
||||||
|
|
||||||
## 3. goctl generated code overview
|
|
||||||
|
|
||||||
All modules with green background are generated, and will be enabled when necessary. The modules with red background are handwritten code, which is typically business logic code.
|
|
||||||
|
|
||||||
* API Gateway
|
|
||||||
|
|
||||||
<img src="images/api-gen.png" alt="api" width="800" />
|
|
||||||
|
|
||||||
* RPC
|
|
||||||
|
|
||||||
<img src="images/rpc-gen.png" alt="rpc" width="800" />
|
|
||||||
|
|
||||||
* model
|
|
||||||
|
|
||||||
<img src="images/model-gen.png" alt="model" width="800" />
|
|
||||||
|
|
||||||
And now, let’s walk through the complete flow of quickly create a microservice with go-zero.
|
|
||||||
|
|
||||||
## 4. Get started
|
|
||||||
|
|
||||||
* install etcd, mysql, redis
|
|
||||||
|
|
||||||
* install protoc-gen-go
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
|
||||||
```
|
|
||||||
|
|
||||||
* install goctl
|
|
||||||
|
|
||||||
```shell
|
|
||||||
GO111MODULE=on go get -u github.com/tal-tech/go-zero/tools/goctl
|
|
||||||
```
|
|
||||||
|
|
||||||
* create the working dir bookstore
|
|
||||||
|
|
||||||
* in `bookstore` dir, execute `go mod init bookstore` to initialize `go.mod``
|
|
||||||
|
|
||||||
## 5. Write code for API Gateway
|
|
||||||
|
|
||||||
* use goctl to generate `api/bookstore.api`
|
|
||||||
|
|
||||||
```Plain Text
|
|
||||||
goctl api -o bookstore.api
|
|
||||||
```
|
|
||||||
|
|
||||||
for simplicity, the leading `info` block is removed, and the code looks like:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type (
|
|
||||||
addReq struct {
|
|
||||||
book string `form:"book"`
|
|
||||||
price int64 `form:"price"`
|
|
||||||
}
|
|
||||||
|
|
||||||
addResp struct {
|
|
||||||
ok bool `json:"ok"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
checkReq struct {
|
|
||||||
book string `form:"book"`
|
|
||||||
}
|
|
||||||
|
|
||||||
checkResp struct {
|
|
||||||
found bool `json:"found"`
|
|
||||||
price int64 `json:"price"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
service bookstore-api {
|
|
||||||
@server(
|
|
||||||
handler: AddHandler
|
|
||||||
)
|
|
||||||
get /add(addReq) returns(addResp)
|
|
||||||
|
|
||||||
@server(
|
|
||||||
handler: CheckHandler
|
|
||||||
)
|
|
||||||
get /check(checkReq) returns(checkResp)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
the usage of `type` keyword is the same as that in go, service is used to define get/post/head/delete api requests, described below:
|
|
||||||
|
|
||||||
* `service bookstore-api { defines the service name
|
|
||||||
* `@server` defines the properties that used in server side
|
|
||||||
* `handler` defines the handler name
|
|
||||||
* `get /add(addReq) returns(addResp)` defines this is a GET request, the request parameters, and the response parameters
|
|
||||||
|
|
||||||
* generate the code for API Gateway by using goctl
|
|
||||||
|
|
||||||
```shell
|
|
||||||
goctl api go -api bookstore.api -dir .
|
|
||||||
```
|
|
||||||
|
|
||||||
the generated file structure looks like:
|
|
||||||
|
|
||||||
```Plain Text
|
|
||||||
api
|
|
||||||
├── bookstore.api // api definition
|
|
||||||
├── bookstore.go // main entrance
|
|
||||||
├── etc
|
|
||||||
│ └── bookstore-api.yaml // configuration file
|
|
||||||
└── internal
|
|
||||||
├── config
|
|
||||||
│ └── config.go // configuration definition
|
|
||||||
├── handler
|
|
||||||
│ ├── addhandler.go // implements addHandler
|
|
||||||
│ ├── checkhandler.go // implements checkHandler
|
|
||||||
│ └── routes.go // routes definition
|
|
||||||
├── logic
|
|
||||||
│ ├── addlogic.go // implements AddLogic
|
|
||||||
│ └── checklogic.go // implements CheckLogic
|
|
||||||
├── svc
|
|
||||||
│ └── servicecontext.go // defines ServiceContext
|
|
||||||
└── types
|
|
||||||
└── types.go // defines request/response
|
|
||||||
```
|
|
||||||
|
|
||||||
* start API Gateway service, listens on port 8888 by default
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go run bookstore.go -f etc/bookstore-api.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
* test API Gateway service
|
|
||||||
|
|
||||||
```shell
|
|
||||||
curl -i "http://localhost:8888/check?book=go-zero"
|
|
||||||
```
|
|
||||||
|
|
||||||
response like:
|
|
||||||
|
|
||||||
```http
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
Date: Thu, 03 Sep 2020 06:46:18 GMT
|
|
||||||
Content-Length: 25
|
|
||||||
|
|
||||||
{"found":false,"price":0}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can see that the API Gateway service did nothing except returned a zero value. And let’s implement the business logic in rpc service.
|
|
||||||
|
|
||||||
* you can modify `internal/svc/servicecontext.go` to pass dependencies if needed
|
|
||||||
|
|
||||||
* implement logic in package `internal/logic`
|
|
||||||
|
|
||||||
* you can use goctl to generate code for clients base on the .api file
|
|
||||||
|
|
||||||
* till now, the client engineer can work with the api, don’t need to wait for the implementation of server side
|
|
||||||
|
|
||||||
## 6. Write code for add rpc service
|
|
||||||
|
|
||||||
* under directory `rpc/add` create `add.proto` file
|
|
||||||
|
|
||||||
```shell
|
|
||||||
goctl rpc template -o add.proto
|
|
||||||
```
|
|
||||||
|
|
||||||
edit the file and make the code looks like:
|
|
||||||
|
|
||||||
```protobuf
|
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package add;
|
|
||||||
|
|
||||||
message addReq {
|
|
||||||
string book = 1;
|
|
||||||
int64 price = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message addResp {
|
|
||||||
bool ok = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
service adder {
|
|
||||||
rpc add(addReq) returns(addResp);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* use goctl to generate the rpc code, execute the following command in `rpc/add`
|
|
||||||
|
|
||||||
```shell
|
|
||||||
goctl rpc proto -src add.proto
|
|
||||||
```
|
|
||||||
|
|
||||||
the generated file structure looks like:
|
|
||||||
|
|
||||||
```Plain Text
|
|
||||||
rpc/add
|
|
||||||
├── add.go // rpc main entrance
|
|
||||||
├── add.proto // rpc definition
|
|
||||||
├── adder
|
|
||||||
│ ├── adder.go // defines how rpc clients call this service
|
|
||||||
│ ├── adder_mock.go // mock file, for test purpose
|
|
||||||
│ └── types.go // request/response definition
|
|
||||||
├── etc
|
|
||||||
│ └── add.yaml // configuration file
|
|
||||||
├── internal
|
|
||||||
│ ├── config
|
|
||||||
│ │ └── config.go // configuration definition
|
|
||||||
│ ├── logic
|
|
||||||
│ │ └── addlogic.go // add logic here
|
|
||||||
│ ├── server
|
|
||||||
│ │ └── adderserver.go // rpc handler
|
|
||||||
│ └── svc
|
|
||||||
│ └── servicecontext.go // defines service context, like dependencies
|
|
||||||
└── pb
|
|
||||||
└── add.pb.go
|
|
||||||
```
|
|
||||||
|
|
||||||
just run it, looks like:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ go run add.go -f etc/add.yaml
|
|
||||||
Starting rpc server at 127.0.0.1:8080...
|
|
||||||
```
|
|
||||||
|
|
||||||
you can change the listening port in file `etc/add.yaml`.
|
|
||||||
|
|
||||||
## 7. Write code for check rpc service
|
|
||||||
|
|
||||||
* under directory `rpc/check` create `check.proto` file
|
|
||||||
|
|
||||||
```shell
|
|
||||||
goctl rpc template -o check.proto
|
|
||||||
```
|
|
||||||
|
|
||||||
edit the file and make the code looks like:
|
|
||||||
|
|
||||||
```protobuf
|
|
||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package check;
|
|
||||||
|
|
||||||
message checkReq {
|
|
||||||
string book = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message checkResp {
|
|
||||||
bool found = 1;
|
|
||||||
int64 price = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
service checker {
|
|
||||||
rpc check(checkReq) returns(checkResp);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* use goctl to generate the rpc code, execute the following command in `rpc/check`
|
|
||||||
|
|
||||||
```shell
|
|
||||||
goctl rpc proto -src check.proto
|
|
||||||
```
|
|
||||||
|
|
||||||
the generated file structure looks like:
|
|
||||||
|
|
||||||
```Plain Text
|
|
||||||
rpc/check
|
|
||||||
├── check.go // rpc main entrance
|
|
||||||
├── check.proto // rpc definition
|
|
||||||
├── checker
|
|
||||||
│ ├── checker.go // defines how rpc clients call this service
|
|
||||||
│ ├── checker_mock.go // mock file, for test purpose
|
|
||||||
│ └── types.go // request/response definition
|
|
||||||
├── etc
|
|
||||||
│ └── check.yaml // configuration file
|
|
||||||
├── internal
|
|
||||||
│ ├── config
|
|
||||||
│ │ └── config.go // configuration definition
|
|
||||||
│ ├── logic
|
|
||||||
│ │ └── checklogic.go // check logic here
|
|
||||||
│ ├── server
|
|
||||||
│ │ └── checkerserver.go // rpc handler
|
|
||||||
│ └── svc
|
|
||||||
│ └── servicecontext.go // defines service context, like dependencies
|
|
||||||
└── pb
|
|
||||||
└── check.pb.go
|
|
||||||
```
|
|
||||||
|
|
||||||
you can change the listening port in `etc/check.yaml`.
|
|
||||||
|
|
||||||
we need to change the port in `etc/check.yaml` to `8081`, because `8080` is used by `add` service.
|
|
||||||
|
|
||||||
just run it, looks like:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ go run check.go -f etc/check.yaml
|
|
||||||
Starting rpc server at 127.0.0.1:8081...
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8. Modify API Gateway to call add/check rpc service
|
|
||||||
|
|
||||||
* modify the configuration file `bookstore-api.yaml`, add the following:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
Add:
|
|
||||||
Etcd:
|
|
||||||
Hosts:
|
|
||||||
- localhost:2379
|
|
||||||
Key: add.rpc
|
|
||||||
Check:
|
|
||||||
Etcd:
|
|
||||||
Hosts:
|
|
||||||
- localhost:2379
|
|
||||||
Key: check.rpc
|
|
||||||
```
|
|
||||||
|
|
||||||
automatically discover the add/check service by using etcd.
|
|
||||||
|
|
||||||
* modify the file `internal/config/config.go`, add dependency on add/check service:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Config struct {
|
|
||||||
rest.RestConf
|
|
||||||
Add zrpc.RpcClientConf // manual code
|
|
||||||
Check zrpc.RpcClientConf // manual code
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* modify the file `internal/svc/servicecontext.go`, like below:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type ServiceContext struct {
|
|
||||||
Config config.Config
|
|
||||||
Adder adder.Adder // manual code
|
|
||||||
Checker checker.Checker // manual code
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
|
||||||
return &ServiceContext{
|
|
||||||
Config: c,
|
|
||||||
Adder: adder.NewAdder(zrpc.MustNewClient(c.Add)), // manual code
|
|
||||||
Checker: checker.NewChecker(zrpc.MustNewClient(c.Check)), // manual code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
passing the dependencies among services within ServiceContext.
|
|
||||||
|
|
||||||
* modify the method `Add` in the file `internal/logic/addlogic.go`, looks like:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (l *AddLogic) Add(req types.AddReq) (*types.AddResp, error) {
|
|
||||||
// manual code start
|
|
||||||
resp, err := l.svcCtx.Adder.Add(l.ctx, &adder.AddReq{
|
|
||||||
Book: req.Book,
|
|
||||||
Price: req.Price,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.AddResp{
|
|
||||||
Ok: resp.Ok,
|
|
||||||
}, nil
|
|
||||||
// manual code stop
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
by calling the method `Add` of `adder` to add books into bookstore.
|
|
||||||
|
|
||||||
* modify the file `internal/logic/checklogic.go`, looks like:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (l *CheckLogic) Check(req types.CheckReq) (*types.CheckResp, error) {
|
|
||||||
// manual code start
|
|
||||||
resp, err := l.svcCtx.Checker.Check(l.ctx, &checker.CheckReq{
|
|
||||||
Book: req.Book,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.CheckResp{
|
|
||||||
Found: resp.Found,
|
|
||||||
Price: resp.Price,
|
|
||||||
}, nil
|
|
||||||
// manual code stop
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
by calling the method `Check` of `checker` to check the prices from the bookstore.
|
|
||||||
|
|
||||||
Till now, we’ve done the modification of API Gateway. All the manually added code are marked.
|
|
||||||
|
|
||||||
## 9. Define the database schema, generate the code for CRUD+cache
|
|
||||||
|
|
||||||
* under bookstore, create the directory `rpc/model`: `mkdir -p rpc/model`
|
|
||||||
|
|
||||||
* under the directory rpc/model create the file called `book.sql`, contents as below:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE `book`
|
|
||||||
(
|
|
||||||
`book` varchar(255) NOT NULL COMMENT 'book name',
|
|
||||||
`price` int NOT NULL COMMENT 'book price',
|
|
||||||
PRIMARY KEY(`book`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
```
|
|
||||||
|
|
||||||
* create DB and table
|
|
||||||
|
|
||||||
```sql
|
|
||||||
create database gozero;
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
source book.sql;
|
|
||||||
```
|
|
||||||
|
|
||||||
* under the directory `rpc/model` execute the following command to genrate CRUD+cache code, `-c` means using `redis cache`
|
|
||||||
|
|
||||||
```shell
|
|
||||||
goctl model mysql ddl -c -src book.sql -dir .
|
|
||||||
```
|
|
||||||
|
|
||||||
you can also generate the code from the database url by using `datasource` subcommand instead of `ddl`
|
|
||||||
|
|
||||||
the generated file structure looks like:
|
|
||||||
|
|
||||||
```Plain Text
|
|
||||||
rpc/model
|
|
||||||
├── bookstore.sql
|
|
||||||
├── bookstoremodel.go // CRUD+cache code
|
|
||||||
└── vars.go // const and var definition
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10. Modify add/check rpc to call crud+cache
|
|
||||||
|
|
||||||
* modify `rpc/add/etc/add.yaml`, add the following:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
DataSource: root:@tcp(localhost:3306)/gozero
|
|
||||||
Table: book
|
|
||||||
Cache:
|
|
||||||
- Host: localhost:6379
|
|
||||||
```
|
|
||||||
|
|
||||||
you can use multiple redis as cache. redis node and cluster are both supported.
|
|
||||||
|
|
||||||
* modify `rpc/add/internal/config.go`, like below:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Config struct {
|
|
||||||
zrpc.RpcServerConf
|
|
||||||
DataSource string // manual code
|
|
||||||
Table string // manual code
|
|
||||||
Cache cache.CacheConf // manual code
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
added the configuration for mysql and redis cache.
|
|
||||||
|
|
||||||
* modify `rpc/add/internal/svc/servicecontext.go` and `rpc/check/internal/svc/servicecontext.go`, like below:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type ServiceContext struct {
|
|
||||||
c config.Config
|
|
||||||
Model *model.BookModel // manual code
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServiceContext(c config.Config) *ServiceContext {
|
|
||||||
return &ServiceContext{
|
|
||||||
c: c,
|
|
||||||
Model: model.NewBookModel(sqlx.NewMysql(c.DataSource), c.Cache, c.Table), // manual code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* modify `rpc/add/internal/logic/addlogic.go`, like below:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (l *AddLogic) Add(in *add.AddReq) (*add.AddResp, error) {
|
|
||||||
// manual code start
|
|
||||||
_, err := l.svcCtx.Model.Insert(model.Book{
|
|
||||||
Book: in.Book,
|
|
||||||
Price: in.Price,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &add.AddResp{
|
|
||||||
Ok: true,
|
|
||||||
}, nil
|
|
||||||
// manual code stop
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* modify `rpc/check/internal/logic/checklogic.go`, like below:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (l *CheckLogic) Check(in *check.CheckReq) (*check.CheckResp, error) {
|
|
||||||
// manual code start
|
|
||||||
resp, err := l.svcCtx.Model.FindOne(in.Book)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &check.CheckResp{
|
|
||||||
Found: true,
|
|
||||||
Price: resp.Price,
|
|
||||||
}, nil
|
|
||||||
// manual code stop
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
till now, we finished modifing the code, all the modified code is marked.
|
|
||||||
|
|
||||||
## 11. Call shorten and expand services
|
|
||||||
|
|
||||||
* call add api
|
|
||||||
|
|
||||||
```shell
|
|
||||||
curl -i "http://localhost:8888/add?book=go-zero&price=10"
|
|
||||||
```
|
|
||||||
|
|
||||||
response like:
|
|
||||||
|
|
||||||
```http
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
Date: Thu, 03 Sep 2020 09:42:13 GMT
|
|
||||||
Content-Length: 11
|
|
||||||
|
|
||||||
{"ok":true}
|
|
||||||
```
|
|
||||||
|
|
||||||
* call check api
|
|
||||||
|
|
||||||
```shell
|
|
||||||
curl -i "http://localhost:8888/check?book=go-zero"
|
|
||||||
```
|
|
||||||
|
|
||||||
response like:
|
|
||||||
|
|
||||||
```http
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
Date: Thu, 03 Sep 2020 09:47:34 GMT
|
|
||||||
Content-Length: 25
|
|
||||||
|
|
||||||
{"found":true,"price":10}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 12. Benchmark
|
|
||||||
|
|
||||||
Because benchmarking the write requests depends on the write throughput of mysql, we only benchmarked the check api. We read the data from mysql and cache it in redis. For simplicity, I only check one book, because of cache, the effect is the same for multiple books.
|
|
||||||
|
|
||||||
Before benchmark, we need to change the max open files:
|
|
||||||
|
|
||||||
```shel
|
|
||||||
ulimit -n 20000
|
|
||||||
```
|
|
||||||
|
|
||||||
And change the log level to error, to avoid too many logs affect the benchmark. Add the following in every yaml file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
Log:
|
|
||||||
Level: error
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
as shown above, in my MacBook Pro, the QPS is like 30K+.
|
|
||||||
|
|
||||||
## 13. Full code
|
|
||||||
|
|
||||||
[https://github.com/tal-tech/go-zero/tree/master/example/bookstore](https://github.com/tal-tech/go-zero/tree/master/example/bookstore)
|
|
||||||
|
|
||||||
## 14. Conclusion
|
|
||||||
|
|
||||||
We always adhere to **prefer tools over conventions and documents**.
|
|
||||||
|
|
||||||
go-zero is not only a framework, but also a tool to simplify and standardize the building of micoservice systems.
|
|
||||||
|
|
||||||
We not only keep the framework simple, but also encapsulate the complexity into the framework. And the developers are free from building the difficult and boilerplate code. Then we get the rapid development and less failure.
|
|
||||||
|
|
||||||
For the generated code by goctl, lots of microservice components are included, like concurrency control, adaptive circuit breaker, adaptive load shedding, auto cache control etc. And it’s easy to deal with the busy sites.
|
|
||||||
|
|
||||||
If you have any ideas that can help us to improve the productivity, tell me any time! 👏
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user