mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-12 01:10:00 +08:00
Compare commits
414 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b245159417 | ||
|
|
c26ea17669 | ||
|
|
a7daff3587 | ||
|
|
6719d06146 | ||
|
|
0c6eaeda9f | ||
|
|
b9c0c0f8b5 | ||
|
|
77da459165 | ||
|
|
13cdbdc98b | ||
|
|
e8c1e6e09b | ||
|
|
f1171e01f2 | ||
|
|
61e562d0c7 | ||
|
|
b71453985c | ||
|
|
31b9ba19a2 | ||
|
|
3170afd57b | ||
|
|
03e365a5d8 | ||
|
|
7d4fce9588 | ||
|
|
916cea858f | ||
|
|
a86942d532 | ||
|
|
f76c70ea9a | ||
|
|
4cbfdb3d74 | ||
|
|
aefa6dfb50 | ||
|
|
9047029475 | ||
|
|
f296c182f7 | ||
|
|
40e7a4cd07 | ||
|
|
92e5819e91 | ||
|
|
8d23ab158b | ||
|
|
bcccfab824 | ||
|
|
f7e701a634 | ||
|
|
7c2d8e5cc2 | ||
|
|
5b622d6265 | ||
|
|
c5510a4e1b | ||
|
|
2a33b74b35 | ||
|
|
45bb547a81 | ||
|
|
f5f5261556 | ||
|
|
b176d5d434 | ||
|
|
92f6c48349 | ||
|
|
71e8230e65 | ||
|
|
018fa8e0a0 | ||
|
|
979fe9718a | ||
|
|
f998803131 | ||
|
|
1262266ac2 | ||
|
|
9c32bf8478 | ||
|
|
37ec7f6443 | ||
|
|
2fdc4dfc0f | ||
|
|
4b2a6ba3de | ||
|
|
7fa3f10f22 | ||
|
|
4a29a0b642 | ||
|
|
a62745a152 | ||
|
|
28314326e7 | ||
|
|
f6bdb6e1de | ||
|
|
efa6940001 | ||
|
|
da81d8f774 | ||
|
|
fd84b27bdc | ||
|
|
6b4d0d89c0 | ||
|
|
d61a55f779 | ||
|
|
8ef4164209 | ||
|
|
50e29e2075 | ||
|
|
452c9dbcaf | ||
|
|
3564e36a35 | ||
|
|
e479e47634 | ||
|
|
ad921a6419 | ||
|
|
44c8d6f269 | ||
|
|
8a4cc4f98d | ||
|
|
e751736516 | ||
|
|
032f2419a2 | ||
|
|
84adc054bc | ||
|
|
b92e706ce1 | ||
|
|
1b5946346e | ||
|
|
28d3905731 | ||
|
|
3726851c7f | ||
|
|
2f2ddd373b | ||
|
|
8d48e34eed | ||
|
|
32f78668db | ||
|
|
cd0f3726ed | ||
|
|
0217044900 | ||
|
|
8b4382dcec | ||
|
|
fa33329a44 | ||
|
|
d76a39ac26 | ||
|
|
76a7a17e57 | ||
|
|
4a2a8d9e45 | ||
|
|
ef26b39b4c | ||
|
|
3ca40001b4 | ||
|
|
278ae3d26a | ||
|
|
fa1d6d50a8 | ||
|
|
0f4973be06 | ||
|
|
a9aac7e420 | ||
|
|
925cf8d3d1 | ||
|
|
99ce24e2ab | ||
|
|
701bb31ed2 | ||
|
|
55e2c7ee83 | ||
|
|
90839965fa | ||
|
|
f7228e9af1 | ||
|
|
f95adae3c1 | ||
|
|
bff5b81ad9 | ||
|
|
f0bdfb928f | ||
|
|
e4a1b7bb39 | ||
|
|
b6906b5d21 | ||
|
|
116da96178 | ||
|
|
9fa98c2bd3 | ||
|
|
b1c4c4736f | ||
|
|
ef410e8083 | ||
|
|
c22bc1c8ea | ||
|
|
1853428011 | ||
|
|
3637e10815 | ||
|
|
93124329ac | ||
|
|
851a72f1cc | ||
|
|
a93c24ce84 | ||
|
|
9f42eda9ff | ||
|
|
8762a3b7ba | ||
|
|
2684a157ff | ||
|
|
63368d8b0c | ||
|
|
4f13fe8188 | ||
|
|
9fc7874336 | ||
|
|
e6518521eb | ||
|
|
8f5a0a2de7 | ||
|
|
774e8d1d08 | ||
|
|
8ad0668612 | ||
|
|
8a043d2443 | ||
|
|
0e2ee97a02 | ||
|
|
42300a7d83 | ||
|
|
fe97fab274 | ||
|
|
f93e752f98 | ||
|
|
3a66fc038f | ||
|
|
b028ed058d | ||
|
|
1fd0c3992b | ||
|
|
1aebb3e5e4 | ||
|
|
8ffe4c01d1 | ||
|
|
a31256b327 | ||
|
|
14caf5c799 | ||
|
|
c0f8a58ed7 | ||
|
|
3189ec7be6 | ||
|
|
f51e9f0ea7 | ||
|
|
ba9d510cdb | ||
|
|
8c9b619199 | ||
|
|
49f73265b9 | ||
|
|
7568674b2b | ||
|
|
3da740b7fc | ||
|
|
ce4eb6ed61 | ||
|
|
9970ff55cd | ||
|
|
d10740f871 | ||
|
|
027193dc99 | ||
|
|
de1e0f2410 | ||
|
|
062073ce58 | ||
|
|
e20b02f311 | ||
|
|
02357d2616 | ||
|
|
489d69f779 | ||
|
|
117611a170 | ||
|
|
0a46ad7ac1 | ||
|
|
bf905eaff3 | ||
|
|
88cb35e3d5 | ||
|
|
078825b4eb | ||
|
|
bbfce6abe9 | ||
|
|
0d11ce03a8 | ||
|
|
757ed19dc5 | ||
|
|
c5fd074aac | ||
|
|
8fa0bd1f1c | ||
|
|
ede19a89ec | ||
|
|
73664b92f0 | ||
|
|
8d9c2fa22a | ||
|
|
22fad4bb9c | ||
|
|
189e9bd9da | ||
|
|
98c9b5928a | ||
|
|
e13fd62d38 | ||
|
|
ffacae89eb | ||
|
|
49135fe25e | ||
|
|
2e6402f4b5 | ||
|
|
07f03ebd0c | ||
|
|
92f2676afc | ||
|
|
1807305e6d | ||
|
|
38a97d4531 | ||
|
|
b9f98ecc4a | ||
|
|
1dc222f4b2 | ||
|
|
a79b8de24d | ||
|
|
5da8a93c75 | ||
|
|
b49fc81618 | ||
|
|
6a692453dc | ||
|
|
8d0cceb80c | ||
|
|
e06abf4f6f | ||
|
|
ee555a85da | ||
|
|
1904af2323 | ||
|
|
95b85336d6 | ||
|
|
ca4ce7bce8 | ||
|
|
9065eb90d9 | ||
|
|
50bc361430 | ||
|
|
455a6c8f97 | ||
|
|
04434646eb | ||
|
|
992a56e90b | ||
|
|
ed4d5e5813 | ||
|
|
fe85e7cb42 | ||
|
|
9c6b516bb8 | ||
|
|
2e9063a9a1 | ||
|
|
c3648be533 | ||
|
|
0ab06f62ca | ||
|
|
6170d7b790 | ||
|
|
18d163c4f7 | ||
|
|
a561048d59 | ||
|
|
7a647ca40c | ||
|
|
3f6f14f976 | ||
|
|
a78d57bebd | ||
|
|
74452eb7b5 | ||
|
|
a9e364a01a | ||
|
|
29c2e20b41 | ||
|
|
42c146bcbd | ||
|
|
b61e364458 | ||
|
|
18a4dcb79f | ||
|
|
60a13f1e53 | ||
|
|
3e093bf34e | ||
|
|
211b9498ef | ||
|
|
cca45be3c5 | ||
|
|
e735915d89 | ||
|
|
f77e2c9cfa | ||
|
|
544aa7c432 | ||
|
|
4cef2b412c | ||
|
|
123c61ad12 | ||
|
|
fbf129d535 | ||
|
|
c8a17a97be | ||
|
|
3a493cd6a6 | ||
|
|
7a0c04bc21 | ||
|
|
3c9fe0b381 | ||
|
|
f8b2dc8c9f | ||
|
|
37cb00d789 | ||
|
|
e3e7bc736b | ||
|
|
fafbee24b8 | ||
|
|
8ec29d29ce | ||
|
|
cb7f3e8a17 | ||
|
|
03391b48ca | ||
|
|
d0dedb0624 | ||
|
|
e136deb3a7 | ||
|
|
a2592a17e9 | ||
|
|
05abf4a2ff | ||
|
|
d40000d4b9 | ||
|
|
4620924105 | ||
|
|
a05fe7bf0a | ||
|
|
dd347e96b0 | ||
|
|
a972f400c6 | ||
|
|
fb7664a764 | ||
|
|
7d5d7d9085 | ||
|
|
9911c11e9c | ||
|
|
0d5a68869d | ||
|
|
d9d79e930d | ||
|
|
d953675085 | ||
|
|
dbc8f9faca | ||
|
|
96998ae570 | ||
|
|
7086fb6dda | ||
|
|
1ad7809fde | ||
|
|
142c46228b | ||
|
|
ba771f8ff1 | ||
|
|
f3cf891d4f | ||
|
|
ba71964b16 | ||
|
|
97ada59175 | ||
|
|
b41ccc5992 | ||
|
|
e23f421976 | ||
|
|
dc5b8dd716 | ||
|
|
a40d8b0684 | ||
|
|
cb39b5836f | ||
|
|
4988f2a4da | ||
|
|
7ca89a85ab | ||
|
|
1ac2384750 | ||
|
|
0e040ec5b4 | ||
|
|
4bc1b78a91 | ||
|
|
148afcf1a7 | ||
|
|
1cd1b17f70 | ||
|
|
59c110688d | ||
|
|
6a25323467 | ||
|
|
6aeb3dfb1c | ||
|
|
0cb61b9a9c | ||
|
|
10d263395c | ||
|
|
d65801f258 | ||
|
|
eaac0ba8de | ||
|
|
b449f2f39e | ||
|
|
c57b0b8f90 | ||
|
|
696406b887 | ||
|
|
cc1779936e | ||
|
|
de4924a274 | ||
|
|
2b08e0510c | ||
|
|
afac48a8ea | ||
|
|
a50b604dc9 | ||
|
|
eda44b6ae8 | ||
|
|
284331b7b1 | ||
|
|
66be213346 | ||
|
|
92c8899f47 | ||
|
|
238c830f17 | ||
|
|
ace125f189 | ||
|
|
a5e5f04bcf | ||
|
|
3bc40d9eaf | ||
|
|
133c40ac1c | ||
|
|
eaaf87cdeb | ||
|
|
6dbcfb5e5d | ||
|
|
16a5f30b0c | ||
|
|
4e6d800877 | ||
|
|
af19addf47 | ||
|
|
ebc425b797 | ||
|
|
b6bedcd522 | ||
|
|
12060c9c0c | ||
|
|
e575bf8317 | ||
|
|
0fe84b225c | ||
|
|
33af0745a0 | ||
|
|
1d0265a77e | ||
|
|
03fe036204 | ||
|
|
03d073a884 | ||
|
|
64ab00e8e3 | ||
|
|
d113e1352c | ||
|
|
32e3116ee3 | ||
|
|
1dd18e2329 | ||
|
|
44b2389f9c | ||
|
|
8bc34c58f4 | ||
|
|
5756627904 | ||
|
|
cddf3875cf | ||
|
|
9be17a2d28 | ||
|
|
b8a86e2135 | ||
|
|
072db116c3 | ||
|
|
cacd5dc91a | ||
|
|
3736dacf1e | ||
|
|
434973c206 | ||
|
|
84f9863b63 | ||
|
|
99a7e6600d | ||
|
|
ea7dab3d26 | ||
|
|
d7d6eccce6 | ||
|
|
0a5a26385d | ||
|
|
62e59837c6 | ||
|
|
981d7dab13 | ||
|
|
d9a732a273 | ||
|
|
b6f1bce695 | ||
|
|
0988c4148f | ||
|
|
165133b91b | ||
|
|
cd8081c567 | ||
|
|
e40a089086 | ||
|
|
d9780fb2a6 | ||
|
|
2c8ae994cf | ||
|
|
67a046b554 | ||
|
|
a019a1f59f | ||
|
|
aed312f3c0 | ||
|
|
58138fd56c | ||
|
|
f2588b238f | ||
|
|
cc5ae722a2 | ||
|
|
1ee61709d9 | ||
|
|
dd117ce9cf | ||
|
|
3c0dc8435e | ||
|
|
fde05ccb28 | ||
|
|
464ed51728 | ||
|
|
413ee919e6 | ||
|
|
35b9568657 | ||
|
|
167d76b46d | ||
|
|
ab9eeff500 | ||
|
|
eab904af64 | ||
|
|
ae87114282 | ||
|
|
7e0ac77139 | ||
|
|
696da4efee | ||
|
|
ceab564429 | ||
|
|
4bd8025c5b | ||
|
|
f3369f8e81 | ||
|
|
c9b05ae07e | ||
|
|
32a59dbc27 | ||
|
|
ba0dff2d61 | ||
|
|
10da5e0424 | ||
|
|
4bed34090f | ||
|
|
2bfecf9354 | ||
|
|
6d129e0264 | ||
|
|
a2df1bb164 | ||
|
|
5f02e623f5 | ||
|
|
963b52fb1b | ||
|
|
02265d0bfe | ||
|
|
2e57e91826 | ||
|
|
82c642d3f4 | ||
|
|
b2571883ca | ||
|
|
00ff50c2cc | ||
|
|
4d7fa08b0b | ||
|
|
367afb544c | ||
|
|
43b8c7f641 | ||
|
|
a2dcb0079a | ||
|
|
f9619328f2 | ||
|
|
bae061a67e | ||
|
|
0b176e17ac | ||
|
|
6340e24c17 | ||
|
|
74e0676617 | ||
|
|
0defb7522f | ||
|
|
0c786ca849 | ||
|
|
26c541b9cb | ||
|
|
ade6f9ee46 | ||
|
|
f4502171ea | ||
|
|
8157e2118d | ||
|
|
e52dace416 | ||
|
|
dc260f196a | ||
|
|
559726112c | ||
|
|
a5fcf24c04 | ||
|
|
fc9b3ffdc1 | ||
|
|
e71c505e94 | ||
|
|
21c49009c0 | ||
|
|
69d355eb4b | ||
|
|
83f88d177f | ||
|
|
641ebf1667 | ||
|
|
cf435bfcc1 | ||
|
|
28f1b15b8e | ||
|
|
42413dc294 | ||
|
|
ec7ac43948 | ||
|
|
deefc1a8eb | ||
|
|
036328f1ea | ||
|
|
85057a623d | ||
|
|
1c544a26be | ||
|
|
20a61ce43e | ||
|
|
dd294e8cd6 | ||
|
|
3e9d0161bc | ||
|
|
cf6c349118 | ||
|
|
c7a0ec428c | ||
|
|
ce1c02f4f9 | ||
|
|
c3756a8f1c | ||
|
|
f4fd735aee | ||
|
|
683d793719 | ||
|
|
affbcb5698 | ||
|
|
f0d1722bbd | ||
|
|
c4f8eca459 | ||
|
|
251c071418 | ||
|
|
6652c4e445 | ||
|
|
f73613dff0 |
10
.codecov.yml
10
.codecov.yml
@@ -1,3 +1,9 @@
|
||||
comment: false
|
||||
comment:
|
||||
layout: "flags, files"
|
||||
behavior: once
|
||||
require_changes: true
|
||||
ignore:
|
||||
- "tools"
|
||||
- "tools"
|
||||
- "**/mock"
|
||||
- "**/*_mock.go"
|
||||
- "**/*test"
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
|
||||
ethereum: 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan
|
||||
ethereum: # 0x5052b7f6B937B02563996D23feb69b38D06Ca150 | kevwan
|
||||
|
||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -9,3 +9,7 @@ updates:
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/tools/goctl" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
16
.github/workflows/go.yml
vendored
16
.github/workflows/go.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ^1.16
|
||||
go-version: 1.18
|
||||
check-latest: true
|
||||
cache: true
|
||||
id: go
|
||||
@@ -29,8 +29,12 @@ jobs:
|
||||
- name: Lint
|
||||
run: |
|
||||
go vet -stdmethods=false $(go list ./...)
|
||||
go install mvdan.cc/gofumpt@latest
|
||||
test -z "$(gofumpt -l -extra .)" || echo "Please run 'gofumpt -l -w -extra .'"
|
||||
|
||||
go mod tidy
|
||||
if ! test -z "$(git status --porcelain)"; then
|
||||
echo "Please run 'go mod tidy'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test
|
||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
@@ -48,8 +52,8 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
# use 1.16 to guarantee Go 1.16 compatibility
|
||||
go-version: 1.16
|
||||
# use 1.18 to guarantee Go 1.18 compatibility
|
||||
go-version: 1.18
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
@@ -57,5 +61,5 @@ jobs:
|
||||
run: |
|
||||
go mod verify
|
||||
go mod download
|
||||
go test -v -race ./...
|
||||
go test ./...
|
||||
cd tools/goctl && go build -v goctl.go
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
goversion: "https://dl.google.com/go/go1.17.5.linux-amd64.tar.gz"
|
||||
goversion: "https://dl.google.com/go/go1.18.10.linux-amd64.tar.gz"
|
||||
project_path: "tools/goctl"
|
||||
binary_name: "goctl"
|
||||
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,12 +11,14 @@
|
||||
!api
|
||||
|
||||
# ignore
|
||||
.idea
|
||||
**/.idea
|
||||
**/.vscode
|
||||
**/.DS_Store
|
||||
**/logs
|
||||
**/adhoc
|
||||
**/coverage.txt
|
||||
|
||||
# for test purpose
|
||||
**/adhoc
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
|
||||
28
ROADMAP.md
28
ROADMAP.md
@@ -1,28 +0,0 @@
|
||||
# go-zero Roadmap
|
||||
|
||||
This document defines a high level roadmap for go-zero development and upcoming releases.
|
||||
Community and contributor involvement is vital for successfully implementing all desired items for each release.
|
||||
We hope that the items listed below will inspire further engagement from the community to keep go-zero progressing and shipping exciting and valuable features.
|
||||
|
||||
## 2021 Q2
|
||||
- [x] Support service discovery through K8S client api
|
||||
- [x] Log full sql statements for easier sql problem solving
|
||||
|
||||
## 2021 Q3
|
||||
- [x] Support `goctl model pg` to support PostgreSQL code generation
|
||||
- [x] Adapt builtin tracing mechanism to opentracing solutions
|
||||
|
||||
## 2021 Q4
|
||||
- [x] Support `username/password` authentication in ETCD
|
||||
- [x] Support `SSL/TLS` in ETCD
|
||||
- [x] Support `SSL/TLS` in `zRPC`
|
||||
- [x] Support `TLS` in redis connections
|
||||
- [x] Support `goctl bug` to report bugs conveniently
|
||||
|
||||
## 2022
|
||||
- [x] Support `context` in redis related methods for timeout and tracing
|
||||
- [x] Support `context` in sql related methods for timeout and tracing
|
||||
- [x] Support `context` in mongodb related methods for timeout and tracing
|
||||
- [x] Add `httpc.Do` with HTTP call governance, like circuit breaker etc.
|
||||
- [ ] Support `goctl doctor` command to report potential issues for given service
|
||||
- [ ] Support `goctl mock` command to start a mocking server with given `.api` file
|
||||
@@ -1,6 +1,7 @@
|
||||
package bloom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
@@ -8,28 +9,29 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
|
||||
// maps as k in the error rate table
|
||||
maps = 14
|
||||
setScript = `
|
||||
// for detailed error rate table, see http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
|
||||
// maps as k in the error rate table
|
||||
const maps = 14
|
||||
|
||||
var (
|
||||
// ErrTooLargeOffset indicates the offset is too large in bitset.
|
||||
ErrTooLargeOffset = errors.New("too large offset")
|
||||
|
||||
setScript = redis.NewScript(`
|
||||
for _, offset in ipairs(ARGV) do
|
||||
redis.call("setbit", KEYS[1], offset, 1)
|
||||
end
|
||||
`
|
||||
testScript = `
|
||||
`)
|
||||
testScript = redis.NewScript(`
|
||||
for _, offset in ipairs(ARGV) do
|
||||
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
`
|
||||
`)
|
||||
)
|
||||
|
||||
// ErrTooLargeOffset indicates the offset is too large in bitset.
|
||||
var ErrTooLargeOffset = errors.New("too large offset")
|
||||
|
||||
type (
|
||||
// A Filter is a bloom filter.
|
||||
Filter struct {
|
||||
@@ -38,8 +40,8 @@ type (
|
||||
}
|
||||
|
||||
bitSetProvider interface {
|
||||
check([]uint) (bool, error)
|
||||
set([]uint) error
|
||||
check(ctx context.Context, offsets []uint) (bool, error)
|
||||
set(ctx context.Context, offsets []uint) error
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,14 +60,24 @@ func New(store *redis.Redis, key string, bits uint) *Filter {
|
||||
|
||||
// Add adds data into f.
|
||||
func (f *Filter) Add(data []byte) error {
|
||||
return f.AddCtx(context.Background(), data)
|
||||
}
|
||||
|
||||
// AddCtx adds data into f with context.
|
||||
func (f *Filter) AddCtx(ctx context.Context, data []byte) error {
|
||||
locations := f.getLocations(data)
|
||||
return f.bitSet.set(locations)
|
||||
return f.bitSet.set(ctx, locations)
|
||||
}
|
||||
|
||||
// Exists checks if data is in f.
|
||||
func (f *Filter) Exists(data []byte) (bool, error) {
|
||||
return f.ExistsCtx(context.Background(), data)
|
||||
}
|
||||
|
||||
// ExistsCtx checks if data is in f with context.
|
||||
func (f *Filter) ExistsCtx(ctx context.Context, data []byte) (bool, error) {
|
||||
locations := f.getLocations(data)
|
||||
isSet, err := f.bitSet.check(locations)
|
||||
isSet, err := f.bitSet.check(ctx, locations)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -111,13 +123,13 @@ func (r *redisBitSet) buildOffsetArgs(offsets []uint) ([]string, error) {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (r *redisBitSet) check(offsets []uint) (bool, error) {
|
||||
func (r *redisBitSet) check(ctx context.Context, offsets []uint) (bool, error) {
|
||||
args, err := r.buildOffsetArgs(offsets)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
resp, err := r.store.Eval(testScript, []string{r.key}, args)
|
||||
resp, err := r.store.ScriptRunCtx(ctx, testScript, []string{r.key}, args)
|
||||
if err == redis.Nil {
|
||||
return false, nil
|
||||
} else if err != nil {
|
||||
@@ -132,22 +144,24 @@ func (r *redisBitSet) check(offsets []uint) (bool, error) {
|
||||
return exists == 1, nil
|
||||
}
|
||||
|
||||
// del only use for testing.
|
||||
func (r *redisBitSet) del() error {
|
||||
_, err := r.store.Del(r.key)
|
||||
return err
|
||||
}
|
||||
|
||||
// expire only use for testing.
|
||||
func (r *redisBitSet) expire(seconds int) error {
|
||||
return r.store.Expire(r.key, seconds)
|
||||
}
|
||||
|
||||
func (r *redisBitSet) set(offsets []uint) error {
|
||||
func (r *redisBitSet) set(ctx context.Context, offsets []uint) error {
|
||||
args, err := r.buildOffsetArgs(offsets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = r.store.Eval(setScript, []string{r.key}, args)
|
||||
_, err = r.store.ScriptRunCtx(ctx, setScript, []string{r.key}, args)
|
||||
if err == redis.Nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
package bloom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
||||
)
|
||||
|
||||
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
store := redistest.CreateRedis(t)
|
||||
ctx := context.Background()
|
||||
|
||||
bitSet := newRedisBitSet(store, "test_key", 1024)
|
||||
isSetBefore, err := bitSet.check([]uint{0})
|
||||
isSetBefore, err := bitSet.check(ctx, []uint{0})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if isSetBefore {
|
||||
t.Fatal("Bit should not be set")
|
||||
}
|
||||
err = bitSet.set([]uint{512})
|
||||
err = bitSet.set(ctx, []uint{512})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
isSetAfter, err := bitSet.check([]uint{512})
|
||||
isSetAfter, err := bitSet.check(ctx, []uint{512})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -42,9 +43,7 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRedisBitSet_Add(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
store := redistest.CreateRedis(t)
|
||||
|
||||
filter := New(store, "test_key", 64)
|
||||
assert.Nil(t, filter.Add([]byte("hello")))
|
||||
@@ -53,3 +52,51 @@ func TestRedisBitSet_Add(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func TestFilter_Exists(t *testing.T) {
|
||||
store, clean := redistest.CreateRedisWithClean(t)
|
||||
|
||||
rbs := New(store, "test", 64)
|
||||
_, err := rbs.Exists([]byte{0, 1, 2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
clean()
|
||||
rbs = New(store, "test", 64)
|
||||
_, err = rbs.Exists([]byte{0, 1, 2})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRedisBitSet_check(t *testing.T) {
|
||||
store, clean := redistest.CreateRedisWithClean(t)
|
||||
ctx := context.Background()
|
||||
|
||||
rbs := newRedisBitSet(store, "test", 0)
|
||||
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||
_, err := rbs.check(ctx, []uint{0, 1, 2})
|
||||
assert.Error(t, err)
|
||||
|
||||
rbs = newRedisBitSet(store, "test", 64)
|
||||
_, err = rbs.check(ctx, []uint{0, 1, 2})
|
||||
assert.NoError(t, err)
|
||||
|
||||
clean()
|
||||
rbs = newRedisBitSet(store, "test", 64)
|
||||
_, err = rbs.check(ctx, []uint{0, 1, 2})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRedisBitSet_set(t *testing.T) {
|
||||
logx.Disable()
|
||||
store, clean := redistest.CreateRedisWithClean(t)
|
||||
ctx := context.Background()
|
||||
|
||||
rbs := newRedisBitSet(store, "test", 0)
|
||||
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||
|
||||
rbs = newRedisBitSet(store, "test", 64)
|
||||
assert.NoError(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||
|
||||
clean()
|
||||
rbs = newRedisBitSet(store, "test", 64)
|
||||
assert.Error(t, rbs.set(ctx, []uint{0, 1, 2}))
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbEncrypter)(newECB(b))
|
||||
}
|
||||
|
||||
// BlockSize returns the mode's block size.
|
||||
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
|
||||
|
||||
// why we don't return error is because cipher.BlockMode doesn't allow this
|
||||
// CryptBlocks encrypts a number of blocks. The length of src must be a multiple of
|
||||
// the block size. Dst and src must overlap entirely or not at all.
|
||||
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%x.blockSize != 0 {
|
||||
logx.Error("crypto/cipher: input not full blocks")
|
||||
@@ -59,11 +61,13 @@ func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
|
||||
return (*ecbDecrypter)(newECB(b))
|
||||
}
|
||||
|
||||
// BlockSize returns the mode's block size.
|
||||
func (x *ecbDecrypter) BlockSize() int {
|
||||
return x.blockSize
|
||||
}
|
||||
|
||||
// why we don't return error is because cipher.BlockMode doesn't allow this
|
||||
// CryptBlocks decrypts a number of blocks. The length of src must be a multiple of
|
||||
// the block size. Dst and src must overlap entirely or not at all.
|
||||
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
|
||||
if len(src)%x.blockSize != 0 {
|
||||
logx.Error("crypto/cipher: input not full blocks")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
@@ -10,7 +11,8 @@ import (
|
||||
func TestAesEcb(t *testing.T) {
|
||||
var (
|
||||
key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
|
||||
val = []byte("hello")
|
||||
val = []byte("helloworld")
|
||||
valLong = []byte("helloworldlong..")
|
||||
badKey1 = []byte("aaaaaaaaa")
|
||||
// more than 32 chars
|
||||
badKey2 = []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
@@ -31,6 +33,39 @@ func TestAesEcb(t *testing.T) {
|
||||
src, err := EcbDecrypt(key, dst)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, val, src)
|
||||
block, err := aes.NewCipher(key)
|
||||
assert.NoError(t, err)
|
||||
encrypter := NewECBEncrypter(block)
|
||||
assert.Equal(t, 16, encrypter.BlockSize())
|
||||
decrypter := NewECBDecrypter(block)
|
||||
assert.Equal(t, 16, decrypter.BlockSize())
|
||||
|
||||
dst = make([]byte, 8)
|
||||
encrypter.CryptBlocks(dst, val)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
dst = make([]byte, 8)
|
||||
encrypter.CryptBlocks(dst, valLong)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
dst = make([]byte, 8)
|
||||
decrypter.CryptBlocks(dst, val)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
dst = make([]byte, 8)
|
||||
decrypter.CryptBlocks(dst, valLong)
|
||||
for _, b := range dst {
|
||||
assert.Equal(t, byte(0), b)
|
||||
}
|
||||
|
||||
_, err = EcbEncryptBase64("cTR0N3dDKkYtSmFOZFJnVWpYbjJyNXU4eC9BP0QK", "aGVsbG93b3JsZGxvbmcuLgo=")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAesEcbBase64(t *testing.T) {
|
||||
|
||||
@@ -80,3 +80,17 @@ func TestKeyBytes(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, len(key.Bytes()) > 0)
|
||||
}
|
||||
|
||||
func TestDHOnErrors(t *testing.T) {
|
||||
key, err := GenerateKey()
|
||||
assert.Nil(t, err)
|
||||
assert.NotEmpty(t, key.Bytes())
|
||||
_, err = ComputeKey(key.PubKey, key.PriKey)
|
||||
assert.NoError(t, err)
|
||||
_, err = ComputeKey(nil, key.PriKey)
|
||||
assert.Error(t, err)
|
||||
_, err = ComputeKey(key.PubKey, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.NotNil(t, NewPublicKey([]byte("")))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package codec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -21,3 +23,45 @@ func TestGzip(t *testing.T) {
|
||||
assert.True(t, len(bs) < buf.Len())
|
||||
assert.Equal(t, buf.Bytes(), actual)
|
||||
}
|
||||
|
||||
func TestGunzip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected []byte
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "valid input",
|
||||
input: func() []byte {
|
||||
var buf bytes.Buffer
|
||||
gz := gzip.NewWriter(&buf)
|
||||
gz.Write([]byte("hello"))
|
||||
gz.Close()
|
||||
return buf.Bytes()
|
||||
}(),
|
||||
expected: []byte("hello"),
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid input",
|
||||
input: []byte("invalid input"),
|
||||
expected: nil,
|
||||
expectedErr: gzip.ErrHeader,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := Gunzip(test.input)
|
||||
|
||||
if !bytes.Equal(result, test.expected) {
|
||||
t.Errorf("unexpected result: %v", result)
|
||||
}
|
||||
|
||||
if !errors.Is(err, test.expectedErr) {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package codec
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -41,6 +42,7 @@ func TestCryption(t *testing.T) {
|
||||
|
||||
file, err := fs.TempFilenameWithText(priKey)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(file)
|
||||
dec, err := NewRsaDecrypter(file)
|
||||
assert.Nil(t, err)
|
||||
actual, err := dec.Decrypt(ret)
|
||||
|
||||
@@ -30,7 +30,7 @@ type (
|
||||
Cache struct {
|
||||
name string
|
||||
lock sync.Mutex
|
||||
data map[string]interface{}
|
||||
data map[string]any
|
||||
expire time.Duration
|
||||
timingWheel *TimingWheel
|
||||
lruCache lru
|
||||
@@ -43,7 +43,7 @@ type (
|
||||
// NewCache returns a Cache with given expire.
|
||||
func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
|
||||
cache := &Cache{
|
||||
data: make(map[string]interface{}),
|
||||
data: make(map[string]any),
|
||||
expire: expire,
|
||||
lruCache: emptyLruCache,
|
||||
barrier: syncx.NewSingleFlight(),
|
||||
@@ -59,7 +59,7 @@ func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
|
||||
}
|
||||
cache.stats = newCacheStat(cache.name, cache.size)
|
||||
|
||||
timingWheel, err := NewTimingWheel(time.Second, slots, func(k, v interface{}) {
|
||||
timingWheel, err := NewTimingWheel(time.Second, slots, func(k, v any) {
|
||||
key, ok := k.(string)
|
||||
if !ok {
|
||||
return
|
||||
@@ -85,7 +85,7 @@ func (c *Cache) Del(key string) {
|
||||
}
|
||||
|
||||
// Get returns the item with the given key from c.
|
||||
func (c *Cache) Get(key string) (interface{}, bool) {
|
||||
func (c *Cache) Get(key string) (any, bool) {
|
||||
value, ok := c.doGet(key)
|
||||
if ok {
|
||||
c.stats.IncrementHit()
|
||||
@@ -97,12 +97,12 @@ func (c *Cache) Get(key string) (interface{}, bool) {
|
||||
}
|
||||
|
||||
// Set sets value into c with key.
|
||||
func (c *Cache) Set(key string, value interface{}) {
|
||||
func (c *Cache) Set(key string, value any) {
|
||||
c.SetWithExpire(key, value, c.expire)
|
||||
}
|
||||
|
||||
// SetWithExpire sets value into c with key and expire with the given value.
|
||||
func (c *Cache) SetWithExpire(key string, value interface{}, expire time.Duration) {
|
||||
func (c *Cache) SetWithExpire(key string, value any, expire time.Duration) {
|
||||
c.lock.Lock()
|
||||
_, ok := c.data[key]
|
||||
c.data[key] = value
|
||||
@@ -120,14 +120,14 @@ func (c *Cache) SetWithExpire(key string, value interface{}, expire time.Duratio
|
||||
// Take returns the item with the given key.
|
||||
// If the item is in c, return it directly.
|
||||
// If not, use fetch method to get the item, set into c and return it.
|
||||
func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}, error) {
|
||||
func (c *Cache) Take(key string, fetch func() (any, error)) (any, error) {
|
||||
if val, ok := c.doGet(key); ok {
|
||||
c.stats.IncrementHit()
|
||||
return val, nil
|
||||
}
|
||||
|
||||
var fresh bool
|
||||
val, err := c.barrier.Do(key, func() (interface{}, error) {
|
||||
val, err := c.barrier.Do(key, func() (any, error) {
|
||||
// because O(1) on map search in memory, and fetch is an IO query
|
||||
// so we do double check, cache might be taken by another call
|
||||
if val, ok := c.doGet(key); ok {
|
||||
@@ -157,7 +157,7 @@ func (c *Cache) Take(key string, fetch func() (interface{}, error)) (interface{}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *Cache) doGet(key string) (interface{}, bool) {
|
||||
func (c *Cache) doGet(key string) (any, bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestCacheTake(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
cache.Take("first", func() (interface{}, error) {
|
||||
cache.Take("first", func() (any, error) {
|
||||
atomic.AddInt32(&count, 1)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
return "first element", nil
|
||||
@@ -76,7 +76,7 @@ func TestCacheTakeExists(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
cache.Set("first", "first element")
|
||||
cache.Take("first", func() (interface{}, error) {
|
||||
cache.Take("first", func() (any, error) {
|
||||
atomic.AddInt32(&count, 1)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
return "first element", nil
|
||||
@@ -99,7 +99,7 @@ func TestCacheTakeError(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
_, err := cache.Take("first", func() (interface{}, error) {
|
||||
_, err := cache.Take("first", func() (any, error) {
|
||||
atomic.AddInt32(&count, 1)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
return "", errDummy
|
||||
|
||||
@@ -5,7 +5,7 @@ import "sync"
|
||||
// A Queue is a FIFO queue.
|
||||
type Queue struct {
|
||||
lock sync.Mutex
|
||||
elements []interface{}
|
||||
elements []any
|
||||
size int
|
||||
head int
|
||||
tail int
|
||||
@@ -15,7 +15,7 @@ type Queue struct {
|
||||
// NewQueue returns a Queue object.
|
||||
func NewQueue(size int) *Queue {
|
||||
return &Queue{
|
||||
elements: make([]interface{}, size),
|
||||
elements: make([]any, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,12 @@ func (q *Queue) Empty() bool {
|
||||
}
|
||||
|
||||
// Put puts element into q at the last position.
|
||||
func (q *Queue) Put(element interface{}) {
|
||||
func (q *Queue) Put(element any) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
if q.head == q.tail && q.count > 0 {
|
||||
nodes := make([]interface{}, len(q.elements)+q.size)
|
||||
nodes := make([]any, len(q.elements)+q.size)
|
||||
copy(nodes, q.elements[q.head:])
|
||||
copy(nodes[len(q.elements)-q.head:], q.elements[:q.head])
|
||||
q.head = 0
|
||||
@@ -49,7 +49,7 @@ func (q *Queue) Put(element interface{}) {
|
||||
}
|
||||
|
||||
// Take takes the first element out of q if not empty.
|
||||
func (q *Queue) Take() (interface{}, bool) {
|
||||
func (q *Queue) Take() (any, bool) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import "sync"
|
||||
|
||||
// A Ring can be used as fixed size ring.
|
||||
type Ring struct {
|
||||
elements []interface{}
|
||||
elements []any
|
||||
index int
|
||||
lock sync.Mutex
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewRing returns a Ring object with the given size n.
|
||||
@@ -16,12 +16,12 @@ func NewRing(n int) *Ring {
|
||||
}
|
||||
|
||||
return &Ring{
|
||||
elements: make([]interface{}, n),
|
||||
elements: make([]any, n),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds v into r.
|
||||
func (r *Ring) Add(v interface{}) {
|
||||
func (r *Ring) Add(v any) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
@@ -30,9 +30,9 @@ func (r *Ring) Add(v interface{}) {
|
||||
}
|
||||
|
||||
// Take takes all items from r.
|
||||
func (r *Ring) Take() []interface{} {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
func (r *Ring) Take() []any {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
var size int
|
||||
var start int
|
||||
@@ -43,7 +43,7 @@ func (r *Ring) Take() []interface{} {
|
||||
size = r.index
|
||||
}
|
||||
|
||||
elements := make([]interface{}, size)
|
||||
elements := make([]any, size)
|
||||
for i := 0; i < size; i++ {
|
||||
elements[i] = r.elements[(start+i)%len(r.elements)]
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestRingLess(t *testing.T) {
|
||||
ring.Add(i)
|
||||
}
|
||||
elements := ring.Take()
|
||||
assert.ElementsMatch(t, []interface{}{0, 1, 2}, elements)
|
||||
assert.ElementsMatch(t, []any{0, 1, 2}, elements)
|
||||
}
|
||||
|
||||
func TestRingMore(t *testing.T) {
|
||||
@@ -28,7 +28,7 @@ func TestRingMore(t *testing.T) {
|
||||
ring.Add(i)
|
||||
}
|
||||
elements := ring.Take()
|
||||
assert.ElementsMatch(t, []interface{}{6, 7, 8, 9, 10}, elements)
|
||||
assert.ElementsMatch(t, []any{6, 7, 8, 9, 10}, elements)
|
||||
}
|
||||
|
||||
func TestRingAdd(t *testing.T) {
|
||||
|
||||
@@ -14,20 +14,20 @@ type SafeMap struct {
|
||||
lock sync.RWMutex
|
||||
deletionOld int
|
||||
deletionNew int
|
||||
dirtyOld map[interface{}]interface{}
|
||||
dirtyNew map[interface{}]interface{}
|
||||
dirtyOld map[any]any
|
||||
dirtyNew map[any]any
|
||||
}
|
||||
|
||||
// NewSafeMap returns a SafeMap.
|
||||
func NewSafeMap() *SafeMap {
|
||||
return &SafeMap{
|
||||
dirtyOld: make(map[interface{}]interface{}),
|
||||
dirtyNew: make(map[interface{}]interface{}),
|
||||
dirtyOld: make(map[any]any),
|
||||
dirtyNew: make(map[any]any),
|
||||
}
|
||||
}
|
||||
|
||||
// Del deletes the value with the given key from m.
|
||||
func (m *SafeMap) Del(key interface{}) {
|
||||
func (m *SafeMap) Del(key any) {
|
||||
m.lock.Lock()
|
||||
if _, ok := m.dirtyOld[key]; ok {
|
||||
delete(m.dirtyOld, key)
|
||||
@@ -42,21 +42,21 @@ func (m *SafeMap) Del(key interface{}) {
|
||||
}
|
||||
m.dirtyOld = m.dirtyNew
|
||||
m.deletionOld = m.deletionNew
|
||||
m.dirtyNew = make(map[interface{}]interface{})
|
||||
m.dirtyNew = make(map[any]any)
|
||||
m.deletionNew = 0
|
||||
}
|
||||
if m.deletionNew >= maxDeletion && len(m.dirtyNew) < copyThreshold {
|
||||
for k, v := range m.dirtyNew {
|
||||
m.dirtyOld[k] = v
|
||||
}
|
||||
m.dirtyNew = make(map[interface{}]interface{})
|
||||
m.dirtyNew = make(map[any]any)
|
||||
m.deletionNew = 0
|
||||
}
|
||||
m.lock.Unlock()
|
||||
}
|
||||
|
||||
// Get gets the value with the given key from m.
|
||||
func (m *SafeMap) Get(key interface{}) (interface{}, bool) {
|
||||
func (m *SafeMap) Get(key any) (any, bool) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
|
||||
@@ -70,7 +70,7 @@ func (m *SafeMap) Get(key interface{}) (interface{}, bool) {
|
||||
|
||||
// Range calls f sequentially for each key and value present in the map.
|
||||
// If f returns false, range stops the iteration.
|
||||
func (m *SafeMap) Range(f func(key, val interface{}) bool) {
|
||||
func (m *SafeMap) Range(f func(key, val any) bool) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
|
||||
@@ -87,7 +87,7 @@ func (m *SafeMap) Range(f func(key, val interface{}) bool) {
|
||||
}
|
||||
|
||||
// Set sets the value into m with the given key.
|
||||
func (m *SafeMap) Set(key, value interface{}) {
|
||||
func (m *SafeMap) Set(key, value any) {
|
||||
m.lock.Lock()
|
||||
if m.deletionOld <= maxDeletion {
|
||||
if _, ok := m.dirtyNew[key]; ok {
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestSafeMap_Range(t *testing.T) {
|
||||
}
|
||||
|
||||
var count int32
|
||||
m.Range(func(k, v interface{}) bool {
|
||||
m.Range(func(k, v any) bool {
|
||||
atomic.AddInt32(&count, 1)
|
||||
newMap.Set(k, v)
|
||||
return true
|
||||
|
||||
@@ -17,14 +17,14 @@ const (
|
||||
|
||||
// Set is not thread-safe, for concurrent use, make sure to use it with synchronization.
|
||||
type Set struct {
|
||||
data map[interface{}]lang.PlaceholderType
|
||||
data map[any]lang.PlaceholderType
|
||||
tp int
|
||||
}
|
||||
|
||||
// NewSet returns a managed Set, can only put the values with the same type.
|
||||
func NewSet() *Set {
|
||||
return &Set{
|
||||
data: make(map[interface{}]lang.PlaceholderType),
|
||||
data: make(map[any]lang.PlaceholderType),
|
||||
tp: untyped,
|
||||
}
|
||||
}
|
||||
@@ -32,13 +32,13 @@ func NewSet() *Set {
|
||||
// NewUnmanagedSet returns an unmanaged Set, which can put values with different types.
|
||||
func NewUnmanagedSet() *Set {
|
||||
return &Set{
|
||||
data: make(map[interface{}]lang.PlaceholderType),
|
||||
data: make(map[any]lang.PlaceholderType),
|
||||
tp: unmanaged,
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds i into s.
|
||||
func (s *Set) Add(i ...interface{}) {
|
||||
func (s *Set) Add(i ...any) {
|
||||
for _, each := range i {
|
||||
s.add(each)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ func (s *Set) AddStr(ss ...string) {
|
||||
}
|
||||
|
||||
// Contains checks if i is in s.
|
||||
func (s *Set) Contains(i interface{}) bool {
|
||||
func (s *Set) Contains(i any) bool {
|
||||
if len(s.data) == 0 {
|
||||
return false
|
||||
}
|
||||
@@ -91,8 +91,8 @@ func (s *Set) Contains(i interface{}) bool {
|
||||
}
|
||||
|
||||
// Keys returns the keys in s.
|
||||
func (s *Set) Keys() []interface{} {
|
||||
var keys []interface{}
|
||||
func (s *Set) Keys() []any {
|
||||
var keys []any
|
||||
|
||||
for key := range s.data {
|
||||
keys = append(keys, key)
|
||||
@@ -167,7 +167,7 @@ func (s *Set) KeysStr() []string {
|
||||
}
|
||||
|
||||
// Remove removes i from s.
|
||||
func (s *Set) Remove(i interface{}) {
|
||||
func (s *Set) Remove(i any) {
|
||||
s.validate(i)
|
||||
delete(s.data, i)
|
||||
}
|
||||
@@ -177,7 +177,7 @@ func (s *Set) Count() int {
|
||||
return len(s.data)
|
||||
}
|
||||
|
||||
func (s *Set) add(i interface{}) {
|
||||
func (s *Set) add(i any) {
|
||||
switch s.tp {
|
||||
case unmanaged:
|
||||
// do nothing
|
||||
@@ -189,7 +189,7 @@ func (s *Set) add(i interface{}) {
|
||||
s.data[i] = lang.Placeholder
|
||||
}
|
||||
|
||||
func (s *Set) setType(i interface{}) {
|
||||
func (s *Set) setType(i any) {
|
||||
// s.tp can only be untyped here
|
||||
switch i.(type) {
|
||||
case int:
|
||||
@@ -205,7 +205,7 @@ func (s *Set) setType(i interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set) validate(i interface{}) {
|
||||
func (s *Set) validate(i any) {
|
||||
if s.tp == unmanaged {
|
||||
return
|
||||
}
|
||||
@@ -213,23 +213,23 @@ func (s *Set) validate(i interface{}) {
|
||||
switch i.(type) {
|
||||
case int:
|
||||
if s.tp != intType {
|
||||
logx.Errorf("Error: element is int, but set contains elements with type %d", s.tp)
|
||||
logx.Errorf("element is int, but set contains elements with type %d", s.tp)
|
||||
}
|
||||
case int64:
|
||||
if s.tp != int64Type {
|
||||
logx.Errorf("Error: element is int64, but set contains elements with type %d", s.tp)
|
||||
logx.Errorf("element is int64, but set contains elements with type %d", s.tp)
|
||||
}
|
||||
case uint:
|
||||
if s.tp != uintType {
|
||||
logx.Errorf("Error: element is uint, but set contains elements with type %d", s.tp)
|
||||
logx.Errorf("element is uint, but set contains elements with type %d", s.tp)
|
||||
}
|
||||
case uint64:
|
||||
if s.tp != uint64Type {
|
||||
logx.Errorf("Error: element is uint64, but set contains elements with type %d", s.tp)
|
||||
logx.Errorf("element is uint64, but set contains elements with type %d", s.tp)
|
||||
}
|
||||
case string:
|
||||
if s.tp != stringType {
|
||||
logx.Errorf("Error: element is string, but set contains elements with type %d", s.tp)
|
||||
logx.Errorf("element is string, but set contains elements with type %d", s.tp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ func init() {
|
||||
}
|
||||
|
||||
func BenchmarkRawSet(b *testing.B) {
|
||||
m := make(map[interface{}]struct{})
|
||||
m := make(map[any]struct{})
|
||||
for i := 0; i < b.N; i++ {
|
||||
m[i] = struct{}{}
|
||||
_ = m[i]
|
||||
@@ -39,7 +39,7 @@ func BenchmarkSet(b *testing.B) {
|
||||
func TestAdd(t *testing.T) {
|
||||
// given
|
||||
set := NewUnmanagedSet()
|
||||
values := []interface{}{1, 2, 3}
|
||||
values := []any{1, 2, 3}
|
||||
|
||||
// when
|
||||
set.Add(values...)
|
||||
@@ -135,7 +135,7 @@ func TestContainsUnmanagedWithoutElements(t *testing.T) {
|
||||
func TestRemove(t *testing.T) {
|
||||
// given
|
||||
set := NewSet()
|
||||
set.Add([]interface{}{1, 2, 3}...)
|
||||
set.Add([]any{1, 2, 3}...)
|
||||
|
||||
// when
|
||||
set.Remove(2)
|
||||
@@ -147,7 +147,7 @@ func TestRemove(t *testing.T) {
|
||||
func TestCount(t *testing.T) {
|
||||
// given
|
||||
set := NewSet()
|
||||
set.Add([]interface{}{1, 2, 3}...)
|
||||
set.Add([]any{1, 2, 3}...)
|
||||
|
||||
// then
|
||||
assert.Equal(t, set.Count(), 3)
|
||||
@@ -198,5 +198,5 @@ func TestSetType(t *testing.T) {
|
||||
set.add(1)
|
||||
set.add("2")
|
||||
vals := set.Keys()
|
||||
assert.ElementsMatch(t, []interface{}{1, "2"}, vals)
|
||||
assert.ElementsMatch(t, []any{1, "2"}, vals)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ var (
|
||||
|
||||
type (
|
||||
// Execute defines the method to execute the task.
|
||||
Execute func(key, value interface{})
|
||||
Execute func(key, value any)
|
||||
|
||||
// A TimingWheel is a timing wheel object to schedule tasks.
|
||||
TimingWheel struct {
|
||||
@@ -33,14 +33,14 @@ type (
|
||||
execute Execute
|
||||
setChannel chan timingEntry
|
||||
moveChannel chan baseEntry
|
||||
removeChannel chan interface{}
|
||||
drainChannel chan func(key, value interface{})
|
||||
removeChannel chan any
|
||||
drainChannel chan func(key, value any)
|
||||
stopChannel chan lang.PlaceholderType
|
||||
}
|
||||
|
||||
timingEntry struct {
|
||||
baseEntry
|
||||
value interface{}
|
||||
value any
|
||||
circle int
|
||||
diff int
|
||||
removed bool
|
||||
@@ -48,7 +48,7 @@ type (
|
||||
|
||||
baseEntry struct {
|
||||
delay time.Duration
|
||||
key interface{}
|
||||
key any
|
||||
}
|
||||
|
||||
positionEntry struct {
|
||||
@@ -57,8 +57,8 @@ type (
|
||||
}
|
||||
|
||||
timingTask struct {
|
||||
key interface{}
|
||||
value interface{}
|
||||
key any
|
||||
value any
|
||||
}
|
||||
)
|
||||
|
||||
@@ -69,10 +69,11 @@ func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*Tim
|
||||
interval, numSlots, execute)
|
||||
}
|
||||
|
||||
return newTimingWheelWithClock(interval, numSlots, execute, timex.NewTicker(interval))
|
||||
return NewTimingWheelWithTicker(interval, numSlots, execute, timex.NewTicker(interval))
|
||||
}
|
||||
|
||||
func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execute,
|
||||
// NewTimingWheelWithTicker returns a TimingWheel with the given ticker.
|
||||
func NewTimingWheelWithTicker(interval time.Duration, numSlots int, execute Execute,
|
||||
ticker timex.Ticker) (*TimingWheel, error) {
|
||||
tw := &TimingWheel{
|
||||
interval: interval,
|
||||
@@ -84,8 +85,8 @@ func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execu
|
||||
numSlots: numSlots,
|
||||
setChannel: make(chan timingEntry),
|
||||
moveChannel: make(chan baseEntry),
|
||||
removeChannel: make(chan interface{}),
|
||||
drainChannel: make(chan func(key, value interface{})),
|
||||
removeChannel: make(chan any),
|
||||
drainChannel: make(chan func(key, value any)),
|
||||
stopChannel: make(chan lang.PlaceholderType),
|
||||
}
|
||||
|
||||
@@ -96,7 +97,7 @@ func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execu
|
||||
}
|
||||
|
||||
// Drain drains all items and executes them.
|
||||
func (tw *TimingWheel) Drain(fn func(key, value interface{})) error {
|
||||
func (tw *TimingWheel) Drain(fn func(key, value any)) error {
|
||||
select {
|
||||
case tw.drainChannel <- fn:
|
||||
return nil
|
||||
@@ -106,7 +107,7 @@ func (tw *TimingWheel) Drain(fn func(key, value interface{})) error {
|
||||
}
|
||||
|
||||
// MoveTimer moves the task with the given key to the given delay.
|
||||
func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) error {
|
||||
func (tw *TimingWheel) MoveTimer(key any, delay time.Duration) error {
|
||||
if delay <= 0 || key == nil {
|
||||
return ErrArgument
|
||||
}
|
||||
@@ -123,7 +124,7 @@ func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) error {
|
||||
}
|
||||
|
||||
// RemoveTimer removes the task with the given key.
|
||||
func (tw *TimingWheel) RemoveTimer(key interface{}) error {
|
||||
func (tw *TimingWheel) RemoveTimer(key any) error {
|
||||
if key == nil {
|
||||
return ErrArgument
|
||||
}
|
||||
@@ -137,7 +138,7 @@ func (tw *TimingWheel) RemoveTimer(key interface{}) error {
|
||||
}
|
||||
|
||||
// SetTimer sets the task value with the given key to the delay.
|
||||
func (tw *TimingWheel) SetTimer(key, value interface{}, delay time.Duration) error {
|
||||
func (tw *TimingWheel) SetTimer(key, value any, delay time.Duration) error {
|
||||
if delay <= 0 || key == nil {
|
||||
return ErrArgument
|
||||
}
|
||||
@@ -161,7 +162,7 @@ func (tw *TimingWheel) Stop() {
|
||||
close(tw.stopChannel)
|
||||
}
|
||||
|
||||
func (tw *TimingWheel) drainAll(fn func(key, value interface{})) {
|
||||
func (tw *TimingWheel) drainAll(fn func(key, value any)) {
|
||||
runner := threading.NewTaskRunner(drainWorkers)
|
||||
for _, slot := range tw.slots {
|
||||
for e := slot.Front(); e != nil; {
|
||||
@@ -231,7 +232,7 @@ func (tw *TimingWheel) onTick() {
|
||||
tw.scanAndRunTasks(l)
|
||||
}
|
||||
|
||||
func (tw *TimingWheel) removeTask(key interface{}) {
|
||||
func (tw *TimingWheel) removeTask(key any) {
|
||||
val, ok := tw.timers.Get(key)
|
||||
if !ok {
|
||||
return
|
||||
|
||||
@@ -20,13 +20,13 @@ const (
|
||||
)
|
||||
|
||||
func TestNewTimingWheel(t *testing.T) {
|
||||
_, err := NewTimingWheel(0, 10, func(key, value interface{}) {})
|
||||
_, err := NewTimingWheel(0, 10, func(key, value any) {})
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestTimingWheel_Drain(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {
|
||||
}, ticker)
|
||||
tw.SetTimer("first", 3, testStep*4)
|
||||
tw.SetTimer("second", 5, testStep*7)
|
||||
@@ -36,7 +36,7 @@ func TestTimingWheel_Drain(t *testing.T) {
|
||||
var lock sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
tw.Drain(func(key, value interface{}) {
|
||||
tw.Drain(func(key, value any) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
keys = append(keys, key.(string))
|
||||
@@ -50,19 +50,19 @@ func TestTimingWheel_Drain(t *testing.T) {
|
||||
assert.EqualValues(t, []string{"first", "second", "third"}, keys)
|
||||
assert.EqualValues(t, []int{3, 5, 7}, vals)
|
||||
var count int
|
||||
tw.Drain(func(key, value interface{}) {
|
||||
tw.Drain(func(key, value any) {
|
||||
count++
|
||||
})
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
assert.Equal(t, 0, count)
|
||||
tw.Stop()
|
||||
assert.Equal(t, ErrClosed, tw.Drain(func(key, value interface{}) {}))
|
||||
assert.Equal(t, ErrClosed, tw.Drain(func(key, value any) {}))
|
||||
}
|
||||
|
||||
func TestTimingWheel_SetTimerSoon(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -78,7 +78,7 @@ func TestTimingWheel_SetTimerSoon(t *testing.T) {
|
||||
func TestTimingWheel_SetTimerTwice(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 5, v.(int))
|
||||
@@ -96,7 +96,7 @@ func TestTimingWheel_SetTimerTwice(t *testing.T) {
|
||||
|
||||
func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {}, ticker)
|
||||
defer tw.Stop()
|
||||
assert.NotPanics(t, func() {
|
||||
tw.SetTimer("any", 3, -testStep)
|
||||
@@ -105,7 +105,7 @@ func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
|
||||
|
||||
func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {}, ticker)
|
||||
tw.Stop()
|
||||
assert.Equal(t, ErrClosed, tw.SetTimer("any", 3, testStep))
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
|
||||
func TestTimingWheel_MoveTimer(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 3, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 3, func(k, v any) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -139,7 +139,7 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
|
||||
func TestTimingWheel_MoveTimerSoon(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 3, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 3, func(k, v any) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -155,7 +155,7 @@ func TestTimingWheel_MoveTimerSoon(t *testing.T) {
|
||||
func TestTimingWheel_MoveTimerEarlier(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {
|
||||
assert.True(t, run.CompareAndSwap(false, true))
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
@@ -173,7 +173,7 @@ func TestTimingWheel_MoveTimerEarlier(t *testing.T) {
|
||||
|
||||
func TestTimingWheel_RemoveTimer(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {}, ticker)
|
||||
tw.SetTimer("any", 3, testStep)
|
||||
assert.NotPanics(t, func() {
|
||||
tw.RemoveTimer("any")
|
||||
@@ -236,7 +236,7 @@ func TestTimingWheel_SetTimer(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value any) {
|
||||
assert.Equal(t, 1, key.(int))
|
||||
assert.Equal(t, 2, value.(int))
|
||||
actual = atomic.LoadInt32(&count)
|
||||
@@ -317,7 +317,7 @@ func TestTimingWheel_SetAndMoveThenStart(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value any) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -405,7 +405,7 @@ func TestTimingWheel_SetAndMoveTwice(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value any) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -486,7 +486,7 @@ func TestTimingWheel_ElapsedAndSet(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value any) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -577,7 +577,7 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
|
||||
}
|
||||
var actual int32
|
||||
done := make(chan lang.PlaceholderType)
|
||||
tw, err := newTimingWheelWithClock(testStep, test.slots, func(key, value interface{}) {
|
||||
tw, err := NewTimingWheelWithTicker(testStep, test.slots, func(key, value any) {
|
||||
actual = atomic.LoadInt32(&count)
|
||||
close(done)
|
||||
}, ticker)
|
||||
@@ -612,7 +612,7 @@ func TestMoveAndRemoveTask(t *testing.T) {
|
||||
}
|
||||
}
|
||||
var keys []int
|
||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {
|
||||
assert.Equal(t, "any", k)
|
||||
assert.Equal(t, 3, v.(int))
|
||||
keys = append(keys, v.(int))
|
||||
@@ -632,7 +632,7 @@ func TestMoveAndRemoveTask(t *testing.T) {
|
||||
func BenchmarkTimingWheel(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
tw, _ := NewTimingWheel(time.Second, 100, func(k, v interface{}) {})
|
||||
tw, _ := NewTimingWheel(time.Second, 100, func(k, v any) {})
|
||||
for i := 0; i < b.N; i++ {
|
||||
tw.SetTimer(i, i, time.Second)
|
||||
tw.SetTimer(b.N+i, b.N+i, time.Second)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/jsonx"
|
||||
@@ -12,17 +13,36 @@ import (
|
||||
"github.com/zeromicro/go-zero/internal/encoding"
|
||||
)
|
||||
|
||||
const distanceBetweenUpperAndLower = 32
|
||||
const (
|
||||
jsonTagKey = "json"
|
||||
jsonTagSep = ','
|
||||
)
|
||||
|
||||
var loaders = map[string]func([]byte, interface{}) error{
|
||||
".json": LoadFromJsonBytes,
|
||||
".toml": LoadFromTomlBytes,
|
||||
".yaml": LoadFromYamlBytes,
|
||||
".yml": LoadFromYamlBytes,
|
||||
var (
|
||||
fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
|
||||
loaders = map[string]func([]byte, any) error{
|
||||
".json": LoadFromJsonBytes,
|
||||
".toml": LoadFromTomlBytes,
|
||||
".yaml": LoadFromYamlBytes,
|
||||
".yml": LoadFromYamlBytes,
|
||||
}
|
||||
)
|
||||
|
||||
// children and mapField should not be both filled.
|
||||
// named fields and map cannot be bound to the same field name.
|
||||
type fieldInfo struct {
|
||||
children map[string]*fieldInfo
|
||||
mapField *fieldInfo
|
||||
}
|
||||
|
||||
// FillDefault fills the default values for the given v,
|
||||
// and the premise is that the value of v must be guaranteed to be empty.
|
||||
func FillDefault(v any) error {
|
||||
return fillDefaultUnmarshaler.Unmarshal(map[string]any{}, v)
|
||||
}
|
||||
|
||||
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
|
||||
func Load(file string, v interface{}, opts ...Option) error {
|
||||
func Load(file string, v any, opts ...Option) error {
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -47,28 +67,35 @@ func Load(file string, v interface{}, opts ...Option) error {
|
||||
|
||||
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
|
||||
// Deprecated: use Load instead.
|
||||
func LoadConfig(file string, v interface{}, opts ...Option) error {
|
||||
func LoadConfig(file string, v any, opts ...Option) error {
|
||||
return Load(file, v, opts...)
|
||||
}
|
||||
|
||||
// LoadFromJsonBytes loads config into v from content json bytes.
|
||||
func LoadFromJsonBytes(content []byte, v interface{}) error {
|
||||
var m map[string]interface{}
|
||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
||||
func LoadFromJsonBytes(content []byte, v any) error {
|
||||
info, err := buildFieldsInfo(reflect.TypeOf(v), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mapping.UnmarshalJsonMap(toCamelCaseKeyMap(m), v, mapping.WithCanonicalKeyFunc(toCamelCase))
|
||||
var m map[string]any
|
||||
if err = jsonx.Unmarshal(content, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lowerCaseKeyMap := toLowerCaseKeyMap(m, info)
|
||||
|
||||
return mapping.UnmarshalJsonMap(lowerCaseKeyMap, v, mapping.WithCanonicalKeyFunc(toLowerCase))
|
||||
}
|
||||
|
||||
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
||||
// Deprecated: use LoadFromJsonBytes instead.
|
||||
func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
|
||||
func LoadConfigFromJsonBytes(content []byte, v any) error {
|
||||
return LoadFromJsonBytes(content, v)
|
||||
}
|
||||
|
||||
// LoadFromTomlBytes loads config into v from content toml bytes.
|
||||
func LoadFromTomlBytes(content []byte, v interface{}) error {
|
||||
func LoadFromTomlBytes(content []byte, v any) error {
|
||||
b, err := encoding.TomlToJson(content)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -78,7 +105,7 @@ func LoadFromTomlBytes(content []byte, v interface{}) error {
|
||||
}
|
||||
|
||||
// LoadFromYamlBytes loads config into v from content yaml bytes.
|
||||
func LoadFromYamlBytes(content []byte, v interface{}) error {
|
||||
func LoadFromYamlBytes(content []byte, v any) error {
|
||||
b, err := encoding.YamlToJson(content)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -89,64 +116,200 @@ func LoadFromYamlBytes(content []byte, v interface{}) error {
|
||||
|
||||
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
|
||||
// Deprecated: use LoadFromYamlBytes instead.
|
||||
func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
|
||||
func LoadConfigFromYamlBytes(content []byte, v any) error {
|
||||
return LoadFromYamlBytes(content, v)
|
||||
}
|
||||
|
||||
// MustLoad loads config into v from path, exits on error.
|
||||
func MustLoad(path string, v interface{}, opts ...Option) {
|
||||
func MustLoad(path string, v any, opts ...Option) {
|
||||
if err := Load(path, v, opts...); err != nil {
|
||||
log.Fatalf("error: config file %s, %s", path, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func toCamelCase(s string) string {
|
||||
var buf strings.Builder
|
||||
buf.Grow(len(s))
|
||||
var capNext bool
|
||||
boundary := true
|
||||
for _, v := range s {
|
||||
isCap := v >= 'A' && v <= 'Z'
|
||||
isLow := v >= 'a' && v <= 'z'
|
||||
if boundary && (isCap || isLow) {
|
||||
if capNext {
|
||||
if isLow {
|
||||
v -= distanceBetweenUpperAndLower
|
||||
}
|
||||
} else {
|
||||
if isCap {
|
||||
v += distanceBetweenUpperAndLower
|
||||
}
|
||||
}
|
||||
boundary = false
|
||||
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName string) error {
|
||||
if prev, ok := info.children[key]; ok {
|
||||
if child.mapField != nil {
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
if isCap || isLow {
|
||||
buf.WriteRune(v)
|
||||
capNext = false
|
||||
} else if v == ' ' || v == '\t' {
|
||||
buf.WriteRune(v)
|
||||
capNext = false
|
||||
boundary = true
|
||||
} else if v == '_' {
|
||||
capNext = true
|
||||
boundary = true
|
||||
} else {
|
||||
buf.WriteRune(v)
|
||||
capNext = true
|
||||
|
||||
if err := mergeFields(prev, key, child.children, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
info.children[key] = child
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
|
||||
switch ft.Kind() {
|
||||
case reflect.Struct:
|
||||
fields, err := buildFieldsInfo(ft, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, v := range fields.children {
|
||||
if err = addOrMergeFields(info, k, v, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := info.children[lowerCaseName]; ok {
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
info.children[lowerCaseName] = &fieldInfo{
|
||||
children: make(map[string]*fieldInfo),
|
||||
mapField: elemField,
|
||||
}
|
||||
default:
|
||||
if _, ok := info.children[lowerCaseName]; ok {
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
info.children[lowerCaseName] = &fieldInfo{
|
||||
children: make(map[string]*fieldInfo),
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func toCamelCaseInterface(v interface{}) interface{} {
|
||||
func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
|
||||
tp = mapping.Deref(tp)
|
||||
|
||||
switch tp.Kind() {
|
||||
case reflect.Struct:
|
||||
return buildStructFieldsInfo(tp, fullName)
|
||||
case reflect.Array, reflect.Slice:
|
||||
return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName)
|
||||
case reflect.Chan, reflect.Func:
|
||||
return nil, fmt.Errorf("unsupported type: %s", tp.Kind())
|
||||
default:
|
||||
return &fieldInfo{
|
||||
children: make(map[string]*fieldInfo),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
|
||||
var finfo *fieldInfo
|
||||
var err error
|
||||
|
||||
switch ft.Kind() {
|
||||
case reflect.Struct:
|
||||
finfo, err = buildFieldsInfo(ft, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
finfo, err = buildFieldsInfo(ft.Elem(), fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Map:
|
||||
elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finfo = &fieldInfo{
|
||||
children: make(map[string]*fieldInfo),
|
||||
mapField: elemInfo,
|
||||
}
|
||||
default:
|
||||
finfo, err = buildFieldsInfo(ft, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return addOrMergeFields(info, lowerCaseName, finfo, fullName)
|
||||
}
|
||||
|
||||
func buildStructFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
|
||||
info := &fieldInfo{
|
||||
children: make(map[string]*fieldInfo),
|
||||
}
|
||||
|
||||
for i := 0; i < tp.NumField(); i++ {
|
||||
field := tp.Field(i)
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
name := getTagName(field)
|
||||
lowerCaseName := toLowerCase(name)
|
||||
ft := mapping.Deref(field.Type)
|
||||
// flatten anonymous fields
|
||||
if field.Anonymous {
|
||||
if err := buildAnonymousFieldInfo(info, lowerCaseName, ft,
|
||||
getFullName(fullName, lowerCaseName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err := buildNamedFieldInfo(info, lowerCaseName, ft,
|
||||
getFullName(fullName, lowerCaseName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// getTagName get the tag name of the given field, if no tag name, use file.Name.
|
||||
// field.Name is returned on tags like `json:""` and `json:",optional"`.
|
||||
func getTagName(field reflect.StructField) string {
|
||||
if tag, ok := field.Tag.Lookup(jsonTagKey); ok {
|
||||
if pos := strings.IndexByte(tag, jsonTagSep); pos >= 0 {
|
||||
tag = tag[:pos]
|
||||
}
|
||||
|
||||
tag = strings.TrimSpace(tag)
|
||||
if len(tag) > 0 {
|
||||
return tag
|
||||
}
|
||||
}
|
||||
|
||||
return field.Name
|
||||
}
|
||||
|
||||
func mergeFields(prev *fieldInfo, key string, children map[string]*fieldInfo, fullName string) error {
|
||||
if len(prev.children) == 0 || len(children) == 0 {
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
// merge fields
|
||||
for k, v := range children {
|
||||
if _, ok := prev.children[k]; ok {
|
||||
return newConflictKeyError(fullName)
|
||||
}
|
||||
|
||||
prev.children[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func toLowerCase(s string) string {
|
||||
return strings.ToLower(s)
|
||||
}
|
||||
|
||||
func toLowerCaseInterface(v any, info *fieldInfo) any {
|
||||
switch vv := v.(type) {
|
||||
case map[string]interface{}:
|
||||
return toCamelCaseKeyMap(vv)
|
||||
case []interface{}:
|
||||
var arr []interface{}
|
||||
case map[string]any:
|
||||
return toLowerCaseKeyMap(vv, info)
|
||||
case []any:
|
||||
var arr []any
|
||||
for _, vvv := range vv {
|
||||
arr = append(arr, toCamelCaseInterface(vvv))
|
||||
arr = append(arr, toLowerCaseInterface(vvv, info))
|
||||
}
|
||||
return arr
|
||||
default:
|
||||
@@ -154,11 +317,45 @@ func toCamelCaseInterface(v interface{}) interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func toCamelCaseKeyMap(m map[string]interface{}) map[string]interface{} {
|
||||
res := make(map[string]interface{})
|
||||
func toLowerCaseKeyMap(m map[string]any, info *fieldInfo) map[string]any {
|
||||
res := make(map[string]any)
|
||||
|
||||
for k, v := range m {
|
||||
res[toCamelCase(k)] = toCamelCaseInterface(v)
|
||||
ti, ok := info.children[k]
|
||||
if ok {
|
||||
res[k] = toLowerCaseInterface(v, ti)
|
||||
continue
|
||||
}
|
||||
|
||||
lk := toLowerCase(k)
|
||||
if ti, ok = info.children[lk]; ok {
|
||||
res[lk] = toLowerCaseInterface(v, ti)
|
||||
} else if info.mapField != nil {
|
||||
res[k] = toLowerCaseInterface(v, info.mapField)
|
||||
} else {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
type conflictKeyError struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func newConflictKeyError(key string) conflictKeyError {
|
||||
return conflictKeyError{key: key}
|
||||
}
|
||||
|
||||
func (e conflictKeyError) Error() string {
|
||||
return fmt.Sprintf("conflict key %s, pay attention to anonymous fields", e.key)
|
||||
}
|
||||
|
||||
func getFullName(parent, child string) string {
|
||||
if len(parent) == 0 {
|
||||
return child
|
||||
}
|
||||
|
||||
return strings.Join([]string{parent, child}, ".")
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
// PropertyError represents a configuration error message.
|
||||
type PropertyError struct {
|
||||
error
|
||||
message string
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,7 @@ func TestPropertiesEnv(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
|
||||
os.Setenv("FOO", "2")
|
||||
defer os.Unsetenv("FOO")
|
||||
t.Setenv("FOO", "2")
|
||||
|
||||
props, err := LoadProperties(tmpfile, UseEnv())
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
```go
|
||||
type RestfulConf struct {
|
||||
ServiceName string `json:",env=SERVICE_NAME"` // read from env automatically
|
||||
Host string `json:",default=0.0.0.0"`
|
||||
Port int
|
||||
LogMode string `json:",options=[file,console]"`
|
||||
@@ -21,20 +22,20 @@ type RestfulConf struct {
|
||||
|
||||
```yaml
|
||||
# most fields are optional or have default values
|
||||
Port: 8080
|
||||
LogMode: console
|
||||
port: 8080
|
||||
logMode: console
|
||||
# you can use env settings
|
||||
MaxBytes: ${MAX_BYTES}
|
||||
maxBytes: ${MAX_BYTES}
|
||||
```
|
||||
|
||||
- toml example
|
||||
|
||||
```toml
|
||||
# most fields are optional or have default values
|
||||
Port = 8_080
|
||||
LogMode = "console"
|
||||
port = 8_080
|
||||
logMode = "console"
|
||||
# you can use env settings
|
||||
MaxBytes = "${MAX_BYTES}"
|
||||
maxBytes = "${MAX_BYTES}"
|
||||
```
|
||||
|
||||
3. Load the config from a file:
|
||||
|
||||
@@ -14,13 +14,13 @@ type contextValuer struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
func (cv contextValuer) Value(key string) (interface{}, bool) {
|
||||
func (cv contextValuer) Value(key string) (any, bool) {
|
||||
v := cv.Context.Value(key)
|
||||
return v, v != nil
|
||||
}
|
||||
|
||||
// For unmarshals ctx into v.
|
||||
func For(ctx context.Context, v interface{}) error {
|
||||
func For(ctx context.Context, v any) error {
|
||||
return unmarshaler.UnmarshalValuer(contextValuer{
|
||||
Context: ctx,
|
||||
}, v)
|
||||
|
||||
@@ -13,6 +13,7 @@ var (
|
||||
type EtcdConf struct {
|
||||
Hosts []string
|
||||
Key string
|
||||
ID int64 `json:",optional"`
|
||||
User string `json:",optional"`
|
||||
Pass string `json:",optional"`
|
||||
CertFile string `json:",optional"`
|
||||
@@ -26,6 +27,11 @@ func (c EtcdConf) HasAccount() bool {
|
||||
return len(c.User) > 0 && len(c.Pass) > 0
|
||||
}
|
||||
|
||||
// HasID returns if ID provided.
|
||||
func (c EtcdConf) HasID() bool {
|
||||
return c.ID > 0
|
||||
}
|
||||
|
||||
// HasTLS returns if TLS CertFile/CertKeyFile/CACertFile are provided.
|
||||
func (c EtcdConf) HasTLS() bool {
|
||||
return len(c.CertFile) > 0 && len(c.CertKeyFile) > 0 && len(c.CACertFile) > 0
|
||||
|
||||
@@ -80,3 +80,90 @@ func TestEtcdConf_HasAccount(t *testing.T) {
|
||||
assert.Equal(t, test.hasAccount, test.EtcdConf.HasAccount())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdConf_HasID(t *testing.T) {
|
||||
tests := []struct {
|
||||
EtcdConf
|
||||
hasServerID bool
|
||||
}{
|
||||
{
|
||||
EtcdConf: EtcdConf{
|
||||
Hosts: []string{"any"},
|
||||
ID: -1,
|
||||
},
|
||||
hasServerID: false,
|
||||
},
|
||||
{
|
||||
EtcdConf: EtcdConf{
|
||||
Hosts: []string{"any"},
|
||||
ID: 0,
|
||||
},
|
||||
hasServerID: false,
|
||||
},
|
||||
{
|
||||
EtcdConf: EtcdConf{
|
||||
Hosts: []string{"any"},
|
||||
ID: 10000,
|
||||
},
|
||||
hasServerID: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.hasServerID, test.EtcdConf.HasID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdConf_HasTLS(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
conf EtcdConf
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty config",
|
||||
conf: EtcdConf{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "missing CertFile",
|
||||
conf: EtcdConf{
|
||||
CertKeyFile: "key",
|
||||
CACertFile: "ca",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "missing CertKeyFile",
|
||||
conf: EtcdConf{
|
||||
CertFile: "cert",
|
||||
CACertFile: "ca",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "missing CACertFile",
|
||||
conf: EtcdConf{
|
||||
CertFile: "cert",
|
||||
CertKeyFile: "key",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "valid config",
|
||||
conf: EtcdConf{
|
||||
CertFile: "cert",
|
||||
CertKeyFile: "key",
|
||||
CACertFile: "ca",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.conf.HasTLS()
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,85 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
const (
|
||||
certContent = `-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUEg9GVO2oaPn+YSmiqmFIuAo10WIwDQYJKoZIhvcNAQEM
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMjNaGA8yMTIz
|
||||
MDIxNTEzMjEyM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBALplXlWsIf0O/IgnIplmiZHKGnxyfyufyE2FBRNk
|
||||
OofRqbKuPH8GNqbkvZm7N29fwTDAQ+mViAggCkDht4hOzoWJMA7KYJt8JnTSWL48
|
||||
M1lcrpc9DL2gszC/JF/FGvyANbBtLklkZPFBGdHUX14pjrT937wqPtm+SqUHSvRT
|
||||
B7bmwmm2drRcmhpVm98LSlV7uQ2EgnJgsLjBPITKUejLmVLHfgX0RwQ2xIpX9pS4
|
||||
FCe1BTacwl2gGp7Mje7y4Mfv3o0ArJW6Tuwbjx59ZXwb1KIP71b7bT04AVS8ZeYO
|
||||
UMLKKuB5UR9x9Rn6cLXOTWBpcMVyzDgrAFLZjnE9LPUolZMCAwEAAaNRME8wHwYD
|
||||
VR0jBBgwFoAUeW8w8pmhncbRgTsl48k4/7wnfx8wCQYDVR0TBAIwADALBgNVHQ8E
|
||||
BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBDAUAA4IBAQAI
|
||||
y9xaoS88CLPBsX6mxfcTAFVfGNTRW9VN9Ng1cCnUR+YGoXGM/l+qP4f7p8ocdGwK
|
||||
iYZErVTzXYIn+D27//wpY3klJk3gAnEUBT3QRkStBw7XnpbeZ2oPBK+cmDnCnZPS
|
||||
BIF1wxPX7vIgaxs5Zsdqwk3qvZ4Djr2wP7LabNWTLSBKgQoUY45Liw6pffLwcGF9
|
||||
UKlu54bvGze2SufISCR3ib+I+FLvqpvJhXToZWYb/pfI/HccuCL1oot1x8vx6DQy
|
||||
U+TYxlZsKS5mdNxAX3dqEkEMsgEi+g/tzDPXJImfeCGGBhIOXLm8SRypiuGdEbc9
|
||||
xkWYxRPegajuEZGvCqVs
|
||||
-----END CERTIFICATE-----`
|
||||
keyContent = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAumVeVawh/Q78iCcimWaJkcoafHJ/K5/ITYUFE2Q6h9Gpsq48
|
||||
fwY2puS9mbs3b1/BMMBD6ZWICCAKQOG3iE7OhYkwDspgm3wmdNJYvjwzWVyulz0M
|
||||
vaCzML8kX8Ua/IA1sG0uSWRk8UEZ0dRfXimOtP3fvCo+2b5KpQdK9FMHtubCabZ2
|
||||
tFyaGlWb3wtKVXu5DYSCcmCwuME8hMpR6MuZUsd+BfRHBDbEilf2lLgUJ7UFNpzC
|
||||
XaAansyN7vLgx+/ejQCslbpO7BuPHn1lfBvUog/vVvttPTgBVLxl5g5Qwsoq4HlR
|
||||
H3H1Gfpwtc5NYGlwxXLMOCsAUtmOcT0s9SiVkwIDAQABAoIBAD5meTJNMgO55Kjg
|
||||
ESExxpRcCIno+tHr5+6rvYtEXqPheOIsmmwb9Gfi4+Z3WpOaht5/Pz0Ppj6yGzyl
|
||||
U//6AgGKb+BDuBvVcDpjwPnOxZIBCSHwejdxeQu0scSuA97MPS0XIAvJ5FEv7ijk
|
||||
5Bht6SyGYURpECltHygoTNuGgGqmO+McCJRLE9L09lTBI6UQ/JQwWJqSr7wx6iPU
|
||||
M1Ze/srIV+7cyEPu6i0DGjS1gSQKkX68Lqn1w6oE290O+OZvleO0gZ02fLDWCZke
|
||||
aeD9+EU/Pw+rqm3H6o0szOFIpzhRp41FUdW9sybB3Yp3u7c/574E+04Z/e30LMKs
|
||||
TCtE1QECgYEA3K7KIpw0NH2HXL5C3RHcLmr204xeBfS70riBQQuVUgYdmxak2ima
|
||||
80RInskY8hRhSGTg0l+VYIH8cmjcUyqMSOELS5XfRH99r4QPiK8AguXg80T4VumY
|
||||
W3Pf+zEC2ssgP/gYthV0g0Xj5m2QxktOF9tRw5nkg739ZR4dI9lm/iECgYEA2Dnf
|
||||
uwEDGqHiQRF6/fh5BG/nGVMvrefkqx6WvTJQ3k/M/9WhxB+lr/8yH46TuS8N2b29
|
||||
FoTf3Mr9T7pr/PWkOPzoY3P56nYbKU8xSwCim9xMzhBMzj8/N9ukJvXy27/VOz56
|
||||
eQaKqnvdXNGtPJrIMDGHps2KKWlKLyAlapzjVTMCgYAA/W++tACv85g13EykfT4F
|
||||
n0k4LbsGP9DP4zABQLIMyiY72eAncmRVjwrcW36XJ2xATOONTgx3gF3HjZzfaqNy
|
||||
eD/6uNNllUTVEryXGmHgNHPL45VRnn6memCY2eFvZdXhM5W4y2PYaunY0MkDercA
|
||||
+GTngbs6tBF88KOk04bYwQKBgFl68cRgsdkmnwwQYNaTKfmVGYzYaQXNzkqmWPko
|
||||
xmCJo6tHzC7ubdG8iRCYHzfmahPuuj6EdGPZuSRyYFgJi5Ftz/nAN+84OxtIQ3zn
|
||||
YWOgskQgaLh9YfsKsQ7Sf1NDOsnOnD5TX7UXl07fEpLe9vNCvAFiU8e5Y9LGudU5
|
||||
4bYTAoGBAMdX3a3bXp4cZvXNBJ/QLVyxC6fP1Q4haCR1Od3m+T00Jth2IX2dk/fl
|
||||
p6xiJT1av5JtYabv1dFKaXOS5s1kLGGuCCSKpkvFZm826aQ2AFm0XGqEQDLeei5b
|
||||
A52Kpy/YJ+RkG4BTFtAooFq6DmA0cnoP6oPvG2h6XtDJwDTPInJb
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
caContent = `-----BEGIN CERTIFICATE-----
|
||||
MIIDbTCCAlWgAwIBAgIUBJvFoCowKich7MMfseJ+DYzzirowDQYJKoZIhvcNAQEM
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMDNaGA8yMTIz
|
||||
MDIxNTEzMjEwM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAO4to2YMYj0bxgr2FCiweSTSFuPx33zSw2x/s9Wf
|
||||
OR41bm2DFsyYT5f3sOIKlXZEdLmOKty2e3ho3yC0EyNpVHdykkkHT3aDI17quZax
|
||||
kYi/URqqtl1Z08A22txolc04hAZisg2BypGi3vql81UW1t3zyloGnJoIAeXR9uca
|
||||
ljP6Bk3bwsxoVBLi1JtHrO0hHLQaeHmKhAyrys06X0LRdn7Px48yRZlt6FaLSa8X
|
||||
YiRM0G44bVy/h6BkoQjMYGwVmCVk6zjJ9U7ZPFqdnDMNxAfR+hjDnYodqdLDMTTR
|
||||
1NPVrnEnNwFx0AMLvgt/ba/45vZCEAmSZnFXFAJJcM7ai9ECAwEAAaNTMFEwHQYD
|
||||
VR0OBBYEFHlvMPKZoZ3G0YE7JePJOP+8J38fMB8GA1UdIwQYMBaAFHlvMPKZoZ3G
|
||||
0YE7JePJOP+8J38fMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggEB
|
||||
AMX8dNulADOo9uQgBMyFb9TVra7iY0zZjzv4GY5XY7scd52n6CnfAPvYBBDnTr/O
|
||||
BgNp5jaujb4+9u/2qhV3f9n+/3WOb2CmPehBgVSzlXqHeQ9lshmgwZPeem2T+8Tm
|
||||
Nnc/xQnsUfCFszUDxpkr55+aLVM22j02RWqcZ4q7TAaVYL+kdFVMc8FoqG/0ro6A
|
||||
BjE/Qn0Nn7ciX1VUjDt8l+k7ummPJTmzdi6i6E4AwO9dzrGNgGJ4aWL8cC6xYcIX
|
||||
goVIRTFeONXSDno/oPjWHpIPt7L15heMpKBHNuzPkKx2YVqPHE5QZxWfS+Lzgx+Q
|
||||
E2oTTM0rYKOZ8p6000mhvKI=
|
||||
-----END CERTIFICATE-----`
|
||||
)
|
||||
|
||||
func TestAccount(t *testing.T) {
|
||||
endpoints := []string{
|
||||
"192.168.0.2:2379",
|
||||
@@ -32,3 +105,34 @@ func TestAccount(t *testing.T) {
|
||||
assert.Equal(t, username, account.User)
|
||||
assert.Equal(t, anotherPassword, account.Pass)
|
||||
}
|
||||
|
||||
func TestTLSMethods(t *testing.T) {
|
||||
certFile := createTempFile(t, []byte(certContent))
|
||||
defer os.Remove(certFile)
|
||||
keyFile := createTempFile(t, []byte(keyContent))
|
||||
defer os.Remove(keyFile)
|
||||
caFile := createTempFile(t, []byte(caContent))
|
||||
defer os.Remove(caFile)
|
||||
|
||||
assert.NoError(t, AddTLS([]string{"foo"}, certFile, keyFile, caFile, false))
|
||||
cfg, ok := GetTLS([]string{"foo"})
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, cfg)
|
||||
|
||||
assert.Error(t, AddTLS([]string{"bar"}, "bad-file", keyFile, caFile, false))
|
||||
assert.Error(t, AddTLS([]string{"bar"}, certFile, keyFile, "bad-file", false))
|
||||
}
|
||||
|
||||
func createTempFile(t *testing.T, body []byte) string {
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), "go-unit-*.tmp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpFile.Close()
|
||||
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return tmpFile.Name()
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func (mr *MockEtcdClientMockRecorder) Ctx() *gomock.Call {
|
||||
// Get mocks base method
|
||||
func (m *MockEtcdClient) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, key}
|
||||
varargs := []any{ctx, key}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
@@ -92,9 +92,9 @@ func (m *MockEtcdClient) Get(ctx context.Context, key string, opts ...clientv3.O
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockEtcdClientMockRecorder) Get(ctx, key interface{}, opts ...interface{}) *gomock.Call {
|
||||
func (mr *MockEtcdClientMockRecorder) Get(ctx, key any, opts ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, key}, opts...)
|
||||
varargs := append([]any{ctx, key}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockEtcdClient)(nil).Get), varargs...)
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ func (m *MockEtcdClient) Grant(ctx context.Context, ttl int64) (*clientv3.LeaseG
|
||||
}
|
||||
|
||||
// Grant indicates an expected call of Grant
|
||||
func (mr *MockEtcdClientMockRecorder) Grant(ctx, ttl interface{}) *gomock.Call {
|
||||
func (mr *MockEtcdClientMockRecorder) Grant(ctx, ttl any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Grant", reflect.TypeOf((*MockEtcdClient)(nil).Grant), ctx, ttl)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (m *MockEtcdClient) KeepAlive(ctx context.Context, id clientv3.LeaseID) (<-
|
||||
}
|
||||
|
||||
// KeepAlive indicates an expected call of KeepAlive
|
||||
func (mr *MockEtcdClientMockRecorder) KeepAlive(ctx, id interface{}) *gomock.Call {
|
||||
func (mr *MockEtcdClientMockRecorder) KeepAlive(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeepAlive", reflect.TypeOf((*MockEtcdClient)(nil).KeepAlive), ctx, id)
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func (mr *MockEtcdClientMockRecorder) KeepAlive(ctx, id interface{}) *gomock.Cal
|
||||
// Put mocks base method
|
||||
func (m *MockEtcdClient) Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, key, val}
|
||||
varargs := []any{ctx, key, val}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
@@ -142,9 +142,9 @@ func (m *MockEtcdClient) Put(ctx context.Context, key, val string, opts ...clien
|
||||
}
|
||||
|
||||
// Put indicates an expected call of Put
|
||||
func (mr *MockEtcdClientMockRecorder) Put(ctx, key, val interface{}, opts ...interface{}) *gomock.Call {
|
||||
func (mr *MockEtcdClientMockRecorder) Put(ctx, key, val any, opts ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, key, val}, opts...)
|
||||
varargs := append([]any{ctx, key, val}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Put", reflect.TypeOf((*MockEtcdClient)(nil).Put), varargs...)
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ func (m *MockEtcdClient) Revoke(ctx context.Context, id clientv3.LeaseID) (*clie
|
||||
}
|
||||
|
||||
// Revoke indicates an expected call of Revoke
|
||||
func (mr *MockEtcdClientMockRecorder) Revoke(ctx, id interface{}) *gomock.Call {
|
||||
func (mr *MockEtcdClientMockRecorder) Revoke(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Revoke", reflect.TypeOf((*MockEtcdClient)(nil).Revoke), ctx, id)
|
||||
}
|
||||
@@ -166,7 +166,7 @@ func (mr *MockEtcdClientMockRecorder) Revoke(ctx, id interface{}) *gomock.Call {
|
||||
// Watch mocks base method
|
||||
func (m *MockEtcdClient) Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{ctx, key}
|
||||
varargs := []any{ctx, key}
|
||||
for _, a := range opts {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
@@ -176,8 +176,8 @@ func (m *MockEtcdClient) Watch(ctx context.Context, key string, opts ...clientv3
|
||||
}
|
||||
|
||||
// Watch indicates an expected call of Watch
|
||||
func (mr *MockEtcdClientMockRecorder) Watch(ctx, key interface{}, opts ...interface{}) *gomock.Call {
|
||||
func (mr *MockEtcdClientMockRecorder) Watch(ctx, key any, opts ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{ctx, key}, opts...)
|
||||
varargs := append([]any{ctx, key}, opts...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockEtcdClient)(nil).Watch), varargs...)
|
||||
}
|
||||
|
||||
@@ -337,13 +337,11 @@ func (c *cluster) watchConnState(cli EtcdClient) {
|
||||
// DialClient dials an etcd cluster with given endpoints.
|
||||
func DialClient(endpoints []string) (EtcdClient, error) {
|
||||
cfg := clientv3.Config{
|
||||
Endpoints: endpoints,
|
||||
AutoSyncInterval: autoSyncInterval,
|
||||
DialTimeout: DialTimeout,
|
||||
DialKeepAliveTime: dialKeepAliveTime,
|
||||
DialKeepAliveTimeout: DialTimeout,
|
||||
RejectOldCluster: true,
|
||||
PermitWithoutStream: true,
|
||||
Endpoints: endpoints,
|
||||
AutoSyncInterval: autoSyncInterval,
|
||||
DialTimeout: DialTimeout,
|
||||
RejectOldCluster: true,
|
||||
PermitWithoutStream: true,
|
||||
}
|
||||
if account, ok := GetAccount(endpoints); ok {
|
||||
cfg.Username = account.User
|
||||
|
||||
@@ -2,8 +2,10 @@ package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
"go.etcd.io/etcd/api/v3/etcdserverpb"
|
||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"go.etcd.io/etcd/client/v3/mock/mockserver"
|
||||
)
|
||||
|
||||
var mockLock sync.Mutex
|
||||
@@ -167,7 +170,7 @@ func TestCluster_Watch(t *testing.T) {
|
||||
assert.Equal(t, "world", kv.Val)
|
||||
wg.Done()
|
||||
}).MaxTimes(1)
|
||||
listener.EXPECT().OnDelete(gomock.Any()).Do(func(_ interface{}) {
|
||||
listener.EXPECT().OnDelete(gomock.Any()).Do(func(_ any) {
|
||||
wg.Done()
|
||||
}).MaxTimes(1)
|
||||
go c.watch(cli, "any", 0)
|
||||
@@ -242,3 +245,58 @@ func TestValueOnlyContext(t *testing.T) {
|
||||
ctx.Done()
|
||||
assert.Nil(t, ctx.Err())
|
||||
}
|
||||
|
||||
func TestDialClient(t *testing.T) {
|
||||
svr, err := mockserver.StartMockServers(1)
|
||||
assert.NoError(t, err)
|
||||
svr.StartAt(0)
|
||||
|
||||
certFile := createTempFile(t, []byte(certContent))
|
||||
defer os.Remove(certFile)
|
||||
keyFile := createTempFile(t, []byte(keyContent))
|
||||
defer os.Remove(keyFile)
|
||||
caFile := createTempFile(t, []byte(caContent))
|
||||
defer os.Remove(caFile)
|
||||
|
||||
endpoints := []string{svr.Servers[0].Address}
|
||||
AddAccount(endpoints, "foo", "bar")
|
||||
assert.NoError(t, AddTLS(endpoints, certFile, keyFile, caFile, false))
|
||||
|
||||
old := DialTimeout
|
||||
DialTimeout = time.Millisecond
|
||||
defer func() {
|
||||
DialTimeout = old
|
||||
}()
|
||||
_, err = DialClient(endpoints)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRegistry_Monitor(t *testing.T) {
|
||||
svr, err := mockserver.StartMockServers(1)
|
||||
assert.NoError(t, err)
|
||||
svr.StartAt(0)
|
||||
|
||||
endpoints := []string{svr.Servers[0].Address}
|
||||
GetRegistry().lock.Lock()
|
||||
GetRegistry().clusters = map[string]*cluster{
|
||||
getClusterKey(endpoints): {
|
||||
listeners: map[string][]UpdateListener{},
|
||||
values: map[string]map[string]string{
|
||||
"foo": {
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
GetRegistry().lock.Unlock()
|
||||
assert.Error(t, GetRegistry().Monitor(endpoints, "foo", new(mockListener)))
|
||||
}
|
||||
|
||||
type mockListener struct {
|
||||
}
|
||||
|
||||
func (m *mockListener) OnAdd(_ KV) {
|
||||
}
|
||||
|
||||
func (m *mockListener) OnDelete(_ KV) {
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func (m *MocketcdConn) WaitForStateChange(ctx context.Context, sourceState conne
|
||||
}
|
||||
|
||||
// WaitForStateChange indicates an expected call of WaitForStateChange
|
||||
func (mr *MocketcdConnMockRecorder) WaitForStateChange(ctx, sourceState interface{}) *gomock.Call {
|
||||
func (mr *MocketcdConnMockRecorder) WaitForStateChange(ctx, sourceState any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForStateChange", reflect.TypeOf((*MocketcdConn)(nil).WaitForStateChange), ctx, sourceState)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (m *MockUpdateListener) OnAdd(kv KV) {
|
||||
}
|
||||
|
||||
// OnAdd indicates an expected call of OnAdd
|
||||
func (mr *MockUpdateListenerMockRecorder) OnAdd(kv interface{}) *gomock.Call {
|
||||
func (mr *MockUpdateListenerMockRecorder) OnAdd(kv any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAdd", reflect.TypeOf((*MockUpdateListener)(nil).OnAdd), kv)
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func (m *MockUpdateListener) OnDelete(kv KV) {
|
||||
}
|
||||
|
||||
// OnDelete indicates an expected call of OnDelete
|
||||
func (mr *MockUpdateListenerMockRecorder) OnDelete(kv interface{}) *gomock.Call {
|
||||
func (mr *MockUpdateListenerMockRecorder) OnDelete(kv any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDelete", reflect.TypeOf((*MockUpdateListener)(nil).OnDelete), kv)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ const (
|
||||
autoSyncInterval = time.Minute
|
||||
coolDownInterval = time.Second
|
||||
dialTimeout = 5 * time.Second
|
||||
dialKeepAliveTime = 5 * time.Second
|
||||
requestTimeout = 3 * time.Second
|
||||
endpointsSeparator = ","
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package discov
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/discov/internal"
|
||||
"github.com/zeromicro/go-zero/core/lang"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
@@ -51,12 +53,7 @@ func NewPublisher(endpoints []string, key, value string, opts ...PubOption) *Pub
|
||||
|
||||
// KeepAlive keeps key:value alive.
|
||||
func (p *Publisher) KeepAlive() error {
|
||||
cli, err := internal.GetRegistry().GetConn(p.endpoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.lease, err = p.register(cli)
|
||||
cli, err := p.doRegister()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,6 +80,43 @@ func (p *Publisher) Stop() {
|
||||
p.quit.Close()
|
||||
}
|
||||
|
||||
func (p *Publisher) doKeepAlive() error {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-p.quit.Done():
|
||||
return nil
|
||||
default:
|
||||
cli, err := p.doRegister()
|
||||
if err != nil {
|
||||
logx.Errorf("etcd publisher doRegister: %s", err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
if err := p.keepAliveAsync(cli); err != nil {
|
||||
logx.Errorf("etcd publisher keepAliveAsync: %s", err.Error())
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Publisher) doRegister() (internal.EtcdClient, error) {
|
||||
cli, err := internal.GetRegistry().GetConn(p.endpoints)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.lease, err = p.register(cli)
|
||||
return cli, err
|
||||
}
|
||||
|
||||
func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
||||
ch, err := cli.KeepAlive(cli.Ctx(), p.lease)
|
||||
if err != nil {
|
||||
@@ -95,8 +129,8 @@ func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
||||
case _, ok := <-ch:
|
||||
if !ok {
|
||||
p.revoke(cli)
|
||||
if err := p.KeepAlive(); err != nil {
|
||||
logx.Errorf("KeepAlive: %s", err.Error())
|
||||
if err := p.doKeepAlive(); err != nil {
|
||||
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -105,8 +139,8 @@ func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
||||
p.revoke(cli)
|
||||
select {
|
||||
case <-p.resumeChan:
|
||||
if err := p.KeepAlive(); err != nil {
|
||||
logx.Errorf("KeepAlive: %s", err.Error())
|
||||
if err := p.doKeepAlive(); err != nil {
|
||||
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
|
||||
}
|
||||
return
|
||||
case <-p.quit.Done():
|
||||
@@ -141,7 +175,7 @@ func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, erro
|
||||
|
||||
func (p *Publisher) revoke(cli internal.EtcdClient) {
|
||||
if _, err := cli.Revoke(cli.Ctx(), p.lease); err != nil {
|
||||
logx.Error(err)
|
||||
logx.Errorf("etcd publisher revoke: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package discov
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -13,6 +16,83 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/resolver/manual"
|
||||
)
|
||||
|
||||
const (
|
||||
certContent = `-----BEGIN CERTIFICATE-----
|
||||
MIIDazCCAlOgAwIBAgIUEg9GVO2oaPn+YSmiqmFIuAo10WIwDQYJKoZIhvcNAQEM
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMjNaGA8yMTIz
|
||||
MDIxNTEzMjEyM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBALplXlWsIf0O/IgnIplmiZHKGnxyfyufyE2FBRNk
|
||||
OofRqbKuPH8GNqbkvZm7N29fwTDAQ+mViAggCkDht4hOzoWJMA7KYJt8JnTSWL48
|
||||
M1lcrpc9DL2gszC/JF/FGvyANbBtLklkZPFBGdHUX14pjrT937wqPtm+SqUHSvRT
|
||||
B7bmwmm2drRcmhpVm98LSlV7uQ2EgnJgsLjBPITKUejLmVLHfgX0RwQ2xIpX9pS4
|
||||
FCe1BTacwl2gGp7Mje7y4Mfv3o0ArJW6Tuwbjx59ZXwb1KIP71b7bT04AVS8ZeYO
|
||||
UMLKKuB5UR9x9Rn6cLXOTWBpcMVyzDgrAFLZjnE9LPUolZMCAwEAAaNRME8wHwYD
|
||||
VR0jBBgwFoAUeW8w8pmhncbRgTsl48k4/7wnfx8wCQYDVR0TBAIwADALBgNVHQ8E
|
||||
BAMCBPAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBDAUAA4IBAQAI
|
||||
y9xaoS88CLPBsX6mxfcTAFVfGNTRW9VN9Ng1cCnUR+YGoXGM/l+qP4f7p8ocdGwK
|
||||
iYZErVTzXYIn+D27//wpY3klJk3gAnEUBT3QRkStBw7XnpbeZ2oPBK+cmDnCnZPS
|
||||
BIF1wxPX7vIgaxs5Zsdqwk3qvZ4Djr2wP7LabNWTLSBKgQoUY45Liw6pffLwcGF9
|
||||
UKlu54bvGze2SufISCR3ib+I+FLvqpvJhXToZWYb/pfI/HccuCL1oot1x8vx6DQy
|
||||
U+TYxlZsKS5mdNxAX3dqEkEMsgEi+g/tzDPXJImfeCGGBhIOXLm8SRypiuGdEbc9
|
||||
xkWYxRPegajuEZGvCqVs
|
||||
-----END CERTIFICATE-----`
|
||||
keyContent = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAumVeVawh/Q78iCcimWaJkcoafHJ/K5/ITYUFE2Q6h9Gpsq48
|
||||
fwY2puS9mbs3b1/BMMBD6ZWICCAKQOG3iE7OhYkwDspgm3wmdNJYvjwzWVyulz0M
|
||||
vaCzML8kX8Ua/IA1sG0uSWRk8UEZ0dRfXimOtP3fvCo+2b5KpQdK9FMHtubCabZ2
|
||||
tFyaGlWb3wtKVXu5DYSCcmCwuME8hMpR6MuZUsd+BfRHBDbEilf2lLgUJ7UFNpzC
|
||||
XaAansyN7vLgx+/ejQCslbpO7BuPHn1lfBvUog/vVvttPTgBVLxl5g5Qwsoq4HlR
|
||||
H3H1Gfpwtc5NYGlwxXLMOCsAUtmOcT0s9SiVkwIDAQABAoIBAD5meTJNMgO55Kjg
|
||||
ESExxpRcCIno+tHr5+6rvYtEXqPheOIsmmwb9Gfi4+Z3WpOaht5/Pz0Ppj6yGzyl
|
||||
U//6AgGKb+BDuBvVcDpjwPnOxZIBCSHwejdxeQu0scSuA97MPS0XIAvJ5FEv7ijk
|
||||
5Bht6SyGYURpECltHygoTNuGgGqmO+McCJRLE9L09lTBI6UQ/JQwWJqSr7wx6iPU
|
||||
M1Ze/srIV+7cyEPu6i0DGjS1gSQKkX68Lqn1w6oE290O+OZvleO0gZ02fLDWCZke
|
||||
aeD9+EU/Pw+rqm3H6o0szOFIpzhRp41FUdW9sybB3Yp3u7c/574E+04Z/e30LMKs
|
||||
TCtE1QECgYEA3K7KIpw0NH2HXL5C3RHcLmr204xeBfS70riBQQuVUgYdmxak2ima
|
||||
80RInskY8hRhSGTg0l+VYIH8cmjcUyqMSOELS5XfRH99r4QPiK8AguXg80T4VumY
|
||||
W3Pf+zEC2ssgP/gYthV0g0Xj5m2QxktOF9tRw5nkg739ZR4dI9lm/iECgYEA2Dnf
|
||||
uwEDGqHiQRF6/fh5BG/nGVMvrefkqx6WvTJQ3k/M/9WhxB+lr/8yH46TuS8N2b29
|
||||
FoTf3Mr9T7pr/PWkOPzoY3P56nYbKU8xSwCim9xMzhBMzj8/N9ukJvXy27/VOz56
|
||||
eQaKqnvdXNGtPJrIMDGHps2KKWlKLyAlapzjVTMCgYAA/W++tACv85g13EykfT4F
|
||||
n0k4LbsGP9DP4zABQLIMyiY72eAncmRVjwrcW36XJ2xATOONTgx3gF3HjZzfaqNy
|
||||
eD/6uNNllUTVEryXGmHgNHPL45VRnn6memCY2eFvZdXhM5W4y2PYaunY0MkDercA
|
||||
+GTngbs6tBF88KOk04bYwQKBgFl68cRgsdkmnwwQYNaTKfmVGYzYaQXNzkqmWPko
|
||||
xmCJo6tHzC7ubdG8iRCYHzfmahPuuj6EdGPZuSRyYFgJi5Ftz/nAN+84OxtIQ3zn
|
||||
YWOgskQgaLh9YfsKsQ7Sf1NDOsnOnD5TX7UXl07fEpLe9vNCvAFiU8e5Y9LGudU5
|
||||
4bYTAoGBAMdX3a3bXp4cZvXNBJ/QLVyxC6fP1Q4haCR1Od3m+T00Jth2IX2dk/fl
|
||||
p6xiJT1av5JtYabv1dFKaXOS5s1kLGGuCCSKpkvFZm826aQ2AFm0XGqEQDLeei5b
|
||||
A52Kpy/YJ+RkG4BTFtAooFq6DmA0cnoP6oPvG2h6XtDJwDTPInJb
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
caContent = `-----BEGIN CERTIFICATE-----
|
||||
MIIDbTCCAlWgAwIBAgIUBJvFoCowKich7MMfseJ+DYzzirowDQYJKoZIhvcNAQEM
|
||||
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMzAzMTExMzIxMDNaGA8yMTIz
|
||||
MDIxNTEzMjEwM1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAO4to2YMYj0bxgr2FCiweSTSFuPx33zSw2x/s9Wf
|
||||
OR41bm2DFsyYT5f3sOIKlXZEdLmOKty2e3ho3yC0EyNpVHdykkkHT3aDI17quZax
|
||||
kYi/URqqtl1Z08A22txolc04hAZisg2BypGi3vql81UW1t3zyloGnJoIAeXR9uca
|
||||
ljP6Bk3bwsxoVBLi1JtHrO0hHLQaeHmKhAyrys06X0LRdn7Px48yRZlt6FaLSa8X
|
||||
YiRM0G44bVy/h6BkoQjMYGwVmCVk6zjJ9U7ZPFqdnDMNxAfR+hjDnYodqdLDMTTR
|
||||
1NPVrnEnNwFx0AMLvgt/ba/45vZCEAmSZnFXFAJJcM7ai9ECAwEAAaNTMFEwHQYD
|
||||
VR0OBBYEFHlvMPKZoZ3G0YE7JePJOP+8J38fMB8GA1UdIwQYMBaAFHlvMPKZoZ3G
|
||||
0YE7JePJOP+8J38fMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggEB
|
||||
AMX8dNulADOo9uQgBMyFb9TVra7iY0zZjzv4GY5XY7scd52n6CnfAPvYBBDnTr/O
|
||||
BgNp5jaujb4+9u/2qhV3f9n+/3WOb2CmPehBgVSzlXqHeQ9lshmgwZPeem2T+8Tm
|
||||
Nnc/xQnsUfCFszUDxpkr55+aLVM22j02RWqcZ4q7TAaVYL+kdFVMc8FoqG/0ro6A
|
||||
BjE/Qn0Nn7ciX1VUjDt8l+k7ummPJTmzdi6i6E4AwO9dzrGNgGJ4aWL8cC6xYcIX
|
||||
goVIRTFeONXSDno/oPjWHpIPt7L15heMpKBHNuzPkKx2YVqPHE5QZxWfS+Lzgx+Q
|
||||
E2oTTM0rYKOZ8p6000mhvKI=
|
||||
-----END CERTIFICATE-----`
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -37,7 +117,7 @@ func TestPublisher_register(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestPublisher_registerWithId(t *testing.T) {
|
||||
func TestPublisher_registerWithOptions(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
const id = 2
|
||||
@@ -49,7 +129,15 @@ func TestPublisher_registerWithId(t *testing.T) {
|
||||
ID: 1,
|
||||
}, nil)
|
||||
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", id), "thevalue", gomock.Any())
|
||||
pub := NewPublisher(nil, "thekey", "thevalue", WithId(id))
|
||||
|
||||
certFile := createTempFile(t, []byte(certContent))
|
||||
defer os.Remove(certFile)
|
||||
keyFile := createTempFile(t, []byte(keyContent))
|
||||
defer os.Remove(keyFile)
|
||||
caFile := createTempFile(t, []byte(caContent))
|
||||
defer os.Remove(caFile)
|
||||
pub := NewPublisher(nil, "thekey", "thevalue", WithId(id),
|
||||
WithPubEtcdTLS(certFile, keyFile, caFile, true))
|
||||
_, err := pub.register(cli)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
@@ -125,7 +213,7 @@ func TestPublisher_keepAliveAsyncQuit(t *testing.T) {
|
||||
cli.EXPECT().KeepAlive(gomock.Any(), id)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ interface{}) {
|
||||
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ any) {
|
||||
wg.Done()
|
||||
})
|
||||
pub := NewPublisher(nil, "thekey", "thevalue")
|
||||
@@ -147,7 +235,7 @@ func TestPublisher_keepAliveAsyncPause(t *testing.T) {
|
||||
pub := NewPublisher(nil, "thekey", "thevalue")
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ interface{}) {
|
||||
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ any) {
|
||||
pub.Stop()
|
||||
wg.Done()
|
||||
})
|
||||
@@ -169,3 +257,92 @@ func TestPublisher_Resume(t *testing.T) {
|
||||
}()
|
||||
<-publisher.resumeChan
|
||||
}
|
||||
|
||||
func TestPublisher_keepAliveAsync(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
const id clientv3.LeaseID = 1
|
||||
conn := createMockConn(t)
|
||||
defer conn.Close()
|
||||
cli := internal.NewMockEtcdClient(ctrl)
|
||||
cli.EXPECT().ActiveConnection().Return(conn).AnyTimes()
|
||||
cli.EXPECT().Close()
|
||||
defer cli.Close()
|
||||
cli.ActiveConnection()
|
||||
restore := setMockClient(cli)
|
||||
defer restore()
|
||||
cli.EXPECT().Ctx().AnyTimes()
|
||||
cli.EXPECT().KeepAlive(gomock.Any(), id)
|
||||
cli.EXPECT().Grant(gomock.Any(), timeToLive).Return(&clientv3.LeaseGrantResponse{
|
||||
ID: 1,
|
||||
}, nil)
|
||||
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", int64(id)), "thevalue", gomock.Any())
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ any) {
|
||||
wg.Done()
|
||||
})
|
||||
pub := NewPublisher([]string{"the-endpoint"}, "thekey", "thevalue")
|
||||
pub.lease = id
|
||||
assert.Nil(t, pub.KeepAlive())
|
||||
pub.Stop()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func createMockConn(t *testing.T) *grpc.ClientConn {
|
||||
lis, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatalf("Error while listening. Err: %v", err)
|
||||
}
|
||||
defer lis.Close()
|
||||
lisAddr := resolver.Address{Addr: lis.Addr().String()}
|
||||
lisDone := make(chan struct{})
|
||||
dialDone := make(chan struct{})
|
||||
// 1st listener accepts the connection and then does nothing
|
||||
go func() {
|
||||
defer close(lisDone)
|
||||
conn, err := lis.Accept()
|
||||
if err != nil {
|
||||
t.Errorf("Error while accepting. Err: %v", err)
|
||||
return
|
||||
}
|
||||
framer := http2.NewFramer(conn, conn)
|
||||
if err := framer.WriteSettings(http2.Setting{}); err != nil {
|
||||
t.Errorf("Error while writing settings. Err: %v", err)
|
||||
return
|
||||
}
|
||||
<-dialDone // Close conn only after dial returns.
|
||||
}()
|
||||
|
||||
r := manual.NewBuilderWithScheme("whatever")
|
||||
r.InitialState(resolver.State{Addresses: []resolver.Address{lisAddr}})
|
||||
client, err := grpc.DialContext(context.Background(), r.Scheme()+":///test.server",
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r))
|
||||
close(dialDone)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial failed. Err: %v", err)
|
||||
}
|
||||
|
||||
timeout := time.After(1 * time.Second)
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Fatal("timed out waiting for server to finish")
|
||||
case <-lisDone:
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func createTempFile(t *testing.T, body []byte) string {
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), "go-unit-*.tmp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tmpFile.Close()
|
||||
if err = os.WriteFile(tmpFile.Name(), body, os.ModePerm); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return tmpFile.Name()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func Wrap(err error, message string) error {
|
||||
}
|
||||
|
||||
// Wrapf returns an error that wraps err with given format and args.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
func Wrapf(err error, format string, args ...any) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func NewBulkExecutor(execute Execute, opts ...BulkOption) *BulkExecutor {
|
||||
}
|
||||
|
||||
// Add adds task into be.
|
||||
func (be *BulkExecutor) Add(task interface{}) error {
|
||||
func (be *BulkExecutor) Add(task any) error {
|
||||
be.executor.Add(task)
|
||||
return nil
|
||||
}
|
||||
@@ -79,22 +79,22 @@ func newBulkOptions() bulkOptions {
|
||||
}
|
||||
|
||||
type bulkContainer struct {
|
||||
tasks []interface{}
|
||||
tasks []any
|
||||
execute Execute
|
||||
maxTasks int
|
||||
}
|
||||
|
||||
func (bc *bulkContainer) AddTask(task interface{}) bool {
|
||||
func (bc *bulkContainer) AddTask(task any) bool {
|
||||
bc.tasks = append(bc.tasks, task)
|
||||
return len(bc.tasks) >= bc.maxTasks
|
||||
}
|
||||
|
||||
func (bc *bulkContainer) Execute(tasks interface{}) {
|
||||
vals := tasks.([]interface{})
|
||||
func (bc *bulkContainer) Execute(tasks any) {
|
||||
vals := tasks.([]any)
|
||||
bc.execute(vals)
|
||||
}
|
||||
|
||||
func (bc *bulkContainer) RemoveAll() interface{} {
|
||||
func (bc *bulkContainer) RemoveAll() any {
|
||||
tasks := bc.tasks
|
||||
bc.tasks = nil
|
||||
return tasks
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestBulkExecutor(t *testing.T) {
|
||||
var values []int
|
||||
var lock sync.Mutex
|
||||
|
||||
executor := NewBulkExecutor(func(items []interface{}) {
|
||||
executor := NewBulkExecutor(func(items []any) {
|
||||
lock.Lock()
|
||||
values = append(values, len(items))
|
||||
lock.Unlock()
|
||||
@@ -40,7 +40,7 @@ func TestBulkExecutorFlushInterval(t *testing.T) {
|
||||
var wait sync.WaitGroup
|
||||
|
||||
wait.Add(1)
|
||||
executor := NewBulkExecutor(func(items []interface{}) {
|
||||
executor := NewBulkExecutor(func(items []any) {
|
||||
assert.Equal(t, size, len(items))
|
||||
wait.Done()
|
||||
}, WithBulkTasks(caches), WithBulkInterval(time.Millisecond*100))
|
||||
@@ -53,7 +53,7 @@ func TestBulkExecutorFlushInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBulkExecutorEmpty(t *testing.T) {
|
||||
NewBulkExecutor(func(items []interface{}) {
|
||||
NewBulkExecutor(func(items []any) {
|
||||
assert.Fail(t, "should not called")
|
||||
}, WithBulkTasks(10), WithBulkInterval(time.Millisecond))
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
@@ -67,7 +67,7 @@ func TestBulkExecutorFlush(t *testing.T) {
|
||||
|
||||
var wait sync.WaitGroup
|
||||
wait.Add(1)
|
||||
be := NewBulkExecutor(func(items []interface{}) {
|
||||
be := NewBulkExecutor(func(items []any) {
|
||||
assert.Equal(t, tasks, len(items))
|
||||
wait.Done()
|
||||
}, WithBulkTasks(caches), WithBulkInterval(time.Minute))
|
||||
@@ -78,11 +78,11 @@ func TestBulkExecutorFlush(t *testing.T) {
|
||||
wait.Wait()
|
||||
}
|
||||
|
||||
func TestBuldExecutorFlushSlowTasks(t *testing.T) {
|
||||
func TestBulkExecutorFlushSlowTasks(t *testing.T) {
|
||||
const total = 1500
|
||||
lock := new(sync.Mutex)
|
||||
result := make([]interface{}, 0, 10000)
|
||||
exec := NewBulkExecutor(func(tasks []interface{}) {
|
||||
result := make([]any, 0, 10000)
|
||||
exec := NewBulkExecutor(func(tasks []any) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
@@ -100,7 +100,7 @@ func TestBuldExecutorFlushSlowTasks(t *testing.T) {
|
||||
func BenchmarkBulkExecutor(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
be := NewBulkExecutor(func(tasks []interface{}) {
|
||||
be := NewBulkExecutor(func(tasks []any) {
|
||||
time.Sleep(time.Millisecond * time.Duration(len(tasks)))
|
||||
})
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -42,7 +42,7 @@ func NewChunkExecutor(execute Execute, opts ...ChunkOption) *ChunkExecutor {
|
||||
}
|
||||
|
||||
// Add adds task with given chunk size into ce.
|
||||
func (ce *ChunkExecutor) Add(task interface{}, size int) error {
|
||||
func (ce *ChunkExecutor) Add(task any, size int) error {
|
||||
ce.executor.Add(chunk{
|
||||
val: task,
|
||||
size: size,
|
||||
@@ -82,25 +82,25 @@ func newChunkOptions() chunkOptions {
|
||||
}
|
||||
|
||||
type chunkContainer struct {
|
||||
tasks []interface{}
|
||||
tasks []any
|
||||
execute Execute
|
||||
size int
|
||||
maxChunkSize int
|
||||
}
|
||||
|
||||
func (bc *chunkContainer) AddTask(task interface{}) bool {
|
||||
func (bc *chunkContainer) AddTask(task any) bool {
|
||||
ck := task.(chunk)
|
||||
bc.tasks = append(bc.tasks, ck.val)
|
||||
bc.size += ck.size
|
||||
return bc.size >= bc.maxChunkSize
|
||||
}
|
||||
|
||||
func (bc *chunkContainer) Execute(tasks interface{}) {
|
||||
vals := tasks.([]interface{})
|
||||
func (bc *chunkContainer) Execute(tasks any) {
|
||||
vals := tasks.([]any)
|
||||
bc.execute(vals)
|
||||
}
|
||||
|
||||
func (bc *chunkContainer) RemoveAll() interface{} {
|
||||
func (bc *chunkContainer) RemoveAll() any {
|
||||
tasks := bc.tasks
|
||||
bc.tasks = nil
|
||||
bc.size = 0
|
||||
@@ -108,6 +108,6 @@ func (bc *chunkContainer) RemoveAll() interface{} {
|
||||
}
|
||||
|
||||
type chunk struct {
|
||||
val interface{}
|
||||
val any
|
||||
size int
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestChunkExecutor(t *testing.T) {
|
||||
var values []int
|
||||
var lock sync.Mutex
|
||||
|
||||
executor := NewChunkExecutor(func(items []interface{}) {
|
||||
executor := NewChunkExecutor(func(items []any) {
|
||||
lock.Lock()
|
||||
values = append(values, len(items))
|
||||
lock.Unlock()
|
||||
@@ -40,7 +40,7 @@ func TestChunkExecutorFlushInterval(t *testing.T) {
|
||||
var wait sync.WaitGroup
|
||||
|
||||
wait.Add(1)
|
||||
executor := NewChunkExecutor(func(items []interface{}) {
|
||||
executor := NewChunkExecutor(func(items []any) {
|
||||
assert.Equal(t, size, len(items))
|
||||
wait.Done()
|
||||
}, WithChunkBytes(caches), WithFlushInterval(time.Millisecond*100))
|
||||
@@ -53,10 +53,11 @@ func TestChunkExecutorFlushInterval(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChunkExecutorEmpty(t *testing.T) {
|
||||
NewChunkExecutor(func(items []interface{}) {
|
||||
executor := NewChunkExecutor(func(items []any) {
|
||||
assert.Fail(t, "should not called")
|
||||
}, WithChunkBytes(10), WithFlushInterval(time.Millisecond))
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
executor.Wait()
|
||||
}
|
||||
|
||||
func TestChunkExecutorFlush(t *testing.T) {
|
||||
@@ -67,7 +68,7 @@ func TestChunkExecutorFlush(t *testing.T) {
|
||||
|
||||
var wait sync.WaitGroup
|
||||
wait.Add(1)
|
||||
be := NewChunkExecutor(func(items []interface{}) {
|
||||
be := NewChunkExecutor(func(items []any) {
|
||||
assert.Equal(t, tasks, len(items))
|
||||
wait.Done()
|
||||
}, WithChunkBytes(caches), WithFlushInterval(time.Minute))
|
||||
@@ -81,7 +82,7 @@ func TestChunkExecutorFlush(t *testing.T) {
|
||||
func BenchmarkChunkExecutor(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
be := NewChunkExecutor(func(tasks []interface{}) {
|
||||
be := NewChunkExecutor(func(tasks []any) {
|
||||
time.Sleep(time.Millisecond * time.Duration(len(tasks)))
|
||||
})
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -21,16 +21,16 @@ type (
|
||||
TaskContainer interface {
|
||||
// AddTask adds the task into the container.
|
||||
// Returns true if the container needs to be flushed after the addition.
|
||||
AddTask(task interface{}) bool
|
||||
AddTask(task any) bool
|
||||
// Execute handles the collected tasks by the container when flushing.
|
||||
Execute(tasks interface{})
|
||||
Execute(tasks any)
|
||||
// RemoveAll removes the contained tasks, and return them.
|
||||
RemoveAll() interface{}
|
||||
RemoveAll() any
|
||||
}
|
||||
|
||||
// A PeriodicalExecutor is an executor that periodically execute tasks.
|
||||
PeriodicalExecutor struct {
|
||||
commander chan interface{}
|
||||
commander chan any
|
||||
interval time.Duration
|
||||
container TaskContainer
|
||||
waitGroup sync.WaitGroup
|
||||
@@ -48,7 +48,7 @@ type (
|
||||
func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor {
|
||||
executor := &PeriodicalExecutor{
|
||||
// buffer 1 to let the caller go quickly
|
||||
commander: make(chan interface{}, 1),
|
||||
commander: make(chan any, 1),
|
||||
interval: interval,
|
||||
container: container,
|
||||
confirmChan: make(chan lang.PlaceholderType),
|
||||
@@ -64,7 +64,7 @@ func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *Per
|
||||
}
|
||||
|
||||
// Add adds tasks into pe.
|
||||
func (pe *PeriodicalExecutor) Add(task interface{}) {
|
||||
func (pe *PeriodicalExecutor) Add(task any) {
|
||||
if vals, ok := pe.addAndCheck(task); ok {
|
||||
pe.commander <- vals
|
||||
<-pe.confirmChan
|
||||
@@ -74,14 +74,14 @@ func (pe *PeriodicalExecutor) Add(task interface{}) {
|
||||
// Flush forces pe to execute tasks.
|
||||
func (pe *PeriodicalExecutor) Flush() bool {
|
||||
pe.enterExecution()
|
||||
return pe.executeTasks(func() interface{} {
|
||||
return pe.executeTasks(func() any {
|
||||
pe.lock.Lock()
|
||||
defer pe.lock.Unlock()
|
||||
return pe.container.RemoveAll()
|
||||
}())
|
||||
}
|
||||
|
||||
// Sync lets caller to run fn thread-safe with pe, especially for the underlying container.
|
||||
// Sync lets caller run fn thread-safe with pe, especially for the underlying container.
|
||||
func (pe *PeriodicalExecutor) Sync(fn func()) {
|
||||
pe.lock.Lock()
|
||||
defer pe.lock.Unlock()
|
||||
@@ -96,7 +96,7 @@ func (pe *PeriodicalExecutor) Wait() {
|
||||
})
|
||||
}
|
||||
|
||||
func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool) {
|
||||
func (pe *PeriodicalExecutor) addAndCheck(task any) (any, bool) {
|
||||
pe.lock.Lock()
|
||||
defer func() {
|
||||
if !pe.guarded {
|
||||
@@ -116,7 +116,7 @@ func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool)
|
||||
}
|
||||
|
||||
func (pe *PeriodicalExecutor) backgroundFlush() {
|
||||
threading.GoSafe(func() {
|
||||
go func() {
|
||||
// flush before quit goroutine to avoid missing tasks
|
||||
defer pe.Flush()
|
||||
|
||||
@@ -144,7 +144,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
func (pe *PeriodicalExecutor) doneExecution() {
|
||||
@@ -157,18 +157,20 @@ func (pe *PeriodicalExecutor) enterExecution() {
|
||||
})
|
||||
}
|
||||
|
||||
func (pe *PeriodicalExecutor) executeTasks(tasks interface{}) bool {
|
||||
func (pe *PeriodicalExecutor) executeTasks(tasks any) bool {
|
||||
defer pe.doneExecution()
|
||||
|
||||
ok := pe.hasTasks(tasks)
|
||||
if ok {
|
||||
pe.container.Execute(tasks)
|
||||
threading.RunSafe(func() {
|
||||
pe.container.Execute(tasks)
|
||||
})
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func (pe *PeriodicalExecutor) hasTasks(tasks interface{}) bool {
|
||||
func (pe *PeriodicalExecutor) hasTasks(tasks any) bool {
|
||||
if tasks == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/proc"
|
||||
"github.com/zeromicro/go-zero/core/timex"
|
||||
)
|
||||
|
||||
@@ -16,22 +17,22 @@ const threshold = 10
|
||||
type container struct {
|
||||
interval time.Duration
|
||||
tasks []int
|
||||
execute func(tasks interface{})
|
||||
execute func(tasks any)
|
||||
}
|
||||
|
||||
func newContainer(interval time.Duration, execute func(tasks interface{})) *container {
|
||||
func newContainer(interval time.Duration, execute func(tasks any)) *container {
|
||||
return &container{
|
||||
interval: interval,
|
||||
execute: execute,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *container) AddTask(task interface{}) bool {
|
||||
func (c *container) AddTask(task any) bool {
|
||||
c.tasks = append(c.tasks, task.(int))
|
||||
return len(c.tasks) > threshold
|
||||
}
|
||||
|
||||
func (c *container) Execute(tasks interface{}) {
|
||||
func (c *container) Execute(tasks any) {
|
||||
if c.execute != nil {
|
||||
c.execute(tasks)
|
||||
} else {
|
||||
@@ -39,7 +40,7 @@ func (c *container) Execute(tasks interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *container) RemoveAll() interface{} {
|
||||
func (c *container) RemoveAll() any {
|
||||
tasks := c.tasks
|
||||
c.tasks = nil
|
||||
return tasks
|
||||
@@ -67,6 +68,7 @@ func TestPeriodicalExecutor_QuitGoroutine(t *testing.T) {
|
||||
ticker.Tick()
|
||||
ticker.Wait(time.Millisecond * idleRound)
|
||||
assert.Equal(t, routines, runtime.NumGoroutine())
|
||||
proc.Shutdown()
|
||||
}
|
||||
|
||||
func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
||||
@@ -74,7 +76,7 @@ func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
||||
var vals []int
|
||||
// avoid data race
|
||||
var lock sync.Mutex
|
||||
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks interface{}) {
|
||||
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks any) {
|
||||
t := tasks.([]int)
|
||||
for _, each := range t {
|
||||
lock.Lock()
|
||||
@@ -106,25 +108,83 @@ func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
func TestPeriodicalExecutor_Panic(t *testing.T) {
|
||||
// avoid data race
|
||||
var lock sync.Mutex
|
||||
ticker := timex.NewFakeTicker()
|
||||
|
||||
var (
|
||||
executedTasks []int
|
||||
expected []int
|
||||
)
|
||||
executor := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks any) {
|
||||
tt := tasks.([]int)
|
||||
lock.Lock()
|
||||
executedTasks = append(executedTasks, tt...)
|
||||
lock.Unlock()
|
||||
if tt[0] == 0 {
|
||||
panic("test")
|
||||
}
|
||||
}))
|
||||
executor.newTicker = func(duration time.Duration) timex.Ticker {
|
||||
return ticker
|
||||
}
|
||||
for i := 0; i < 30; i++ {
|
||||
executor.Add(i)
|
||||
expected = append(expected, i)
|
||||
}
|
||||
ticker.Tick()
|
||||
ticker.Tick()
|
||||
time.Sleep(time.Millisecond)
|
||||
lock.Lock()
|
||||
assert.Equal(t, expected, executedTasks)
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
func TestPeriodicalExecutor_FlushPanic(t *testing.T) {
|
||||
var (
|
||||
executedTasks []int
|
||||
expected []int
|
||||
lock sync.Mutex
|
||||
)
|
||||
executor := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, func(tasks any) {
|
||||
tt := tasks.([]int)
|
||||
lock.Lock()
|
||||
executedTasks = append(executedTasks, tt...)
|
||||
lock.Unlock()
|
||||
if tt[0] == 0 {
|
||||
panic("flush panic")
|
||||
}
|
||||
}))
|
||||
for i := 0; i < 8; i++ {
|
||||
executor.Add(i)
|
||||
expected = append(expected, i)
|
||||
}
|
||||
executor.Flush()
|
||||
lock.Lock()
|
||||
assert.Equal(t, expected, executedTasks)
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
func TestPeriodicalExecutor_Wait(t *testing.T) {
|
||||
var lock sync.Mutex
|
||||
executer := NewBulkExecutor(func(tasks []interface{}) {
|
||||
executor := NewBulkExecutor(func(tasks []any) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}, WithBulkTasks(1), WithBulkInterval(time.Second))
|
||||
for i := 0; i < 10; i++ {
|
||||
executer.Add(1)
|
||||
executor.Add(1)
|
||||
}
|
||||
executer.Flush()
|
||||
executer.Wait()
|
||||
executor.Flush()
|
||||
executor.Wait()
|
||||
}
|
||||
|
||||
func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
||||
const total = 3
|
||||
var cnt int
|
||||
var lock sync.Mutex
|
||||
executer := NewBulkExecutor(func(tasks []interface{}) {
|
||||
executor := NewBulkExecutor(func(tasks []any) {
|
||||
defer func() {
|
||||
cnt++
|
||||
}()
|
||||
@@ -133,15 +193,15 @@ func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}, WithBulkTasks(1), WithBulkInterval(10*time.Millisecond))
|
||||
for i := 0; i < total; i++ {
|
||||
executer.Add(2)
|
||||
executor.Add(2)
|
||||
}
|
||||
executer.Flush()
|
||||
executer.Wait()
|
||||
executor.Flush()
|
||||
executor.Wait()
|
||||
assert.Equal(t, total, cnt)
|
||||
}
|
||||
|
||||
func TestPeriodicalExecutor_Deadlock(t *testing.T) {
|
||||
executor := NewBulkExecutor(func(tasks []interface{}) {
|
||||
executor := NewBulkExecutor(func(tasks []any) {
|
||||
}, WithBulkTasks(1), WithBulkInterval(time.Millisecond))
|
||||
for i := 0; i < 1e5; i++ {
|
||||
executor.Add(1)
|
||||
@@ -149,13 +209,7 @@ func TestPeriodicalExecutor_Deadlock(t *testing.T) {
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@ import "time"
|
||||
const defaultFlushInterval = time.Second
|
||||
|
||||
// Execute defines the method to execute tasks.
|
||||
type Execute func(tasks []interface{})
|
||||
type Execute func(tasks []any)
|
||||
|
||||
@@ -74,6 +74,11 @@ func TestFirstLineShort(t *testing.T) {
|
||||
assert.Equal(t, "first line", val)
|
||||
}
|
||||
|
||||
func TestFirstLineError(t *testing.T) {
|
||||
_, err := FirstLine("/tmp/does-not-exist")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestLastLine(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText(text)
|
||||
assert.Nil(t, err)
|
||||
@@ -113,3 +118,8 @@ func TestLastLineWithLastNewlineShort(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "last line", val)
|
||||
}
|
||||
|
||||
func TestLastLineError(t *testing.T) {
|
||||
_, err := LastLine("/tmp/does-not-exist")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build linux || darwin
|
||||
// +build linux darwin
|
||||
|
||||
package fs
|
||||
|
||||
|
||||
@@ -11,29 +11,29 @@ import (
|
||||
// The file is kept as open, the caller should close the file handle,
|
||||
// and remove the file by name.
|
||||
func TempFileWithText(text string) (*os.File, error) {
|
||||
tmpfile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text)))
|
||||
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(tmpfile.Name(), []byte(text), os.ModeTemporary); err != nil {
|
||||
if err := os.WriteFile(tmpFile.Name(), []byte(text), os.ModeTemporary); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tmpfile, nil
|
||||
return tmpFile, nil
|
||||
}
|
||||
|
||||
// TempFilenameWithText creates the file with the given content,
|
||||
// and returns the filename (full path).
|
||||
// The caller should remove the file after use.
|
||||
func TempFilenameWithText(text string) (string, error) {
|
||||
tmpfile, err := TempFileWithText(text)
|
||||
tmpFile, err := TempFileWithText(text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
filename := tmpfile.Name()
|
||||
if err = tmpfile.Close(); err != nil {
|
||||
filename := tmpFile.Name()
|
||||
if err = tmpFile.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,87 @@
|
||||
package fx
|
||||
|
||||
import "github.com/zeromicro/go-zero/core/errorx"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/errorx"
|
||||
)
|
||||
|
||||
const defaultRetryTimes = 3
|
||||
|
||||
var errTimeout = errors.New("retry timeout")
|
||||
|
||||
type (
|
||||
// RetryOption defines the method to customize DoWithRetry.
|
||||
RetryOption func(*retryOptions)
|
||||
|
||||
retryOptions struct {
|
||||
times int
|
||||
times int
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
}
|
||||
)
|
||||
|
||||
// DoWithRetry runs fn, and retries if failed. Default to retry 3 times.
|
||||
// Note that if the fn function accesses global variables outside the function
|
||||
// and performs modification operations, it is best to lock them,
|
||||
// otherwise there may be data race issues
|
||||
func DoWithRetry(fn func() error, opts ...RetryOption) error {
|
||||
return retry(func(errChan chan error, retryCount int) {
|
||||
errChan <- fn()
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// DoWithRetryCtx runs fn, and retries if failed. Default to retry 3 times.
|
||||
// fn retryCount indicates the current number of retries, starting from 0
|
||||
// Note that if the fn function accesses global variables outside the function
|
||||
// and performs modification operations, it is best to lock them,
|
||||
// otherwise there may be data race issues
|
||||
func DoWithRetryCtx(ctx context.Context, fn func(ctx context.Context, retryCount int) error,
|
||||
opts ...RetryOption) error {
|
||||
return retry(func(errChan chan error, retryCount int) {
|
||||
errChan <- fn(ctx, retryCount)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
func retry(fn func(errChan chan error, retryCount int), opts ...RetryOption) error {
|
||||
options := newRetryOptions()
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
var berr errorx.BatchError
|
||||
var cancelFunc context.CancelFunc
|
||||
ctx := context.Background()
|
||||
if options.timeout > 0 {
|
||||
ctx, cancelFunc = context.WithTimeout(ctx, options.timeout)
|
||||
defer cancelFunc()
|
||||
}
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
for i := 0; i < options.times; i++ {
|
||||
if err := fn(); err != nil {
|
||||
berr.Add(err)
|
||||
} else {
|
||||
return nil
|
||||
go fn(errChan, i)
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
berr.Add(err)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
berr.Add(errTimeout)
|
||||
return berr.Err()
|
||||
}
|
||||
|
||||
if options.interval > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
berr.Add(errTimeout)
|
||||
return berr.Err()
|
||||
case <-time.After(options.interval):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +95,18 @@ func WithRetry(times int) RetryOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithInterval(interval time.Duration) RetryOption {
|
||||
return func(options *retryOptions) {
|
||||
options.interval = interval
|
||||
}
|
||||
}
|
||||
|
||||
func WithTimeout(timeout time.Duration) RetryOption {
|
||||
return func(options *retryOptions) {
|
||||
options.timeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
func newRetryOptions() *retryOptions {
|
||||
return &retryOptions{
|
||||
times: defaultRetryTimes,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package fx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -12,31 +14,103 @@ func TestRetry(t *testing.T) {
|
||||
return errors.New("any")
|
||||
}))
|
||||
|
||||
var times int
|
||||
times1 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times++
|
||||
if times == defaultRetryTimes {
|
||||
times1++
|
||||
if times1 == defaultRetryTimes {
|
||||
return nil
|
||||
}
|
||||
return errors.New("any")
|
||||
}))
|
||||
|
||||
times = 0
|
||||
times2 := 0
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
times++
|
||||
if times == defaultRetryTimes+1 {
|
||||
times2++
|
||||
if times2 == defaultRetryTimes+1 {
|
||||
return nil
|
||||
}
|
||||
return errors.New("any")
|
||||
}))
|
||||
|
||||
total := 2 * defaultRetryTimes
|
||||
times = 0
|
||||
times3 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times++
|
||||
if times == total {
|
||||
times3++
|
||||
if times3 == total {
|
||||
return nil
|
||||
}
|
||||
return errors.New("any")
|
||||
}, WithRetry(total)))
|
||||
}
|
||||
|
||||
func TestRetryWithTimeout(t *testing.T) {
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*500)))
|
||||
|
||||
times1 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times1++
|
||||
if times1 == 1 {
|
||||
return errors.New("any ")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*250)))
|
||||
|
||||
total := defaultRetryTimes
|
||||
times2 := 0
|
||||
assert.Nil(t, DoWithRetry(func() error {
|
||||
times2++
|
||||
if times2 == total {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
return errors.New("any")
|
||||
}, WithTimeout(time.Millisecond*50*(time.Duration(total)+2))))
|
||||
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
return errors.New("any")
|
||||
}, WithTimeout(time.Millisecond*250)))
|
||||
}
|
||||
|
||||
func TestRetryWithInterval(t *testing.T) {
|
||||
times1 := 0
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
times1++
|
||||
if times1 == 1 {
|
||||
return errors.New("any")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
|
||||
times2 := 0
|
||||
assert.NotNil(t, DoWithRetry(func() error {
|
||||
times2++
|
||||
if times2 == 2 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return errors.New("any ")
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
|
||||
}
|
||||
|
||||
func TestRetryCtx(t *testing.T) {
|
||||
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
||||
if retryCount == 0 {
|
||||
return errors.New("any")
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return nil
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
|
||||
assert.NotNil(t, DoWithRetryCtx(context.Background(), func(ctx context.Context, retryCount int) error {
|
||||
if retryCount == 1 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(time.Millisecond * 150)
|
||||
return errors.New("any ")
|
||||
}, WithTimeout(time.Millisecond*250), WithInterval(time.Millisecond*150)))
|
||||
}
|
||||
|
||||
@@ -21,31 +21,31 @@ type (
|
||||
}
|
||||
|
||||
// FilterFunc defines the method to filter a Stream.
|
||||
FilterFunc func(item interface{}) bool
|
||||
FilterFunc func(item any) bool
|
||||
// ForAllFunc defines the method to handle all elements in a Stream.
|
||||
ForAllFunc func(pipe <-chan interface{})
|
||||
ForAllFunc func(pipe <-chan any)
|
||||
// ForEachFunc defines the method to handle each element in a Stream.
|
||||
ForEachFunc func(item interface{})
|
||||
ForEachFunc func(item any)
|
||||
// GenerateFunc defines the method to send elements into a Stream.
|
||||
GenerateFunc func(source chan<- interface{})
|
||||
GenerateFunc func(source chan<- any)
|
||||
// KeyFunc defines the method to generate keys for the elements in a Stream.
|
||||
KeyFunc func(item interface{}) interface{}
|
||||
KeyFunc func(item any) any
|
||||
// LessFunc defines the method to compare the elements in a Stream.
|
||||
LessFunc func(a, b interface{}) bool
|
||||
LessFunc func(a, b any) bool
|
||||
// MapFunc defines the method to map each element to another object in a Stream.
|
||||
MapFunc func(item interface{}) interface{}
|
||||
MapFunc func(item any) any
|
||||
// Option defines the method to customize a Stream.
|
||||
Option func(opts *rxOptions)
|
||||
// ParallelFunc defines the method to handle elements parallelly.
|
||||
ParallelFunc func(item interface{})
|
||||
ParallelFunc func(item any)
|
||||
// ReduceFunc defines the method to reduce all the elements in a Stream.
|
||||
ReduceFunc func(pipe <-chan interface{}) (interface{}, error)
|
||||
ReduceFunc func(pipe <-chan any) (any, error)
|
||||
// WalkFunc defines the method to walk through all the elements in a Stream.
|
||||
WalkFunc func(item interface{}, pipe chan<- interface{})
|
||||
WalkFunc func(item any, pipe chan<- any)
|
||||
|
||||
// A Stream is a stream that can be used to do stream processing.
|
||||
Stream struct {
|
||||
source <-chan interface{}
|
||||
source <-chan any
|
||||
}
|
||||
)
|
||||
|
||||
@@ -56,7 +56,7 @@ func Concat(s Stream, others ...Stream) Stream {
|
||||
|
||||
// From constructs a Stream from the given GenerateFunc.
|
||||
func From(generate GenerateFunc) Stream {
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
|
||||
threading.GoSafe(func() {
|
||||
defer close(source)
|
||||
@@ -67,8 +67,8 @@ func From(generate GenerateFunc) Stream {
|
||||
}
|
||||
|
||||
// Just converts the given arbitrary items to a Stream.
|
||||
func Just(items ...interface{}) Stream {
|
||||
source := make(chan interface{}, len(items))
|
||||
func Just(items ...any) Stream {
|
||||
source := make(chan any, len(items))
|
||||
for _, item := range items {
|
||||
source <- item
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func Just(items ...interface{}) Stream {
|
||||
}
|
||||
|
||||
// Range converts the given channel to a Stream.
|
||||
func Range(source <-chan interface{}) Stream {
|
||||
func Range(source <-chan any) Stream {
|
||||
return Stream{
|
||||
source: source,
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func Range(source <-chan interface{}) Stream {
|
||||
// AllMach returns whether all elements of this stream match the provided predicate.
|
||||
// May not evaluate the predicate on all elements if not necessary for determining the result.
|
||||
// If the stream is empty then true is returned and the predicate is not evaluated.
|
||||
func (s Stream) AllMach(predicate func(item interface{}) bool) bool {
|
||||
func (s Stream) AllMach(predicate func(item any) bool) bool {
|
||||
for item := range s.source {
|
||||
if !predicate(item) {
|
||||
// make sure the former goroutine not block, and current func returns fast.
|
||||
@@ -102,7 +102,7 @@ func (s Stream) AllMach(predicate func(item interface{}) bool) bool {
|
||||
// AnyMach returns whether any elements of this stream match the provided predicate.
|
||||
// May not evaluate the predicate on all elements if not necessary for determining the result.
|
||||
// If the stream is empty then false is returned and the predicate is not evaluated.
|
||||
func (s Stream) AnyMach(predicate func(item interface{}) bool) bool {
|
||||
func (s Stream) AnyMach(predicate func(item any) bool) bool {
|
||||
for item := range s.source {
|
||||
if predicate(item) {
|
||||
// make sure the former goroutine not block, and current func returns fast.
|
||||
@@ -121,7 +121,7 @@ func (s Stream) Buffer(n int) Stream {
|
||||
n = 0
|
||||
}
|
||||
|
||||
source := make(chan interface{}, n)
|
||||
source := make(chan any, n)
|
||||
go func() {
|
||||
for item := range s.source {
|
||||
source <- item
|
||||
@@ -134,7 +134,7 @@ func (s Stream) Buffer(n int) Stream {
|
||||
|
||||
// Concat returns a Stream that concatenated other streams
|
||||
func (s Stream) Concat(others ...Stream) Stream {
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
|
||||
go func() {
|
||||
group := threading.NewRoutineGroup()
|
||||
@@ -170,12 +170,12 @@ func (s Stream) Count() (count int) {
|
||||
|
||||
// Distinct removes the duplicated items base on the given KeyFunc.
|
||||
func (s Stream) Distinct(fn KeyFunc) Stream {
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
|
||||
threading.GoSafe(func() {
|
||||
defer close(source)
|
||||
|
||||
keys := make(map[interface{}]lang.PlaceholderType)
|
||||
keys := make(map[any]lang.PlaceholderType)
|
||||
for item := range s.source {
|
||||
key := fn(item)
|
||||
if _, ok := keys[key]; !ok {
|
||||
@@ -195,7 +195,7 @@ func (s Stream) Done() {
|
||||
|
||||
// Filter filters the items by the given FilterFunc.
|
||||
func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
||||
return s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
return s.Walk(func(item any, pipe chan<- any) {
|
||||
if fn(item) {
|
||||
pipe <- item
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
||||
}
|
||||
|
||||
// First returns the first item, nil if no items.
|
||||
func (s Stream) First() interface{} {
|
||||
func (s Stream) First() any {
|
||||
for item := range s.source {
|
||||
// make sure the former goroutine not block, and current func returns fast.
|
||||
go drain(s.source)
|
||||
@@ -229,13 +229,13 @@ func (s Stream) ForEach(fn ForEachFunc) {
|
||||
|
||||
// Group groups the elements into different groups based on their keys.
|
||||
func (s Stream) Group(fn KeyFunc) Stream {
|
||||
groups := make(map[interface{}][]interface{})
|
||||
groups := make(map[any][]any)
|
||||
for item := range s.source {
|
||||
key := fn(item)
|
||||
groups[key] = append(groups[key], item)
|
||||
}
|
||||
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
go func() {
|
||||
for _, group := range groups {
|
||||
source <- group
|
||||
@@ -252,7 +252,7 @@ func (s Stream) Head(n int64) Stream {
|
||||
panic("n must be greater than 0")
|
||||
}
|
||||
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
|
||||
go func() {
|
||||
for item := range s.source {
|
||||
@@ -279,7 +279,7 @@ func (s Stream) Head(n int64) Stream {
|
||||
}
|
||||
|
||||
// Last returns the last item, or nil if no items.
|
||||
func (s Stream) Last() (item interface{}) {
|
||||
func (s Stream) Last() (item any) {
|
||||
for item = range s.source {
|
||||
}
|
||||
return
|
||||
@@ -287,29 +287,53 @@ func (s Stream) Last() (item interface{}) {
|
||||
|
||||
// Map converts each item to another corresponding item, which means it's a 1:1 model.
|
||||
func (s Stream) Map(fn MapFunc, opts ...Option) Stream {
|
||||
return s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
return s.Walk(func(item any, pipe chan<- any) {
|
||||
pipe <- fn(item)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// Max returns the maximum item from the underlying source.
|
||||
func (s Stream) Max(less LessFunc) any {
|
||||
var max any
|
||||
for item := range s.source {
|
||||
if max == nil || less(max, item) {
|
||||
max = item
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
}
|
||||
|
||||
// Merge merges all the items into a slice and generates a new stream.
|
||||
func (s Stream) Merge() Stream {
|
||||
var items []interface{}
|
||||
var items []any
|
||||
for item := range s.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
source := make(chan interface{}, 1)
|
||||
source := make(chan any, 1)
|
||||
source <- items
|
||||
close(source)
|
||||
|
||||
return Range(source)
|
||||
}
|
||||
|
||||
// Min returns the minimum item from the underlying source.
|
||||
func (s Stream) Min(less LessFunc) any {
|
||||
var min any
|
||||
for item := range s.source {
|
||||
if min == nil || less(item, min) {
|
||||
min = item
|
||||
}
|
||||
}
|
||||
|
||||
return min
|
||||
}
|
||||
|
||||
// NoneMatch returns whether all elements of this stream don't match the provided predicate.
|
||||
// May not evaluate the predicate on all elements if not necessary for determining the result.
|
||||
// If the stream is empty then true is returned and the predicate is not evaluated.
|
||||
func (s Stream) NoneMatch(predicate func(item interface{}) bool) bool {
|
||||
func (s Stream) NoneMatch(predicate func(item any) bool) bool {
|
||||
for item := range s.source {
|
||||
if predicate(item) {
|
||||
// make sure the former goroutine not block, and current func returns fast.
|
||||
@@ -323,19 +347,19 @@ func (s Stream) NoneMatch(predicate func(item interface{}) bool) bool {
|
||||
|
||||
// Parallel applies the given ParallelFunc to each item concurrently with given number of workers.
|
||||
func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
|
||||
s.Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
s.Walk(func(item any, pipe chan<- any) {
|
||||
fn(item)
|
||||
}, opts...).Done()
|
||||
}
|
||||
|
||||
// Reduce is an utility method to let the caller deal with the underlying channel.
|
||||
func (s Stream) Reduce(fn ReduceFunc) (interface{}, error) {
|
||||
func (s Stream) Reduce(fn ReduceFunc) (any, error) {
|
||||
return fn(s.source)
|
||||
}
|
||||
|
||||
// Reverse reverses the elements in the stream.
|
||||
func (s Stream) Reverse() Stream {
|
||||
var items []interface{}
|
||||
var items []any
|
||||
for item := range s.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
@@ -357,7 +381,7 @@ func (s Stream) Skip(n int64) Stream {
|
||||
return s
|
||||
}
|
||||
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
|
||||
go func() {
|
||||
for item := range s.source {
|
||||
@@ -376,7 +400,7 @@ func (s Stream) Skip(n int64) Stream {
|
||||
|
||||
// Sort sorts the items from the underlying source.
|
||||
func (s Stream) Sort(less LessFunc) Stream {
|
||||
var items []interface{}
|
||||
var items []any
|
||||
for item := range s.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
@@ -394,9 +418,9 @@ func (s Stream) Split(n int) Stream {
|
||||
panic("n should be greater than 0")
|
||||
}
|
||||
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
go func() {
|
||||
var chunk []interface{}
|
||||
var chunk []any
|
||||
for item := range s.source {
|
||||
chunk = append(chunk, item)
|
||||
if len(chunk) == n {
|
||||
@@ -419,7 +443,7 @@ func (s Stream) Tail(n int64) Stream {
|
||||
panic("n should be greater than 0")
|
||||
}
|
||||
|
||||
source := make(chan interface{})
|
||||
source := make(chan any)
|
||||
|
||||
go func() {
|
||||
ring := collection.NewRing(int(n))
|
||||
@@ -446,7 +470,7 @@ func (s Stream) Walk(fn WalkFunc, opts ...Option) Stream {
|
||||
}
|
||||
|
||||
func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
pipe := make(chan interface{}, option.workers)
|
||||
pipe := make(chan any, option.workers)
|
||||
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
@@ -477,7 +501,7 @@ func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
}
|
||||
|
||||
func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
pipe := make(chan interface{}, option.workers)
|
||||
pipe := make(chan any, option.workers)
|
||||
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
@@ -529,7 +553,7 @@ func buildOptions(opts ...Option) *rxOptions {
|
||||
}
|
||||
|
||||
// drain drains the given channel.
|
||||
func drain(channel <-chan interface{}) {
|
||||
func drain(channel <-chan any) {
|
||||
for range channel {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestBuffer(t *testing.T) {
|
||||
var count int32
|
||||
var wait sync.WaitGroup
|
||||
wait.Add(1)
|
||||
From(func(source chan<- interface{}) {
|
||||
From(func(source chan<- any) {
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestBuffer(t *testing.T) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}).Buffer(N).ForAll(func(pipe <-chan interface{}) {
|
||||
}).Buffer(N).ForAll(func(pipe <-chan any) {
|
||||
wait.Wait()
|
||||
// why N+1, because take one more to wait for sending into the channel
|
||||
assert.Equal(t, int32(N+1), atomic.LoadInt32(&count))
|
||||
@@ -47,7 +47,7 @@ func TestBuffer(t *testing.T) {
|
||||
func TestBufferNegative(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var result int
|
||||
Just(1, 2, 3, 4).Buffer(-1).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||
Just(1, 2, 3, 4).Buffer(-1).Reduce(func(pipe <-chan any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -61,22 +61,22 @@ func TestCount(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
elements []interface{}
|
||||
elements []any
|
||||
}{
|
||||
{
|
||||
name: "no elements with nil",
|
||||
},
|
||||
{
|
||||
name: "no elements",
|
||||
elements: []interface{}{},
|
||||
elements: []any{},
|
||||
},
|
||||
{
|
||||
name: "1 element",
|
||||
elements: []interface{}{1},
|
||||
elements: []any{1},
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
elements: []interface{}{1, 2, 3},
|
||||
elements: []any{1, 2, 3},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestCount(t *testing.T) {
|
||||
func TestDone(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var count int32
|
||||
Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) {
|
||||
Just(1, 2, 3).Walk(func(item any, pipe chan<- any) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
atomic.AddInt32(&count, int32(item.(int)))
|
||||
}).Done()
|
||||
@@ -103,7 +103,7 @@ func TestDone(t *testing.T) {
|
||||
func TestJust(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var result int
|
||||
Just(1, 2, 3, 4).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||
Just(1, 2, 3, 4).Reduce(func(pipe <-chan any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -116,9 +116,9 @@ func TestJust(t *testing.T) {
|
||||
func TestDistinct(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var result int
|
||||
Just(4, 1, 3, 2, 3, 4).Distinct(func(item interface{}) interface{} {
|
||||
Just(4, 1, 3, 2, 3, 4).Distinct(func(item any) any {
|
||||
return item
|
||||
}).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||
}).Reduce(func(pipe <-chan any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -131,9 +131,9 @@ func TestDistinct(t *testing.T) {
|
||||
func TestFilter(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var result int
|
||||
Just(1, 2, 3, 4).Filter(func(item interface{}) bool {
|
||||
Just(1, 2, 3, 4).Filter(func(item any) bool {
|
||||
return item.(int)%2 == 0
|
||||
}).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||
}).Reduce(func(pipe <-chan any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -154,9 +154,9 @@ func TestFirst(t *testing.T) {
|
||||
func TestForAll(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var result int
|
||||
Just(1, 2, 3, 4).Filter(func(item interface{}) bool {
|
||||
Just(1, 2, 3, 4).Filter(func(item any) bool {
|
||||
return item.(int)%2 == 0
|
||||
}).ForAll(func(pipe <-chan interface{}) {
|
||||
}).ForAll(func(pipe <-chan any) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -168,11 +168,11 @@ func TestForAll(t *testing.T) {
|
||||
func TestGroup(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var groups [][]int
|
||||
Just(10, 11, 20, 21).Group(func(item interface{}) interface{} {
|
||||
Just(10, 11, 20, 21).Group(func(item any) any {
|
||||
v := item.(int)
|
||||
return v / 10
|
||||
}).ForEach(func(item interface{}) {
|
||||
v := item.([]interface{})
|
||||
}).ForEach(func(item any) {
|
||||
v := item.([]any)
|
||||
var group []int
|
||||
for _, each := range v {
|
||||
group = append(group, each.(int))
|
||||
@@ -191,7 +191,7 @@ func TestGroup(t *testing.T) {
|
||||
func TestHead(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var result int
|
||||
Just(1, 2, 3, 4).Head(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||
Just(1, 2, 3, 4).Head(2).Reduce(func(pipe <-chan any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -204,7 +204,7 @@ func TestHead(t *testing.T) {
|
||||
func TestHeadZero(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
Just(1, 2, 3, 4).Head(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||
Just(1, 2, 3, 4).Head(0).Reduce(func(pipe <-chan any) (any, error) {
|
||||
return nil, nil
|
||||
})
|
||||
})
|
||||
@@ -214,7 +214,7 @@ func TestHeadZero(t *testing.T) {
|
||||
func TestHeadMore(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
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 any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -245,14 +245,14 @@ func TestMap(t *testing.T) {
|
||||
expect int
|
||||
}{
|
||||
{
|
||||
mapper: func(item interface{}) interface{} {
|
||||
mapper: func(item any) any {
|
||||
v := item.(int)
|
||||
return v * v
|
||||
},
|
||||
expect: 30,
|
||||
},
|
||||
{
|
||||
mapper: func(item interface{}) interface{} {
|
||||
mapper: func(item any) any {
|
||||
v := item.(int)
|
||||
if v%2 == 0 {
|
||||
return 0
|
||||
@@ -262,7 +262,7 @@ func TestMap(t *testing.T) {
|
||||
expect: 10,
|
||||
},
|
||||
{
|
||||
mapper: func(item interface{}) interface{} {
|
||||
mapper: func(item any) any {
|
||||
v := item.(int)
|
||||
if v%2 == 0 {
|
||||
panic(v)
|
||||
@@ -283,12 +283,12 @@ func TestMap(t *testing.T) {
|
||||
} else {
|
||||
workers = runtime.NumCPU()
|
||||
}
|
||||
From(func(source chan<- interface{}) {
|
||||
From(func(source chan<- any) {
|
||||
for i := 1; i < 5; i++ {
|
||||
source <- i
|
||||
}
|
||||
}).Map(test.mapper, WithWorkers(workers)).Reduce(
|
||||
func(pipe <-chan interface{}) (interface{}, error) {
|
||||
func(pipe <-chan any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -303,8 +303,8 @@ func TestMap(t *testing.T) {
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
Just(1, 2, 3, 4).Merge().ForEach(func(item interface{}) {
|
||||
assert.ElementsMatch(t, []interface{}{1, 2, 3, 4}, item.([]interface{}))
|
||||
Just(1, 2, 3, 4).Merge().ForEach(func(item any) {
|
||||
assert.ElementsMatch(t, []any{1, 2, 3, 4}, item.([]any))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -312,7 +312,7 @@ func TestMerge(t *testing.T) {
|
||||
func TestParallelJust(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var count int32
|
||||
Just(1, 2, 3).Parallel(func(item interface{}) {
|
||||
Just(1, 2, 3).Parallel(func(item any) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
atomic.AddInt32(&count, int32(item.(int)))
|
||||
}, UnlimitedWorkers())
|
||||
@@ -322,8 +322,8 @@ func TestParallelJust(t *testing.T) {
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
Just(1, 2, 3, 4).Reverse().Merge().ForEach(func(item interface{}) {
|
||||
assert.ElementsMatch(t, []interface{}{4, 3, 2, 1}, item.([]interface{}))
|
||||
Just(1, 2, 3, 4).Reverse().Merge().ForEach(func(item any) {
|
||||
assert.ElementsMatch(t, []any{4, 3, 2, 1}, item.([]any))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -331,9 +331,9 @@ func TestReverse(t *testing.T) {
|
||||
func TestSort(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
var prev int
|
||||
Just(5, 3, 7, 1, 9, 6, 4, 8, 2).Sort(func(a, b interface{}) bool {
|
||||
Just(5, 3, 7, 1, 9, 6, 4, 8, 2).Sort(func(a, b any) bool {
|
||||
return a.(int) < b.(int)
|
||||
}).ForEach(func(item interface{}) {
|
||||
}).ForEach(func(item any) {
|
||||
next := item.(int)
|
||||
assert.True(t, prev < next)
|
||||
prev = next
|
||||
@@ -346,12 +346,12 @@ 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{})
|
||||
var chunks [][]any
|
||||
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(4).ForEach(func(item any) {
|
||||
chunk := item.([]any)
|
||||
chunks = append(chunks, chunk)
|
||||
})
|
||||
assert.EqualValues(t, [][]interface{}{
|
||||
assert.EqualValues(t, [][]any{
|
||||
{1, 2, 3, 4},
|
||||
{5, 6, 7, 8},
|
||||
{9, 10},
|
||||
@@ -362,7 +362,7 @@ func TestSplit(t *testing.T) {
|
||||
func TestTail(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
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 any) (any, error) {
|
||||
for item := range pipe {
|
||||
result += item.(int)
|
||||
}
|
||||
@@ -375,7 +375,7 @@ func TestTail(t *testing.T) {
|
||||
func TestTailZero(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
Just(1, 2, 3, 4).Tail(0).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
||||
Just(1, 2, 3, 4).Tail(0).Reduce(func(pipe <-chan any) (any, error) {
|
||||
return nil, nil
|
||||
})
|
||||
})
|
||||
@@ -385,11 +385,11 @@ func TestTailZero(t *testing.T) {
|
||||
func TestWalk(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
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 any, pipe chan<- any) {
|
||||
if item.(int)%2 != 0 {
|
||||
pipe <- item
|
||||
}
|
||||
}, UnlimitedWorkers()).ForEach(func(item interface{}) {
|
||||
}, UnlimitedWorkers()).ForEach(func(item any) {
|
||||
result += item.(int)
|
||||
})
|
||||
assert.Equal(t, 9, result)
|
||||
@@ -398,16 +398,16 @@ func TestWalk(t *testing.T) {
|
||||
|
||||
func TestStream_AnyMach(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
assetEqual(t, false, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||
assetEqual(t, false, Just(1, 2, 3).AnyMach(func(item any) bool {
|
||||
return item.(int) == 4
|
||||
}))
|
||||
assetEqual(t, false, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||
assetEqual(t, false, Just(1, 2, 3).AnyMach(func(item any) bool {
|
||||
return item.(int) == 0
|
||||
}))
|
||||
assetEqual(t, true, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||
assetEqual(t, true, Just(1, 2, 3).AnyMach(func(item any) bool {
|
||||
return item.(int) == 2
|
||||
}))
|
||||
assetEqual(t, true, Just(1, 2, 3).AnyMach(func(item interface{}) bool {
|
||||
assetEqual(t, true, Just(1, 2, 3).AnyMach(func(item any) bool {
|
||||
return item.(int) == 2
|
||||
}))
|
||||
})
|
||||
@@ -416,17 +416,17 @@ func TestStream_AnyMach(t *testing.T) {
|
||||
func TestStream_AllMach(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
assetEqual(
|
||||
t, true, Just(1, 2, 3).AllMach(func(item interface{}) bool {
|
||||
t, true, Just(1, 2, 3).AllMach(func(item any) bool {
|
||||
return true
|
||||
}),
|
||||
)
|
||||
assetEqual(
|
||||
t, false, Just(1, 2, 3).AllMach(func(item interface{}) bool {
|
||||
t, false, Just(1, 2, 3).AllMach(func(item any) bool {
|
||||
return false
|
||||
}),
|
||||
)
|
||||
assetEqual(
|
||||
t, false, Just(1, 2, 3).AllMach(func(item interface{}) bool {
|
||||
t, false, Just(1, 2, 3).AllMach(func(item any) bool {
|
||||
return item.(int) == 1
|
||||
}),
|
||||
)
|
||||
@@ -436,17 +436,17 @@ func TestStream_AllMach(t *testing.T) {
|
||||
func TestStream_NoneMatch(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
assetEqual(
|
||||
t, true, Just(1, 2, 3).NoneMatch(func(item interface{}) bool {
|
||||
t, true, Just(1, 2, 3).NoneMatch(func(item any) bool {
|
||||
return false
|
||||
}),
|
||||
)
|
||||
assetEqual(
|
||||
t, false, Just(1, 2, 3).NoneMatch(func(item interface{}) bool {
|
||||
t, false, Just(1, 2, 3).NoneMatch(func(item any) bool {
|
||||
return true
|
||||
}),
|
||||
)
|
||||
assetEqual(
|
||||
t, true, Just(1, 2, 3).NoneMatch(func(item interface{}) bool {
|
||||
t, true, Just(1, 2, 3).NoneMatch(func(item any) bool {
|
||||
return item.(int) == 4
|
||||
}),
|
||||
)
|
||||
@@ -455,19 +455,19 @@ func TestStream_NoneMatch(t *testing.T) {
|
||||
|
||||
func TestConcat(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
a1 := []interface{}{1, 2, 3}
|
||||
a2 := []interface{}{4, 5, 6}
|
||||
a1 := []any{1, 2, 3}
|
||||
a2 := []any{4, 5, 6}
|
||||
s1 := Just(a1...)
|
||||
s2 := Just(a2...)
|
||||
stream := Concat(s1, s2)
|
||||
var items []interface{}
|
||||
var items []any
|
||||
for item := range stream.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].(int) < items[j].(int)
|
||||
})
|
||||
ints := make([]interface{}, 0)
|
||||
ints := make([]any, 0)
|
||||
ints = append(ints, a1...)
|
||||
ints = append(ints, a2...)
|
||||
assetEqual(t, ints, items)
|
||||
@@ -479,7 +479,7 @@ func TestStream_Skip(t *testing.T) {
|
||||
assetEqual(t, 3, Just(1, 2, 3, 4).Skip(1).Count())
|
||||
assetEqual(t, 1, Just(1, 2, 3, 4).Skip(3).Count())
|
||||
assetEqual(t, 4, Just(1, 2, 3, 4).Skip(0).Count())
|
||||
equal(t, Just(1, 2, 3, 4).Skip(3), []interface{}{4})
|
||||
equal(t, Just(1, 2, 3, 4).Skip(3), []any{4})
|
||||
assert.Panics(t, func() {
|
||||
Just(1, 2, 3, 4).Skip(-1)
|
||||
})
|
||||
@@ -489,27 +489,104 @@ func TestStream_Skip(t *testing.T) {
|
||||
func TestStream_Concat(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
stream := Just(1).Concat(Just(2), Just(3))
|
||||
var items []interface{}
|
||||
var items []any
|
||||
for item := range stream.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].(int) < items[j].(int)
|
||||
})
|
||||
assetEqual(t, []interface{}{1, 2, 3}, items)
|
||||
assetEqual(t, []any{1, 2, 3}, items)
|
||||
|
||||
just := Just(1)
|
||||
equal(t, just.Concat(just), []interface{}{1})
|
||||
equal(t, just.Concat(just), []any{1})
|
||||
})
|
||||
}
|
||||
|
||||
func TestStream_Max(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
elements []any
|
||||
max any
|
||||
}{
|
||||
{
|
||||
name: "no elements with nil",
|
||||
},
|
||||
{
|
||||
name: "no elements",
|
||||
elements: []any{},
|
||||
max: nil,
|
||||
},
|
||||
{
|
||||
name: "1 element",
|
||||
elements: []any{1},
|
||||
max: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
elements: []any{1, 2, 9, 5, 8},
|
||||
max: 9,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
val := Just(test.elements...).Max(func(a, b any) bool {
|
||||
return a.(int) < b.(int)
|
||||
})
|
||||
assetEqual(t, test.max, val)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestStream_Min(t *testing.T) {
|
||||
runCheckedTest(t, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
elements []any
|
||||
min any
|
||||
}{
|
||||
{
|
||||
name: "no elements with nil",
|
||||
min: nil,
|
||||
},
|
||||
{
|
||||
name: "no elements",
|
||||
elements: []any{},
|
||||
min: nil,
|
||||
},
|
||||
{
|
||||
name: "1 element",
|
||||
elements: []any{1},
|
||||
min: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
elements: []any{-1, 1, 2, 9, 5, 8},
|
||||
min: -1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
val := Just(test.elements...).Min(func(a, b any) bool {
|
||||
return a.(int) < b.(int)
|
||||
})
|
||||
assetEqual(t, test.min, val)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkParallelMapReduce(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
mapper := func(v interface{}) interface{} {
|
||||
mapper := func(v any) any {
|
||||
return v.(int64) * v.(int64)
|
||||
}
|
||||
reducer := func(input <-chan interface{}) (interface{}, error) {
|
||||
reducer := func(input <-chan any) (any, error) {
|
||||
var result int64
|
||||
for v := range input {
|
||||
result += v.(int64)
|
||||
@@ -517,7 +594,7 @@ func BenchmarkParallelMapReduce(b *testing.B) {
|
||||
return result, nil
|
||||
}
|
||||
b.ResetTimer()
|
||||
From(func(input chan<- interface{}) {
|
||||
From(func(input chan<- any) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
input <- int64(rand.Int())
|
||||
@@ -529,10 +606,10 @@ func BenchmarkParallelMapReduce(b *testing.B) {
|
||||
func BenchmarkMapReduce(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
mapper := func(v interface{}) interface{} {
|
||||
mapper := func(v any) any {
|
||||
return v.(int64) * v.(int64)
|
||||
}
|
||||
reducer := func(input <-chan interface{}) (interface{}, error) {
|
||||
reducer := func(input <-chan any) (any, error) {
|
||||
var result int64
|
||||
for v := range input {
|
||||
result += v.(int64)
|
||||
@@ -540,21 +617,21 @@ func BenchmarkMapReduce(b *testing.B) {
|
||||
return result, nil
|
||||
}
|
||||
b.ResetTimer()
|
||||
From(func(input chan<- interface{}) {
|
||||
From(func(input chan<- any) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
input <- int64(rand.Int())
|
||||
}
|
||||
}).Map(mapper).Reduce(reducer)
|
||||
}
|
||||
|
||||
func assetEqual(t *testing.T, except, data interface{}) {
|
||||
func assetEqual(t *testing.T, except, data any) {
|
||||
if !reflect.DeepEqual(except, data) {
|
||||
t.Errorf(" %v, want %v", data, except)
|
||||
}
|
||||
}
|
||||
|
||||
func equal(t *testing.T, stream Stream, data []interface{}) {
|
||||
items := make([]interface{}, 0)
|
||||
func equal(t *testing.T, stream Stream, data []any) {
|
||||
items := make([]any, 0)
|
||||
for item := range stream.source {
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func DoWithTimeout(fn func() error, timeout time.Duration, opts ...DoOption) err
|
||||
|
||||
// create channel with buffer size 1 to avoid goroutine leak
|
||||
done := make(chan error, 1)
|
||||
panicChan := make(chan interface{}, 1)
|
||||
panicChan := make(chan any, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
|
||||
@@ -26,7 +26,7 @@ type (
|
||||
hashFunc Func
|
||||
replicas int
|
||||
keys []uint64
|
||||
ring map[uint64][]interface{}
|
||||
ring map[uint64][]any
|
||||
nodes map[string]lang.PlaceholderType
|
||||
lock sync.RWMutex
|
||||
}
|
||||
@@ -50,21 +50,21 @@ func NewCustomConsistentHash(replicas int, fn Func) *ConsistentHash {
|
||||
return &ConsistentHash{
|
||||
hashFunc: fn,
|
||||
replicas: replicas,
|
||||
ring: make(map[uint64][]interface{}),
|
||||
ring: make(map[uint64][]any),
|
||||
nodes: make(map[string]lang.PlaceholderType),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds the node with the number of h.replicas,
|
||||
// the later call will overwrite the replicas of the former calls.
|
||||
func (h *ConsistentHash) Add(node interface{}) {
|
||||
func (h *ConsistentHash) Add(node any) {
|
||||
h.AddWithReplicas(node, h.replicas)
|
||||
}
|
||||
|
||||
// AddWithReplicas adds the node with the number of replicas,
|
||||
// replicas will be truncated to h.replicas if it's larger than h.replicas,
|
||||
// the later call will overwrite the replicas of the former calls.
|
||||
func (h *ConsistentHash) AddWithReplicas(node interface{}, replicas int) {
|
||||
func (h *ConsistentHash) AddWithReplicas(node any, replicas int) {
|
||||
h.Remove(node)
|
||||
|
||||
if replicas > h.replicas {
|
||||
@@ -89,7 +89,7 @@ func (h *ConsistentHash) AddWithReplicas(node interface{}, replicas int) {
|
||||
|
||||
// AddWithWeight adds the node with weight, the weight can be 1 to 100, indicates the percent,
|
||||
// the later call will overwrite the replicas of the former calls.
|
||||
func (h *ConsistentHash) AddWithWeight(node interface{}, weight int) {
|
||||
func (h *ConsistentHash) AddWithWeight(node any, weight int) {
|
||||
// don't need to make sure weight not larger than TopWeight,
|
||||
// because AddWithReplicas makes sure replicas cannot be larger than h.replicas
|
||||
replicas := h.replicas * weight / TopWeight
|
||||
@@ -97,7 +97,7 @@ func (h *ConsistentHash) AddWithWeight(node interface{}, weight int) {
|
||||
}
|
||||
|
||||
// Get returns the corresponding node from h base on the given v.
|
||||
func (h *ConsistentHash) Get(v interface{}) (interface{}, bool) {
|
||||
func (h *ConsistentHash) Get(v any) (any, bool) {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
|
||||
@@ -124,7 +124,7 @@ func (h *ConsistentHash) Get(v interface{}) (interface{}, bool) {
|
||||
}
|
||||
|
||||
// Remove removes the given node from h.
|
||||
func (h *ConsistentHash) Remove(node interface{}) {
|
||||
func (h *ConsistentHash) Remove(node any) {
|
||||
nodeRepr := repr(node)
|
||||
|
||||
h.lock.Lock()
|
||||
@@ -177,10 +177,10 @@ func (h *ConsistentHash) removeNode(nodeRepr string) {
|
||||
delete(h.nodes, nodeRepr)
|
||||
}
|
||||
|
||||
func innerRepr(node interface{}) string {
|
||||
func innerRepr(node any) string {
|
||||
return fmt.Sprintf("%d:%v", prime, node)
|
||||
}
|
||||
|
||||
func repr(node interface{}) string {
|
||||
func repr(node any) string {
|
||||
return lang.Repr(node)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestConsistentHash(t *testing.T) {
|
||||
keys[key.(string)]++
|
||||
}
|
||||
|
||||
mi := make(map[interface{}]int, len(keys))
|
||||
mi := make(map[any]int, len(keys))
|
||||
for k, v := range keys {
|
||||
mi[k] = v
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func NewBufferPool(capability int) *BufferPool {
|
||||
return &BufferPool{
|
||||
capability: capability,
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -40,11 +40,11 @@ b`,
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
tmpfile, err := fs.TempFilenameWithText(test.input)
|
||||
tmpFile, err := fs.TempFilenameWithText(test.input)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
defer os.Remove(tmpFile)
|
||||
|
||||
content, err := ReadText(tmpfile)
|
||||
content, err := ReadText(tmpFile)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.expect, content)
|
||||
})
|
||||
@@ -59,9 +59,9 @@ func TestReadTextLines(t *testing.T) {
|
||||
#a
|
||||
3`
|
||||
|
||||
tmpfile, err := fs.TempFilenameWithText(text)
|
||||
tmpFile, err := fs.TempFilenameWithText(text)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(tmpfile)
|
||||
defer os.Remove(tmpFile)
|
||||
|
||||
tests := []struct {
|
||||
options []TextReadOption
|
||||
@@ -87,7 +87,7 @@ func TestReadTextLines(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(stringx.Rand(), func(t *testing.T) {
|
||||
lines, err := ReadTextLines(tmpfile, test.options...)
|
||||
lines, err := ReadTextLines(tmpFile, test.options...)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.expectLines, len(lines))
|
||||
})
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package jsontype
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// MilliTime represents time.Time that works better with mongodb.
|
||||
type MilliTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// MarshalJSON marshals mt to json bytes.
|
||||
func (mt MilliTime) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(mt.Milli())
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals data into mt.
|
||||
func (mt *MilliTime) UnmarshalJSON(data []byte) error {
|
||||
var milli int64
|
||||
if err := json.Unmarshal(data, &milli); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mt.Time = time.Unix(0, milli*int64(time.Millisecond))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBSON returns BSON base on mt.
|
||||
func (mt MilliTime) GetBSON() (interface{}, error) {
|
||||
return mt.Time, nil
|
||||
}
|
||||
|
||||
// SetBSON sets raw into mt.
|
||||
func (mt *MilliTime) SetBSON(raw bson.Raw) error {
|
||||
return raw.Unmarshal(&mt.Time)
|
||||
}
|
||||
|
||||
// Milli returns milliseconds for mt.
|
||||
func (mt MilliTime) Milli() int64 {
|
||||
return mt.UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package jsontype
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMilliTime_GetBSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, err := MilliTime{test.tm}.GetBSON()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, test.tm, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilliTime_MarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
b, err := MilliTime{test.tm}.MarshalJSON()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, strconv.FormatInt(test.tm.UnixNano()/1e6, 10), string(b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilliTime_Milli(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
n := MilliTime{test.tm}.Milli()
|
||||
assert.Equal(t, test.tm.UnixNano()/1e6, n)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilliTime_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tm time.Time
|
||||
}{
|
||||
{
|
||||
name: "now",
|
||||
tm: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "future",
|
||||
tm: time.Now().Add(time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var mt MilliTime
|
||||
s := strconv.FormatInt(test.tm.UnixNano()/1e6, 10)
|
||||
err := mt.UnmarshalJSON([]byte(s))
|
||||
assert.Nil(t, err)
|
||||
s1, err := mt.MarshalJSON()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s, string(s1))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalWithError(t *testing.T) {
|
||||
var mt MilliTime
|
||||
assert.NotNil(t, mt.UnmarshalJSON([]byte("hello")))
|
||||
}
|
||||
|
||||
func TestSetBSON(t *testing.T) {
|
||||
data, err := bson.Marshal(time.Now())
|
||||
assert.Nil(t, err)
|
||||
|
||||
var raw bson.Raw
|
||||
assert.Nil(t, bson.Unmarshal(data, &raw))
|
||||
|
||||
var mt MilliTime
|
||||
assert.Nil(t, mt.SetBSON(raw))
|
||||
assert.NotNil(t, mt.SetBSON(bson.Raw{}))
|
||||
}
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
)
|
||||
|
||||
// Marshal marshals v into json bytes.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// MarshalToString marshals v into a string.
|
||||
func MarshalToString(v interface{}) (string, error) {
|
||||
func MarshalToString(v any) (string, error) {
|
||||
data, err := Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -24,7 +24,7 @@ func MarshalToString(v interface{}) (string, error) {
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals data bytes into v.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
func Unmarshal(data []byte, v any) error {
|
||||
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||
if err := unmarshalUseNumber(decoder, v); err != nil {
|
||||
return formatError(string(data), err)
|
||||
@@ -34,7 +34,7 @@ func Unmarshal(data []byte, v interface{}) error {
|
||||
}
|
||||
|
||||
// UnmarshalFromString unmarshals v from str.
|
||||
func UnmarshalFromString(str string, v interface{}) error {
|
||||
func UnmarshalFromString(str string, v any) error {
|
||||
decoder := json.NewDecoder(strings.NewReader(str))
|
||||
if err := unmarshalUseNumber(decoder, v); err != nil {
|
||||
return formatError(str, err)
|
||||
@@ -44,7 +44,7 @@ func UnmarshalFromString(str string, v interface{}) error {
|
||||
}
|
||||
|
||||
// UnmarshalFromReader unmarshals v from reader.
|
||||
func UnmarshalFromReader(reader io.Reader, v interface{}) error {
|
||||
func UnmarshalFromReader(reader io.Reader, v any) error {
|
||||
var buf strings.Builder
|
||||
teeReader := io.TeeReader(reader, &buf)
|
||||
decoder := json.NewDecoder(teeReader)
|
||||
@@ -55,7 +55,7 @@ func UnmarshalFromReader(reader io.Reader, v interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalUseNumber(decoder *json.Decoder, v interface{}) error {
|
||||
func unmarshalUseNumber(decoder *json.Decoder, v any) error {
|
||||
decoder.UseNumber()
|
||||
return decoder.Decode(v)
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ var Placeholder PlaceholderType
|
||||
|
||||
type (
|
||||
// AnyType can be used to hold any type.
|
||||
AnyType = interface{}
|
||||
AnyType = any
|
||||
// PlaceholderType represents a placeholder type.
|
||||
PlaceholderType = struct{}
|
||||
)
|
||||
|
||||
// Repr returns the string representation of v.
|
||||
func Repr(v interface{}) string {
|
||||
func Repr(v any) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func Repr(v interface{}) string {
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
||||
for val.Kind() == reflect.Ptr && !val.IsNil() {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -20,7 +23,7 @@ func TestRepr(t *testing.T) {
|
||||
u64 uint64 = 8
|
||||
)
|
||||
tests := []struct {
|
||||
v interface{}
|
||||
v any
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
@@ -110,6 +113,28 @@ func TestRepr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReprOfValue(t *testing.T) {
|
||||
t.Run("error", func(t *testing.T) {
|
||||
assert.Equal(t, "error", reprOfValue(reflect.ValueOf(errors.New("error"))))
|
||||
})
|
||||
|
||||
t.Run("stringer", func(t *testing.T) {
|
||||
assert.Equal(t, "1.23", reprOfValue(reflect.ValueOf(json.Number("1.23"))))
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
assert.Equal(t, "1", reprOfValue(reflect.ValueOf(1)))
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
assert.Equal(t, "1", reprOfValue(reflect.ValueOf("1")))
|
||||
})
|
||||
|
||||
t.Run("int", func(t *testing.T) {
|
||||
assert.Equal(t, "1", reprOfValue(reflect.ValueOf(uint(1))))
|
||||
})
|
||||
}
|
||||
|
||||
type mockStringable struct{}
|
||||
|
||||
func (m mockStringable) String() string {
|
||||
|
||||
@@ -9,21 +9,6 @@ import (
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||
const periodScript = `local limit = tonumber(ARGV[1])
|
||||
local window = tonumber(ARGV[2])
|
||||
local current = redis.call("INCRBY", KEYS[1], 1)
|
||||
if current == 1 then
|
||||
redis.call("expire", KEYS[1], window)
|
||||
end
|
||||
if current < limit then
|
||||
return 1
|
||||
elseif current == limit then
|
||||
return 2
|
||||
else
|
||||
return 0
|
||||
end`
|
||||
|
||||
const (
|
||||
// Unknown means not initialized state.
|
||||
Unknown = iota
|
||||
@@ -39,8 +24,25 @@ const (
|
||||
internalHitQuota = 2
|
||||
)
|
||||
|
||||
// ErrUnknownCode is an error that represents unknown status code.
|
||||
var ErrUnknownCode = errors.New("unknown status code")
|
||||
var (
|
||||
// ErrUnknownCode is an error that represents unknown status code.
|
||||
ErrUnknownCode = errors.New("unknown status code")
|
||||
|
||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||
periodScript = redis.NewScript(`local limit = tonumber(ARGV[1])
|
||||
local window = tonumber(ARGV[2])
|
||||
local current = redis.call("INCRBY", KEYS[1], 1)
|
||||
if current == 1 then
|
||||
redis.call("expire", KEYS[1], window)
|
||||
end
|
||||
if current < limit then
|
||||
return 1
|
||||
elseif current == limit then
|
||||
return 2
|
||||
else
|
||||
return 0
|
||||
end`)
|
||||
)
|
||||
|
||||
type (
|
||||
// PeriodOption defines the method to customize a PeriodLimit.
|
||||
@@ -80,7 +82,7 @@ func (h *PeriodLimit) Take(key string) (int, error) {
|
||||
|
||||
// TakeCtx requests a permit with context, it returns the permit state.
|
||||
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
|
||||
resp, err := h.limitStore.EvalCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
|
||||
resp, err := h.limitStore.ScriptRunCtx(ctx, periodScript, []string{h.keyPrefix + key}, []string{
|
||||
strconv.Itoa(h.quota),
|
||||
strconv.Itoa(h.calcExpireSeconds()),
|
||||
})
|
||||
|
||||
@@ -33,9 +33,7 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
|
||||
}
|
||||
|
||||
func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
store := redistest.CreateRedis(t)
|
||||
|
||||
const (
|
||||
seconds = 1
|
||||
|
||||
@@ -15,10 +15,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||
// KEYS[1] as tokens_key
|
||||
// KEYS[2] as timestamp_key
|
||||
script = `local rate = tonumber(ARGV[1])
|
||||
tokenFormat = "{%s}.tokens"
|
||||
timestampFormat = "{%s}.ts"
|
||||
pingInterval = time.Millisecond * 100
|
||||
)
|
||||
|
||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
||||
// KEYS[1] as tokens_key
|
||||
// KEYS[2] as timestamp_key
|
||||
var script = redis.NewScript(`local rate = tonumber(ARGV[1])
|
||||
local capacity = tonumber(ARGV[2])
|
||||
local now = tonumber(ARGV[3])
|
||||
local requested = tonumber(ARGV[4])
|
||||
@@ -45,11 +50,7 @@ end
|
||||
redis.call("setex", KEYS[1], ttl, new_tokens)
|
||||
redis.call("setex", KEYS[2], ttl, now)
|
||||
|
||||
return allowed`
|
||||
tokenFormat = "{%s}.tokens"
|
||||
timestampFormat = "{%s}.ts"
|
||||
pingInterval = time.Millisecond * 100
|
||||
)
|
||||
return allowed`)
|
||||
|
||||
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
|
||||
type TokenLimiter struct {
|
||||
@@ -110,7 +111,7 @@ func (lim *TokenLimiter) reserveN(ctx context.Context, now time.Time, n int) boo
|
||||
return lim.rescueLimiter.AllowN(now, n)
|
||||
}
|
||||
|
||||
resp, err := lim.store.EvalCtx(ctx,
|
||||
resp, err := lim.store.ScriptRunCtx(ctx,
|
||||
script,
|
||||
[]string{
|
||||
lim.tokenKey,
|
||||
|
||||
@@ -70,9 +70,7 @@ func TestTokenLimit_Rescue(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTokenLimit_Take(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
store := redistest.CreateRedis(t)
|
||||
|
||||
const (
|
||||
total = 100
|
||||
@@ -92,9 +90,7 @@ func TestTokenLimit_Take(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTokenLimit_TakeBurst(t *testing.T) {
|
||||
store, clean, err := redistest.CreateRedis()
|
||||
assert.Nil(t, err)
|
||||
defer clean()
|
||||
store := redistest.CreateRedis(t)
|
||||
|
||||
const (
|
||||
total = 100
|
||||
|
||||
@@ -27,19 +27,39 @@ func Close() error {
|
||||
return logx.Close()
|
||||
}
|
||||
|
||||
// Debug writes v into access log.
|
||||
func Debug(ctx context.Context, v ...interface{}) {
|
||||
getLogger(ctx).Debug(v...)
|
||||
}
|
||||
|
||||
// Debugf writes v with format into access log.
|
||||
func Debugf(ctx context.Context, format string, v ...interface{}) {
|
||||
getLogger(ctx).Debugf(format, v...)
|
||||
}
|
||||
|
||||
// Debugv writes v into access log with json content.
|
||||
func Debugv(ctx context.Context, v interface{}) {
|
||||
getLogger(ctx).Debugv(v)
|
||||
}
|
||||
|
||||
// Debugw writes msg along with fields into access log.
|
||||
func Debugw(ctx context.Context, msg string, fields ...LogField) {
|
||||
getLogger(ctx).Debugw(msg, fields...)
|
||||
}
|
||||
|
||||
// Error writes v into error log.
|
||||
func Error(ctx context.Context, v ...interface{}) {
|
||||
func Error(ctx context.Context, v ...any) {
|
||||
getLogger(ctx).Error(v...)
|
||||
}
|
||||
|
||||
// Errorf writes v with format into error log.
|
||||
func Errorf(ctx context.Context, format string, v ...interface{}) {
|
||||
func Errorf(ctx context.Context, format string, v ...any) {
|
||||
getLogger(ctx).Errorf(fmt.Errorf(format, v...).Error())
|
||||
}
|
||||
|
||||
// Errorv writes v into error log with json content.
|
||||
// No call stack attached, because not elegant to pack the messages.
|
||||
func Errorv(ctx context.Context, v interface{}) {
|
||||
func Errorv(ctx context.Context, v any) {
|
||||
getLogger(ctx).Errorv(v)
|
||||
}
|
||||
|
||||
@@ -49,22 +69,22 @@ func Errorw(ctx context.Context, msg string, fields ...LogField) {
|
||||
}
|
||||
|
||||
// Field returns a LogField for the given key and value.
|
||||
func Field(key string, value interface{}) LogField {
|
||||
func Field(key string, value any) LogField {
|
||||
return logx.Field(key, value)
|
||||
}
|
||||
|
||||
// Info writes v into access log.
|
||||
func Info(ctx context.Context, v ...interface{}) {
|
||||
func Info(ctx context.Context, v ...any) {
|
||||
getLogger(ctx).Info(v...)
|
||||
}
|
||||
|
||||
// Infof writes v with format into access log.
|
||||
func Infof(ctx context.Context, format string, v ...interface{}) {
|
||||
func Infof(ctx context.Context, format string, v ...any) {
|
||||
getLogger(ctx).Infof(format, v...)
|
||||
}
|
||||
|
||||
// Infov writes v into access log with json content.
|
||||
func Infov(ctx context.Context, v interface{}) {
|
||||
func Infov(ctx context.Context, v any) {
|
||||
getLogger(ctx).Infov(v)
|
||||
}
|
||||
|
||||
@@ -97,17 +117,17 @@ func SetUp(c LogConf) error {
|
||||
}
|
||||
|
||||
// Slow writes v into slow log.
|
||||
func Slow(ctx context.Context, v ...interface{}) {
|
||||
func Slow(ctx context.Context, v ...any) {
|
||||
getLogger(ctx).Slow(v...)
|
||||
}
|
||||
|
||||
// Slowf writes v with format into slow log.
|
||||
func Slowf(ctx context.Context, format string, v ...interface{}) {
|
||||
func Slowf(ctx context.Context, format string, v ...any) {
|
||||
getLogger(ctx).Slowf(format, v...)
|
||||
}
|
||||
|
||||
// Slowv writes v into slow log with json content.
|
||||
func Slowv(ctx context.Context, v interface{}) {
|
||||
func Slowv(ctx context.Context, v any) {
|
||||
getLogger(ctx).Slowv(v)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package logc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -11,14 +10,11 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||
)
|
||||
|
||||
func TestAddGlobalFields(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
buf := logtest.NewCollector(t)
|
||||
|
||||
Info(context.Background(), "hello")
|
||||
buf.Reset()
|
||||
@@ -26,7 +22,7 @@ func TestAddGlobalFields(t *testing.T) {
|
||||
AddGlobalFields(Field("a", "1"), Field("b", "2"))
|
||||
AddGlobalFields(Field("c", "3"))
|
||||
Info(context.Background(), "world")
|
||||
var m map[string]interface{}
|
||||
var m map[string]any
|
||||
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
|
||||
assert.Equal(t, "1", m["a"])
|
||||
assert.Equal(t, "2", m["b"])
|
||||
@@ -34,112 +30,95 @@ func TestAddGlobalFields(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlert(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
Alert(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), "foo"), buf.String())
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Error(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestErrorf(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Errorf(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestErrorv(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Errorv(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestErrorw(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Errorw(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Info(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfof(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Infof(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfov(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Infov(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestInfow(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Infow(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebug(t *testing.T) {
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debug(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebugf(t *testing.T) {
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debugf(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebugv(t *testing.T) {
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debugv(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestDebugw(t *testing.T) {
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Debugw(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestMust(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
Must(nil)
|
||||
@@ -156,48 +135,28 @@ func TestMisc(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSlow(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Slow(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
}
|
||||
|
||||
func TestSlowf(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Slowf(context.Background(), "foo %s", "bar")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
}
|
||||
|
||||
func TestSlowv(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Slowv(context.Background(), "foo")
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
}
|
||||
|
||||
func TestSloww(t *testing.T) {
|
||||
var buf strings.Builder
|
||||
writer := logx.NewWriter(&buf)
|
||||
old := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
defer logx.SetWriter(old)
|
||||
|
||||
buf := logtest.NewCollector(t)
|
||||
file, line := getFileLine()
|
||||
Sloww(context.Background(), "foo", Field("a", "b"))
|
||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||
|
||||
@@ -2,24 +2,43 @@ package logx
|
||||
|
||||
// A LogConf is a logging config.
|
||||
type LogConf struct {
|
||||
ServiceName string `json:",optional"`
|
||||
Mode string `json:",default=console,options=[console,file,volume]"`
|
||||
Encoding string `json:",default=json,options=[json,plain]"`
|
||||
TimeFormat string `json:",optional"`
|
||||
Path string `json:",default=logs"`
|
||||
Level string `json:",default=info,options=[debug,info,error,severe]"`
|
||||
Compress bool `json:",optional"`
|
||||
KeepDays int `json:",optional"`
|
||||
StackCooldownMillis int `json:",default=100"`
|
||||
// ServiceName represents the service name.
|
||||
ServiceName string `json:",optional"`
|
||||
// Mode represents the logging mode, default is `console`.
|
||||
// console: log to console.
|
||||
// file: log to file.
|
||||
// volume: used in k8s, prepend the hostname to the log file name.
|
||||
Mode string `json:",default=console,options=[console,file,volume]"`
|
||||
// Encoding represents the encoding type, default is `json`.
|
||||
// json: json encoding.
|
||||
// plain: plain text encoding, typically used in development.
|
||||
Encoding string `json:",default=json,options=[json,plain]"`
|
||||
// TimeFormat represents the time format, default is `2006-01-02T15:04:05.000Z07:00`.
|
||||
TimeFormat string `json:",optional"`
|
||||
// Path represents the log file path, default is `logs`.
|
||||
Path string `json:",default=logs"`
|
||||
// Level represents the log level, default is `info`.
|
||||
Level string `json:",default=info,options=[debug,info,error,severe]"`
|
||||
// MaxContentLength represents the max content bytes, default is no limit.
|
||||
MaxContentLength uint32 `json:",optional"`
|
||||
// Compress represents whether to compress the log file, default is `false`.
|
||||
Compress bool `json:",optional"`
|
||||
// Stat represents whether to log statistics, default is `true`.
|
||||
Stat bool `json:",default=true"`
|
||||
// KeepDays represents how many days the log files will be kept. Default to keep all files.
|
||||
// Only take effect when Mode is `file` or `volume`, both work when Rotation is `daily` or `size`.
|
||||
KeepDays int `json:",optional"`
|
||||
// StackCooldownMillis represents the cooldown time for stack logging, default is 100ms.
|
||||
StackCooldownMillis int `json:",default=100"`
|
||||
// MaxBackups represents how many backup log files will be kept. 0 means all files will be kept forever.
|
||||
// Only take effect when RotationRuleType is `size`.
|
||||
// Even thougth `MaxBackups` sets 0, log files will still be removed
|
||||
// Even though `MaxBackups` sets 0, log files will still be removed
|
||||
// if the `KeepDays` limitation is reached.
|
||||
MaxBackups int `json:",default=0"`
|
||||
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
|
||||
// Only take effect when RotationRuleType is `size`
|
||||
MaxSize int `json:",default=0"`
|
||||
// RotationRuleType represents the type of log rotation rule. Default is `daily`.
|
||||
// Rotation represents the type of log rotation rule. Default is `daily`.
|
||||
// daily: daily rotation.
|
||||
// size: size limited rotation.
|
||||
Rotation string `json:",default=daily,options=[daily,size]"`
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestAddGlobalFields(t *testing.T) {
|
||||
AddGlobalFields(Field("a", "1"), Field("b", "2"))
|
||||
AddGlobalFields(Field("c", "3"))
|
||||
Info("world")
|
||||
var m map[string]interface{}
|
||||
var m map[string]any
|
||||
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
|
||||
assert.Equal(t, "1", m["a"])
|
||||
assert.Equal(t, "2", m["b"])
|
||||
|
||||
40
core/logx/fs.go
Normal file
40
core/logx/fs.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var fileSys realFileSystem
|
||||
|
||||
type (
|
||||
fileSystem interface {
|
||||
Close(closer io.Closer) error
|
||||
Copy(writer io.Writer, reader io.Reader) (int64, error)
|
||||
Create(name string) (*os.File, error)
|
||||
Open(name string) (*os.File, error)
|
||||
Remove(name string) error
|
||||
}
|
||||
|
||||
realFileSystem struct{}
|
||||
)
|
||||
|
||||
func (fs realFileSystem) Close(closer io.Closer) error {
|
||||
return closer.Close()
|
||||
}
|
||||
|
||||
func (fs realFileSystem) Copy(writer io.Writer, reader io.Reader) (int64, error) {
|
||||
return io.Copy(writer, reader)
|
||||
}
|
||||
|
||||
func (fs realFileSystem) Create(name string) (*os.File, error) {
|
||||
return os.Create(name)
|
||||
}
|
||||
|
||||
func (fs realFileSystem) Open(name string) (*os.File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
func (fs realFileSystem) Remove(name string) error {
|
||||
return os.Remove(name)
|
||||
}
|
||||
@@ -13,14 +13,14 @@ func NewLessLogger(milliseconds int) *LessLogger {
|
||||
}
|
||||
|
||||
// Error logs v into error log or discard it if more than once in the given duration.
|
||||
func (logger *LessLogger) Error(v ...interface{}) {
|
||||
func (logger *LessLogger) Error(v ...any) {
|
||||
logger.logOrDiscard(func() {
|
||||
Error(v...)
|
||||
})
|
||||
}
|
||||
|
||||
// Errorf logs v with format into error log or discard it if more than once in the given duration.
|
||||
func (logger *LessLogger) Errorf(format string, v ...interface{}) {
|
||||
func (logger *LessLogger) Errorf(format string, v ...any) {
|
||||
logger.logOrDiscard(func() {
|
||||
Errorf(format, v...)
|
||||
})
|
||||
|
||||
@@ -8,35 +8,35 @@ import (
|
||||
// A Logger represents a logger.
|
||||
type Logger interface {
|
||||
// Debug logs a message at info level.
|
||||
Debug(...interface{})
|
||||
Debug(...any)
|
||||
// Debugf logs a message at info level.
|
||||
Debugf(string, ...interface{})
|
||||
Debugf(string, ...any)
|
||||
// Debugv logs a message at info level.
|
||||
Debugv(interface{})
|
||||
Debugv(any)
|
||||
// Debugw logs a message at info level.
|
||||
Debugw(string, ...LogField)
|
||||
// Error logs a message at error level.
|
||||
Error(...interface{})
|
||||
Error(...any)
|
||||
// Errorf logs a message at error level.
|
||||
Errorf(string, ...interface{})
|
||||
Errorf(string, ...any)
|
||||
// Errorv logs a message at error level.
|
||||
Errorv(interface{})
|
||||
Errorv(any)
|
||||
// Errorw logs a message at error level.
|
||||
Errorw(string, ...LogField)
|
||||
// Info logs a message at info level.
|
||||
Info(...interface{})
|
||||
Info(...any)
|
||||
// Infof logs a message at info level.
|
||||
Infof(string, ...interface{})
|
||||
Infof(string, ...any)
|
||||
// Infov logs a message at info level.
|
||||
Infov(interface{})
|
||||
Infov(any)
|
||||
// Infow logs a message at info level.
|
||||
Infow(string, ...LogField)
|
||||
// Slow logs a message at slow level.
|
||||
Slow(...interface{})
|
||||
Slow(...any)
|
||||
// Slowf logs a message at slow level.
|
||||
Slowf(string, ...interface{})
|
||||
Slowf(string, ...any)
|
||||
// Slowv logs a message at slow level.
|
||||
Slowv(interface{})
|
||||
Slowv(any)
|
||||
// Sloww logs a message at slow level.
|
||||
Sloww(string, ...LogField)
|
||||
// WithCallerSkip returns a new logger with the given caller skip.
|
||||
|
||||
@@ -20,6 +20,8 @@ var (
|
||||
timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
||||
logLevel uint32
|
||||
encoding uint32 = jsonEncodingType
|
||||
// maxContentLength is used to truncate the log content, 0 for not truncating.
|
||||
maxContentLength uint32
|
||||
// use uint32 for atomic operations
|
||||
disableLog uint32
|
||||
disableStat uint32
|
||||
@@ -32,13 +34,13 @@ type (
|
||||
// LogField is a key-value pair that will be added to the log entry.
|
||||
LogField struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
Value any
|
||||
}
|
||||
|
||||
// LogOption defines the method to customize the logging.
|
||||
LogOption func(options *logOptions)
|
||||
|
||||
logEntry map[string]interface{}
|
||||
logEntry map[string]any
|
||||
|
||||
logOptions struct {
|
||||
gzipEnabled bool
|
||||
@@ -65,23 +67,31 @@ func Close() error {
|
||||
}
|
||||
|
||||
// Debug writes v into access log.
|
||||
func Debug(v ...interface{}) {
|
||||
writeDebug(fmt.Sprint(v...))
|
||||
func Debug(v ...any) {
|
||||
if shallLog(DebugLevel) {
|
||||
writeDebug(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Debugf writes v with format into access log.
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
writeDebug(fmt.Sprintf(format, v...))
|
||||
func Debugf(format string, v ...any) {
|
||||
if shallLog(DebugLevel) {
|
||||
writeDebug(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Debugv writes v into access log with json content.
|
||||
func Debugv(v interface{}) {
|
||||
writeDebug(v)
|
||||
func Debugv(v any) {
|
||||
if shallLog(DebugLevel) {
|
||||
writeDebug(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Debugw writes msg along with fields into access log.
|
||||
func Debugw(msg string, fields ...LogField) {
|
||||
writeDebug(msg, fields...)
|
||||
if shallLog(DebugLevel) {
|
||||
writeDebug(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
// Disable disables the logging.
|
||||
@@ -96,40 +106,52 @@ func DisableStat() {
|
||||
}
|
||||
|
||||
// Error writes v into error log.
|
||||
func Error(v ...interface{}) {
|
||||
writeError(fmt.Sprint(v...))
|
||||
func Error(v ...any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
writeError(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf writes v with format into error log.
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
writeError(fmt.Errorf(format, v...).Error())
|
||||
func Errorf(format string, v ...any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
writeError(fmt.Errorf(format, v...).Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorStack writes v along with call stack into error log.
|
||||
func ErrorStack(v ...interface{}) {
|
||||
// there is newline in stack string
|
||||
writeStack(fmt.Sprint(v...))
|
||||
func ErrorStack(v ...any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
// there is newline in stack string
|
||||
writeStack(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorStackf writes v along with call stack in format into error log.
|
||||
func ErrorStackf(format string, v ...interface{}) {
|
||||
// there is newline in stack string
|
||||
writeStack(fmt.Sprintf(format, v...))
|
||||
func ErrorStackf(format string, v ...any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
// there is newline in stack string
|
||||
writeStack(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Errorv writes v into error log with json content.
|
||||
// No call stack attached, because not elegant to pack the messages.
|
||||
func Errorv(v interface{}) {
|
||||
writeError(v)
|
||||
func Errorv(v any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
writeError(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Errorw writes msg along with fields into error log.
|
||||
func Errorw(msg string, fields ...LogField) {
|
||||
writeError(msg, fields...)
|
||||
if shallLog(ErrorLevel) {
|
||||
writeError(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
// Field returns a LogField for the given key and value.
|
||||
func Field(key string, value interface{}) LogField {
|
||||
func Field(key string, value any) LogField {
|
||||
switch val := value.(type) {
|
||||
case error:
|
||||
return LogField{Key: key, Value: val.Error()}
|
||||
@@ -167,23 +189,31 @@ func Field(key string, value interface{}) LogField {
|
||||
}
|
||||
|
||||
// Info writes v into access log.
|
||||
func Info(v ...interface{}) {
|
||||
writeInfo(fmt.Sprint(v...))
|
||||
func Info(v ...any) {
|
||||
if shallLog(InfoLevel) {
|
||||
writeInfo(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Infof writes v with format into access log.
|
||||
func Infof(format string, v ...interface{}) {
|
||||
writeInfo(fmt.Sprintf(format, v...))
|
||||
func Infof(format string, v ...any) {
|
||||
if shallLog(InfoLevel) {
|
||||
writeInfo(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Infov writes v into access log with json content.
|
||||
func Infov(v interface{}) {
|
||||
writeInfo(v)
|
||||
func Infov(v any) {
|
||||
if shallLog(InfoLevel) {
|
||||
writeInfo(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Infow writes msg along with fields into access log.
|
||||
func Infow(msg string, fields ...LogField) {
|
||||
writeInfo(msg, fields...)
|
||||
if shallLog(InfoLevel) {
|
||||
writeInfo(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
// Must checks if err is nil, otherwise logs the error and exits.
|
||||
@@ -192,10 +222,15 @@ func Must(err error) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := err.Error()
|
||||
msg := fmt.Sprintf("%+v\n\n%s", err.Error(), debug.Stack())
|
||||
log.Print(msg)
|
||||
getWriter().Severe(msg)
|
||||
os.Exit(1)
|
||||
|
||||
if ExitOnFatal.True() {
|
||||
os.Exit(1)
|
||||
} else {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// MustSetup sets up logging with given config c. It exits on error.
|
||||
@@ -230,10 +265,16 @@ func SetUp(c LogConf) (err error) {
|
||||
setupOnce.Do(func() {
|
||||
setupLogLevel(c)
|
||||
|
||||
if !c.Stat {
|
||||
DisableStat()
|
||||
}
|
||||
|
||||
if len(c.TimeFormat) > 0 {
|
||||
timeFormat = c.TimeFormat
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&maxContentLength, c.MaxContentLength)
|
||||
|
||||
switch c.Encoding {
|
||||
case plainEncoding:
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
@@ -255,43 +296,59 @@ func SetUp(c LogConf) (err error) {
|
||||
}
|
||||
|
||||
// Severe writes v into severe log.
|
||||
func Severe(v ...interface{}) {
|
||||
writeSevere(fmt.Sprint(v...))
|
||||
func Severe(v ...any) {
|
||||
if shallLog(SevereLevel) {
|
||||
writeSevere(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Severef writes v with format into severe log.
|
||||
func Severef(format string, v ...interface{}) {
|
||||
writeSevere(fmt.Sprintf(format, v...))
|
||||
func Severef(format string, v ...any) {
|
||||
if shallLog(SevereLevel) {
|
||||
writeSevere(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Slow writes v into slow log.
|
||||
func Slow(v ...interface{}) {
|
||||
writeSlow(fmt.Sprint(v...))
|
||||
func Slow(v ...any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
writeSlow(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Slowf writes v with format into slow log.
|
||||
func Slowf(format string, v ...interface{}) {
|
||||
writeSlow(fmt.Sprintf(format, v...))
|
||||
func Slowf(format string, v ...any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
writeSlow(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Slowv writes v into slow log with json content.
|
||||
func Slowv(v interface{}) {
|
||||
writeSlow(v)
|
||||
func Slowv(v any) {
|
||||
if shallLog(ErrorLevel) {
|
||||
writeSlow(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Sloww writes msg along with fields into slow log.
|
||||
func Sloww(msg string, fields ...LogField) {
|
||||
writeSlow(msg, fields...)
|
||||
if shallLog(ErrorLevel) {
|
||||
writeSlow(msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
// Stat writes v into stat log.
|
||||
func Stat(v ...interface{}) {
|
||||
writeStat(fmt.Sprint(v...))
|
||||
func Stat(v ...any) {
|
||||
if shallLogStat() && shallLog(InfoLevel) {
|
||||
writeStat(fmt.Sprint(v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Statf writes v with format into stat log.
|
||||
func Statf(format string, v ...interface{}) {
|
||||
writeStat(fmt.Sprintf(format, v...))
|
||||
func Statf(format string, v ...any) {
|
||||
if shallLogStat() && shallLog(InfoLevel) {
|
||||
writeStat(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// WithCooldownMillis customizes logging on writing call stack interval.
|
||||
@@ -345,14 +402,16 @@ func createOutput(path string) (io.WriteCloser, error) {
|
||||
return nil, ErrLogPathNotSet
|
||||
}
|
||||
|
||||
var rule RotateRule
|
||||
switch options.rotationRule {
|
||||
case sizeRotationRule:
|
||||
return NewLogger(path, NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays,
|
||||
options.maxSize, options.maxBackups, options.gzipEnabled), options.gzipEnabled)
|
||||
rule = NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays, options.maxSize,
|
||||
options.maxBackups, options.gzipEnabled)
|
||||
default:
|
||||
return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays,
|
||||
options.gzipEnabled), options.gzipEnabled)
|
||||
rule = DefaultRotateRule(path, backupFileDelimiter, options.keepDays, options.gzipEnabled)
|
||||
}
|
||||
|
||||
return NewLogger(path, rule, options.gzipEnabled)
|
||||
}
|
||||
|
||||
func getWriter() Writer {
|
||||
@@ -414,44 +473,58 @@ func shallLogStat() bool {
|
||||
return atomic.LoadUint32(&disableStat) == 0
|
||||
}
|
||||
|
||||
func writeDebug(val interface{}, fields ...LogField) {
|
||||
if shallLog(DebugLevel) {
|
||||
getWriter().Debug(val, addCaller(fields...)...)
|
||||
}
|
||||
// writeDebug writes v into debug log.
|
||||
// Not checking shallLog here is for performance consideration.
|
||||
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||
// The caller should check shallLog before calling this function.
|
||||
func writeDebug(val any, fields ...LogField) {
|
||||
getWriter().Debug(val, addCaller(fields...)...)
|
||||
}
|
||||
|
||||
func writeError(val interface{}, fields ...LogField) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Error(val, addCaller(fields...)...)
|
||||
}
|
||||
// writeError writes v into error log.
|
||||
// Not checking shallLog here is for performance consideration.
|
||||
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||
// The caller should check shallLog before calling this function.
|
||||
func writeError(val any, fields ...LogField) {
|
||||
getWriter().Error(val, addCaller(fields...)...)
|
||||
}
|
||||
|
||||
func writeInfo(val interface{}, fields ...LogField) {
|
||||
if shallLog(InfoLevel) {
|
||||
getWriter().Info(val, addCaller(fields...)...)
|
||||
}
|
||||
// writeInfo writes v into info log.
|
||||
// Not checking shallLog here is for performance consideration.
|
||||
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||
// The caller should check shallLog before calling this function.
|
||||
func writeInfo(val any, fields ...LogField) {
|
||||
getWriter().Info(val, addCaller(fields...)...)
|
||||
}
|
||||
|
||||
// writeSevere writes v into severe log.
|
||||
// Not checking shallLog here is for performance consideration.
|
||||
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||
// The caller should check shallLog before calling this function.
|
||||
func writeSevere(msg string) {
|
||||
if shallLog(SevereLevel) {
|
||||
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||
}
|
||||
getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||
}
|
||||
|
||||
func writeSlow(val interface{}, fields ...LogField) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Slow(val, addCaller(fields...)...)
|
||||
}
|
||||
// writeSlow writes v into slow log.
|
||||
// Not checking shallLog here is for performance consideration.
|
||||
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||
// The caller should check shallLog before calling this function.
|
||||
func writeSlow(val any, fields ...LogField) {
|
||||
getWriter().Slow(val, addCaller(fields...)...)
|
||||
}
|
||||
|
||||
// writeStack writes v into stack log.
|
||||
// Not checking shallLog here is for performance consideration.
|
||||
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||
// The caller should check shallLog before calling this function.
|
||||
func writeStack(msg string) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||
}
|
||||
getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
|
||||
}
|
||||
|
||||
// writeStat writes v into stat log.
|
||||
// Not checking shallLog here is for performance consideration.
|
||||
// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled.
|
||||
// The caller should check shallLog before calling this function.
|
||||
func writeStat(msg string) {
|
||||
if shallLogStat() && shallLog(InfoLevel) {
|
||||
getWriter().Stat(msg, addCaller()...)
|
||||
}
|
||||
getWriter().Stat(msg, addCaller()...)
|
||||
}
|
||||
|
||||
@@ -24,54 +24,58 @@ var (
|
||||
_ Writer = (*mockWriter)(nil)
|
||||
)
|
||||
|
||||
func init() {
|
||||
ExitOnFatal.Set(false)
|
||||
}
|
||||
|
||||
type mockWriter struct {
|
||||
lock sync.Mutex
|
||||
builder strings.Builder
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Alert(v interface{}) {
|
||||
func (mw *mockWriter) Alert(v any) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelAlert, v)
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Debug(v interface{}, fields ...LogField) {
|
||||
func (mw *mockWriter) Debug(v any, fields ...LogField) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelDebug, v, fields...)
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Error(v interface{}, fields ...LogField) {
|
||||
func (mw *mockWriter) Error(v any, fields ...LogField) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelError, v, fields...)
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Info(v interface{}, fields ...LogField) {
|
||||
func (mw *mockWriter) Info(v any, fields ...LogField) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelInfo, v, fields...)
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Severe(v interface{}) {
|
||||
func (mw *mockWriter) Severe(v any) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelSevere, v)
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Slow(v interface{}, fields ...LogField) {
|
||||
func (mw *mockWriter) Slow(v any, fields ...LogField) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelSlow, v, fields...)
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Stack(v interface{}) {
|
||||
func (mw *mockWriter) Stack(v any) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelError, v)
|
||||
}
|
||||
|
||||
func (mw *mockWriter) Stat(v interface{}, fields ...LogField) {
|
||||
func (mw *mockWriter) Stat(v any, fields ...LogField) {
|
||||
mw.lock.Lock()
|
||||
defer mw.lock.Unlock()
|
||||
output(&mw.builder, levelStat, v, fields...)
|
||||
@@ -103,41 +107,41 @@ func TestField(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
f LogField
|
||||
want map[string]interface{}
|
||||
want map[string]any
|
||||
}{
|
||||
{
|
||||
name: "error",
|
||||
f: Field("foo", errors.New("bar")),
|
||||
want: map[string]interface{}{
|
||||
want: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "errors",
|
||||
f: Field("foo", []error{errors.New("bar"), errors.New("baz")}),
|
||||
want: map[string]interface{}{
|
||||
"foo": []interface{}{"bar", "baz"},
|
||||
want: map[string]any{
|
||||
"foo": []any{"bar", "baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "strings",
|
||||
f: Field("foo", []string{"bar", "baz"}),
|
||||
want: map[string]interface{}{
|
||||
"foo": []interface{}{"bar", "baz"},
|
||||
want: map[string]any{
|
||||
"foo": []any{"bar", "baz"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duration",
|
||||
f: Field("foo", time.Second),
|
||||
want: map[string]interface{}{
|
||||
want: map[string]any{
|
||||
"foo": "1s",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "durations",
|
||||
f: Field("foo", []time.Duration{time.Second, 2 * time.Second}),
|
||||
want: map[string]interface{}{
|
||||
"foo": []interface{}{"1s", "2s"},
|
||||
want: map[string]any{
|
||||
"foo": []any{"1s", "2s"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -146,22 +150,22 @@ func TestField(t *testing.T) {
|
||||
time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC),
|
||||
time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC),
|
||||
}),
|
||||
want: map[string]interface{}{
|
||||
"foo": []interface{}{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
|
||||
want: map[string]any{
|
||||
"foo": []any{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stringer",
|
||||
f: Field("foo", ValStringer{val: "bar"}),
|
||||
want: map[string]interface{}{
|
||||
want: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stringers",
|
||||
f: Field("foo", []fmt.Stringer{ValStringer{val: "bar"}, ValStringer{val: "baz"}}),
|
||||
want: map[string]interface{}{
|
||||
"foo": []interface{}{"bar", "baz"},
|
||||
want: map[string]any{
|
||||
"foo": []any{"bar", "baz"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -208,12 +212,18 @@ func TestFileLineConsoleMode(t *testing.T) {
|
||||
assert.True(t, w.Contains(fmt.Sprintf("%s:%d", file, line+1)))
|
||||
}
|
||||
|
||||
func TestMust(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
Must(errors.New("foo"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestStructedLogAlert(t *testing.T) {
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelAlert, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelAlert, w, func(v ...any) {
|
||||
Alert(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -223,7 +233,7 @@ func TestStructedLogDebug(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...any) {
|
||||
Debug(v...)
|
||||
})
|
||||
}
|
||||
@@ -233,7 +243,7 @@ func TestStructedLogDebugf(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...any) {
|
||||
Debugf(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -243,7 +253,7 @@ func TestStructedLogDebugv(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...any) {
|
||||
Debugv(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -253,7 +263,7 @@ func TestStructedLogDebugw(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelDebug, w, func(v ...any) {
|
||||
Debugw(fmt.Sprint(v...), Field("foo", time.Second))
|
||||
})
|
||||
}
|
||||
@@ -263,7 +273,7 @@ func TestStructedLogError(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelError, w, func(v ...any) {
|
||||
Error(v...)
|
||||
})
|
||||
}
|
||||
@@ -273,7 +283,7 @@ func TestStructedLogErrorf(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelError, w, func(v ...any) {
|
||||
Errorf("%s", fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -283,7 +293,7 @@ func TestStructedLogErrorv(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelError, w, func(v ...any) {
|
||||
Errorv(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -293,7 +303,7 @@ func TestStructedLogErrorw(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelError, w, func(v ...any) {
|
||||
Errorw(fmt.Sprint(v...), Field("foo", "bar"))
|
||||
})
|
||||
}
|
||||
@@ -303,7 +313,7 @@ func TestStructedLogInfo(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...any) {
|
||||
Info(v...)
|
||||
})
|
||||
}
|
||||
@@ -313,7 +323,7 @@ func TestStructedLogInfof(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...any) {
|
||||
Infof("%s", fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -323,7 +333,7 @@ func TestStructedLogInfov(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...any) {
|
||||
Infov(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -333,7 +343,7 @@ func TestStructedLogInfow(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelInfo, w, func(v ...any) {
|
||||
Infow(fmt.Sprint(v...), Field("foo", "bar"))
|
||||
})
|
||||
}
|
||||
@@ -343,7 +353,7 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -359,7 +369,7 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -375,7 +385,7 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -391,7 +401,7 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -409,7 +419,7 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
||||
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||
old := atomic.LoadUint32(&encoding)
|
||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||
defer func() {
|
||||
@@ -425,7 +435,7 @@ func TestStructedLogSlow(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...any) {
|
||||
Slow(v...)
|
||||
})
|
||||
}
|
||||
@@ -435,7 +445,7 @@ func TestStructedLogSlowf(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...any) {
|
||||
Slowf(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -445,7 +455,7 @@ func TestStructedLogSlowv(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...any) {
|
||||
Slowv(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -455,7 +465,7 @@ func TestStructedLogSloww(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelSlow, w, func(v ...any) {
|
||||
Sloww(fmt.Sprint(v...), Field("foo", time.Second))
|
||||
})
|
||||
}
|
||||
@@ -465,7 +475,7 @@ func TestStructedLogStat(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelStat, w, func(v ...any) {
|
||||
Stat(v...)
|
||||
})
|
||||
}
|
||||
@@ -475,7 +485,7 @@ func TestStructedLogStatf(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelStat, w, func(v ...any) {
|
||||
Statf(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -485,7 +495,7 @@ func TestStructedLogSevere(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelSevere, w, func(v ...any) {
|
||||
Severe(v...)
|
||||
})
|
||||
}
|
||||
@@ -495,7 +505,7 @@ func TestStructedLogSeveref(t *testing.T) {
|
||||
old := writer.Swap(w)
|
||||
defer writer.Store(old)
|
||||
|
||||
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
|
||||
doTestStructedLog(t, levelSevere, w, func(v ...any) {
|
||||
Severef(fmt.Sprint(v...))
|
||||
})
|
||||
}
|
||||
@@ -507,7 +517,7 @@ func TestStructedLogWithDuration(t *testing.T) {
|
||||
defer writer.Store(old)
|
||||
|
||||
WithDuration(time.Second).Info(message)
|
||||
var entry map[string]interface{}
|
||||
var entry map[string]any
|
||||
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -529,9 +539,9 @@ func TestSetLevel(t *testing.T) {
|
||||
|
||||
func TestSetLevelTwiceWithMode(t *testing.T) {
|
||||
testModes := []string{
|
||||
"mode",
|
||||
"console",
|
||||
"volumn",
|
||||
"mode",
|
||||
}
|
||||
w := new(mockWriter)
|
||||
old := writer.Swap(w)
|
||||
@@ -574,26 +584,38 @@ func TestSetup(t *testing.T) {
|
||||
atomic.StoreUint32(&encoding, jsonEncodingType)
|
||||
}()
|
||||
|
||||
setupOnce = sync.Once{}
|
||||
MustSetup(LogConf{
|
||||
ServiceName: "any",
|
||||
Mode: "console",
|
||||
Encoding: "json",
|
||||
TimeFormat: timeFormat,
|
||||
})
|
||||
setupOnce = sync.Once{}
|
||||
MustSetup(LogConf{
|
||||
ServiceName: "any",
|
||||
Mode: "console",
|
||||
TimeFormat: timeFormat,
|
||||
})
|
||||
setupOnce = sync.Once{}
|
||||
MustSetup(LogConf{
|
||||
ServiceName: "any",
|
||||
Mode: "file",
|
||||
Path: os.TempDir(),
|
||||
})
|
||||
setupOnce = sync.Once{}
|
||||
MustSetup(LogConf{
|
||||
ServiceName: "any",
|
||||
Mode: "volume",
|
||||
Path: os.TempDir(),
|
||||
})
|
||||
setupOnce = sync.Once{}
|
||||
MustSetup(LogConf{
|
||||
ServiceName: "any",
|
||||
Mode: "console",
|
||||
TimeFormat: timeFormat,
|
||||
})
|
||||
setupOnce = sync.Once{}
|
||||
MustSetup(LogConf{
|
||||
ServiceName: "any",
|
||||
Mode: "console",
|
||||
@@ -767,11 +789,11 @@ func put(b []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...interface{})) {
|
||||
func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...any)) {
|
||||
const message = "hello there"
|
||||
write(message)
|
||||
|
||||
var entry map[string]interface{}
|
||||
var entry map[string]any
|
||||
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -782,7 +804,7 @@ func doTestStructedLog(t *testing.T, level string, w *mockWriter, write func(...
|
||||
assert.True(t, strings.Contains(val.(string), message))
|
||||
}
|
||||
|
||||
func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...interface{})) {
|
||||
func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...any)) {
|
||||
const message = "hello there"
|
||||
write(message)
|
||||
assert.True(t, strings.Contains(w.String(), message))
|
||||
@@ -791,9 +813,12 @@ func doTestStructedLogConsole(t *testing.T, w *mockWriter, write func(...interfa
|
||||
func testSetLevelTwiceWithMode(t *testing.T, mode string, w *mockWriter) {
|
||||
writer.Store(nil)
|
||||
SetUp(LogConf{
|
||||
Mode: mode,
|
||||
Level: "error",
|
||||
Path: "/dev/null",
|
||||
Mode: mode,
|
||||
Level: "debug",
|
||||
Path: "/dev/null",
|
||||
Encoding: plainEncoding,
|
||||
Stat: false,
|
||||
TimeFormat: time.RFC3339,
|
||||
})
|
||||
SetUp(LogConf{
|
||||
Mode: mode,
|
||||
@@ -819,8 +844,8 @@ func (v ValStringer) String() string {
|
||||
return v.val
|
||||
}
|
||||
|
||||
func validateFields(t *testing.T, content string, fields map[string]interface{}) {
|
||||
var m map[string]interface{}
|
||||
func validateFields(t *testing.T, content string, fields map[string]any) {
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(content), &m); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
84
core/logx/logtest/logtest.go
Normal file
84
core/logx/logtest/logtest.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package logtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
buf *bytes.Buffer
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func Discard(t *testing.T) {
|
||||
prev := logx.Reset()
|
||||
logx.SetWriter(logx.NewWriter(io.Discard))
|
||||
|
||||
t.Cleanup(func() {
|
||||
logx.SetWriter(prev)
|
||||
})
|
||||
}
|
||||
|
||||
func NewCollector(t *testing.T) *Buffer {
|
||||
var buf bytes.Buffer
|
||||
writer := logx.NewWriter(&buf)
|
||||
prev := logx.Reset()
|
||||
logx.SetWriter(writer)
|
||||
|
||||
t.Cleanup(func() {
|
||||
logx.SetWriter(prev)
|
||||
})
|
||||
|
||||
return &Buffer{
|
||||
buf: &buf,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
return b.buf.Bytes()
|
||||
}
|
||||
|
||||
func (b *Buffer) Content() string {
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(b.buf.Bytes(), &m); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
content, ok := m["content"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch val := content.(type) {
|
||||
case string:
|
||||
return val
|
||||
default:
|
||||
// err is impossible to be not nil, unmarshaled from b.buf.Bytes()
|
||||
bs, _ := json.Marshal(content)
|
||||
return string(bs)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Reset() {
|
||||
b.buf.Reset()
|
||||
}
|
||||
|
||||
func (b *Buffer) String() string {
|
||||
return b.buf.String()
|
||||
}
|
||||
|
||||
func PanicOnFatal(t *testing.T) {
|
||||
ok := logx.ExitOnFatal.CompareAndSwap(true, false)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
logx.ExitOnFatal.CompareAndSwap(false, true)
|
||||
})
|
||||
}
|
||||
44
core/logx/logtest/logtest_test.go
Normal file
44
core/logx/logtest/logtest_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package logtest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
func TestCollector(t *testing.T) {
|
||||
const input = "hello"
|
||||
c := NewCollector(t)
|
||||
logx.Info(input)
|
||||
assert.Equal(t, input, c.Content())
|
||||
assert.Contains(t, c.String(), input)
|
||||
c.Reset()
|
||||
assert.Empty(t, c.Bytes())
|
||||
}
|
||||
|
||||
func TestPanicOnFatal(t *testing.T) {
|
||||
const input = "hello"
|
||||
Discard(t)
|
||||
logx.Info(input)
|
||||
|
||||
PanicOnFatal(t)
|
||||
PanicOnFatal(t)
|
||||
assert.Panics(t, func() {
|
||||
logx.Must(errors.New("foo"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestCollectorContent(t *testing.T) {
|
||||
const input = "hello"
|
||||
c := NewCollector(t)
|
||||
c.buf.WriteString(input)
|
||||
assert.Empty(t, c.Content())
|
||||
c.Reset()
|
||||
c.buf.WriteString(`{}`)
|
||||
assert.Empty(t, c.Content())
|
||||
c.Reset()
|
||||
c.buf.WriteString(`{"content":1}`)
|
||||
assert.Equal(t, "1", c.Content())
|
||||
}
|
||||
@@ -52,27 +52,27 @@ type LogConf struct {
|
||||
```go
|
||||
type Logger interface {
|
||||
// Error logs a message at error level.
|
||||
Error(...interface{})
|
||||
Error(...any)
|
||||
// Errorf logs a message at error level.
|
||||
Errorf(string, ...interface{})
|
||||
Errorf(string, ...any)
|
||||
// Errorv logs a message at error level.
|
||||
Errorv(interface{})
|
||||
Errorv(any)
|
||||
// Errorw logs a message at error level.
|
||||
Errorw(string, ...LogField)
|
||||
// Info logs a message at info level.
|
||||
Info(...interface{})
|
||||
Info(...any)
|
||||
// Infof logs a message at info level.
|
||||
Infof(string, ...interface{})
|
||||
Infof(string, ...any)
|
||||
// Infov logs a message at info level.
|
||||
Infov(interface{})
|
||||
Infov(any)
|
||||
// Infow logs a message at info level.
|
||||
Infow(string, ...LogField)
|
||||
// Slow logs a message at slow level.
|
||||
Slow(...interface{})
|
||||
Slow(...any)
|
||||
// Slowf logs a message at slow level.
|
||||
Slowf(string, ...interface{})
|
||||
Slowf(string, ...any)
|
||||
// Slowv logs a message at slow level.
|
||||
Slowv(interface{})
|
||||
Slowv(any)
|
||||
// Sloww logs a message at slow level.
|
||||
Sloww(string, ...LogField)
|
||||
// WithContext returns a new logger with the given context.
|
||||
@@ -165,7 +165,7 @@ func NewSensitiveLogger(writer logx.Writer) *SensitiveLogger {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *SensitiveLogger) Info(msg interface{}, fields ...logx.LogField) {
|
||||
func (l *SensitiveLogger) Info(msg any, fields ...logx.LogField) {
|
||||
if m, ok := msg.(Message); ok {
|
||||
l.Writer.Info(Message{
|
||||
Name: m.Name,
|
||||
|
||||
@@ -51,27 +51,27 @@ type LogConf struct {
|
||||
```go
|
||||
type Logger interface {
|
||||
// Error logs a message at error level.
|
||||
Error(...interface{})
|
||||
Error(...any)
|
||||
// Errorf logs a message at error level.
|
||||
Errorf(string, ...interface{})
|
||||
Errorf(string, ...any)
|
||||
// Errorv logs a message at error level.
|
||||
Errorv(interface{})
|
||||
Errorv(any)
|
||||
// Errorw logs a message at error level.
|
||||
Errorw(string, ...LogField)
|
||||
// Info logs a message at info level.
|
||||
Info(...interface{})
|
||||
Info(...any)
|
||||
// Infof logs a message at info level.
|
||||
Infof(string, ...interface{})
|
||||
Infof(string, ...any)
|
||||
// Infov logs a message at info level.
|
||||
Infov(interface{})
|
||||
Infov(any)
|
||||
// Infow logs a message at info level.
|
||||
Infow(string, ...LogField)
|
||||
// Slow logs a message at slow level.
|
||||
Slow(...interface{})
|
||||
Slow(...any)
|
||||
// Slowf logs a message at slow level.
|
||||
Slowf(string, ...interface{})
|
||||
Slowf(string, ...any)
|
||||
// Slowv logs a message at slow level.
|
||||
Slowv(interface{})
|
||||
Slowv(any)
|
||||
// Sloww logs a message at slow level.
|
||||
Sloww(string, ...LogField)
|
||||
// WithContext returns a new logger with the given context.
|
||||
@@ -164,7 +164,7 @@ func NewSensitiveLogger(writer logx.Writer) *SensitiveLogger {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *SensitiveLogger) Info(msg interface{}, fields ...logx.LogField) {
|
||||
func (l *SensitiveLogger) Info(msg any, fields ...logx.LogField) {
|
||||
if m, ok := msg.(Message); ok {
|
||||
l.Writer.Info(Message{
|
||||
Name: m.Name,
|
||||
|
||||
@@ -40,15 +40,15 @@ type richLogger struct {
|
||||
fields []LogField
|
||||
}
|
||||
|
||||
func (l *richLogger) Debug(v ...interface{}) {
|
||||
func (l *richLogger) Debug(v ...any) {
|
||||
l.debug(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Debugf(format string, v ...interface{}) {
|
||||
func (l *richLogger) Debugf(format string, v ...any) {
|
||||
l.debug(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Debugv(v interface{}) {
|
||||
func (l *richLogger) Debugv(v any) {
|
||||
l.debug(v)
|
||||
}
|
||||
|
||||
@@ -56,31 +56,31 @@ func (l *richLogger) Debugw(msg string, fields ...LogField) {
|
||||
l.debug(msg, fields...)
|
||||
}
|
||||
|
||||
func (l *richLogger) Error(v ...interface{}) {
|
||||
func (l *richLogger) Error(v ...any) {
|
||||
l.err(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Errorf(format string, v ...interface{}) {
|
||||
func (l *richLogger) Errorf(format string, v ...any) {
|
||||
l.err(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Errorv(v interface{}) {
|
||||
l.err(fmt.Sprint(v))
|
||||
func (l *richLogger) Errorv(v any) {
|
||||
l.err(v)
|
||||
}
|
||||
|
||||
func (l *richLogger) Errorw(msg string, fields ...LogField) {
|
||||
l.err(msg, fields...)
|
||||
}
|
||||
|
||||
func (l *richLogger) Info(v ...interface{}) {
|
||||
func (l *richLogger) Info(v ...any) {
|
||||
l.info(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Infof(format string, v ...interface{}) {
|
||||
func (l *richLogger) Infof(format string, v ...any) {
|
||||
l.info(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Infov(v interface{}) {
|
||||
func (l *richLogger) Infov(v any) {
|
||||
l.info(v)
|
||||
}
|
||||
|
||||
@@ -88,15 +88,15 @@ func (l *richLogger) Infow(msg string, fields ...LogField) {
|
||||
l.info(msg, fields...)
|
||||
}
|
||||
|
||||
func (l *richLogger) Slow(v ...interface{}) {
|
||||
func (l *richLogger) Slow(v ...any) {
|
||||
l.slow(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Slowf(format string, v ...interface{}) {
|
||||
func (l *richLogger) Slowf(format string, v ...any) {
|
||||
l.slow(fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *richLogger) Slowv(v interface{}) {
|
||||
func (l *richLogger) Slowv(v any) {
|
||||
l.slow(v)
|
||||
}
|
||||
|
||||
@@ -156,25 +156,25 @@ func (l *richLogger) buildFields(fields ...LogField) []LogField {
|
||||
return fields
|
||||
}
|
||||
|
||||
func (l *richLogger) debug(v interface{}, fields ...LogField) {
|
||||
func (l *richLogger) debug(v any, fields ...LogField) {
|
||||
if shallLog(DebugLevel) {
|
||||
getWriter().Debug(v, l.buildFields(fields...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *richLogger) err(v interface{}, fields ...LogField) {
|
||||
func (l *richLogger) err(v any, fields ...LogField) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Error(v, l.buildFields(fields...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *richLogger) info(v interface{}, fields ...LogField) {
|
||||
func (l *richLogger) info(v any, fields ...LogField) {
|
||||
if shallLog(InfoLevel) {
|
||||
getWriter().Info(v, l.buildFields(fields...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *richLogger) slow(v interface{}, fields ...LogField) {
|
||||
func (l *richLogger) slow(v any, fields ...LogField) {
|
||||
if shallLog(ErrorLevel) {
|
||||
getWriter().Slow(v, l.buildFields(fields...)...)
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ func TestTraceDebug(t *testing.T) {
|
||||
l.WithDuration(time.Second).Debugv(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Debugv(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Debugw(testlog, Field("foo", "bar"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
|
||||
@@ -103,6 +106,9 @@ func TestTraceError(t *testing.T) {
|
||||
l.WithDuration(time.Second).Errorv(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Errorv(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
||||
@@ -137,6 +143,9 @@ func TestTraceInfo(t *testing.T) {
|
||||
l.WithDuration(time.Second).Infov(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
||||
@@ -173,6 +182,9 @@ func TestTraceInfoConsole(t *testing.T) {
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Infov(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
}
|
||||
|
||||
func TestTraceSlow(t *testing.T) {
|
||||
@@ -204,6 +216,9 @@ func TestTraceSlow(t *testing.T) {
|
||||
l.WithDuration(time.Second).Slowv(testlog)
|
||||
validate(t, w.String(), true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Slowv(testobj)
|
||||
validateContentType(t, w.String(), map[string]any{}, true, true)
|
||||
w.Reset()
|
||||
l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
|
||||
validate(t, w.String(), true, true)
|
||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
||||
@@ -311,8 +326,32 @@ func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
|
||||
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
|
||||
}
|
||||
|
||||
type mockValue struct {
|
||||
Trace string `json:"trace"`
|
||||
Span string `json:"span"`
|
||||
Foo string `json:"foo"`
|
||||
func validateContentType(t *testing.T, body string, expectedType any, expectedTrace, expectedSpan bool) {
|
||||
var val mockValue
|
||||
dec := json.NewDecoder(strings.NewReader(body))
|
||||
|
||||
for {
|
||||
var doc mockValue
|
||||
err := dec.Decode(&doc)
|
||||
if err == io.EOF {
|
||||
// all done
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
val = doc
|
||||
}
|
||||
|
||||
assert.IsType(t, expectedType, val.Content, body)
|
||||
assert.Equal(t, expectedTrace, len(val.Trace) > 0, body)
|
||||
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
|
||||
}
|
||||
|
||||
type mockValue struct {
|
||||
Trace string `json:"trace"`
|
||||
Span string `json:"span"`
|
||||
Foo string `json:"foo"`
|
||||
Content any `json:"content"`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -237,7 +236,7 @@ func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger,
|
||||
rule: rule,
|
||||
compress: compress,
|
||||
}
|
||||
if err := l.init(); err != nil {
|
||||
if err := l.initialize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -281,7 +280,7 @@ func (l *RotateLogger) getBackupFilename() string {
|
||||
return l.backup
|
||||
}
|
||||
|
||||
func (l *RotateLogger) init() error {
|
||||
func (l *RotateLogger) initialize() error {
|
||||
l.backup = l.rule.BackupFileName()
|
||||
|
||||
if fileInfo, err := os.Stat(l.filename); err != nil {
|
||||
@@ -406,7 +405,7 @@ func (l *RotateLogger) write(v []byte) {
|
||||
func compressLogFile(file string) {
|
||||
start := time.Now()
|
||||
Infof("compressing log file: %s", file)
|
||||
if err := gzipFile(file); err != nil {
|
||||
if err := gzipFile(file, fileSys); err != nil {
|
||||
Errorf("compress error: %s", err)
|
||||
} else {
|
||||
Infof("compressed log file: %s, took %s", file, time.Since(start))
|
||||
@@ -421,25 +420,37 @@ func getNowDateInRFC3339Format() string {
|
||||
return time.Now().Format(fileTimeFormat)
|
||||
}
|
||||
|
||||
func gzipFile(file string) error {
|
||||
in, err := os.Open(file)
|
||||
func gzipFile(file string, fsys fileSystem) (err error) {
|
||||
in, err := fsys.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
defer func() {
|
||||
if e := fsys.Close(in); e != nil {
|
||||
Errorf("failed to close file: %s, error: %v", file, e)
|
||||
}
|
||||
if err == nil {
|
||||
// only remove the original file when compression is successful
|
||||
err = fsys.Remove(file)
|
||||
}
|
||||
}()
|
||||
|
||||
out, err := os.Create(fmt.Sprintf("%s%s", file, gzipExt))
|
||||
out, err := fsys.Create(fmt.Sprintf("%s%s", file, gzipExt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
defer func() {
|
||||
e := fsys.Close(out)
|
||||
if err == nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
|
||||
w := gzip.NewWriter(out)
|
||||
if _, err = io.Copy(w, in); err != nil {
|
||||
return err
|
||||
} else if err = w.Close(); err != nil {
|
||||
if _, err = fsys.Copy(w, in); err != nil {
|
||||
// failed to copy, no need to close w
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(file)
|
||||
return fsys.Close(w)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,74 @@
|
||||
package logx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/fs"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
)
|
||||
|
||||
func TestDailyRotateRuleMarkRotated(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDate(), rule.rotatedTime)
|
||||
t.Run("daily rule", func(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDate(), rule.rotatedTime)
|
||||
})
|
||||
|
||||
t.Run("daily rule", func(t *testing.T) {
|
||||
rule := DefaultRotateRule("test", "-", 1, false)
|
||||
_, ok := rule.(*DailyRotateRule)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
t.Run("no files", func(t *testing.T) {
|
||||
var rule DailyRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("bad files", func(t *testing.T) {
|
||||
rule := DailyRotateRule{
|
||||
filename: "[a-z",
|
||||
}
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("temp files", func(t *testing.T) {
|
||||
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
_ = f1.Close()
|
||||
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
_ = f2.Close()
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(f1.Name())
|
||||
_ = os.Remove(f2.Name())
|
||||
})
|
||||
rule := DailyRotateRule{
|
||||
filename: path.Join(os.TempDir(), "go-zero-test-"),
|
||||
days: 1,
|
||||
}
|
||||
assert.NotEmpty(t, rule.OutdatedFiles())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDailyRotateRuleShallRotate(t *testing.T) {
|
||||
@@ -33,20 +78,101 @@ func TestDailyRotateRuleShallRotate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
|
||||
t.Run("size limit rule", func(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
|
||||
})
|
||||
|
||||
t.Run("size limit rule", func(t *testing.T) {
|
||||
rule := NewSizeLimitRotateRule("foo", "-", 1, 1, 1, false)
|
||||
rule.MarkRotated()
|
||||
assert.Equal(t, getNowDateInRFC3339Format(), rule.(*SizeLimitRotateRule).rotatedTime)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSizeLimitRotateRuleOutdatedFiles(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.maxBackups = 0
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
t.Run("no files", func(t *testing.T) {
|
||||
var rule SizeLimitRotateRule
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.maxBackups = 0
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("bad files", func(t *testing.T) {
|
||||
rule := SizeLimitRotateRule{
|
||||
DailyRotateRule: DailyRotateRule{
|
||||
filename: "[a-z",
|
||||
},
|
||||
}
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.days = 1
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
rule.gzip = true
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("temp files", func(t *testing.T) {
|
||||
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = f1.Close()
|
||||
_ = os.Remove(f1.Name())
|
||||
_ = f2.Close()
|
||||
_ = os.Remove(f2.Name())
|
||||
_ = f3.Close()
|
||||
_ = os.Remove(f3.Name())
|
||||
})
|
||||
rule := SizeLimitRotateRule{
|
||||
DailyRotateRule: DailyRotateRule{
|
||||
filename: path.Join(os.TempDir(), "go-zero-test-"),
|
||||
days: 1,
|
||||
},
|
||||
maxBackups: 3,
|
||||
}
|
||||
assert.NotEmpty(t, rule.OutdatedFiles())
|
||||
})
|
||||
|
||||
t.Run("no backups", func(t *testing.T) {
|
||||
boundary := time.Now().Add(-time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f1, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
f2, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary)
|
||||
assert.NoError(t, err)
|
||||
boundary1 := time.Now().Add(time.Hour * time.Duration(hoursPerDay) * 2).Format(dateFormat)
|
||||
f3, err := os.CreateTemp(os.TempDir(), "go-zero-test-"+boundary1)
|
||||
assert.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = f1.Close()
|
||||
_ = os.Remove(f1.Name())
|
||||
_ = f2.Close()
|
||||
_ = os.Remove(f2.Name())
|
||||
_ = f3.Close()
|
||||
_ = os.Remove(f3.Name())
|
||||
})
|
||||
rule := SizeLimitRotateRule{
|
||||
DailyRotateRule: DailyRotateRule{
|
||||
filename: path.Join(os.TempDir(), "go-zero-test-"),
|
||||
days: 1,
|
||||
},
|
||||
}
|
||||
assert.NotEmpty(t, rule.OutdatedFiles())
|
||||
|
||||
logger := new(RotateLogger)
|
||||
logger.rule = &rule
|
||||
logger.maybeDeleteOutdatedFiles()
|
||||
assert.Empty(t, rule.OutdatedFiles())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
|
||||
@@ -60,14 +186,26 @@ func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRotateLoggerClose(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer os.Remove(filename)
|
||||
}
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, logger.Close())
|
||||
t.Run("close", func(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
if len(filename) > 0 {
|
||||
defer os.Remove(filename)
|
||||
}
|
||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
_, err = logger.Write([]byte("foo"))
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, logger.Close())
|
||||
})
|
||||
|
||||
t.Run("close and write", func(t *testing.T) {
|
||||
logger := new(RotateLogger)
|
||||
logger.done = make(chan struct{})
|
||||
close(logger.done)
|
||||
_, err := logger.Write([]byte("foo"))
|
||||
assert.ErrorIs(t, err, ErrLogFileClosed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRotateLoggerGetBackupFilename(t *testing.T) {
|
||||
@@ -178,7 +316,7 @@ func TestRotateLoggerWithSizeLimitRotateRuleClose(t *testing.T) {
|
||||
}
|
||||
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, logger.Close())
|
||||
_ = logger.Close()
|
||||
}
|
||||
|
||||
func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
|
||||
@@ -232,6 +370,23 @@ func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileTrue(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileFailed(t *testing.T) {
|
||||
old := os.Stdout
|
||||
os.Stdout = os.NewFile(0, os.DevNull)
|
||||
defer func() {
|
||||
os.Stdout = old
|
||||
}()
|
||||
|
||||
filename := stringx.RandId()
|
||||
logger, err := NewLogger(filename, new(SizeLimitRotateRule), true)
|
||||
defer os.Remove(filename)
|
||||
if assert.NoError(t, err) {
|
||||
assert.NotPanics(t, func() {
|
||||
logger.maybeCompressFile(stringx.RandId())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRotateLoggerWithSizeLimitRotateRuleRotate(t *testing.T) {
|
||||
filename, err := fs.TempFilenameWithText("foo")
|
||||
assert.Nil(t, err)
|
||||
@@ -277,6 +432,70 @@ func TestRotateLoggerWithSizeLimitRotateRuleWrite(t *testing.T) {
|
||||
logger.write([]byte(`baz`))
|
||||
}
|
||||
|
||||
func TestGzipFile(t *testing.T) {
|
||||
err := errors.New("any error")
|
||||
|
||||
t.Run("gzip file open failed", func(t *testing.T) {
|
||||
fsys := &fakeFileSystem{
|
||||
openFn: func(name string) (*os.File, error) {
|
||||
return nil, err
|
||||
},
|
||||
}
|
||||
assert.ErrorIs(t, err, gzipFile("any", fsys))
|
||||
assert.False(t, fsys.Removed())
|
||||
})
|
||||
|
||||
t.Run("gzip file create failed", func(t *testing.T) {
|
||||
fsys := &fakeFileSystem{
|
||||
createFn: func(name string) (*os.File, error) {
|
||||
return nil, err
|
||||
},
|
||||
}
|
||||
assert.ErrorIs(t, err, gzipFile("any", fsys))
|
||||
assert.False(t, fsys.Removed())
|
||||
})
|
||||
|
||||
t.Run("gzip file copy failed", func(t *testing.T) {
|
||||
fsys := &fakeFileSystem{
|
||||
copyFn: func(writer io.Writer, reader io.Reader) (int64, error) {
|
||||
return 0, err
|
||||
},
|
||||
}
|
||||
assert.ErrorIs(t, err, gzipFile("any", fsys))
|
||||
assert.False(t, fsys.Removed())
|
||||
})
|
||||
|
||||
t.Run("gzip file last close failed", func(t *testing.T) {
|
||||
var called int32
|
||||
fsys := &fakeFileSystem{
|
||||
closeFn: func(closer io.Closer) error {
|
||||
if atomic.AddInt32(&called, 1) > 2 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
assert.NoError(t, gzipFile("any", fsys))
|
||||
assert.True(t, fsys.Removed())
|
||||
})
|
||||
|
||||
t.Run("gzip file remove failed", func(t *testing.T) {
|
||||
fsys := &fakeFileSystem{
|
||||
removeFn: func(name string) error {
|
||||
return err
|
||||
},
|
||||
}
|
||||
assert.Error(t, err, gzipFile("any", fsys))
|
||||
assert.True(t, fsys.Removed())
|
||||
})
|
||||
|
||||
t.Run("gzip file everything ok", func(t *testing.T) {
|
||||
fsys := &fakeFileSystem{}
|
||||
assert.NoError(t, gzipFile("any", fsys))
|
||||
assert.True(t, fsys.Removed())
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRotateLogger(b *testing.B) {
|
||||
filename := "./test.log"
|
||||
filename2 := "./test2.log"
|
||||
@@ -328,3 +547,53 @@ func BenchmarkRotateLogger(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type fakeFileSystem struct {
|
||||
removed int32
|
||||
closeFn func(closer io.Closer) error
|
||||
copyFn func(writer io.Writer, reader io.Reader) (int64, error)
|
||||
createFn func(name string) (*os.File, error)
|
||||
openFn func(name string) (*os.File, error)
|
||||
removeFn func(name string) error
|
||||
}
|
||||
|
||||
func (f *fakeFileSystem) Close(closer io.Closer) error {
|
||||
if f.closeFn != nil {
|
||||
return f.closeFn(closer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeFileSystem) Copy(writer io.Writer, reader io.Reader) (int64, error) {
|
||||
if f.copyFn != nil {
|
||||
return f.copyFn(writer, reader)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (f *fakeFileSystem) Create(name string) (*os.File, error) {
|
||||
if f.createFn != nil {
|
||||
return f.createFn(name)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeFileSystem) Open(name string) (*os.File, error) {
|
||||
if f.openFn != nil {
|
||||
return f.openFn(name)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeFileSystem) Remove(name string) error {
|
||||
atomic.AddInt32(&f.removed, 1)
|
||||
|
||||
if f.removeFn != nil {
|
||||
return f.removeFn(name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeFileSystem) Removed() bool {
|
||||
return atomic.LoadInt32(&f.removed) > 0
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
|
||||
const testlog = "Stay hungry, stay foolish."
|
||||
|
||||
var testobj = map[string]any{"foo": "bar"}
|
||||
|
||||
func TestCollectSysLog(t *testing.T) {
|
||||
CollectSysLog()
|
||||
content := getContent(captureOutput(func() {
|
||||
@@ -42,7 +44,7 @@ func captureOutput(f func()) string {
|
||||
}
|
||||
|
||||
func getContent(jsonStr string) string {
|
||||
var entry map[string]interface{}
|
||||
var entry map[string]any
|
||||
json.Unmarshal([]byte(jsonStr), &entry)
|
||||
|
||||
val, ok := entry[contentKey]
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package logx
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/syncx"
|
||||
)
|
||||
|
||||
const (
|
||||
// DebugLevel logs everything
|
||||
@@ -16,13 +20,13 @@ const (
|
||||
const (
|
||||
jsonEncodingType = iota
|
||||
plainEncodingType
|
||||
|
||||
plainEncoding = "plain"
|
||||
plainEncodingSep = '\t'
|
||||
sizeRotationRule = "size"
|
||||
)
|
||||
|
||||
const (
|
||||
plainEncoding = "plain"
|
||||
plainEncodingSep = '\t'
|
||||
sizeRotationRule = "size"
|
||||
|
||||
accessFilename = "access.log"
|
||||
errorFilename = "error.log"
|
||||
severeFilename = "severe.log"
|
||||
@@ -53,6 +57,7 @@ const (
|
||||
spanKey = "span"
|
||||
timestampKey = "@timestamp"
|
||||
traceKey = "trace"
|
||||
truncatedKey = "truncated"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -60,4 +65,8 @@ var (
|
||||
ErrLogPathNotSet = errors.New("log path must be set")
|
||||
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
|
||||
ErrLogServiceNameNotSet = errors.New("log service name must be set")
|
||||
// ExitOnFatal defines whether to exit on fatal errors, defined here to make it easier to test.
|
||||
ExitOnFatal = syncx.ForAtomicBool(true)
|
||||
|
||||
truncatedField = Field(truncatedKey, true)
|
||||
)
|
||||
|
||||
@@ -16,15 +16,15 @@ import (
|
||||
|
||||
type (
|
||||
Writer interface {
|
||||
Alert(v interface{})
|
||||
Alert(v any)
|
||||
Close() error
|
||||
Debug(v interface{}, fields ...LogField)
|
||||
Error(v interface{}, fields ...LogField)
|
||||
Info(v interface{}, fields ...LogField)
|
||||
Severe(v interface{})
|
||||
Slow(v interface{}, fields ...LogField)
|
||||
Stack(v interface{})
|
||||
Stat(v interface{}, fields ...LogField)
|
||||
Debug(v any, fields ...LogField)
|
||||
Error(v any, fields ...LogField)
|
||||
Info(v any, fields ...LogField)
|
||||
Severe(v any)
|
||||
Slow(v any, fields ...LogField)
|
||||
Stack(v any)
|
||||
Stat(v any, fields ...LogField)
|
||||
}
|
||||
|
||||
atomicWriter struct {
|
||||
@@ -171,7 +171,7 @@ func newFileWriter(c LogConf) (Writer, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Alert(v interface{}) {
|
||||
func (w *concreteWriter) Alert(v any) {
|
||||
output(w.errorLog, levelAlert, v)
|
||||
}
|
||||
|
||||
@@ -195,69 +195,69 @@ func (w *concreteWriter) Close() error {
|
||||
return w.statLog.Close()
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Debug(v interface{}, fields ...LogField) {
|
||||
func (w *concreteWriter) Debug(v any, fields ...LogField) {
|
||||
output(w.infoLog, levelDebug, v, fields...)
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Error(v interface{}, fields ...LogField) {
|
||||
func (w *concreteWriter) Error(v any, fields ...LogField) {
|
||||
output(w.errorLog, levelError, v, fields...)
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Info(v interface{}, fields ...LogField) {
|
||||
func (w *concreteWriter) Info(v any, fields ...LogField) {
|
||||
output(w.infoLog, levelInfo, v, fields...)
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Severe(v interface{}) {
|
||||
func (w *concreteWriter) Severe(v any) {
|
||||
output(w.severeLog, levelFatal, v)
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Slow(v interface{}, fields ...LogField) {
|
||||
func (w *concreteWriter) Slow(v any, fields ...LogField) {
|
||||
output(w.slowLog, levelSlow, v, fields...)
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Stack(v interface{}) {
|
||||
func (w *concreteWriter) Stack(v any) {
|
||||
output(w.stackLog, levelError, v)
|
||||
}
|
||||
|
||||
func (w *concreteWriter) Stat(v interface{}, fields ...LogField) {
|
||||
func (w *concreteWriter) Stat(v any, fields ...LogField) {
|
||||
output(w.statLog, levelStat, v, fields...)
|
||||
}
|
||||
|
||||
type nopWriter struct{}
|
||||
|
||||
func (n nopWriter) Alert(_ interface{}) {
|
||||
func (n nopWriter) Alert(_ any) {
|
||||
}
|
||||
|
||||
func (n nopWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n nopWriter) Debug(_ interface{}, _ ...LogField) {
|
||||
func (n nopWriter) Debug(_ any, _ ...LogField) {
|
||||
}
|
||||
|
||||
func (n nopWriter) Error(_ interface{}, _ ...LogField) {
|
||||
func (n nopWriter) Error(_ any, _ ...LogField) {
|
||||
}
|
||||
|
||||
func (n nopWriter) Info(_ interface{}, _ ...LogField) {
|
||||
func (n nopWriter) Info(_ any, _ ...LogField) {
|
||||
}
|
||||
|
||||
func (n nopWriter) Severe(_ interface{}) {
|
||||
func (n nopWriter) Severe(_ any) {
|
||||
}
|
||||
|
||||
func (n nopWriter) Slow(_ interface{}, _ ...LogField) {
|
||||
func (n nopWriter) Slow(_ any, _ ...LogField) {
|
||||
}
|
||||
|
||||
func (n nopWriter) Stack(_ interface{}) {
|
||||
func (n nopWriter) Stack(_ any) {
|
||||
}
|
||||
|
||||
func (n nopWriter) Stat(_ interface{}, _ ...LogField) {
|
||||
func (n nopWriter) Stat(_ any, _ ...LogField) {
|
||||
}
|
||||
|
||||
func buildFields(fields ...LogField) []string {
|
||||
func buildPlainFields(fields ...LogField) []string {
|
||||
var items []string
|
||||
|
||||
for _, field := range fields {
|
||||
items = append(items, fmt.Sprintf("%s=%v", field.Key, field.Value))
|
||||
items = append(items, fmt.Sprintf("%s=%+v", field.Key, field.Value))
|
||||
}
|
||||
|
||||
return items
|
||||
@@ -269,15 +269,29 @@ func combineGlobalFields(fields []LogField) []LogField {
|
||||
return fields
|
||||
}
|
||||
|
||||
return append(globals.([]LogField), fields...)
|
||||
gf := globals.([]LogField)
|
||||
ret := make([]LogField, 0, len(gf)+len(fields))
|
||||
ret = append(ret, gf...)
|
||||
ret = append(ret, fields...)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func output(writer io.Writer, level string, val interface{}, fields ...LogField) {
|
||||
func output(writer io.Writer, level string, val any, fields ...LogField) {
|
||||
// only truncate string content, don't know how to truncate the values of other types.
|
||||
if v, ok := val.(string); ok {
|
||||
maxLen := atomic.LoadUint32(&maxContentLength)
|
||||
if maxLen > 0 && len(v) > int(maxLen) {
|
||||
val = v[:maxLen]
|
||||
fields = append(fields, truncatedField)
|
||||
}
|
||||
}
|
||||
|
||||
fields = combineGlobalFields(fields)
|
||||
|
||||
switch atomic.LoadUint32(&encoding) {
|
||||
case plainEncodingType:
|
||||
writePlainAny(writer, level, val, buildFields(fields...)...)
|
||||
writePlainAny(writer, level, val, buildPlainFields(fields...)...)
|
||||
default:
|
||||
entry := make(logEntry)
|
||||
for _, field := range fields {
|
||||
@@ -316,7 +330,7 @@ func wrapLevelWithColor(level string) string {
|
||||
return color.WithColorPadding(level, colour)
|
||||
}
|
||||
|
||||
func writeJson(writer io.Writer, info interface{}) {
|
||||
func writeJson(writer io.Writer, info any) {
|
||||
if content, err := json.Marshal(info); err != nil {
|
||||
log.Println(err.Error())
|
||||
} else if writer == nil {
|
||||
@@ -326,7 +340,7 @@ func writeJson(writer io.Writer, info interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func writePlainAny(writer io.Writer, level string, val interface{}, fields ...string) {
|
||||
func writePlainAny(writer io.Writer, level string, val any, fields ...string) {
|
||||
level = wrapLevelWithColor(level)
|
||||
|
||||
switch v := val.(type) {
|
||||
@@ -363,7 +377,7 @@ func writePlainText(writer io.Writer, level, msg string, fields ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
func writePlainValue(writer io.Writer, level string, val interface{}, fields ...string) {
|
||||
func writePlainValue(writer io.Writer, level string, val any, fields ...string) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(getTimestamp())
|
||||
buf.WriteByte(plainEncodingSep)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -96,6 +97,15 @@ func TestConsoleWriter(t *testing.T) {
|
||||
w.(*concreteWriter).statLog = easyToCloseWriter{}
|
||||
}
|
||||
|
||||
func TestNewFileWriter(t *testing.T) {
|
||||
t.Run("access", func(t *testing.T) {
|
||||
_, err := newFileWriter(LogConf{
|
||||
Path: "/not-exists",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNopWriter(t *testing.T) {
|
||||
assert.NotPanics(t, func() {
|
||||
var w nopWriter
|
||||
@@ -107,7 +117,7 @@ func TestNopWriter(t *testing.T) {
|
||||
w.Stack("foo")
|
||||
w.Stat("foo")
|
||||
w.Slow("foo")
|
||||
w.Close()
|
||||
_ = w.Close()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -157,9 +167,40 @@ func TestWritePlainAny(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestLogWithLimitContentLength(t *testing.T) {
|
||||
maxLen := atomic.LoadUint32(&maxContentLength)
|
||||
atomic.StoreUint32(&maxContentLength, 10)
|
||||
|
||||
t.Cleanup(func() {
|
||||
atomic.StoreUint32(&maxContentLength, maxLen)
|
||||
})
|
||||
|
||||
t.Run("alert", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
w := NewWriter(&buf)
|
||||
w.Info("1234567890")
|
||||
var v1 mockedEntry
|
||||
if err := json.Unmarshal(buf.Bytes(), &v1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "1234567890", v1.Content)
|
||||
assert.False(t, v1.Truncated)
|
||||
|
||||
buf.Reset()
|
||||
var v2 mockedEntry
|
||||
w.Info("12345678901")
|
||||
if err := json.Unmarshal(buf.Bytes(), &v2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "1234567890", v2.Content)
|
||||
assert.True(t, v2.Truncated)
|
||||
})
|
||||
}
|
||||
|
||||
type mockedEntry struct {
|
||||
Level string `json:"level"`
|
||||
Content string `json:"content"`
|
||||
Level string `json:"level"`
|
||||
Content string `json:"content"`
|
||||
Truncated bool `json:"truncated"`
|
||||
}
|
||||
|
||||
type easyToCloseWriter struct{}
|
||||
|
||||
@@ -11,17 +11,17 @@ const jsonTagKey = "json"
|
||||
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
|
||||
|
||||
// UnmarshalJsonBytes unmarshals content into v.
|
||||
func UnmarshalJsonBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
|
||||
func UnmarshalJsonBytes(content []byte, v any, opts ...UnmarshalOption) error {
|
||||
return unmarshalJsonBytes(content, v, getJsonUnmarshaler(opts...))
|
||||
}
|
||||
|
||||
// UnmarshalJsonMap unmarshals content from m into v.
|
||||
func UnmarshalJsonMap(m map[string]interface{}, v interface{}, opts ...UnmarshalOption) error {
|
||||
func UnmarshalJsonMap(m map[string]any, v any, opts ...UnmarshalOption) error {
|
||||
return getJsonUnmarshaler(opts...).Unmarshal(m, v)
|
||||
}
|
||||
|
||||
// UnmarshalJsonReader unmarshals content from reader into v.
|
||||
func UnmarshalJsonReader(reader io.Reader, v interface{}, opts ...UnmarshalOption) error {
|
||||
func UnmarshalJsonReader(reader io.Reader, v any, opts ...UnmarshalOption) error {
|
||||
return unmarshalJsonReader(reader, v, getJsonUnmarshaler(opts...))
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ func getJsonUnmarshaler(opts ...UnmarshalOption) *Unmarshaler {
|
||||
return jsonUnmarshaler
|
||||
}
|
||||
|
||||
func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var m map[string]interface{}
|
||||
func unmarshalJsonBytes(content []byte, v any, unmarshaler *Unmarshaler) error {
|
||||
var m any
|
||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,8 +42,8 @@ func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler)
|
||||
return unmarshaler.Unmarshal(m, v)
|
||||
}
|
||||
|
||||
func unmarshalJsonReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
|
||||
var m map[string]interface{}
|
||||
func unmarshalJsonReader(reader io.Reader, v any, unmarshaler *Unmarshaler) error {
|
||||
var m any
|
||||
if err := jsonx.UnmarshalFromReader(reader, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -856,8 +856,7 @@ func TestUnmarshalBytesError(t *testing.T) {
|
||||
}
|
||||
|
||||
err := UnmarshalJsonBytes([]byte(payload), &v)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), payload))
|
||||
assert.Equal(t, errTypeMismatch, err)
|
||||
}
|
||||
|
||||
func TestUnmarshalReaderError(t *testing.T) {
|
||||
@@ -867,14 +866,12 @@ func TestUnmarshalReaderError(t *testing.T) {
|
||||
Any string
|
||||
}
|
||||
|
||||
err := UnmarshalJsonReader(reader, &v)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), payload))
|
||||
assert.Equal(t, errTypeMismatch, UnmarshalJsonReader(reader, &v))
|
||||
}
|
||||
|
||||
func TestUnmarshalMap(t *testing.T) {
|
||||
t.Run("nil map and valid", func(t *testing.T) {
|
||||
var m map[string]interface{}
|
||||
var m map[string]any
|
||||
var v struct {
|
||||
Any string `json:",optional"`
|
||||
}
|
||||
@@ -885,7 +882,7 @@ func TestUnmarshalMap(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("empty map but not valid", func(t *testing.T) {
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
var v struct {
|
||||
Any string
|
||||
}
|
||||
@@ -895,7 +892,7 @@ func TestUnmarshalMap(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("empty map and valid", func(t *testing.T) {
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
var v struct {
|
||||
Any string `json:",optional"`
|
||||
}
|
||||
@@ -908,7 +905,7 @@ func TestUnmarshalMap(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("valid map", func(t *testing.T) {
|
||||
m := map[string]interface{}{
|
||||
m := map[string]any{
|
||||
"Any": "foo",
|
||||
}
|
||||
var v struct {
|
||||
@@ -920,3 +917,26 @@ func TestUnmarshalMap(t *testing.T) {
|
||||
assert.Equal(t, "foo", v.Any)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonArray(t *testing.T) {
|
||||
var v []struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
body := `[{"name":"kevin", "age": 18}]`
|
||||
assert.NoError(t, UnmarshalJsonBytes([]byte(body), &v))
|
||||
assert.Equal(t, 1, len(v))
|
||||
assert.Equal(t, "kevin", v[0].Name)
|
||||
assert.Equal(t, 18, v[0].Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesError(t *testing.T) {
|
||||
var v []struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
assert.Error(t, UnmarshalJsonBytes([]byte((``)), &v))
|
||||
assert.Error(t, UnmarshalJsonReader(strings.NewReader(``), &v))
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ const (
|
||||
|
||||
// Marshal marshals the given val and returns the map that contains the fields.
|
||||
// optional=another is not implemented, and it's hard to implement and not common used.
|
||||
func Marshal(val interface{}) (map[string]map[string]interface{}, error) {
|
||||
ret := make(map[string]map[string]interface{})
|
||||
func Marshal(val any) (map[string]map[string]any, error) {
|
||||
ret := make(map[string]map[string]any)
|
||||
tp := reflect.TypeOf(val)
|
||||
if tp.Kind() == reflect.Ptr {
|
||||
tp = tp.Elem()
|
||||
@@ -45,7 +45,7 @@ func getTag(field reflect.StructField) (string, bool) {
|
||||
}
|
||||
|
||||
func processMember(field reflect.StructField, value reflect.Value,
|
||||
collector map[string]map[string]interface{}) error {
|
||||
collector map[string]map[string]any) error {
|
||||
var key string
|
||||
var opt *fieldOptions
|
||||
var err error
|
||||
@@ -73,7 +73,7 @@ func processMember(field reflect.StructField, value reflect.Value,
|
||||
if ok {
|
||||
m[key] = val
|
||||
} else {
|
||||
m = map[string]interface{}{
|
||||
m = map[string]any{
|
||||
key: val,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ func TestMarshal_Range(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMarshal_RangeOut(t *testing.T) {
|
||||
tests := []interface{}{
|
||||
tests := []any{
|
||||
struct {
|
||||
Int int `json:"int,range=[1:3]"`
|
||||
}{
|
||||
@@ -262,7 +262,7 @@ func TestMarshal_RangeOut(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMarshal_RangeIllegal(t *testing.T) {
|
||||
tests := []interface{}{
|
||||
tests := []any{
|
||||
struct {
|
||||
Int int `json:"int,range=[3:1]"`
|
||||
}{
|
||||
@@ -284,7 +284,7 @@ func TestMarshal_RangeIllegal(t *testing.T) {
|
||||
func TestMarshal_RangeLeftEqualsToRight(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
value any
|
||||
err error
|
||||
}{
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// UnmarshalTomlBytes unmarshals TOML bytes into the given v.
|
||||
func UnmarshalTomlBytes(content []byte, v interface{}, opts ...UnmarshalOption) error {
|
||||
func UnmarshalTomlBytes(content []byte, v any, opts ...UnmarshalOption) error {
|
||||
b, err := encoding.TomlToJson(content)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -17,7 +17,7 @@ func UnmarshalTomlBytes(content []byte, v interface{}, opts ...UnmarshalOption)
|
||||
}
|
||||
|
||||
// UnmarshalTomlReader unmarshals TOML from the given io.Reader into the given v.
|
||||
func UnmarshalTomlReader(r io.Reader, v interface{}, opts ...UnmarshalOption) error {
|
||||
func UnmarshalTomlReader(r io.Reader, v any, opts ...UnmarshalOption) error {
|
||||
b, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -30,9 +30,9 @@ var (
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
cacheKeys = make(map[string][]string)
|
||||
cacheKeysLock sync.Mutex
|
||||
defaultCache = make(map[string]interface{})
|
||||
defaultCache = make(map[string]any)
|
||||
defaultCacheLock sync.Mutex
|
||||
emptyMap = map[string]interface{}{}
|
||||
emptyMap = map[string]any{}
|
||||
emptyValue = reflect.ValueOf(lang.Placeholder)
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ type (
|
||||
UnmarshalOption func(*unmarshalOptions)
|
||||
|
||||
unmarshalOptions struct {
|
||||
fillDefault bool
|
||||
fromString bool
|
||||
canonicalKey func(key string) string
|
||||
}
|
||||
@@ -66,21 +67,42 @@ func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
|
||||
}
|
||||
|
||||
// UnmarshalKey unmarshals m into v with tag key.
|
||||
func UnmarshalKey(m map[string]interface{}, v interface{}) error {
|
||||
func UnmarshalKey(m map[string]any, v any) error {
|
||||
return keyUnmarshaler.Unmarshal(m, v)
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals m into v.
|
||||
func (u *Unmarshaler) Unmarshal(m map[string]interface{}, v interface{}) error {
|
||||
return u.UnmarshalValuer(mapValuer(m), v)
|
||||
func (u *Unmarshaler) Unmarshal(i any, v any) error {
|
||||
valueType := reflect.TypeOf(v)
|
||||
if valueType.Kind() != reflect.Ptr {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
elemType := Deref(valueType)
|
||||
switch iv := i.(type) {
|
||||
case map[string]any:
|
||||
if elemType.Kind() != reflect.Struct {
|
||||
return errTypeMismatch
|
||||
}
|
||||
|
||||
return u.UnmarshalValuer(mapValuer(iv), v)
|
||||
case []any:
|
||||
if elemType.Kind() != reflect.Slice {
|
||||
return errTypeMismatch
|
||||
}
|
||||
|
||||
return u.fillSlice(elemType, reflect.ValueOf(v).Elem(), iv)
|
||||
default:
|
||||
return errUnsupportedType
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalValuer unmarshals m into v.
|
||||
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v interface{}) error {
|
||||
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v any) error {
|
||||
return u.unmarshalWithFullName(simpleValuer{current: m}, v, "")
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue interface{}) error {
|
||||
func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue any) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
@@ -100,7 +122,7 @@ func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapVa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillMapFromString(value reflect.Value, mapValue interface{}) error {
|
||||
func (u *Unmarshaler) fillMapFromString(value reflect.Value, mapValue any) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
@@ -121,20 +143,22 @@ func (u *Unmarshaler) fillMapFromString(value reflect.Value, mapValue interface{
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue interface{}) error {
|
||||
func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue any) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
}
|
||||
|
||||
baseType := fieldType.Elem()
|
||||
baseKind := baseType.Kind()
|
||||
dereffedBaseType := Deref(baseType)
|
||||
dereffedBaseKind := dereffedBaseType.Kind()
|
||||
refValue := reflect.ValueOf(mapValue)
|
||||
if refValue.Kind() != reflect.Slice {
|
||||
return errTypeMismatch
|
||||
}
|
||||
if refValue.IsNil() {
|
||||
return nil
|
||||
}
|
||||
|
||||
baseType := fieldType.Elem()
|
||||
dereffedBaseType := Deref(baseType)
|
||||
dereffedBaseKind := dereffedBaseType.Kind()
|
||||
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
|
||||
if refValue.Len() == 0 {
|
||||
value.Set(conv)
|
||||
@@ -152,15 +176,16 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
||||
switch dereffedBaseKind {
|
||||
case reflect.Struct:
|
||||
target := reflect.New(dereffedBaseType)
|
||||
if err := u.Unmarshal(ithValue.(map[string]interface{}), target.Interface()); err != nil {
|
||||
val, ok := ithValue.(map[string]any)
|
||||
if !ok {
|
||||
return errTypeMismatch
|
||||
}
|
||||
|
||||
if err := u.Unmarshal(val, target.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if baseKind == reflect.Ptr {
|
||||
conv.Index(i).Set(target)
|
||||
} else {
|
||||
conv.Index(i).Set(target.Elem())
|
||||
}
|
||||
SetValue(fieldType.Elem(), conv.Index(i), target.Elem())
|
||||
case reflect.Slice:
|
||||
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil {
|
||||
return err
|
||||
@@ -180,8 +205,8 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value,
|
||||
mapValue interface{}) error {
|
||||
var slice []interface{}
|
||||
mapValue any) error {
|
||||
var slice []any
|
||||
switch v := mapValue.(type) {
|
||||
case fmt.Stringer:
|
||||
if err := jsonx.UnmarshalFromString(v.String(), &slice); err != nil {
|
||||
@@ -210,14 +235,14 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
||||
baseKind reflect.Kind, value interface{}) error {
|
||||
baseKind reflect.Kind, value any) error {
|
||||
ithVal := slice.Index(index)
|
||||
switch v := value.(type) {
|
||||
case fmt.Stringer:
|
||||
return setValue(baseKind, ithVal, v.String())
|
||||
return setValueFromString(baseKind, ithVal, v.String())
|
||||
case string:
|
||||
return setValue(baseKind, ithVal, v)
|
||||
case map[string]interface{}:
|
||||
return setValueFromString(baseKind, ithVal, v)
|
||||
case map[string]any:
|
||||
return u.fillMap(ithVal.Type(), ithVal, value)
|
||||
default:
|
||||
// don't need to consider the difference between int, int8, int16, int32, int64,
|
||||
@@ -230,7 +255,7 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
|
||||
|
||||
target := reflect.New(baseType).Elem()
|
||||
target.Set(reflect.ValueOf(value))
|
||||
ithVal.Set(target.Addr())
|
||||
SetValue(ithVal.Type(), ithVal, target)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -265,16 +290,19 @@ func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value refle
|
||||
return u.fillSlice(derefedType, value, slice)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue interface{}) (reflect.Value, error) {
|
||||
func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any) (reflect.Value, error) {
|
||||
mapType := reflect.MapOf(keyType, elemType)
|
||||
valueType := reflect.TypeOf(mapValue)
|
||||
if mapType == valueType {
|
||||
return reflect.ValueOf(mapValue), nil
|
||||
}
|
||||
|
||||
if keyType != valueType.Key() {
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
|
||||
refValue := reflect.ValueOf(mapValue)
|
||||
targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
|
||||
fieldElemKind := elemType.Kind()
|
||||
dereffedElemType := Deref(elemType)
|
||||
dereffedElemKind := dereffedElemType.Kind()
|
||||
|
||||
@@ -291,7 +319,7 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue inter
|
||||
|
||||
targetValue.SetMapIndex(key, target.Elem())
|
||||
case reflect.Struct:
|
||||
keythMap, ok := keythData.(map[string]interface{})
|
||||
keythMap, ok := keythData.(map[string]any)
|
||||
if !ok {
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
@@ -301,13 +329,9 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue inter
|
||||
return emptyValue, err
|
||||
}
|
||||
|
||||
if fieldElemKind == reflect.Ptr {
|
||||
targetValue.SetMapIndex(key, target)
|
||||
} else {
|
||||
targetValue.SetMapIndex(key, target.Elem())
|
||||
}
|
||||
SetMapIndexValue(elemType, targetValue, key, target.Elem())
|
||||
case reflect.Map:
|
||||
keythMap, ok := keythData.(map[string]interface{})
|
||||
keythMap, ok := keythData.(map[string]any)
|
||||
if !ok {
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
@@ -331,10 +355,15 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue inter
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
|
||||
targetValue.SetMapIndex(key, reflect.ValueOf(v))
|
||||
val := reflect.ValueOf(v)
|
||||
if !val.Type().AssignableTo(dereffedElemType) {
|
||||
return emptyValue, errTypeMismatch
|
||||
}
|
||||
|
||||
targetValue.SetMapIndex(key, val)
|
||||
case json.Number:
|
||||
target := reflect.New(dereffedElemType)
|
||||
if err := setValue(dereffedElemKind, target.Elem(), v.String()); err != nil {
|
||||
if err := setValueFromString(dereffedElemKind, target.Elem(), v.String()); err != nil {
|
||||
return emptyValue, err
|
||||
}
|
||||
|
||||
@@ -361,6 +390,26 @@ func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Value
|
||||
return key, nil, nil
|
||||
}
|
||||
|
||||
if u.opts.canonicalKey != nil {
|
||||
key = u.opts.canonicalKey(key)
|
||||
|
||||
if len(options.OptionalDep) > 0 {
|
||||
// need to create a new fieldOption, because the original one is shared through cache.
|
||||
options = &fieldOptions{
|
||||
fieldOptionsWithContext: fieldOptionsWithContext{
|
||||
Inherit: options.Inherit,
|
||||
FromString: options.FromString,
|
||||
Optional: options.Optional,
|
||||
Options: options.Options,
|
||||
Default: options.Default,
|
||||
EnvVar: options.EnvVar,
|
||||
Range: options.Range,
|
||||
},
|
||||
OptionalDep: u.opts.canonicalKey(options.OptionalDep),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optsWithContext, err := options.toOptionsWithContext(key, m, fullName)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
@@ -376,19 +425,51 @@ func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value ref
|
||||
return err
|
||||
}
|
||||
|
||||
if _, hasValue := getValue(m, key); hasValue {
|
||||
return fmt.Errorf("fields of %s can't be wrapped inside, because it's anonymous", key)
|
||||
}
|
||||
|
||||
if options.optional() {
|
||||
return u.processAnonymousFieldOptional(field.Type, value, key, m, fullName)
|
||||
return u.processAnonymousFieldOptional(field, value, key, m, fullName)
|
||||
}
|
||||
|
||||
return u.processAnonymousFieldRequired(field.Type, value, m, fullName)
|
||||
return u.processAnonymousFieldRequired(field, value, m, fullName)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processAnonymousFieldOptional(fieldType reflect.Type, value reflect.Value,
|
||||
func (u *Unmarshaler) processAnonymousFieldOptional(field reflect.StructField, value reflect.Value,
|
||||
key string, m valuerWithParent, fullName string) error {
|
||||
derefedFieldType := Deref(field.Type)
|
||||
|
||||
switch derefedFieldType.Kind() {
|
||||
case reflect.Struct:
|
||||
return u.processAnonymousStructFieldOptional(field.Type, value, key, m, fullName)
|
||||
default:
|
||||
return u.processNamedField(field, value, m, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processAnonymousFieldRequired(field reflect.StructField, value reflect.Value,
|
||||
m valuerWithParent, fullName string) error {
|
||||
fieldType := field.Type
|
||||
maybeNewValue(fieldType, value)
|
||||
derefedFieldType := Deref(fieldType)
|
||||
indirectValue := reflect.Indirect(value)
|
||||
|
||||
switch derefedFieldType.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < derefedFieldType.NumField(); i++ {
|
||||
if err := u.processField(derefedFieldType.Field(i), indirectValue.Field(i),
|
||||
m, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
if err := u.processNamedField(field, indirectValue, m, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processAnonymousStructFieldOptional(fieldType reflect.Type,
|
||||
value reflect.Value, key string, m valuerWithParent, fullName string) error {
|
||||
var filled bool
|
||||
var required int
|
||||
var requiredFilled int
|
||||
@@ -422,22 +503,7 @@ func (u *Unmarshaler) processAnonymousFieldOptional(fieldType reflect.Type, valu
|
||||
}
|
||||
|
||||
if filled && required != requiredFilled {
|
||||
return fmt.Errorf("%s is not fully set", key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processAnonymousFieldRequired(fieldType reflect.Type, value reflect.Value,
|
||||
m valuerWithParent, fullName string) error {
|
||||
maybeNewValue(fieldType, value)
|
||||
derefedFieldType := Deref(fieldType)
|
||||
indirectValue := reflect.Indirect(value)
|
||||
|
||||
for i := 0; i < derefedFieldType.NumField(); i++ {
|
||||
if err := u.processField(derefedFieldType.Field(i), indirectValue.Field(i), m, fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%q is not fully set", key)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -460,12 +526,12 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
||||
vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error {
|
||||
derefedFieldType := Deref(fieldType)
|
||||
typeKind := derefedFieldType.Kind()
|
||||
valueKind := reflect.TypeOf(vp.value).Kind()
|
||||
mapValue := vp.value
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
|
||||
switch {
|
||||
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
||||
mv, ok := mapValue.(map[string]interface{})
|
||||
mv, ok := mapValue.(map[string]any)
|
||||
if !ok {
|
||||
return errTypeMismatch
|
||||
}
|
||||
@@ -474,6 +540,8 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
||||
current: mapValuer(mv),
|
||||
parent: vp.parent,
|
||||
}, fullName)
|
||||
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
||||
return u.fillSlice(fieldType, value, mapValue)
|
||||
case valueKind == reflect.Map && typeKind == reflect.Map:
|
||||
return u.fillMap(fieldType, value, mapValue)
|
||||
case valueKind == reflect.String && typeKind == reflect.Map:
|
||||
@@ -481,34 +549,27 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
||||
case valueKind == reflect.String && typeKind == reflect.Slice:
|
||||
return u.fillSliceFromString(fieldType, value, mapValue)
|
||||
case valueKind == reflect.String && derefedFieldType == durationType:
|
||||
return fillDurationValue(fieldType.Kind(), value, mapValue.(string))
|
||||
return fillDurationValue(fieldType, value, mapValue.(string))
|
||||
default:
|
||||
return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processFieldPrimitive(fieldType reflect.Type, value reflect.Value,
|
||||
mapValue interface{}, opts *fieldOptionsWithContext, fullName string) error {
|
||||
mapValue any, opts *fieldOptionsWithContext, fullName string) error {
|
||||
typeKind := Deref(fieldType).Kind()
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
|
||||
switch {
|
||||
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
||||
return u.fillSlice(fieldType, value, mapValue)
|
||||
case typeKind == reflect.Map && valueKind == reflect.Map:
|
||||
return u.fillMap(fieldType, value, mapValue)
|
||||
switch v := mapValue.(type) {
|
||||
case json.Number:
|
||||
return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName)
|
||||
default:
|
||||
switch v := mapValue.(type) {
|
||||
case json.Number:
|
||||
return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName)
|
||||
default:
|
||||
if typeKind == valueKind {
|
||||
if err := validateValueInOptions(mapValue, opts.options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fillWithSameType(fieldType, value, mapValue, opts)
|
||||
if typeKind == valueKind {
|
||||
if err := validateValueInOptions(mapValue, opts.options()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fillWithSameType(fieldType, value, mapValue, opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,8 +578,8 @@ func (u *Unmarshaler) processFieldPrimitive(fieldType reflect.Type, value reflec
|
||||
|
||||
func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type, value reflect.Value,
|
||||
v json.Number, opts *fieldOptionsWithContext, fullName string) error {
|
||||
fieldKind := fieldType.Kind()
|
||||
typeKind := Deref(fieldType).Kind()
|
||||
baseType := Deref(fieldType)
|
||||
typeKind := baseType.Kind()
|
||||
|
||||
if err := validateJsonNumberRange(v, opts); err != nil {
|
||||
return err
|
||||
@@ -528,9 +589,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
||||
return err
|
||||
}
|
||||
|
||||
if fieldKind == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
target := reflect.New(Deref(fieldType)).Elem()
|
||||
|
||||
switch typeKind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
@@ -539,7 +598,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
||||
return err
|
||||
}
|
||||
|
||||
value.SetInt(iValue)
|
||||
target.SetInt(iValue)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
iValue, err := v.Int64()
|
||||
if err != nil {
|
||||
@@ -550,18 +609,20 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
||||
return fmt.Errorf("unmarshal %q with bad value %q", fullName, v.String())
|
||||
}
|
||||
|
||||
value.SetUint(uint64(iValue))
|
||||
target.SetUint(uint64(iValue))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
fValue, err := v.Float64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value.SetFloat(fValue)
|
||||
target.SetFloat(fValue)
|
||||
default:
|
||||
return newTypeMismatchError(fullName)
|
||||
return newTypeMismatchErrorWithHint(fullName, typeKind.String(), value.Type().String())
|
||||
}
|
||||
|
||||
SetValue(fieldType, value, target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -574,7 +635,7 @@ func (u *Unmarshaler) processFieldStruct(fieldType reflect.Type, value reflect.V
|
||||
return err
|
||||
}
|
||||
|
||||
value.Set(target.Addr())
|
||||
SetValue(fieldType, value, target)
|
||||
} else if err := u.unmarshalWithFullName(m, value.Addr().Interface(), fullName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -583,12 +644,18 @@ func (u *Unmarshaler) processFieldStruct(fieldType reflect.Type, value reflect.V
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processFieldTextUnmarshaler(fieldType reflect.Type, value reflect.Value,
|
||||
mapValue interface{}) (bool, error) {
|
||||
mapValue any) (bool, error) {
|
||||
var tval encoding.TextUnmarshaler
|
||||
var ok bool
|
||||
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
tval, ok = value.Interface().(encoding.TextUnmarshaler)
|
||||
if value.Elem().Kind() == reflect.Ptr {
|
||||
target := reflect.New(Deref(fieldType))
|
||||
SetValue(fieldType.Elem(), value, target)
|
||||
tval, ok = target.Interface().(encoding.TextUnmarshaler)
|
||||
} else {
|
||||
tval, ok = value.Interface().(encoding.TextUnmarshaler)
|
||||
}
|
||||
} else {
|
||||
tval, ok = value.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
}
|
||||
@@ -621,7 +688,7 @@ func (u *Unmarshaler) processFieldWithEnvValue(fieldType reflect.Type, value ref
|
||||
value.SetBool(val)
|
||||
return nil
|
||||
case durationType.Kind():
|
||||
if err := fillDurationValue(fieldKind, value, envVal); err != nil {
|
||||
if err := fillDurationValue(fieldType, value, envVal); err != nil {
|
||||
return fmt.Errorf("unmarshal field %q with environment variable, %w", fullName, err)
|
||||
}
|
||||
|
||||
@@ -636,6 +703,10 @@ func (u *Unmarshaler) processFieldWithEnvValue(fieldType reflect.Type, value ref
|
||||
|
||||
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
|
||||
m valuerWithParent, fullName string) error {
|
||||
if !field.IsExported() {
|
||||
return nil
|
||||
}
|
||||
|
||||
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -656,7 +727,14 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
||||
|
||||
valuer := createValuer(m, opts)
|
||||
mapValue, hasValue := getValue(valuer, canonicalKey)
|
||||
if !hasValue {
|
||||
|
||||
// When fillDefault is used, m is a null value, hasValue must be false, all priority judgments fillDefault.
|
||||
if u.opts.fillDefault {
|
||||
if !value.IsZero() {
|
||||
return fmt.Errorf("set the default value, %q must be zero", fullName)
|
||||
}
|
||||
return u.processNamedFieldWithoutValue(field.Type, value, opts, fullName)
|
||||
} else if !hasValue {
|
||||
return u.processNamedFieldWithoutValue(field.Type, value, opts, fullName)
|
||||
}
|
||||
|
||||
@@ -674,11 +752,11 @@ func (u *Unmarshaler) processNamedFieldWithValue(fieldType reflect.Type, value r
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("field %s mustn't be nil", key)
|
||||
return fmt.Errorf("field %q mustn't be nil", key)
|
||||
}
|
||||
|
||||
if !value.CanSet() {
|
||||
return fmt.Errorf("field %s is not settable", key)
|
||||
return fmt.Errorf("field %q is not settable", key)
|
||||
}
|
||||
|
||||
maybeNewValue(fieldType, value)
|
||||
@@ -693,47 +771,69 @@ func (u *Unmarshaler) processNamedFieldWithValue(fieldType reflect.Type, value r
|
||||
return u.processFieldNotFromString(fieldType, value, vp, opts, fullName)
|
||||
default:
|
||||
if u.opts.fromString || opts.fromString() {
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
if valueKind != reflect.String {
|
||||
return fmt.Errorf("error: the value in map is not string, but %s", valueKind)
|
||||
}
|
||||
|
||||
options := opts.options()
|
||||
if len(options) > 0 {
|
||||
if !stringx.Contains(options, mapValue.(string)) {
|
||||
return fmt.Errorf(`error: value "%s" for field "%s" is not defined in options "%v"`,
|
||||
mapValue, key, options)
|
||||
}
|
||||
}
|
||||
|
||||
return fillPrimitive(fieldType, value, mapValue, opts, fullName)
|
||||
return u.processNamedFieldWithValueFromString(fieldType, value, mapValue,
|
||||
key, opts, fullName)
|
||||
}
|
||||
|
||||
return u.processFieldNotFromString(fieldType, value, vp, opts, fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processNamedFieldWithValueFromString(fieldType reflect.Type, value reflect.Value,
|
||||
mapValue any, key string, opts *fieldOptionsWithContext, fullName string) error {
|
||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||
if valueKind != reflect.String {
|
||||
return fmt.Errorf("the value in map is not string, but %s", valueKind)
|
||||
}
|
||||
|
||||
options := opts.options()
|
||||
if len(options) > 0 {
|
||||
var checkValue string
|
||||
switch mt := mapValue.(type) {
|
||||
case string:
|
||||
checkValue = mt
|
||||
case fmt.Stringer:
|
||||
checkValue = mt.String()
|
||||
default:
|
||||
return fmt.Errorf("the value in map is not string or json.Number, but %s",
|
||||
valueKind.String())
|
||||
}
|
||||
|
||||
if !stringx.Contains(options, checkValue) {
|
||||
return fmt.Errorf(`value "%s" for field %q is not defined in options "%v"`,
|
||||
mapValue, key, options)
|
||||
}
|
||||
}
|
||||
|
||||
return fillPrimitive(fieldType, value, mapValue, opts, fullName)
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, value reflect.Value,
|
||||
opts *fieldOptionsWithContext, fullName string) error {
|
||||
derefedType := Deref(fieldType)
|
||||
fieldKind := derefedType.Kind()
|
||||
if defaultValue, ok := opts.getDefault(); ok {
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
maybeNewValue(fieldType, value)
|
||||
value = value.Elem()
|
||||
}
|
||||
if derefedType == durationType {
|
||||
return fillDurationValue(fieldKind, value, defaultValue)
|
||||
return fillDurationValue(fieldType, value, defaultValue)
|
||||
}
|
||||
|
||||
switch fieldKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return u.fillSliceWithDefault(derefedType, value, defaultValue)
|
||||
default:
|
||||
return setValue(fieldKind, value, defaultValue)
|
||||
return setValueFromString(fieldKind, value, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
if u.opts.fillDefault {
|
||||
if fieldType.Kind() != reflect.Ptr && fieldKind == reflect.Struct {
|
||||
return u.processFieldNotFromString(fieldType, value, valueWithParent{
|
||||
value: emptyMap,
|
||||
}, opts, fullName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch fieldKind {
|
||||
case reflect.Array, reflect.Map, reflect.Slice:
|
||||
if !opts.optional() {
|
||||
@@ -765,21 +865,35 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v interface{}, fullName string) error {
|
||||
func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName string) error {
|
||||
rv := reflect.ValueOf(v)
|
||||
if err := ValidatePtr(&rv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rte := reflect.TypeOf(v).Elem()
|
||||
if rte.Kind() != reflect.Struct {
|
||||
valueType := reflect.TypeOf(v)
|
||||
baseType := Deref(valueType)
|
||||
if baseType.Kind() != reflect.Struct {
|
||||
return errValueNotStruct
|
||||
}
|
||||
|
||||
rve := rv.Elem()
|
||||
numFields := rte.NumField()
|
||||
valElem := rv.Elem()
|
||||
if valElem.Kind() == reflect.Ptr {
|
||||
target := reflect.New(baseType).Elem()
|
||||
SetValue(valueType.Elem(), valElem, target)
|
||||
valElem = target
|
||||
}
|
||||
|
||||
numFields := baseType.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if err := u.processField(rte.Field(i), rve.Field(i), m, fullName); err != nil {
|
||||
typeField := baseType.Field(i)
|
||||
valueField := valElem.Field(i)
|
||||
if err := u.processField(typeField, valueField, m, fullName); err != nil {
|
||||
if len(fullName) > 0 {
|
||||
err = fmt.Errorf("%w, fullName: %s, field: %s, type: %s",
|
||||
err, fullName, typeField.Name, valueField.Type().Name())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -794,13 +908,20 @@ func WithStringValues() UnmarshalOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithCanonicalKeyFunc customizes an Unmarshaler with Canonical Key func
|
||||
// WithCanonicalKeyFunc customizes an Unmarshaler with Canonical Key func.
|
||||
func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption {
|
||||
return func(opt *unmarshalOptions) {
|
||||
opt.canonicalKey = f
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefault customizes an Unmarshaler with fill default values.
|
||||
func WithDefault() UnmarshalOption {
|
||||
return func(opt *unmarshalOptions) {
|
||||
opt.fillDefault = true
|
||||
}
|
||||
}
|
||||
|
||||
func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent {
|
||||
if opts.inherit() {
|
||||
return recursiveValuer{
|
||||
@@ -815,22 +936,18 @@ func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithP
|
||||
}
|
||||
}
|
||||
|
||||
func fillDurationValue(fieldKind reflect.Kind, value reflect.Value, dur string) error {
|
||||
func fillDurationValue(fieldType reflect.Type, value reflect.Value, dur string) error {
|
||||
d, err := time.ParseDuration(dur)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fieldKind == reflect.Ptr {
|
||||
value.Elem().Set(reflect.ValueOf(d))
|
||||
} else {
|
||||
value.Set(reflect.ValueOf(d))
|
||||
}
|
||||
SetValue(fieldType, value, reflect.ValueOf(d))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue interface{},
|
||||
func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue any,
|
||||
opts *fieldOptionsWithContext, fullName string) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
@@ -841,7 +958,7 @@ func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue interfa
|
||||
target := reflect.New(baseType).Elem()
|
||||
switch mapValue.(type) {
|
||||
case string, json.Number:
|
||||
value.Set(target.Addr())
|
||||
SetValue(fieldType, value, target)
|
||||
value = target
|
||||
}
|
||||
}
|
||||
@@ -853,13 +970,13 @@ func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue interfa
|
||||
if err := validateJsonNumberRange(v, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return setValue(baseType.Kind(), value, v.String())
|
||||
return setValueFromString(baseType.Kind(), value, v.String())
|
||||
default:
|
||||
return newTypeMismatchError(fullName)
|
||||
}
|
||||
}
|
||||
|
||||
func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue interface{},
|
||||
func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue any,
|
||||
opts *fieldOptionsWithContext) error {
|
||||
if !value.CanSet() {
|
||||
return errValueNotSettable
|
||||
@@ -873,7 +990,7 @@ func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue inte
|
||||
baseType := Deref(fieldType)
|
||||
target := reflect.New(baseType).Elem()
|
||||
setSameKindValue(baseType, target, mapValue)
|
||||
value.Set(target.Addr())
|
||||
SetValue(fieldType, value, target)
|
||||
} else {
|
||||
setSameKindValue(fieldType, value, mapValue)
|
||||
}
|
||||
@@ -882,12 +999,12 @@ func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue inte
|
||||
}
|
||||
|
||||
// getValue gets the value for the specific key, the key can be in the format of parentKey.childKey
|
||||
func getValue(m valuerWithParent, key string) (interface{}, bool) {
|
||||
func getValue(m valuerWithParent, key string) (any, bool) {
|
||||
keys := readKeys(key)
|
||||
return getValueWithChainedKeys(m, keys)
|
||||
}
|
||||
|
||||
func getValueWithChainedKeys(m valuerWithParent, keys []string) (interface{}, bool) {
|
||||
func getValueWithChainedKeys(m valuerWithParent, keys []string) (any, bool) {
|
||||
switch len(keys) {
|
||||
case 0:
|
||||
return nil, false
|
||||
@@ -896,7 +1013,7 @@ func getValueWithChainedKeys(m valuerWithParent, keys []string) (interface{}, bo
|
||||
return v, ok
|
||||
default:
|
||||
if v, ok := m.Value(keys[0]); ok {
|
||||
if nextm, ok := v.(map[string]interface{}); ok {
|
||||
if nextm, ok := v.(map[string]any); ok {
|
||||
return getValueWithChainedKeys(recursiveValuer{
|
||||
current: mapValuer(nextm),
|
||||
parent: m,
|
||||
@@ -930,11 +1047,16 @@ func join(elem ...string) string {
|
||||
}
|
||||
|
||||
func newInitError(name string) error {
|
||||
return fmt.Errorf("field %s is not set", name)
|
||||
return fmt.Errorf("field %q is not set", name)
|
||||
}
|
||||
|
||||
func newTypeMismatchError(name string) error {
|
||||
return fmt.Errorf("error: type mismatch for field %s", name)
|
||||
return fmt.Errorf("type mismatch for field %q", name)
|
||||
}
|
||||
|
||||
func newTypeMismatchErrorWithHint(name, expectType, actualType string) error {
|
||||
return fmt.Errorf("type mismatch for field %q, expect %q, actual %q",
|
||||
name, expectType, actualType)
|
||||
}
|
||||
|
||||
func readKeys(key string) []string {
|
||||
@@ -955,7 +1077,7 @@ func readKeys(key string) []string {
|
||||
return keys
|
||||
}
|
||||
|
||||
func setSameKindValue(targetType reflect.Type, target reflect.Value, value interface{}) {
|
||||
func setSameKindValue(targetType reflect.Type, target reflect.Value, value any) {
|
||||
if reflect.ValueOf(value).Type().AssignableTo(targetType) {
|
||||
target.Set(reflect.ValueOf(value))
|
||||
} else {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user