mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-26 16:15:30 +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:
|
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
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
otechie: # Replace with a single Otechie username
|
otechie: # Replace with a single Otechie username
|
||||||
custom: # https://gitee.com/kevwan/static/raw/master/images/sponsor.jpg
|
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
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
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
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ^1.16
|
go-version: 1.18
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: true
|
cache: true
|
||||||
id: go
|
id: go
|
||||||
@@ -29,8 +29,12 @@ jobs:
|
|||||||
- name: Lint
|
- name: Lint
|
||||||
run: |
|
run: |
|
||||||
go vet -stdmethods=false $(go list ./...)
|
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
|
- name: Test
|
||||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
@@ -48,8 +52,8 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
# use 1.16 to guarantee Go 1.16 compatibility
|
# use 1.18 to guarantee Go 1.18 compatibility
|
||||||
go-version: 1.16
|
go-version: 1.18
|
||||||
check-latest: true
|
check-latest: true
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
@@ -57,5 +61,5 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go mod verify
|
go mod verify
|
||||||
go mod download
|
go mod download
|
||||||
go test -v -race ./...
|
go test ./...
|
||||||
cd tools/goctl && go build -v goctl.go
|
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 }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goos: ${{ matrix.goos }}
|
goos: ${{ matrix.goos }}
|
||||||
goarch: ${{ matrix.goarch }}
|
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"
|
project_path: "tools/goctl"
|
||||||
binary_name: "goctl"
|
binary_name: "goctl"
|
||||||
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -11,12 +11,14 @@
|
|||||||
!api
|
!api
|
||||||
|
|
||||||
# ignore
|
# ignore
|
||||||
.idea
|
**/.idea
|
||||||
|
**/.vscode
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/logs
|
**/logs
|
||||||
|
**/adhoc
|
||||||
|
**/coverage.txt
|
||||||
|
|
||||||
# for test purpose
|
# for test purpose
|
||||||
**/adhoc
|
|
||||||
go.work
|
go.work
|
||||||
go.work.sum
|
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
|
package bloom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -8,28 +9,29 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"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
|
||||||
// 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 as k in the error rate table
|
const maps = 14
|
||||||
maps = 14
|
|
||||||
setScript = `
|
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
|
for _, offset in ipairs(ARGV) do
|
||||||
redis.call("setbit", KEYS[1], offset, 1)
|
redis.call("setbit", KEYS[1], offset, 1)
|
||||||
end
|
end
|
||||||
`
|
`)
|
||||||
testScript = `
|
testScript = redis.NewScript(`
|
||||||
for _, offset in ipairs(ARGV) do
|
for _, offset in ipairs(ARGV) do
|
||||||
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
|
if tonumber(redis.call("getbit", KEYS[1], offset)) == 0 then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
`
|
`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrTooLargeOffset indicates the offset is too large in bitset.
|
|
||||||
var ErrTooLargeOffset = errors.New("too large offset")
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// A Filter is a bloom filter.
|
// A Filter is a bloom filter.
|
||||||
Filter struct {
|
Filter struct {
|
||||||
@@ -38,8 +40,8 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitSetProvider interface {
|
bitSetProvider interface {
|
||||||
check([]uint) (bool, error)
|
check(ctx context.Context, offsets []uint) (bool, error)
|
||||||
set([]uint) 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.
|
// Add adds data into f.
|
||||||
func (f *Filter) Add(data []byte) error {
|
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)
|
locations := f.getLocations(data)
|
||||||
return f.bitSet.set(locations)
|
return f.bitSet.set(ctx, locations)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exists checks if data is in f.
|
// Exists checks if data is in f.
|
||||||
func (f *Filter) Exists(data []byte) (bool, error) {
|
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)
|
locations := f.getLocations(data)
|
||||||
isSet, err := f.bitSet.check(locations)
|
isSet, err := f.bitSet.check(ctx, locations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -111,13 +123,13 @@ func (r *redisBitSet) buildOffsetArgs(offsets []uint) ([]string, error) {
|
|||||||
return args, nil
|
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)
|
args, err := r.buildOffsetArgs(offsets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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 {
|
if err == redis.Nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -132,22 +144,24 @@ func (r *redisBitSet) check(offsets []uint) (bool, error) {
|
|||||||
return exists == 1, nil
|
return exists == 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// del only use for testing.
|
||||||
func (r *redisBitSet) del() error {
|
func (r *redisBitSet) del() error {
|
||||||
_, err := r.store.Del(r.key)
|
_, err := r.store.Del(r.key)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expire only use for testing.
|
||||||
func (r *redisBitSet) expire(seconds int) error {
|
func (r *redisBitSet) expire(seconds int) error {
|
||||||
return r.store.Expire(r.key, seconds)
|
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)
|
args, err := r.buildOffsetArgs(offsets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err == redis.Nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
package bloom
|
package bloom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
"github.com/zeromicro/go-zero/core/stores/redis/redistest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
ctx := context.Background()
|
||||||
defer clean()
|
|
||||||
|
|
||||||
bitSet := newRedisBitSet(store, "test_key", 1024)
|
bitSet := newRedisBitSet(store, "test_key", 1024)
|
||||||
isSetBefore, err := bitSet.check([]uint{0})
|
isSetBefore, err := bitSet.check(ctx, []uint{0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if isSetBefore {
|
if isSetBefore {
|
||||||
t.Fatal("Bit should not be set")
|
t.Fatal("Bit should not be set")
|
||||||
}
|
}
|
||||||
err = bitSet.set([]uint{512})
|
err = bitSet.set(ctx, []uint{512})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
isSetAfter, err := bitSet.check([]uint{512})
|
isSetAfter, err := bitSet.check(ctx, []uint{512})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -42,9 +43,7 @@ func TestRedisBitSet_New_Set_Test(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRedisBitSet_Add(t *testing.T) {
|
func TestRedisBitSet_Add(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
filter := New(store, "test_key", 64)
|
filter := New(store, "test_key", 64)
|
||||||
assert.Nil(t, filter.Add([]byte("hello")))
|
assert.Nil(t, filter.Add([]byte("hello")))
|
||||||
@@ -53,3 +52,51 @@ func TestRedisBitSet_Add(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, ok)
|
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))
|
return (*ecbEncrypter)(newECB(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockSize returns the mode's block size.
|
||||||
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
|
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) {
|
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
|
||||||
if len(src)%x.blockSize != 0 {
|
if len(src)%x.blockSize != 0 {
|
||||||
logx.Error("crypto/cipher: input not full blocks")
|
logx.Error("crypto/cipher: input not full blocks")
|
||||||
@@ -59,11 +61,13 @@ func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
|
|||||||
return (*ecbDecrypter)(newECB(b))
|
return (*ecbDecrypter)(newECB(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockSize returns the mode's block size.
|
||||||
func (x *ecbDecrypter) BlockSize() int {
|
func (x *ecbDecrypter) BlockSize() int {
|
||||||
return x.blockSize
|
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) {
|
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
|
||||||
if len(src)%x.blockSize != 0 {
|
if len(src)%x.blockSize != 0 {
|
||||||
logx.Error("crypto/cipher: input not full blocks")
|
logx.Error("crypto/cipher: input not full blocks")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package codec
|
package codec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/aes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -10,7 +11,8 @@ import (
|
|||||||
func TestAesEcb(t *testing.T) {
|
func TestAesEcb(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
|
key = []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
|
||||||
val = []byte("hello")
|
val = []byte("helloworld")
|
||||||
|
valLong = []byte("helloworldlong..")
|
||||||
badKey1 = []byte("aaaaaaaaa")
|
badKey1 = []byte("aaaaaaaaa")
|
||||||
// more than 32 chars
|
// more than 32 chars
|
||||||
badKey2 = []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
badKey2 = []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||||
@@ -31,6 +33,39 @@ func TestAesEcb(t *testing.T) {
|
|||||||
src, err := EcbDecrypt(key, dst)
|
src, err := EcbDecrypt(key, dst)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, val, src)
|
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) {
|
func TestAesEcbBase64(t *testing.T) {
|
||||||
|
|||||||
@@ -80,3 +80,17 @@ func TestKeyBytes(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.True(t, len(key.Bytes()) > 0)
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -21,3 +23,45 @@ func TestGzip(t *testing.T) {
|
|||||||
assert.True(t, len(bs) < buf.Len())
|
assert.True(t, len(bs) < buf.Len())
|
||||||
assert.Equal(t, buf.Bytes(), actual)
|
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 (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -41,6 +42,7 @@ func TestCryption(t *testing.T) {
|
|||||||
|
|
||||||
file, err := fs.TempFilenameWithText(priKey)
|
file, err := fs.TempFilenameWithText(priKey)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
defer os.Remove(file)
|
||||||
dec, err := NewRsaDecrypter(file)
|
dec, err := NewRsaDecrypter(file)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
actual, err := dec.Decrypt(ret)
|
actual, err := dec.Decrypt(ret)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type (
|
|||||||
Cache struct {
|
Cache struct {
|
||||||
name string
|
name string
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
data map[string]interface{}
|
data map[string]any
|
||||||
expire time.Duration
|
expire time.Duration
|
||||||
timingWheel *TimingWheel
|
timingWheel *TimingWheel
|
||||||
lruCache lru
|
lruCache lru
|
||||||
@@ -43,7 +43,7 @@ type (
|
|||||||
// NewCache returns a Cache with given expire.
|
// NewCache returns a Cache with given expire.
|
||||||
func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
|
func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
|
||||||
cache := &Cache{
|
cache := &Cache{
|
||||||
data: make(map[string]interface{}),
|
data: make(map[string]any),
|
||||||
expire: expire,
|
expire: expire,
|
||||||
lruCache: emptyLruCache,
|
lruCache: emptyLruCache,
|
||||||
barrier: syncx.NewSingleFlight(),
|
barrier: syncx.NewSingleFlight(),
|
||||||
@@ -59,7 +59,7 @@ func NewCache(expire time.Duration, opts ...CacheOption) (*Cache, error) {
|
|||||||
}
|
}
|
||||||
cache.stats = newCacheStat(cache.name, cache.size)
|
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)
|
key, ok := k.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@@ -85,7 +85,7 @@ func (c *Cache) Del(key string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the item with the given key from c.
|
// 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)
|
value, ok := c.doGet(key)
|
||||||
if ok {
|
if ok {
|
||||||
c.stats.IncrementHit()
|
c.stats.IncrementHit()
|
||||||
@@ -97,12 +97,12 @@ func (c *Cache) Get(key string) (interface{}, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set sets value into c with key.
|
// 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)
|
c.SetWithExpire(key, value, c.expire)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWithExpire sets value into c with key and expire with the given value.
|
// 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()
|
c.lock.Lock()
|
||||||
_, ok := c.data[key]
|
_, ok := c.data[key]
|
||||||
c.data[key] = value
|
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.
|
// Take returns the item with the given key.
|
||||||
// If the item is in c, return it directly.
|
// If the item is in c, return it directly.
|
||||||
// If not, use fetch method to get the item, set into c and return it.
|
// 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 {
|
if val, ok := c.doGet(key); ok {
|
||||||
c.stats.IncrementHit()
|
c.stats.IncrementHit()
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var fresh bool
|
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
|
// 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
|
// so we do double check, cache might be taken by another call
|
||||||
if val, ok := c.doGet(key); ok {
|
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
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) doGet(key string) (interface{}, bool) {
|
func (c *Cache) doGet(key string) (any, bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func TestCacheTake(t *testing.T) {
|
|||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
cache.Take("first", func() (interface{}, error) {
|
cache.Take("first", func() (any, error) {
|
||||||
atomic.AddInt32(&count, 1)
|
atomic.AddInt32(&count, 1)
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
return "first element", nil
|
return "first element", nil
|
||||||
@@ -76,7 +76,7 @@ func TestCacheTakeExists(t *testing.T) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
cache.Set("first", "first element")
|
cache.Set("first", "first element")
|
||||||
cache.Take("first", func() (interface{}, error) {
|
cache.Take("first", func() (any, error) {
|
||||||
atomic.AddInt32(&count, 1)
|
atomic.AddInt32(&count, 1)
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
return "first element", nil
|
return "first element", nil
|
||||||
@@ -99,7 +99,7 @@ func TestCacheTakeError(t *testing.T) {
|
|||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := cache.Take("first", func() (interface{}, error) {
|
_, err := cache.Take("first", func() (any, error) {
|
||||||
atomic.AddInt32(&count, 1)
|
atomic.AddInt32(&count, 1)
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
return "", errDummy
|
return "", errDummy
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "sync"
|
|||||||
// A Queue is a FIFO queue.
|
// A Queue is a FIFO queue.
|
||||||
type Queue struct {
|
type Queue struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
elements []interface{}
|
elements []any
|
||||||
size int
|
size int
|
||||||
head int
|
head int
|
||||||
tail int
|
tail int
|
||||||
@@ -15,7 +15,7 @@ type Queue struct {
|
|||||||
// NewQueue returns a Queue object.
|
// NewQueue returns a Queue object.
|
||||||
func NewQueue(size int) *Queue {
|
func NewQueue(size int) *Queue {
|
||||||
return &Queue{
|
return &Queue{
|
||||||
elements: make([]interface{}, size),
|
elements: make([]any, size),
|
||||||
size: size,
|
size: size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,12 +30,12 @@ func (q *Queue) Empty() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Put puts element into q at the last position.
|
// Put puts element into q at the last position.
|
||||||
func (q *Queue) Put(element interface{}) {
|
func (q *Queue) Put(element any) {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
if q.head == q.tail && q.count > 0 {
|
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, q.elements[q.head:])
|
||||||
copy(nodes[len(q.elements)-q.head:], q.elements[:q.head])
|
copy(nodes[len(q.elements)-q.head:], q.elements[:q.head])
|
||||||
q.head = 0
|
q.head = 0
|
||||||
@@ -49,7 +49,7 @@ func (q *Queue) Put(element interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Take takes the first element out of q if not empty.
|
// 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()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import "sync"
|
|||||||
|
|
||||||
// A Ring can be used as fixed size ring.
|
// A Ring can be used as fixed size ring.
|
||||||
type Ring struct {
|
type Ring struct {
|
||||||
elements []interface{}
|
elements []any
|
||||||
index int
|
index int
|
||||||
lock sync.Mutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRing returns a Ring object with the given size n.
|
// NewRing returns a Ring object with the given size n.
|
||||||
@@ -16,12 +16,12 @@ func NewRing(n int) *Ring {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Ring{
|
return &Ring{
|
||||||
elements: make([]interface{}, n),
|
elements: make([]any, n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds v into r.
|
// Add adds v into r.
|
||||||
func (r *Ring) Add(v interface{}) {
|
func (r *Ring) Add(v any) {
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
|
|
||||||
@@ -30,9 +30,9 @@ func (r *Ring) Add(v interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Take takes all items from r.
|
// Take takes all items from r.
|
||||||
func (r *Ring) Take() []interface{} {
|
func (r *Ring) Take() []any {
|
||||||
r.lock.Lock()
|
r.lock.RLock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
var size int
|
var size int
|
||||||
var start int
|
var start int
|
||||||
@@ -43,7 +43,7 @@ func (r *Ring) Take() []interface{} {
|
|||||||
size = r.index
|
size = r.index
|
||||||
}
|
}
|
||||||
|
|
||||||
elements := make([]interface{}, size)
|
elements := make([]any, size)
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
elements[i] = r.elements[(start+i)%len(r.elements)]
|
elements[i] = r.elements[(start+i)%len(r.elements)]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestRingLess(t *testing.T) {
|
|||||||
ring.Add(i)
|
ring.Add(i)
|
||||||
}
|
}
|
||||||
elements := ring.Take()
|
elements := ring.Take()
|
||||||
assert.ElementsMatch(t, []interface{}{0, 1, 2}, elements)
|
assert.ElementsMatch(t, []any{0, 1, 2}, elements)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRingMore(t *testing.T) {
|
func TestRingMore(t *testing.T) {
|
||||||
@@ -28,7 +28,7 @@ func TestRingMore(t *testing.T) {
|
|||||||
ring.Add(i)
|
ring.Add(i)
|
||||||
}
|
}
|
||||||
elements := ring.Take()
|
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) {
|
func TestRingAdd(t *testing.T) {
|
||||||
|
|||||||
@@ -14,20 +14,20 @@ type SafeMap struct {
|
|||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
deletionOld int
|
deletionOld int
|
||||||
deletionNew int
|
deletionNew int
|
||||||
dirtyOld map[interface{}]interface{}
|
dirtyOld map[any]any
|
||||||
dirtyNew map[interface{}]interface{}
|
dirtyNew map[any]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSafeMap returns a SafeMap.
|
// NewSafeMap returns a SafeMap.
|
||||||
func NewSafeMap() *SafeMap {
|
func NewSafeMap() *SafeMap {
|
||||||
return &SafeMap{
|
return &SafeMap{
|
||||||
dirtyOld: make(map[interface{}]interface{}),
|
dirtyOld: make(map[any]any),
|
||||||
dirtyNew: make(map[interface{}]interface{}),
|
dirtyNew: make(map[any]any),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Del deletes the value with the given key from m.
|
// 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()
|
m.lock.Lock()
|
||||||
if _, ok := m.dirtyOld[key]; ok {
|
if _, ok := m.dirtyOld[key]; ok {
|
||||||
delete(m.dirtyOld, key)
|
delete(m.dirtyOld, key)
|
||||||
@@ -42,21 +42,21 @@ func (m *SafeMap) Del(key interface{}) {
|
|||||||
}
|
}
|
||||||
m.dirtyOld = m.dirtyNew
|
m.dirtyOld = m.dirtyNew
|
||||||
m.deletionOld = m.deletionNew
|
m.deletionOld = m.deletionNew
|
||||||
m.dirtyNew = make(map[interface{}]interface{})
|
m.dirtyNew = make(map[any]any)
|
||||||
m.deletionNew = 0
|
m.deletionNew = 0
|
||||||
}
|
}
|
||||||
if m.deletionNew >= maxDeletion && len(m.dirtyNew) < copyThreshold {
|
if m.deletionNew >= maxDeletion && len(m.dirtyNew) < copyThreshold {
|
||||||
for k, v := range m.dirtyNew {
|
for k, v := range m.dirtyNew {
|
||||||
m.dirtyOld[k] = v
|
m.dirtyOld[k] = v
|
||||||
}
|
}
|
||||||
m.dirtyNew = make(map[interface{}]interface{})
|
m.dirtyNew = make(map[any]any)
|
||||||
m.deletionNew = 0
|
m.deletionNew = 0
|
||||||
}
|
}
|
||||||
m.lock.Unlock()
|
m.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the value with the given key from m.
|
// 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()
|
m.lock.RLock()
|
||||||
defer m.lock.RUnlock()
|
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.
|
// Range calls f sequentially for each key and value present in the map.
|
||||||
// If f returns false, range stops the iteration.
|
// 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()
|
m.lock.RLock()
|
||||||
defer m.lock.RUnlock()
|
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.
|
// 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()
|
m.lock.Lock()
|
||||||
if m.deletionOld <= maxDeletion {
|
if m.deletionOld <= maxDeletion {
|
||||||
if _, ok := m.dirtyNew[key]; ok {
|
if _, ok := m.dirtyNew[key]; ok {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ func TestSafeMap_Range(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var count int32
|
var count int32
|
||||||
m.Range(func(k, v interface{}) bool {
|
m.Range(func(k, v any) bool {
|
||||||
atomic.AddInt32(&count, 1)
|
atomic.AddInt32(&count, 1)
|
||||||
newMap.Set(k, v)
|
newMap.Set(k, v)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ const (
|
|||||||
|
|
||||||
// Set is not thread-safe, for concurrent use, make sure to use it with synchronization.
|
// Set is not thread-safe, for concurrent use, make sure to use it with synchronization.
|
||||||
type Set struct {
|
type Set struct {
|
||||||
data map[interface{}]lang.PlaceholderType
|
data map[any]lang.PlaceholderType
|
||||||
tp int
|
tp int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSet returns a managed Set, can only put the values with the same type.
|
// NewSet returns a managed Set, can only put the values with the same type.
|
||||||
func NewSet() *Set {
|
func NewSet() *Set {
|
||||||
return &Set{
|
return &Set{
|
||||||
data: make(map[interface{}]lang.PlaceholderType),
|
data: make(map[any]lang.PlaceholderType),
|
||||||
tp: untyped,
|
tp: untyped,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,13 +32,13 @@ func NewSet() *Set {
|
|||||||
// NewUnmanagedSet returns an unmanaged Set, which can put values with different types.
|
// NewUnmanagedSet returns an unmanaged Set, which can put values with different types.
|
||||||
func NewUnmanagedSet() *Set {
|
func NewUnmanagedSet() *Set {
|
||||||
return &Set{
|
return &Set{
|
||||||
data: make(map[interface{}]lang.PlaceholderType),
|
data: make(map[any]lang.PlaceholderType),
|
||||||
tp: unmanaged,
|
tp: unmanaged,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds i into s.
|
// Add adds i into s.
|
||||||
func (s *Set) Add(i ...interface{}) {
|
func (s *Set) Add(i ...any) {
|
||||||
for _, each := range i {
|
for _, each := range i {
|
||||||
s.add(each)
|
s.add(each)
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func (s *Set) AddStr(ss ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Contains checks if i is in s.
|
// 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 {
|
if len(s.data) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -91,8 +91,8 @@ func (s *Set) Contains(i interface{}) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Keys returns the keys in s.
|
// Keys returns the keys in s.
|
||||||
func (s *Set) Keys() []interface{} {
|
func (s *Set) Keys() []any {
|
||||||
var keys []interface{}
|
var keys []any
|
||||||
|
|
||||||
for key := range s.data {
|
for key := range s.data {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
@@ -167,7 +167,7 @@ func (s *Set) KeysStr() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes i from s.
|
// Remove removes i from s.
|
||||||
func (s *Set) Remove(i interface{}) {
|
func (s *Set) Remove(i any) {
|
||||||
s.validate(i)
|
s.validate(i)
|
||||||
delete(s.data, i)
|
delete(s.data, i)
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ func (s *Set) Count() int {
|
|||||||
return len(s.data)
|
return len(s.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) add(i interface{}) {
|
func (s *Set) add(i any) {
|
||||||
switch s.tp {
|
switch s.tp {
|
||||||
case unmanaged:
|
case unmanaged:
|
||||||
// do nothing
|
// do nothing
|
||||||
@@ -189,7 +189,7 @@ func (s *Set) add(i interface{}) {
|
|||||||
s.data[i] = lang.Placeholder
|
s.data[i] = lang.Placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Set) setType(i interface{}) {
|
func (s *Set) setType(i any) {
|
||||||
// s.tp can only be untyped here
|
// s.tp can only be untyped here
|
||||||
switch i.(type) {
|
switch i.(type) {
|
||||||
case int:
|
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 {
|
if s.tp == unmanaged {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -213,23 +213,23 @@ func (s *Set) validate(i interface{}) {
|
|||||||
switch i.(type) {
|
switch i.(type) {
|
||||||
case int:
|
case int:
|
||||||
if s.tp != intType {
|
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:
|
case int64:
|
||||||
if s.tp != int64Type {
|
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:
|
case uint:
|
||||||
if s.tp != uintType {
|
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:
|
case uint64:
|
||||||
if s.tp != uint64Type {
|
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:
|
case string:
|
||||||
if s.tp != stringType {
|
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) {
|
func BenchmarkRawSet(b *testing.B) {
|
||||||
m := make(map[interface{}]struct{})
|
m := make(map[any]struct{})
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
m[i] = struct{}{}
|
m[i] = struct{}{}
|
||||||
_ = m[i]
|
_ = m[i]
|
||||||
@@ -39,7 +39,7 @@ func BenchmarkSet(b *testing.B) {
|
|||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
// given
|
// given
|
||||||
set := NewUnmanagedSet()
|
set := NewUnmanagedSet()
|
||||||
values := []interface{}{1, 2, 3}
|
values := []any{1, 2, 3}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
set.Add(values...)
|
set.Add(values...)
|
||||||
@@ -135,7 +135,7 @@ func TestContainsUnmanagedWithoutElements(t *testing.T) {
|
|||||||
func TestRemove(t *testing.T) {
|
func TestRemove(t *testing.T) {
|
||||||
// given
|
// given
|
||||||
set := NewSet()
|
set := NewSet()
|
||||||
set.Add([]interface{}{1, 2, 3}...)
|
set.Add([]any{1, 2, 3}...)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
set.Remove(2)
|
set.Remove(2)
|
||||||
@@ -147,7 +147,7 @@ func TestRemove(t *testing.T) {
|
|||||||
func TestCount(t *testing.T) {
|
func TestCount(t *testing.T) {
|
||||||
// given
|
// given
|
||||||
set := NewSet()
|
set := NewSet()
|
||||||
set.Add([]interface{}{1, 2, 3}...)
|
set.Add([]any{1, 2, 3}...)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert.Equal(t, set.Count(), 3)
|
assert.Equal(t, set.Count(), 3)
|
||||||
@@ -198,5 +198,5 @@ func TestSetType(t *testing.T) {
|
|||||||
set.add(1)
|
set.add(1)
|
||||||
set.add("2")
|
set.add("2")
|
||||||
vals := set.Keys()
|
vals := set.Keys()
|
||||||
assert.ElementsMatch(t, []interface{}{1, "2"}, vals)
|
assert.ElementsMatch(t, []any{1, "2"}, vals)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ var (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
// Execute defines the method to execute the task.
|
// 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.
|
// A TimingWheel is a timing wheel object to schedule tasks.
|
||||||
TimingWheel struct {
|
TimingWheel struct {
|
||||||
@@ -33,14 +33,14 @@ type (
|
|||||||
execute Execute
|
execute Execute
|
||||||
setChannel chan timingEntry
|
setChannel chan timingEntry
|
||||||
moveChannel chan baseEntry
|
moveChannel chan baseEntry
|
||||||
removeChannel chan interface{}
|
removeChannel chan any
|
||||||
drainChannel chan func(key, value interface{})
|
drainChannel chan func(key, value any)
|
||||||
stopChannel chan lang.PlaceholderType
|
stopChannel chan lang.PlaceholderType
|
||||||
}
|
}
|
||||||
|
|
||||||
timingEntry struct {
|
timingEntry struct {
|
||||||
baseEntry
|
baseEntry
|
||||||
value interface{}
|
value any
|
||||||
circle int
|
circle int
|
||||||
diff int
|
diff int
|
||||||
removed bool
|
removed bool
|
||||||
@@ -48,7 +48,7 @@ type (
|
|||||||
|
|
||||||
baseEntry struct {
|
baseEntry struct {
|
||||||
delay time.Duration
|
delay time.Duration
|
||||||
key interface{}
|
key any
|
||||||
}
|
}
|
||||||
|
|
||||||
positionEntry struct {
|
positionEntry struct {
|
||||||
@@ -57,8 +57,8 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
timingTask struct {
|
timingTask struct {
|
||||||
key interface{}
|
key any
|
||||||
value interface{}
|
value any
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,10 +69,11 @@ func NewTimingWheel(interval time.Duration, numSlots int, execute Execute) (*Tim
|
|||||||
interval, numSlots, execute)
|
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) {
|
ticker timex.Ticker) (*TimingWheel, error) {
|
||||||
tw := &TimingWheel{
|
tw := &TimingWheel{
|
||||||
interval: interval,
|
interval: interval,
|
||||||
@@ -84,8 +85,8 @@ func newTimingWheelWithClock(interval time.Duration, numSlots int, execute Execu
|
|||||||
numSlots: numSlots,
|
numSlots: numSlots,
|
||||||
setChannel: make(chan timingEntry),
|
setChannel: make(chan timingEntry),
|
||||||
moveChannel: make(chan baseEntry),
|
moveChannel: make(chan baseEntry),
|
||||||
removeChannel: make(chan interface{}),
|
removeChannel: make(chan any),
|
||||||
drainChannel: make(chan func(key, value interface{})),
|
drainChannel: make(chan func(key, value any)),
|
||||||
stopChannel: make(chan lang.PlaceholderType),
|
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.
|
// 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 {
|
select {
|
||||||
case tw.drainChannel <- fn:
|
case tw.drainChannel <- fn:
|
||||||
return nil
|
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.
|
// 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 {
|
if delay <= 0 || key == nil {
|
||||||
return ErrArgument
|
return ErrArgument
|
||||||
}
|
}
|
||||||
@@ -123,7 +124,7 @@ func (tw *TimingWheel) MoveTimer(key interface{}, delay time.Duration) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveTimer removes the task with the given key.
|
// 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 {
|
if key == nil {
|
||||||
return ErrArgument
|
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.
|
// 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 {
|
if delay <= 0 || key == nil {
|
||||||
return ErrArgument
|
return ErrArgument
|
||||||
}
|
}
|
||||||
@@ -161,7 +162,7 @@ func (tw *TimingWheel) Stop() {
|
|||||||
close(tw.stopChannel)
|
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)
|
runner := threading.NewTaskRunner(drainWorkers)
|
||||||
for _, slot := range tw.slots {
|
for _, slot := range tw.slots {
|
||||||
for e := slot.Front(); e != nil; {
|
for e := slot.Front(); e != nil; {
|
||||||
@@ -231,7 +232,7 @@ func (tw *TimingWheel) onTick() {
|
|||||||
tw.scanAndRunTasks(l)
|
tw.scanAndRunTasks(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tw *TimingWheel) removeTask(key interface{}) {
|
func (tw *TimingWheel) removeTask(key any) {
|
||||||
val, ok := tw.timers.Get(key)
|
val, ok := tw.timers.Get(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNewTimingWheel(t *testing.T) {
|
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)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimingWheel_Drain(t *testing.T) {
|
func TestTimingWheel_Drain(t *testing.T) {
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {
|
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {
|
||||||
}, ticker)
|
}, ticker)
|
||||||
tw.SetTimer("first", 3, testStep*4)
|
tw.SetTimer("first", 3, testStep*4)
|
||||||
tw.SetTimer("second", 5, testStep*7)
|
tw.SetTimer("second", 5, testStep*7)
|
||||||
@@ -36,7 +36,7 @@ func TestTimingWheel_Drain(t *testing.T) {
|
|||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(3)
|
wg.Add(3)
|
||||||
tw.Drain(func(key, value interface{}) {
|
tw.Drain(func(key, value any) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
keys = append(keys, key.(string))
|
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, []string{"first", "second", "third"}, keys)
|
||||||
assert.EqualValues(t, []int{3, 5, 7}, vals)
|
assert.EqualValues(t, []int{3, 5, 7}, vals)
|
||||||
var count int
|
var count int
|
||||||
tw.Drain(func(key, value interface{}) {
|
tw.Drain(func(key, value any) {
|
||||||
count++
|
count++
|
||||||
})
|
})
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
assert.Equal(t, 0, count)
|
assert.Equal(t, 0, count)
|
||||||
tw.Stop()
|
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) {
|
func TestTimingWheel_SetTimerSoon(t *testing.T) {
|
||||||
run := syncx.NewAtomicBool()
|
run := syncx.NewAtomicBool()
|
||||||
ticker := timex.NewFakeTicker()
|
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.True(t, run.CompareAndSwap(false, true))
|
||||||
assert.Equal(t, "any", k)
|
assert.Equal(t, "any", k)
|
||||||
assert.Equal(t, 3, v.(int))
|
assert.Equal(t, 3, v.(int))
|
||||||
@@ -78,7 +78,7 @@ func TestTimingWheel_SetTimerSoon(t *testing.T) {
|
|||||||
func TestTimingWheel_SetTimerTwice(t *testing.T) {
|
func TestTimingWheel_SetTimerTwice(t *testing.T) {
|
||||||
run := syncx.NewAtomicBool()
|
run := syncx.NewAtomicBool()
|
||||||
ticker := timex.NewFakeTicker()
|
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.True(t, run.CompareAndSwap(false, true))
|
||||||
assert.Equal(t, "any", k)
|
assert.Equal(t, "any", k)
|
||||||
assert.Equal(t, 5, v.(int))
|
assert.Equal(t, 5, v.(int))
|
||||||
@@ -96,7 +96,7 @@ func TestTimingWheel_SetTimerTwice(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
|
func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {}, ticker)
|
||||||
defer tw.Stop()
|
defer tw.Stop()
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
tw.SetTimer("any", 3, -testStep)
|
tw.SetTimer("any", 3, -testStep)
|
||||||
@@ -105,7 +105,7 @@ func TestTimingWheel_SetTimerWrongDelay(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
|
func TestTimingWheel_SetTimerAfterClose(t *testing.T) {
|
||||||
ticker := timex.NewFakeTicker()
|
ticker := timex.NewFakeTicker()
|
||||||
tw, _ := newTimingWheelWithClock(testStep, 10, func(k, v interface{}) {}, ticker)
|
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(k, v any) {}, ticker)
|
||||||
tw.Stop()
|
tw.Stop()
|
||||||
assert.Equal(t, ErrClosed, tw.SetTimer("any", 3, testStep))
|
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) {
|
func TestTimingWheel_MoveTimer(t *testing.T) {
|
||||||
run := syncx.NewAtomicBool()
|
run := syncx.NewAtomicBool()
|
||||||
ticker := timex.NewFakeTicker()
|
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.True(t, run.CompareAndSwap(false, true))
|
||||||
assert.Equal(t, "any", k)
|
assert.Equal(t, "any", k)
|
||||||
assert.Equal(t, 3, v.(int))
|
assert.Equal(t, 3, v.(int))
|
||||||
@@ -139,7 +139,7 @@ func TestTimingWheel_MoveTimer(t *testing.T) {
|
|||||||
func TestTimingWheel_MoveTimerSoon(t *testing.T) {
|
func TestTimingWheel_MoveTimerSoon(t *testing.T) {
|
||||||
run := syncx.NewAtomicBool()
|
run := syncx.NewAtomicBool()
|
||||||
ticker := timex.NewFakeTicker()
|
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.True(t, run.CompareAndSwap(false, true))
|
||||||
assert.Equal(t, "any", k)
|
assert.Equal(t, "any", k)
|
||||||
assert.Equal(t, 3, v.(int))
|
assert.Equal(t, 3, v.(int))
|
||||||
@@ -155,7 +155,7 @@ func TestTimingWheel_MoveTimerSoon(t *testing.T) {
|
|||||||
func TestTimingWheel_MoveTimerEarlier(t *testing.T) {
|
func TestTimingWheel_MoveTimerEarlier(t *testing.T) {
|
||||||
run := syncx.NewAtomicBool()
|
run := syncx.NewAtomicBool()
|
||||||
ticker := timex.NewFakeTicker()
|
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.True(t, run.CompareAndSwap(false, true))
|
||||||
assert.Equal(t, "any", k)
|
assert.Equal(t, "any", k)
|
||||||
assert.Equal(t, 3, v.(int))
|
assert.Equal(t, 3, v.(int))
|
||||||
@@ -173,7 +173,7 @@ func TestTimingWheel_MoveTimerEarlier(t *testing.T) {
|
|||||||
|
|
||||||
func TestTimingWheel_RemoveTimer(t *testing.T) {
|
func TestTimingWheel_RemoveTimer(t *testing.T) {
|
||||||
ticker := timex.NewFakeTicker()
|
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)
|
tw.SetTimer("any", 3, testStep)
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
tw.RemoveTimer("any")
|
tw.RemoveTimer("any")
|
||||||
@@ -236,7 +236,7 @@ func TestTimingWheel_SetTimer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var actual int32
|
var actual int32
|
||||||
done := make(chan lang.PlaceholderType)
|
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, 1, key.(int))
|
||||||
assert.Equal(t, 2, value.(int))
|
assert.Equal(t, 2, value.(int))
|
||||||
actual = atomic.LoadInt32(&count)
|
actual = atomic.LoadInt32(&count)
|
||||||
@@ -317,7 +317,7 @@ func TestTimingWheel_SetAndMoveThenStart(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var actual int32
|
var actual int32
|
||||||
done := make(chan lang.PlaceholderType)
|
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)
|
actual = atomic.LoadInt32(&count)
|
||||||
close(done)
|
close(done)
|
||||||
}, ticker)
|
}, ticker)
|
||||||
@@ -405,7 +405,7 @@ func TestTimingWheel_SetAndMoveTwice(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var actual int32
|
var actual int32
|
||||||
done := make(chan lang.PlaceholderType)
|
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)
|
actual = atomic.LoadInt32(&count)
|
||||||
close(done)
|
close(done)
|
||||||
}, ticker)
|
}, ticker)
|
||||||
@@ -486,7 +486,7 @@ func TestTimingWheel_ElapsedAndSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var actual int32
|
var actual int32
|
||||||
done := make(chan lang.PlaceholderType)
|
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)
|
actual = atomic.LoadInt32(&count)
|
||||||
close(done)
|
close(done)
|
||||||
}, ticker)
|
}, ticker)
|
||||||
@@ -577,7 +577,7 @@ func TestTimingWheel_ElapsedAndSetThenMove(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var actual int32
|
var actual int32
|
||||||
done := make(chan lang.PlaceholderType)
|
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)
|
actual = atomic.LoadInt32(&count)
|
||||||
close(done)
|
close(done)
|
||||||
}, ticker)
|
}, ticker)
|
||||||
@@ -612,7 +612,7 @@ func TestMoveAndRemoveTask(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var keys []int
|
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, "any", k)
|
||||||
assert.Equal(t, 3, v.(int))
|
assert.Equal(t, 3, v.(int))
|
||||||
keys = append(keys, v.(int))
|
keys = append(keys, v.(int))
|
||||||
@@ -632,7 +632,7 @@ func TestMoveAndRemoveTask(t *testing.T) {
|
|||||||
func BenchmarkTimingWheel(b *testing.B) {
|
func BenchmarkTimingWheel(b *testing.B) {
|
||||||
b.ReportAllocs()
|
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++ {
|
for i := 0; i < b.N; i++ {
|
||||||
tw.SetTimer(i, i, time.Second)
|
tw.SetTimer(i, i, time.Second)
|
||||||
tw.SetTimer(b.N+i, b.N+i, time.Second)
|
tw.SetTimer(b.N+i, b.N+i, time.Second)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/jsonx"
|
"github.com/zeromicro/go-zero/core/jsonx"
|
||||||
@@ -12,17 +13,36 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/internal/encoding"
|
"github.com/zeromicro/go-zero/internal/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
const distanceBetweenUpperAndLower = 32
|
const (
|
||||||
|
jsonTagKey = "json"
|
||||||
|
jsonTagSep = ','
|
||||||
|
)
|
||||||
|
|
||||||
var loaders = map[string]func([]byte, interface{}) error{
|
var (
|
||||||
".json": LoadFromJsonBytes,
|
fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
|
||||||
".toml": LoadFromTomlBytes,
|
loaders = map[string]func([]byte, any) error{
|
||||||
".yaml": LoadFromYamlBytes,
|
".json": LoadFromJsonBytes,
|
||||||
".yml": LoadFromYamlBytes,
|
".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.
|
// 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)
|
content, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
|
||||||
// Deprecated: use Load instead.
|
// 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...)
|
return Load(file, v, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFromJsonBytes loads config into v from content json bytes.
|
// LoadFromJsonBytes loads config into v from content json bytes.
|
||||||
func LoadFromJsonBytes(content []byte, v interface{}) error {
|
func LoadFromJsonBytes(content []byte, v any) error {
|
||||||
var m map[string]interface{}
|
info, err := buildFieldsInfo(reflect.TypeOf(v), "")
|
||||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// LoadConfigFromJsonBytes loads config into v from content json bytes.
|
||||||
// Deprecated: use LoadFromJsonBytes instead.
|
// Deprecated: use LoadFromJsonBytes instead.
|
||||||
func LoadConfigFromJsonBytes(content []byte, v interface{}) error {
|
func LoadConfigFromJsonBytes(content []byte, v any) error {
|
||||||
return LoadFromJsonBytes(content, v)
|
return LoadFromJsonBytes(content, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFromTomlBytes loads config into v from content toml bytes.
|
// 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)
|
b, err := encoding.TomlToJson(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -78,7 +105,7 @@ func LoadFromTomlBytes(content []byte, v interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadFromYamlBytes loads config into v from content yaml bytes.
|
// 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)
|
b, err := encoding.YamlToJson(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -89,64 +116,200 @@ func LoadFromYamlBytes(content []byte, v interface{}) error {
|
|||||||
|
|
||||||
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
|
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
|
||||||
// Deprecated: use LoadFromYamlBytes instead.
|
// Deprecated: use LoadFromYamlBytes instead.
|
||||||
func LoadConfigFromYamlBytes(content []byte, v interface{}) error {
|
func LoadConfigFromYamlBytes(content []byte, v any) error {
|
||||||
return LoadFromYamlBytes(content, v)
|
return LoadFromYamlBytes(content, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustLoad loads config into v from path, exits on error.
|
// 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 {
|
if err := Load(path, v, opts...); err != nil {
|
||||||
log.Fatalf("error: config file %s, %s", path, err.Error())
|
log.Fatalf("error: config file %s, %s", path, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toCamelCase(s string) string {
|
func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName string) error {
|
||||||
var buf strings.Builder
|
if prev, ok := info.children[key]; ok {
|
||||||
buf.Grow(len(s))
|
if child.mapField != nil {
|
||||||
var capNext bool
|
return newConflictKeyError(fullName)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
if isCap || isLow {
|
|
||||||
buf.WriteRune(v)
|
if err := mergeFields(prev, key, child.children, fullName); err != nil {
|
||||||
capNext = false
|
return err
|
||||||
} else if v == ' ' || v == '\t' {
|
}
|
||||||
buf.WriteRune(v)
|
} else {
|
||||||
capNext = false
|
info.children[key] = child
|
||||||
boundary = true
|
}
|
||||||
} else if v == '_' {
|
|
||||||
capNext = true
|
return nil
|
||||||
boundary = true
|
}
|
||||||
} else {
|
|
||||||
buf.WriteRune(v)
|
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
|
||||||
capNext = true
|
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) {
|
switch vv := v.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
return toCamelCaseKeyMap(vv)
|
return toLowerCaseKeyMap(vv, info)
|
||||||
case []interface{}:
|
case []any:
|
||||||
var arr []interface{}
|
var arr []any
|
||||||
for _, vvv := range vv {
|
for _, vvv := range vv {
|
||||||
arr = append(arr, toCamelCaseInterface(vvv))
|
arr = append(arr, toLowerCaseInterface(vvv, info))
|
||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
default:
|
default:
|
||||||
@@ -154,11 +317,45 @@ func toCamelCaseInterface(v interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toCamelCaseKeyMap(m map[string]interface{}) map[string]interface{} {
|
func toLowerCaseKeyMap(m map[string]any, info *fieldInfo) map[string]any {
|
||||||
res := make(map[string]interface{})
|
res := make(map[string]any)
|
||||||
|
|
||||||
for k, v := range m {
|
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
|
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.
|
// PropertyError represents a configuration error message.
|
||||||
type PropertyError struct {
|
type PropertyError struct {
|
||||||
error
|
|
||||||
message string
|
message string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ func TestPropertiesEnv(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer os.Remove(tmpfile)
|
defer os.Remove(tmpfile)
|
||||||
|
|
||||||
os.Setenv("FOO", "2")
|
t.Setenv("FOO", "2")
|
||||||
defer os.Unsetenv("FOO")
|
|
||||||
|
|
||||||
props, err := LoadProperties(tmpfile, UseEnv())
|
props, err := LoadProperties(tmpfile, UseEnv())
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
type RestfulConf struct {
|
type RestfulConf struct {
|
||||||
|
ServiceName string `json:",env=SERVICE_NAME"` // read from env automatically
|
||||||
Host string `json:",default=0.0.0.0"`
|
Host string `json:",default=0.0.0.0"`
|
||||||
Port int
|
Port int
|
||||||
LogMode string `json:",options=[file,console]"`
|
LogMode string `json:",options=[file,console]"`
|
||||||
@@ -21,20 +22,20 @@ type RestfulConf struct {
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# most fields are optional or have default values
|
# most fields are optional or have default values
|
||||||
Port: 8080
|
port: 8080
|
||||||
LogMode: console
|
logMode: console
|
||||||
# you can use env settings
|
# you can use env settings
|
||||||
MaxBytes: ${MAX_BYTES}
|
maxBytes: ${MAX_BYTES}
|
||||||
```
|
```
|
||||||
|
|
||||||
- toml example
|
- toml example
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# most fields are optional or have default values
|
# most fields are optional or have default values
|
||||||
Port = 8_080
|
port = 8_080
|
||||||
LogMode = "console"
|
logMode = "console"
|
||||||
# you can use env settings
|
# you can use env settings
|
||||||
MaxBytes = "${MAX_BYTES}"
|
maxBytes = "${MAX_BYTES}"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Load the config from a file:
|
3. Load the config from a file:
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ type contextValuer struct {
|
|||||||
context.Context
|
context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cv contextValuer) Value(key string) (interface{}, bool) {
|
func (cv contextValuer) Value(key string) (any, bool) {
|
||||||
v := cv.Context.Value(key)
|
v := cv.Context.Value(key)
|
||||||
return v, v != nil
|
return v, v != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// For unmarshals ctx into v.
|
// 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{
|
return unmarshaler.UnmarshalValuer(contextValuer{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
}, v)
|
}, v)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ var (
|
|||||||
type EtcdConf struct {
|
type EtcdConf struct {
|
||||||
Hosts []string
|
Hosts []string
|
||||||
Key string
|
Key string
|
||||||
|
ID int64 `json:",optional"`
|
||||||
User string `json:",optional"`
|
User string `json:",optional"`
|
||||||
Pass string `json:",optional"`
|
Pass string `json:",optional"`
|
||||||
CertFile 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
|
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.
|
// HasTLS returns if TLS CertFile/CertKeyFile/CACertFile are provided.
|
||||||
func (c EtcdConf) HasTLS() bool {
|
func (c EtcdConf) HasTLS() bool {
|
||||||
return len(c.CertFile) > 0 && len(c.CertKeyFile) > 0 && len(c.CACertFile) > 0
|
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())
|
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
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/stringx"
|
"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) {
|
func TestAccount(t *testing.T) {
|
||||||
endpoints := []string{
|
endpoints := []string{
|
||||||
"192.168.0.2:2379",
|
"192.168.0.2:2379",
|
||||||
@@ -32,3 +105,34 @@ func TestAccount(t *testing.T) {
|
|||||||
assert.Equal(t, username, account.User)
|
assert.Equal(t, username, account.User)
|
||||||
assert.Equal(t, anotherPassword, account.Pass)
|
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
|
// Get mocks base method
|
||||||
func (m *MockEtcdClient) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
|
func (m *MockEtcdClient) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{ctx, key}
|
varargs := []any{ctx, key}
|
||||||
for _, a := range opts {
|
for _, a := range opts {
|
||||||
varargs = append(varargs, a)
|
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
|
// 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()
|
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...)
|
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
|
// 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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Grant", reflect.TypeOf((*MockEtcdClient)(nil).Grant), ctx, ttl)
|
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
|
// 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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeepAlive", reflect.TypeOf((*MockEtcdClient)(nil).KeepAlive), ctx, id)
|
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
|
// Put mocks base method
|
||||||
func (m *MockEtcdClient) Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
|
func (m *MockEtcdClient) Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{ctx, key, val}
|
varargs := []any{ctx, key, val}
|
||||||
for _, a := range opts {
|
for _, a := range opts {
|
||||||
varargs = append(varargs, a)
|
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
|
// 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()
|
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...)
|
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
|
// 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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Revoke", reflect.TypeOf((*MockEtcdClient)(nil).Revoke), ctx, id)
|
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
|
// Watch mocks base method
|
||||||
func (m *MockEtcdClient) Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan {
|
func (m *MockEtcdClient) Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{ctx, key}
|
varargs := []any{ctx, key}
|
||||||
for _, a := range opts {
|
for _, a := range opts {
|
||||||
varargs = append(varargs, a)
|
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
|
// 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()
|
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...)
|
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.
|
// DialClient dials an etcd cluster with given endpoints.
|
||||||
func DialClient(endpoints []string) (EtcdClient, error) {
|
func DialClient(endpoints []string) (EtcdClient, error) {
|
||||||
cfg := clientv3.Config{
|
cfg := clientv3.Config{
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
AutoSyncInterval: autoSyncInterval,
|
AutoSyncInterval: autoSyncInterval,
|
||||||
DialTimeout: DialTimeout,
|
DialTimeout: DialTimeout,
|
||||||
DialKeepAliveTime: dialKeepAliveTime,
|
RejectOldCluster: true,
|
||||||
DialKeepAliveTimeout: DialTimeout,
|
PermitWithoutStream: true,
|
||||||
RejectOldCluster: true,
|
|
||||||
PermitWithoutStream: true,
|
|
||||||
}
|
}
|
||||||
if account, ok := GetAccount(endpoints); ok {
|
if account, ok := GetAccount(endpoints); ok {
|
||||||
cfg.Username = account.User
|
cfg.Username = account.User
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"go.etcd.io/etcd/api/v3/etcdserverpb"
|
"go.etcd.io/etcd/api/v3/etcdserverpb"
|
||||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
|
"go.etcd.io/etcd/client/v3/mock/mockserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mockLock sync.Mutex
|
var mockLock sync.Mutex
|
||||||
@@ -167,7 +170,7 @@ func TestCluster_Watch(t *testing.T) {
|
|||||||
assert.Equal(t, "world", kv.Val)
|
assert.Equal(t, "world", kv.Val)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}).MaxTimes(1)
|
}).MaxTimes(1)
|
||||||
listener.EXPECT().OnDelete(gomock.Any()).Do(func(_ interface{}) {
|
listener.EXPECT().OnDelete(gomock.Any()).Do(func(_ any) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}).MaxTimes(1)
|
}).MaxTimes(1)
|
||||||
go c.watch(cli, "any", 0)
|
go c.watch(cli, "any", 0)
|
||||||
@@ -242,3 +245,58 @@ func TestValueOnlyContext(t *testing.T) {
|
|||||||
ctx.Done()
|
ctx.Done()
|
||||||
assert.Nil(t, ctx.Err())
|
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
|
// 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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForStateChange", reflect.TypeOf((*MocketcdConn)(nil).WaitForStateChange), ctx, sourceState)
|
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
|
// 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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnAdd", reflect.TypeOf((*MockUpdateListener)(nil).OnAdd), kv)
|
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
|
// 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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDelete", reflect.TypeOf((*MockUpdateListener)(nil).OnDelete), kv)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnDelete", reflect.TypeOf((*MockUpdateListener)(nil).OnDelete), kv)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ const (
|
|||||||
autoSyncInterval = time.Minute
|
autoSyncInterval = time.Minute
|
||||||
coolDownInterval = time.Second
|
coolDownInterval = time.Second
|
||||||
dialTimeout = 5 * time.Second
|
dialTimeout = 5 * time.Second
|
||||||
dialKeepAliveTime = 5 * time.Second
|
|
||||||
requestTimeout = 3 * time.Second
|
requestTimeout = 3 * time.Second
|
||||||
endpointsSeparator = ","
|
endpointsSeparator = ","
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package discov
|
package discov
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/zeromicro/go-zero/core/discov/internal"
|
"github.com/zeromicro/go-zero/core/discov/internal"
|
||||||
"github.com/zeromicro/go-zero/core/lang"
|
"github.com/zeromicro/go-zero/core/lang"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"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.
|
// KeepAlive keeps key:value alive.
|
||||||
func (p *Publisher) KeepAlive() error {
|
func (p *Publisher) KeepAlive() error {
|
||||||
cli, err := internal.GetRegistry().GetConn(p.endpoints)
|
cli, err := p.doRegister()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.lease, err = p.register(cli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -83,6 +80,43 @@ func (p *Publisher) Stop() {
|
|||||||
p.quit.Close()
|
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 {
|
func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
||||||
ch, err := cli.KeepAlive(cli.Ctx(), p.lease)
|
ch, err := cli.KeepAlive(cli.Ctx(), p.lease)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -95,8 +129,8 @@ func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
|||||||
case _, ok := <-ch:
|
case _, ok := <-ch:
|
||||||
if !ok {
|
if !ok {
|
||||||
p.revoke(cli)
|
p.revoke(cli)
|
||||||
if err := p.KeepAlive(); err != nil {
|
if err := p.doKeepAlive(); err != nil {
|
||||||
logx.Errorf("KeepAlive: %s", err.Error())
|
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -105,8 +139,8 @@ func (p *Publisher) keepAliveAsync(cli internal.EtcdClient) error {
|
|||||||
p.revoke(cli)
|
p.revoke(cli)
|
||||||
select {
|
select {
|
||||||
case <-p.resumeChan:
|
case <-p.resumeChan:
|
||||||
if err := p.KeepAlive(); err != nil {
|
if err := p.doKeepAlive(); err != nil {
|
||||||
logx.Errorf("KeepAlive: %s", err.Error())
|
logx.Errorf("etcd publisher KeepAlive: %s", err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-p.quit.Done():
|
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) {
|
func (p *Publisher) revoke(cli internal.EtcdClient) {
|
||||||
if _, err := cli.Revoke(cli.Ctx(), p.lease); err != nil {
|
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
|
package discov
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,6 +16,83 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
"github.com/zeromicro/go-zero/core/stringx"
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
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() {
|
func init() {
|
||||||
@@ -37,7 +117,7 @@ func TestPublisher_register(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublisher_registerWithId(t *testing.T) {
|
func TestPublisher_registerWithOptions(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
const id = 2
|
const id = 2
|
||||||
@@ -49,7 +129,15 @@ func TestPublisher_registerWithId(t *testing.T) {
|
|||||||
ID: 1,
|
ID: 1,
|
||||||
}, nil)
|
}, nil)
|
||||||
cli.EXPECT().Put(gomock.Any(), makeEtcdKey("thekey", id), "thevalue", gomock.Any())
|
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)
|
_, err := pub.register(cli)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
||||||
@@ -125,7 +213,7 @@ func TestPublisher_keepAliveAsyncQuit(t *testing.T) {
|
|||||||
cli.EXPECT().KeepAlive(gomock.Any(), id)
|
cli.EXPECT().KeepAlive(gomock.Any(), id)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ interface{}) {
|
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ any) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
})
|
})
|
||||||
pub := NewPublisher(nil, "thekey", "thevalue")
|
pub := NewPublisher(nil, "thekey", "thevalue")
|
||||||
@@ -147,7 +235,7 @@ func TestPublisher_keepAliveAsyncPause(t *testing.T) {
|
|||||||
pub := NewPublisher(nil, "thekey", "thevalue")
|
pub := NewPublisher(nil, "thekey", "thevalue")
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ interface{}) {
|
cli.EXPECT().Revoke(gomock.Any(), id).Do(func(_, _ any) {
|
||||||
pub.Stop()
|
pub.Stop()
|
||||||
wg.Done()
|
wg.Done()
|
||||||
})
|
})
|
||||||
@@ -169,3 +257,92 @@ func TestPublisher_Resume(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
<-publisher.resumeChan
|
<-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.
|
// 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 {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func NewBulkExecutor(execute Execute, opts ...BulkOption) *BulkExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds task into be.
|
// Add adds task into be.
|
||||||
func (be *BulkExecutor) Add(task interface{}) error {
|
func (be *BulkExecutor) Add(task any) error {
|
||||||
be.executor.Add(task)
|
be.executor.Add(task)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -79,22 +79,22 @@ func newBulkOptions() bulkOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bulkContainer struct {
|
type bulkContainer struct {
|
||||||
tasks []interface{}
|
tasks []any
|
||||||
execute Execute
|
execute Execute
|
||||||
maxTasks int
|
maxTasks int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *bulkContainer) AddTask(task interface{}) bool {
|
func (bc *bulkContainer) AddTask(task any) bool {
|
||||||
bc.tasks = append(bc.tasks, task)
|
bc.tasks = append(bc.tasks, task)
|
||||||
return len(bc.tasks) >= bc.maxTasks
|
return len(bc.tasks) >= bc.maxTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *bulkContainer) Execute(tasks interface{}) {
|
func (bc *bulkContainer) Execute(tasks any) {
|
||||||
vals := tasks.([]interface{})
|
vals := tasks.([]any)
|
||||||
bc.execute(vals)
|
bc.execute(vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *bulkContainer) RemoveAll() interface{} {
|
func (bc *bulkContainer) RemoveAll() any {
|
||||||
tasks := bc.tasks
|
tasks := bc.tasks
|
||||||
bc.tasks = nil
|
bc.tasks = nil
|
||||||
return tasks
|
return tasks
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func TestBulkExecutor(t *testing.T) {
|
|||||||
var values []int
|
var values []int
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
executor := NewBulkExecutor(func(items []interface{}) {
|
executor := NewBulkExecutor(func(items []any) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
values = append(values, len(items))
|
values = append(values, len(items))
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
@@ -40,7 +40,7 @@ func TestBulkExecutorFlushInterval(t *testing.T) {
|
|||||||
var wait sync.WaitGroup
|
var wait sync.WaitGroup
|
||||||
|
|
||||||
wait.Add(1)
|
wait.Add(1)
|
||||||
executor := NewBulkExecutor(func(items []interface{}) {
|
executor := NewBulkExecutor(func(items []any) {
|
||||||
assert.Equal(t, size, len(items))
|
assert.Equal(t, size, len(items))
|
||||||
wait.Done()
|
wait.Done()
|
||||||
}, WithBulkTasks(caches), WithBulkInterval(time.Millisecond*100))
|
}, WithBulkTasks(caches), WithBulkInterval(time.Millisecond*100))
|
||||||
@@ -53,7 +53,7 @@ func TestBulkExecutorFlushInterval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBulkExecutorEmpty(t *testing.T) {
|
func TestBulkExecutorEmpty(t *testing.T) {
|
||||||
NewBulkExecutor(func(items []interface{}) {
|
NewBulkExecutor(func(items []any) {
|
||||||
assert.Fail(t, "should not called")
|
assert.Fail(t, "should not called")
|
||||||
}, WithBulkTasks(10), WithBulkInterval(time.Millisecond))
|
}, WithBulkTasks(10), WithBulkInterval(time.Millisecond))
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
@@ -67,7 +67,7 @@ func TestBulkExecutorFlush(t *testing.T) {
|
|||||||
|
|
||||||
var wait sync.WaitGroup
|
var wait sync.WaitGroup
|
||||||
wait.Add(1)
|
wait.Add(1)
|
||||||
be := NewBulkExecutor(func(items []interface{}) {
|
be := NewBulkExecutor(func(items []any) {
|
||||||
assert.Equal(t, tasks, len(items))
|
assert.Equal(t, tasks, len(items))
|
||||||
wait.Done()
|
wait.Done()
|
||||||
}, WithBulkTasks(caches), WithBulkInterval(time.Minute))
|
}, WithBulkTasks(caches), WithBulkInterval(time.Minute))
|
||||||
@@ -78,11 +78,11 @@ func TestBulkExecutorFlush(t *testing.T) {
|
|||||||
wait.Wait()
|
wait.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuldExecutorFlushSlowTasks(t *testing.T) {
|
func TestBulkExecutorFlushSlowTasks(t *testing.T) {
|
||||||
const total = 1500
|
const total = 1500
|
||||||
lock := new(sync.Mutex)
|
lock := new(sync.Mutex)
|
||||||
result := make([]interface{}, 0, 10000)
|
result := make([]any, 0, 10000)
|
||||||
exec := NewBulkExecutor(func(tasks []interface{}) {
|
exec := NewBulkExecutor(func(tasks []any) {
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
@@ -100,7 +100,7 @@ func TestBuldExecutorFlushSlowTasks(t *testing.T) {
|
|||||||
func BenchmarkBulkExecutor(b *testing.B) {
|
func BenchmarkBulkExecutor(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
be := NewBulkExecutor(func(tasks []interface{}) {
|
be := NewBulkExecutor(func(tasks []any) {
|
||||||
time.Sleep(time.Millisecond * time.Duration(len(tasks)))
|
time.Sleep(time.Millisecond * time.Duration(len(tasks)))
|
||||||
})
|
})
|
||||||
for i := 0; i < b.N; i++ {
|
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.
|
// 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{
|
ce.executor.Add(chunk{
|
||||||
val: task,
|
val: task,
|
||||||
size: size,
|
size: size,
|
||||||
@@ -82,25 +82,25 @@ func newChunkOptions() chunkOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type chunkContainer struct {
|
type chunkContainer struct {
|
||||||
tasks []interface{}
|
tasks []any
|
||||||
execute Execute
|
execute Execute
|
||||||
size int
|
size int
|
||||||
maxChunkSize int
|
maxChunkSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *chunkContainer) AddTask(task interface{}) bool {
|
func (bc *chunkContainer) AddTask(task any) bool {
|
||||||
ck := task.(chunk)
|
ck := task.(chunk)
|
||||||
bc.tasks = append(bc.tasks, ck.val)
|
bc.tasks = append(bc.tasks, ck.val)
|
||||||
bc.size += ck.size
|
bc.size += ck.size
|
||||||
return bc.size >= bc.maxChunkSize
|
return bc.size >= bc.maxChunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *chunkContainer) Execute(tasks interface{}) {
|
func (bc *chunkContainer) Execute(tasks any) {
|
||||||
vals := tasks.([]interface{})
|
vals := tasks.([]any)
|
||||||
bc.execute(vals)
|
bc.execute(vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *chunkContainer) RemoveAll() interface{} {
|
func (bc *chunkContainer) RemoveAll() any {
|
||||||
tasks := bc.tasks
|
tasks := bc.tasks
|
||||||
bc.tasks = nil
|
bc.tasks = nil
|
||||||
bc.size = 0
|
bc.size = 0
|
||||||
@@ -108,6 +108,6 @@ func (bc *chunkContainer) RemoveAll() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type chunk struct {
|
type chunk struct {
|
||||||
val interface{}
|
val any
|
||||||
size int
|
size int
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func TestChunkExecutor(t *testing.T) {
|
|||||||
var values []int
|
var values []int
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
executor := NewChunkExecutor(func(items []interface{}) {
|
executor := NewChunkExecutor(func(items []any) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
values = append(values, len(items))
|
values = append(values, len(items))
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
@@ -40,7 +40,7 @@ func TestChunkExecutorFlushInterval(t *testing.T) {
|
|||||||
var wait sync.WaitGroup
|
var wait sync.WaitGroup
|
||||||
|
|
||||||
wait.Add(1)
|
wait.Add(1)
|
||||||
executor := NewChunkExecutor(func(items []interface{}) {
|
executor := NewChunkExecutor(func(items []any) {
|
||||||
assert.Equal(t, size, len(items))
|
assert.Equal(t, size, len(items))
|
||||||
wait.Done()
|
wait.Done()
|
||||||
}, WithChunkBytes(caches), WithFlushInterval(time.Millisecond*100))
|
}, WithChunkBytes(caches), WithFlushInterval(time.Millisecond*100))
|
||||||
@@ -53,10 +53,11 @@ func TestChunkExecutorFlushInterval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestChunkExecutorEmpty(t *testing.T) {
|
func TestChunkExecutorEmpty(t *testing.T) {
|
||||||
NewChunkExecutor(func(items []interface{}) {
|
executor := NewChunkExecutor(func(items []any) {
|
||||||
assert.Fail(t, "should not called")
|
assert.Fail(t, "should not called")
|
||||||
}, WithChunkBytes(10), WithFlushInterval(time.Millisecond))
|
}, WithChunkBytes(10), WithFlushInterval(time.Millisecond))
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
executor.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChunkExecutorFlush(t *testing.T) {
|
func TestChunkExecutorFlush(t *testing.T) {
|
||||||
@@ -67,7 +68,7 @@ func TestChunkExecutorFlush(t *testing.T) {
|
|||||||
|
|
||||||
var wait sync.WaitGroup
|
var wait sync.WaitGroup
|
||||||
wait.Add(1)
|
wait.Add(1)
|
||||||
be := NewChunkExecutor(func(items []interface{}) {
|
be := NewChunkExecutor(func(items []any) {
|
||||||
assert.Equal(t, tasks, len(items))
|
assert.Equal(t, tasks, len(items))
|
||||||
wait.Done()
|
wait.Done()
|
||||||
}, WithChunkBytes(caches), WithFlushInterval(time.Minute))
|
}, WithChunkBytes(caches), WithFlushInterval(time.Minute))
|
||||||
@@ -81,7 +82,7 @@ func TestChunkExecutorFlush(t *testing.T) {
|
|||||||
func BenchmarkChunkExecutor(b *testing.B) {
|
func BenchmarkChunkExecutor(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
be := NewChunkExecutor(func(tasks []interface{}) {
|
be := NewChunkExecutor(func(tasks []any) {
|
||||||
time.Sleep(time.Millisecond * time.Duration(len(tasks)))
|
time.Sleep(time.Millisecond * time.Duration(len(tasks)))
|
||||||
})
|
})
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
|||||||
@@ -21,16 +21,16 @@ type (
|
|||||||
TaskContainer interface {
|
TaskContainer interface {
|
||||||
// AddTask adds the task into the container.
|
// AddTask adds the task into the container.
|
||||||
// Returns true if the container needs to be flushed after the addition.
|
// 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 handles the collected tasks by the container when flushing.
|
||||||
Execute(tasks interface{})
|
Execute(tasks any)
|
||||||
// RemoveAll removes the contained tasks, and return them.
|
// RemoveAll removes the contained tasks, and return them.
|
||||||
RemoveAll() interface{}
|
RemoveAll() any
|
||||||
}
|
}
|
||||||
|
|
||||||
// A PeriodicalExecutor is an executor that periodically execute tasks.
|
// A PeriodicalExecutor is an executor that periodically execute tasks.
|
||||||
PeriodicalExecutor struct {
|
PeriodicalExecutor struct {
|
||||||
commander chan interface{}
|
commander chan any
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
container TaskContainer
|
container TaskContainer
|
||||||
waitGroup sync.WaitGroup
|
waitGroup sync.WaitGroup
|
||||||
@@ -48,7 +48,7 @@ type (
|
|||||||
func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor {
|
func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *PeriodicalExecutor {
|
||||||
executor := &PeriodicalExecutor{
|
executor := &PeriodicalExecutor{
|
||||||
// buffer 1 to let the caller go quickly
|
// buffer 1 to let the caller go quickly
|
||||||
commander: make(chan interface{}, 1),
|
commander: make(chan any, 1),
|
||||||
interval: interval,
|
interval: interval,
|
||||||
container: container,
|
container: container,
|
||||||
confirmChan: make(chan lang.PlaceholderType),
|
confirmChan: make(chan lang.PlaceholderType),
|
||||||
@@ -64,7 +64,7 @@ func NewPeriodicalExecutor(interval time.Duration, container TaskContainer) *Per
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds tasks into pe.
|
// Add adds tasks into pe.
|
||||||
func (pe *PeriodicalExecutor) Add(task interface{}) {
|
func (pe *PeriodicalExecutor) Add(task any) {
|
||||||
if vals, ok := pe.addAndCheck(task); ok {
|
if vals, ok := pe.addAndCheck(task); ok {
|
||||||
pe.commander <- vals
|
pe.commander <- vals
|
||||||
<-pe.confirmChan
|
<-pe.confirmChan
|
||||||
@@ -74,14 +74,14 @@ func (pe *PeriodicalExecutor) Add(task interface{}) {
|
|||||||
// Flush forces pe to execute tasks.
|
// Flush forces pe to execute tasks.
|
||||||
func (pe *PeriodicalExecutor) Flush() bool {
|
func (pe *PeriodicalExecutor) Flush() bool {
|
||||||
pe.enterExecution()
|
pe.enterExecution()
|
||||||
return pe.executeTasks(func() interface{} {
|
return pe.executeTasks(func() any {
|
||||||
pe.lock.Lock()
|
pe.lock.Lock()
|
||||||
defer pe.lock.Unlock()
|
defer pe.lock.Unlock()
|
||||||
return pe.container.RemoveAll()
|
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()) {
|
func (pe *PeriodicalExecutor) Sync(fn func()) {
|
||||||
pe.lock.Lock()
|
pe.lock.Lock()
|
||||||
defer pe.lock.Unlock()
|
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()
|
pe.lock.Lock()
|
||||||
defer func() {
|
defer func() {
|
||||||
if !pe.guarded {
|
if !pe.guarded {
|
||||||
@@ -116,7 +116,7 @@ func (pe *PeriodicalExecutor) addAndCheck(task interface{}) (interface{}, bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pe *PeriodicalExecutor) backgroundFlush() {
|
func (pe *PeriodicalExecutor) backgroundFlush() {
|
||||||
threading.GoSafe(func() {
|
go func() {
|
||||||
// flush before quit goroutine to avoid missing tasks
|
// flush before quit goroutine to avoid missing tasks
|
||||||
defer pe.Flush()
|
defer pe.Flush()
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ func (pe *PeriodicalExecutor) backgroundFlush() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *PeriodicalExecutor) doneExecution() {
|
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()
|
defer pe.doneExecution()
|
||||||
|
|
||||||
ok := pe.hasTasks(tasks)
|
ok := pe.hasTasks(tasks)
|
||||||
if ok {
|
if ok {
|
||||||
pe.container.Execute(tasks)
|
threading.RunSafe(func() {
|
||||||
|
pe.container.Execute(tasks)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pe *PeriodicalExecutor) hasTasks(tasks interface{}) bool {
|
func (pe *PeriodicalExecutor) hasTasks(tasks any) bool {
|
||||||
if tasks == nil {
|
if tasks == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/zeromicro/go-zero/core/proc"
|
||||||
"github.com/zeromicro/go-zero/core/timex"
|
"github.com/zeromicro/go-zero/core/timex"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,22 +17,22 @@ const threshold = 10
|
|||||||
type container struct {
|
type container struct {
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
tasks []int
|
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{
|
return &container{
|
||||||
interval: interval,
|
interval: interval,
|
||||||
execute: execute,
|
execute: execute,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *container) AddTask(task interface{}) bool {
|
func (c *container) AddTask(task any) bool {
|
||||||
c.tasks = append(c.tasks, task.(int))
|
c.tasks = append(c.tasks, task.(int))
|
||||||
return len(c.tasks) > threshold
|
return len(c.tasks) > threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *container) Execute(tasks interface{}) {
|
func (c *container) Execute(tasks any) {
|
||||||
if c.execute != nil {
|
if c.execute != nil {
|
||||||
c.execute(tasks)
|
c.execute(tasks)
|
||||||
} else {
|
} else {
|
||||||
@@ -39,7 +40,7 @@ func (c *container) Execute(tasks interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *container) RemoveAll() interface{} {
|
func (c *container) RemoveAll() any {
|
||||||
tasks := c.tasks
|
tasks := c.tasks
|
||||||
c.tasks = nil
|
c.tasks = nil
|
||||||
return tasks
|
return tasks
|
||||||
@@ -67,6 +68,7 @@ func TestPeriodicalExecutor_QuitGoroutine(t *testing.T) {
|
|||||||
ticker.Tick()
|
ticker.Tick()
|
||||||
ticker.Wait(time.Millisecond * idleRound)
|
ticker.Wait(time.Millisecond * idleRound)
|
||||||
assert.Equal(t, routines, runtime.NumGoroutine())
|
assert.Equal(t, routines, runtime.NumGoroutine())
|
||||||
|
proc.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
||||||
@@ -74,7 +76,7 @@ func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
|||||||
var vals []int
|
var vals []int
|
||||||
// avoid data race
|
// avoid data race
|
||||||
var lock sync.Mutex
|
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)
|
t := tasks.([]int)
|
||||||
for _, each := range t {
|
for _, each := range t {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
@@ -106,25 +108,83 @@ func TestPeriodicalExecutor_Bulk(t *testing.T) {
|
|||||||
lock.Unlock()
|
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) {
|
func TestPeriodicalExecutor_Wait(t *testing.T) {
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
executer := NewBulkExecutor(func(tasks []interface{}) {
|
executor := NewBulkExecutor(func(tasks []any) {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}, WithBulkTasks(1), WithBulkInterval(time.Second))
|
}, WithBulkTasks(1), WithBulkInterval(time.Second))
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
executer.Add(1)
|
executor.Add(1)
|
||||||
}
|
}
|
||||||
executer.Flush()
|
executor.Flush()
|
||||||
executer.Wait()
|
executor.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
||||||
const total = 3
|
const total = 3
|
||||||
var cnt int
|
var cnt int
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
executer := NewBulkExecutor(func(tasks []interface{}) {
|
executor := NewBulkExecutor(func(tasks []any) {
|
||||||
defer func() {
|
defer func() {
|
||||||
cnt++
|
cnt++
|
||||||
}()
|
}()
|
||||||
@@ -133,15 +193,15 @@ func TestPeriodicalExecutor_WaitFast(t *testing.T) {
|
|||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}, WithBulkTasks(1), WithBulkInterval(10*time.Millisecond))
|
}, WithBulkTasks(1), WithBulkInterval(10*time.Millisecond))
|
||||||
for i := 0; i < total; i++ {
|
for i := 0; i < total; i++ {
|
||||||
executer.Add(2)
|
executor.Add(2)
|
||||||
}
|
}
|
||||||
executer.Flush()
|
executor.Flush()
|
||||||
executer.Wait()
|
executor.Wait()
|
||||||
assert.Equal(t, total, cnt)
|
assert.Equal(t, total, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeriodicalExecutor_Deadlock(t *testing.T) {
|
func TestPeriodicalExecutor_Deadlock(t *testing.T) {
|
||||||
executor := NewBulkExecutor(func(tasks []interface{}) {
|
executor := NewBulkExecutor(func(tasks []any) {
|
||||||
}, WithBulkTasks(1), WithBulkInterval(time.Millisecond))
|
}, WithBulkTasks(1), WithBulkInterval(time.Millisecond))
|
||||||
for i := 0; i < 1e5; i++ {
|
for i := 0; i < 1e5; i++ {
|
||||||
executor.Add(1)
|
executor.Add(1)
|
||||||
@@ -149,13 +209,7 @@ func TestPeriodicalExecutor_Deadlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
|
func TestPeriodicalExecutor_hasTasks(t *testing.T) {
|
||||||
ticker := timex.NewFakeTicker()
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
exec := NewPeriodicalExecutor(time.Millisecond, newContainer(time.Millisecond, nil))
|
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.False(t, exec.hasTasks(nil))
|
||||||
assert.True(t, exec.hasTasks(1))
|
assert.True(t, exec.hasTasks(1))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ import "time"
|
|||||||
const defaultFlushInterval = time.Second
|
const defaultFlushInterval = time.Second
|
||||||
|
|
||||||
// Execute defines the method to execute tasks.
|
// 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)
|
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) {
|
func TestLastLine(t *testing.T) {
|
||||||
filename, err := fs.TempFilenameWithText(text)
|
filename, err := fs.TempFilenameWithText(text)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -113,3 +118,8 @@ func TestLastLineWithLastNewlineShort(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "last line", val)
|
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
|
//go:build windows
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux || darwin
|
//go:build linux || darwin
|
||||||
// +build linux darwin
|
|
||||||
|
|
||||||
package fs
|
package fs
|
||||||
|
|
||||||
|
|||||||
@@ -11,29 +11,29 @@ import (
|
|||||||
// The file is kept as open, the caller should close the file handle,
|
// The file is kept as open, the caller should close the file handle,
|
||||||
// and remove the file by name.
|
// and remove the file by name.
|
||||||
func TempFileWithText(text string) (*os.File, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmpfile, nil
|
return tmpFile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TempFilenameWithText creates the file with the given content,
|
// TempFilenameWithText creates the file with the given content,
|
||||||
// and returns the filename (full path).
|
// and returns the filename (full path).
|
||||||
// The caller should remove the file after use.
|
// The caller should remove the file after use.
|
||||||
func TempFilenameWithText(text string) (string, error) {
|
func TempFilenameWithText(text string) (string, error) {
|
||||||
tmpfile, err := TempFileWithText(text)
|
tmpFile, err := TempFileWithText(text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := tmpfile.Name()
|
filename := tmpFile.Name()
|
||||||
if err = tmpfile.Close(); err != nil {
|
if err = tmpFile.Close(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,87 @@
|
|||||||
package fx
|
package fx
|
||||||
|
|
||||||
import "github.com/zeromicro/go-zero/core/errorx"
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/errorx"
|
||||||
|
)
|
||||||
|
|
||||||
const defaultRetryTimes = 3
|
const defaultRetryTimes = 3
|
||||||
|
|
||||||
|
var errTimeout = errors.New("retry timeout")
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// RetryOption defines the method to customize DoWithRetry.
|
// RetryOption defines the method to customize DoWithRetry.
|
||||||
RetryOption func(*retryOptions)
|
RetryOption func(*retryOptions)
|
||||||
|
|
||||||
retryOptions struct {
|
retryOptions struct {
|
||||||
times int
|
times int
|
||||||
|
interval time.Duration
|
||||||
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// DoWithRetry runs fn, and retries if failed. Default to retry 3 times.
|
// 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 {
|
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()
|
options := newRetryOptions()
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
var berr errorx.BatchError
|
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++ {
|
for i := 0; i < options.times; i++ {
|
||||||
if err := fn(); err != nil {
|
go fn(errChan, i)
|
||||||
berr.Add(err)
|
|
||||||
} else {
|
select {
|
||||||
return nil
|
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 {
|
func newRetryOptions() *retryOptions {
|
||||||
return &retryOptions{
|
return &retryOptions{
|
||||||
times: defaultRetryTimes,
|
times: defaultRetryTimes,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package fx
|
package fx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -12,31 +14,103 @@ func TestRetry(t *testing.T) {
|
|||||||
return errors.New("any")
|
return errors.New("any")
|
||||||
}))
|
}))
|
||||||
|
|
||||||
var times int
|
times1 := 0
|
||||||
assert.Nil(t, DoWithRetry(func() error {
|
assert.Nil(t, DoWithRetry(func() error {
|
||||||
times++
|
times1++
|
||||||
if times == defaultRetryTimes {
|
if times1 == defaultRetryTimes {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("any")
|
return errors.New("any")
|
||||||
}))
|
}))
|
||||||
|
|
||||||
times = 0
|
times2 := 0
|
||||||
assert.NotNil(t, DoWithRetry(func() error {
|
assert.NotNil(t, DoWithRetry(func() error {
|
||||||
times++
|
times2++
|
||||||
if times == defaultRetryTimes+1 {
|
if times2 == defaultRetryTimes+1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("any")
|
return errors.New("any")
|
||||||
}))
|
}))
|
||||||
|
|
||||||
total := 2 * defaultRetryTimes
|
total := 2 * defaultRetryTimes
|
||||||
times = 0
|
times3 := 0
|
||||||
assert.Nil(t, DoWithRetry(func() error {
|
assert.Nil(t, DoWithRetry(func() error {
|
||||||
times++
|
times3++
|
||||||
if times == total {
|
if times3 == total {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return errors.New("any")
|
return errors.New("any")
|
||||||
}, WithRetry(total)))
|
}, 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 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 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 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 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 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 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 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 defines the method to customize a Stream.
|
||||||
Option func(opts *rxOptions)
|
Option func(opts *rxOptions)
|
||||||
// ParallelFunc defines the method to handle elements parallelly.
|
// 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 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 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.
|
// A Stream is a stream that can be used to do stream processing.
|
||||||
Stream struct {
|
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.
|
// From constructs a Stream from the given GenerateFunc.
|
||||||
func From(generate GenerateFunc) Stream {
|
func From(generate GenerateFunc) Stream {
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
|
|
||||||
threading.GoSafe(func() {
|
threading.GoSafe(func() {
|
||||||
defer close(source)
|
defer close(source)
|
||||||
@@ -67,8 +67,8 @@ func From(generate GenerateFunc) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Just converts the given arbitrary items to a Stream.
|
// Just converts the given arbitrary items to a Stream.
|
||||||
func Just(items ...interface{}) Stream {
|
func Just(items ...any) Stream {
|
||||||
source := make(chan interface{}, len(items))
|
source := make(chan any, len(items))
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
source <- item
|
source <- item
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ func Just(items ...interface{}) Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Range converts the given channel to a Stream.
|
// Range converts the given channel to a Stream.
|
||||||
func Range(source <-chan interface{}) Stream {
|
func Range(source <-chan any) Stream {
|
||||||
return Stream{
|
return Stream{
|
||||||
source: source,
|
source: source,
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ func Range(source <-chan interface{}) Stream {
|
|||||||
// AllMach returns whether all elements of this stream match the provided predicate.
|
// 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.
|
// 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.
|
// 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 {
|
for item := range s.source {
|
||||||
if !predicate(item) {
|
if !predicate(item) {
|
||||||
// make sure the former goroutine not block, and current func returns fast.
|
// 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.
|
// 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.
|
// 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.
|
// 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 {
|
for item := range s.source {
|
||||||
if predicate(item) {
|
if predicate(item) {
|
||||||
// make sure the former goroutine not block, and current func returns fast.
|
// 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
|
n = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{}, n)
|
source := make(chan any, n)
|
||||||
go func() {
|
go func() {
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
source <- item
|
source <- item
|
||||||
@@ -134,7 +134,7 @@ func (s Stream) Buffer(n int) Stream {
|
|||||||
|
|
||||||
// Concat returns a Stream that concatenated other streams
|
// Concat returns a Stream that concatenated other streams
|
||||||
func (s Stream) Concat(others ...Stream) Stream {
|
func (s Stream) Concat(others ...Stream) Stream {
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
group := threading.NewRoutineGroup()
|
group := threading.NewRoutineGroup()
|
||||||
@@ -170,12 +170,12 @@ func (s Stream) Count() (count int) {
|
|||||||
|
|
||||||
// Distinct removes the duplicated items base on the given KeyFunc.
|
// Distinct removes the duplicated items base on the given KeyFunc.
|
||||||
func (s Stream) Distinct(fn KeyFunc) Stream {
|
func (s Stream) Distinct(fn KeyFunc) Stream {
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
|
|
||||||
threading.GoSafe(func() {
|
threading.GoSafe(func() {
|
||||||
defer close(source)
|
defer close(source)
|
||||||
|
|
||||||
keys := make(map[interface{}]lang.PlaceholderType)
|
keys := make(map[any]lang.PlaceholderType)
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
key := fn(item)
|
key := fn(item)
|
||||||
if _, ok := keys[key]; !ok {
|
if _, ok := keys[key]; !ok {
|
||||||
@@ -195,7 +195,7 @@ func (s Stream) Done() {
|
|||||||
|
|
||||||
// Filter filters the items by the given FilterFunc.
|
// Filter filters the items by the given FilterFunc.
|
||||||
func (s Stream) Filter(fn FilterFunc, opts ...Option) Stream {
|
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) {
|
if fn(item) {
|
||||||
pipe <- 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.
|
// First returns the first item, nil if no items.
|
||||||
func (s Stream) First() interface{} {
|
func (s Stream) First() any {
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
// make sure the former goroutine not block, and current func returns fast.
|
// make sure the former goroutine not block, and current func returns fast.
|
||||||
go drain(s.source)
|
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.
|
// Group groups the elements into different groups based on their keys.
|
||||||
func (s Stream) Group(fn KeyFunc) Stream {
|
func (s Stream) Group(fn KeyFunc) Stream {
|
||||||
groups := make(map[interface{}][]interface{})
|
groups := make(map[any][]any)
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
key := fn(item)
|
key := fn(item)
|
||||||
groups[key] = append(groups[key], item)
|
groups[key] = append(groups[key], item)
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
go func() {
|
go func() {
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
source <- group
|
source <- group
|
||||||
@@ -252,7 +252,7 @@ func (s Stream) Head(n int64) Stream {
|
|||||||
panic("n must be greater than 0")
|
panic("n must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for item := range s.source {
|
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.
|
// 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 {
|
for item = range s.source {
|
||||||
}
|
}
|
||||||
return
|
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.
|
// 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 {
|
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)
|
pipe <- fn(item)
|
||||||
}, opts...)
|
}, 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.
|
// Merge merges all the items into a slice and generates a new stream.
|
||||||
func (s Stream) Merge() Stream {
|
func (s Stream) Merge() Stream {
|
||||||
var items []interface{}
|
var items []any
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{}, 1)
|
source := make(chan any, 1)
|
||||||
source <- items
|
source <- items
|
||||||
close(source)
|
close(source)
|
||||||
|
|
||||||
return Range(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.
|
// 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.
|
// 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.
|
// 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 {
|
for item := range s.source {
|
||||||
if predicate(item) {
|
if predicate(item) {
|
||||||
// make sure the former goroutine not block, and current func returns fast.
|
// 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.
|
// Parallel applies the given ParallelFunc to each item concurrently with given number of workers.
|
||||||
func (s Stream) Parallel(fn ParallelFunc, opts ...Option) {
|
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)
|
fn(item)
|
||||||
}, opts...).Done()
|
}, opts...).Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce is an utility method to let the caller deal with the underlying channel.
|
// 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)
|
return fn(s.source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse reverses the elements in the stream.
|
// Reverse reverses the elements in the stream.
|
||||||
func (s Stream) Reverse() Stream {
|
func (s Stream) Reverse() Stream {
|
||||||
var items []interface{}
|
var items []any
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
@@ -357,7 +381,7 @@ func (s Stream) Skip(n int64) Stream {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
@@ -376,7 +400,7 @@ func (s Stream) Skip(n int64) Stream {
|
|||||||
|
|
||||||
// Sort sorts the items from the underlying source.
|
// Sort sorts the items from the underlying source.
|
||||||
func (s Stream) Sort(less LessFunc) Stream {
|
func (s Stream) Sort(less LessFunc) Stream {
|
||||||
var items []interface{}
|
var items []any
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
@@ -394,9 +418,9 @@ func (s Stream) Split(n int) Stream {
|
|||||||
panic("n should be greater than 0")
|
panic("n should be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
go func() {
|
go func() {
|
||||||
var chunk []interface{}
|
var chunk []any
|
||||||
for item := range s.source {
|
for item := range s.source {
|
||||||
chunk = append(chunk, item)
|
chunk = append(chunk, item)
|
||||||
if len(chunk) == n {
|
if len(chunk) == n {
|
||||||
@@ -419,7 +443,7 @@ func (s Stream) Tail(n int64) Stream {
|
|||||||
panic("n should be greater than 0")
|
panic("n should be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
source := make(chan interface{})
|
source := make(chan any)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
ring := collection.NewRing(int(n))
|
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 {
|
func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||||
pipe := make(chan interface{}, option.workers)
|
pipe := make(chan any, option.workers)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var wg sync.WaitGroup
|
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 {
|
func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
|
||||||
pipe := make(chan interface{}, option.workers)
|
pipe := make(chan any, option.workers)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@@ -529,7 +553,7 @@ func buildOptions(opts ...Option) *rxOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// drain drains the given channel.
|
// drain drains the given channel.
|
||||||
func drain(channel <-chan interface{}) {
|
func drain(channel <-chan any) {
|
||||||
for range channel {
|
for range channel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestBuffer(t *testing.T) {
|
|||||||
var count int32
|
var count int32
|
||||||
var wait sync.WaitGroup
|
var wait sync.WaitGroup
|
||||||
wait.Add(1)
|
wait.Add(1)
|
||||||
From(func(source chan<- interface{}) {
|
From(func(source chan<- any) {
|
||||||
ticker := time.NewTicker(10 * time.Millisecond)
|
ticker := time.NewTicker(10 * time.Millisecond)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ func TestBuffer(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).Buffer(N).ForAll(func(pipe <-chan interface{}) {
|
}).Buffer(N).ForAll(func(pipe <-chan any) {
|
||||||
wait.Wait()
|
wait.Wait()
|
||||||
// why N+1, because take one more to wait for sending into the channel
|
// why N+1, because take one more to wait for sending into the channel
|
||||||
assert.Equal(t, int32(N+1), atomic.LoadInt32(&count))
|
assert.Equal(t, int32(N+1), atomic.LoadInt32(&count))
|
||||||
@@ -47,7 +47,7 @@ func TestBuffer(t *testing.T) {
|
|||||||
func TestBufferNegative(t *testing.T) {
|
func TestBufferNegative(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
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 {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -61,22 +61,22 @@ func TestCount(t *testing.T) {
|
|||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
elements []interface{}
|
elements []any
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no elements with nil",
|
name: "no elements with nil",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no elements",
|
name: "no elements",
|
||||||
elements: []interface{}{},
|
elements: []any{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "1 element",
|
name: "1 element",
|
||||||
elements: []interface{}{1},
|
elements: []any{1},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "multiple elements",
|
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) {
|
func TestDone(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var count int32
|
var count int32
|
||||||
Just(1, 2, 3).Walk(func(item interface{}, pipe chan<- interface{}) {
|
Just(1, 2, 3).Walk(func(item any, pipe chan<- any) {
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
atomic.AddInt32(&count, int32(item.(int)))
|
atomic.AddInt32(&count, int32(item.(int)))
|
||||||
}).Done()
|
}).Done()
|
||||||
@@ -103,7 +103,7 @@ func TestDone(t *testing.T) {
|
|||||||
func TestJust(t *testing.T) {
|
func TestJust(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
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 {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -116,9 +116,9 @@ func TestJust(t *testing.T) {
|
|||||||
func TestDistinct(t *testing.T) {
|
func TestDistinct(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
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
|
return item
|
||||||
}).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
}).Reduce(func(pipe <-chan any) (any, error) {
|
||||||
for item := range pipe {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -131,9 +131,9 @@ func TestDistinct(t *testing.T) {
|
|||||||
func TestFilter(t *testing.T) {
|
func TestFilter(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
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
|
return item.(int)%2 == 0
|
||||||
}).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
}).Reduce(func(pipe <-chan any) (any, error) {
|
||||||
for item := range pipe {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -154,9 +154,9 @@ func TestFirst(t *testing.T) {
|
|||||||
func TestForAll(t *testing.T) {
|
func TestForAll(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
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
|
return item.(int)%2 == 0
|
||||||
}).ForAll(func(pipe <-chan interface{}) {
|
}).ForAll(func(pipe <-chan any) {
|
||||||
for item := range pipe {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -168,11 +168,11 @@ func TestForAll(t *testing.T) {
|
|||||||
func TestGroup(t *testing.T) {
|
func TestGroup(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var groups [][]int
|
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)
|
v := item.(int)
|
||||||
return v / 10
|
return v / 10
|
||||||
}).ForEach(func(item interface{}) {
|
}).ForEach(func(item any) {
|
||||||
v := item.([]interface{})
|
v := item.([]any)
|
||||||
var group []int
|
var group []int
|
||||||
for _, each := range v {
|
for _, each := range v {
|
||||||
group = append(group, each.(int))
|
group = append(group, each.(int))
|
||||||
@@ -191,7 +191,7 @@ func TestGroup(t *testing.T) {
|
|||||||
func TestHead(t *testing.T) {
|
func TestHead(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
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 {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ func TestHead(t *testing.T) {
|
|||||||
func TestHeadZero(t *testing.T) {
|
func TestHeadZero(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
assert.Panics(t, func() {
|
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
|
return nil, nil
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -214,7 +214,7 @@ func TestHeadZero(t *testing.T) {
|
|||||||
func TestHeadMore(t *testing.T) {
|
func TestHeadMore(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
var result int
|
||||||
Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
Just(1, 2, 3, 4).Head(6).Reduce(func(pipe <-chan any) (any, error) {
|
||||||
for item := range pipe {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -245,14 +245,14 @@ func TestMap(t *testing.T) {
|
|||||||
expect int
|
expect int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
mapper: func(item interface{}) interface{} {
|
mapper: func(item any) any {
|
||||||
v := item.(int)
|
v := item.(int)
|
||||||
return v * v
|
return v * v
|
||||||
},
|
},
|
||||||
expect: 30,
|
expect: 30,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
mapper: func(item interface{}) interface{} {
|
mapper: func(item any) any {
|
||||||
v := item.(int)
|
v := item.(int)
|
||||||
if v%2 == 0 {
|
if v%2 == 0 {
|
||||||
return 0
|
return 0
|
||||||
@@ -262,7 +262,7 @@ func TestMap(t *testing.T) {
|
|||||||
expect: 10,
|
expect: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
mapper: func(item interface{}) interface{} {
|
mapper: func(item any) any {
|
||||||
v := item.(int)
|
v := item.(int)
|
||||||
if v%2 == 0 {
|
if v%2 == 0 {
|
||||||
panic(v)
|
panic(v)
|
||||||
@@ -283,12 +283,12 @@ func TestMap(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
workers = runtime.NumCPU()
|
workers = runtime.NumCPU()
|
||||||
}
|
}
|
||||||
From(func(source chan<- interface{}) {
|
From(func(source chan<- any) {
|
||||||
for i := 1; i < 5; i++ {
|
for i := 1; i < 5; i++ {
|
||||||
source <- i
|
source <- i
|
||||||
}
|
}
|
||||||
}).Map(test.mapper, WithWorkers(workers)).Reduce(
|
}).Map(test.mapper, WithWorkers(workers)).Reduce(
|
||||||
func(pipe <-chan interface{}) (interface{}, error) {
|
func(pipe <-chan any) (any, error) {
|
||||||
for item := range pipe {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -303,8 +303,8 @@ func TestMap(t *testing.T) {
|
|||||||
|
|
||||||
func TestMerge(t *testing.T) {
|
func TestMerge(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
Just(1, 2, 3, 4).Merge().ForEach(func(item interface{}) {
|
Just(1, 2, 3, 4).Merge().ForEach(func(item any) {
|
||||||
assert.ElementsMatch(t, []interface{}{1, 2, 3, 4}, item.([]interface{}))
|
assert.ElementsMatch(t, []any{1, 2, 3, 4}, item.([]any))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -312,7 +312,7 @@ func TestMerge(t *testing.T) {
|
|||||||
func TestParallelJust(t *testing.T) {
|
func TestParallelJust(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var count int32
|
var count int32
|
||||||
Just(1, 2, 3).Parallel(func(item interface{}) {
|
Just(1, 2, 3).Parallel(func(item any) {
|
||||||
time.Sleep(time.Millisecond * 100)
|
time.Sleep(time.Millisecond * 100)
|
||||||
atomic.AddInt32(&count, int32(item.(int)))
|
atomic.AddInt32(&count, int32(item.(int)))
|
||||||
}, UnlimitedWorkers())
|
}, UnlimitedWorkers())
|
||||||
@@ -322,8 +322,8 @@ func TestParallelJust(t *testing.T) {
|
|||||||
|
|
||||||
func TestReverse(t *testing.T) {
|
func TestReverse(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
Just(1, 2, 3, 4).Reverse().Merge().ForEach(func(item interface{}) {
|
Just(1, 2, 3, 4).Reverse().Merge().ForEach(func(item any) {
|
||||||
assert.ElementsMatch(t, []interface{}{4, 3, 2, 1}, item.([]interface{}))
|
assert.ElementsMatch(t, []any{4, 3, 2, 1}, item.([]any))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -331,9 +331,9 @@ func TestReverse(t *testing.T) {
|
|||||||
func TestSort(t *testing.T) {
|
func TestSort(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var prev int
|
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)
|
return a.(int) < b.(int)
|
||||||
}).ForEach(func(item interface{}) {
|
}).ForEach(func(item any) {
|
||||||
next := item.(int)
|
next := item.(int)
|
||||||
assert.True(t, prev < next)
|
assert.True(t, prev < next)
|
||||||
prev = next
|
prev = next
|
||||||
@@ -346,12 +346,12 @@ func TestSplit(t *testing.T) {
|
|||||||
assert.Panics(t, func() {
|
assert.Panics(t, func() {
|
||||||
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(0).Done()
|
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(0).Done()
|
||||||
})
|
})
|
||||||
var chunks [][]interface{}
|
var chunks [][]any
|
||||||
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(4).ForEach(func(item interface{}) {
|
Just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).Split(4).ForEach(func(item any) {
|
||||||
chunk := item.([]interface{})
|
chunk := item.([]any)
|
||||||
chunks = append(chunks, chunk)
|
chunks = append(chunks, chunk)
|
||||||
})
|
})
|
||||||
assert.EqualValues(t, [][]interface{}{
|
assert.EqualValues(t, [][]any{
|
||||||
{1, 2, 3, 4},
|
{1, 2, 3, 4},
|
||||||
{5, 6, 7, 8},
|
{5, 6, 7, 8},
|
||||||
{9, 10},
|
{9, 10},
|
||||||
@@ -362,7 +362,7 @@ func TestSplit(t *testing.T) {
|
|||||||
func TestTail(t *testing.T) {
|
func TestTail(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
var result int
|
||||||
Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan interface{}) (interface{}, error) {
|
Just(1, 2, 3, 4).Tail(2).Reduce(func(pipe <-chan any) (any, error) {
|
||||||
for item := range pipe {
|
for item := range pipe {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,7 @@ func TestTail(t *testing.T) {
|
|||||||
func TestTailZero(t *testing.T) {
|
func TestTailZero(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
assert.Panics(t, func() {
|
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
|
return nil, nil
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -385,11 +385,11 @@ func TestTailZero(t *testing.T) {
|
|||||||
func TestWalk(t *testing.T) {
|
func TestWalk(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
var result int
|
var result int
|
||||||
Just(1, 2, 3, 4, 5).Walk(func(item interface{}, pipe chan<- interface{}) {
|
Just(1, 2, 3, 4, 5).Walk(func(item any, pipe chan<- any) {
|
||||||
if item.(int)%2 != 0 {
|
if item.(int)%2 != 0 {
|
||||||
pipe <- item
|
pipe <- item
|
||||||
}
|
}
|
||||||
}, UnlimitedWorkers()).ForEach(func(item interface{}) {
|
}, UnlimitedWorkers()).ForEach(func(item any) {
|
||||||
result += item.(int)
|
result += item.(int)
|
||||||
})
|
})
|
||||||
assert.Equal(t, 9, result)
|
assert.Equal(t, 9, result)
|
||||||
@@ -398,16 +398,16 @@ func TestWalk(t *testing.T) {
|
|||||||
|
|
||||||
func TestStream_AnyMach(t *testing.T) {
|
func TestStream_AnyMach(t *testing.T) {
|
||||||
runCheckedTest(t, func(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
|
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
|
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
|
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
|
return item.(int) == 2
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
@@ -416,17 +416,17 @@ func TestStream_AnyMach(t *testing.T) {
|
|||||||
func TestStream_AllMach(t *testing.T) {
|
func TestStream_AllMach(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
assetEqual(
|
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
|
return true
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
assetEqual(
|
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
|
return false
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
assetEqual(
|
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
|
return item.(int) == 1
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -436,17 +436,17 @@ func TestStream_AllMach(t *testing.T) {
|
|||||||
func TestStream_NoneMatch(t *testing.T) {
|
func TestStream_NoneMatch(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
assetEqual(
|
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
|
return false
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
assetEqual(
|
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
|
return true
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
assetEqual(
|
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
|
return item.(int) == 4
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -455,19 +455,19 @@ func TestStream_NoneMatch(t *testing.T) {
|
|||||||
|
|
||||||
func TestConcat(t *testing.T) {
|
func TestConcat(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
a1 := []interface{}{1, 2, 3}
|
a1 := []any{1, 2, 3}
|
||||||
a2 := []interface{}{4, 5, 6}
|
a2 := []any{4, 5, 6}
|
||||||
s1 := Just(a1...)
|
s1 := Just(a1...)
|
||||||
s2 := Just(a2...)
|
s2 := Just(a2...)
|
||||||
stream := Concat(s1, s2)
|
stream := Concat(s1, s2)
|
||||||
var items []interface{}
|
var items []any
|
||||||
for item := range stream.source {
|
for item := range stream.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
sort.Slice(items, func(i, j int) bool {
|
sort.Slice(items, func(i, j int) bool {
|
||||||
return items[i].(int) < items[j].(int)
|
return items[i].(int) < items[j].(int)
|
||||||
})
|
})
|
||||||
ints := make([]interface{}, 0)
|
ints := make([]any, 0)
|
||||||
ints = append(ints, a1...)
|
ints = append(ints, a1...)
|
||||||
ints = append(ints, a2...)
|
ints = append(ints, a2...)
|
||||||
assetEqual(t, ints, items)
|
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, 3, Just(1, 2, 3, 4).Skip(1).Count())
|
||||||
assetEqual(t, 1, Just(1, 2, 3, 4).Skip(3).Count())
|
assetEqual(t, 1, Just(1, 2, 3, 4).Skip(3).Count())
|
||||||
assetEqual(t, 4, Just(1, 2, 3, 4).Skip(0).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() {
|
assert.Panics(t, func() {
|
||||||
Just(1, 2, 3, 4).Skip(-1)
|
Just(1, 2, 3, 4).Skip(-1)
|
||||||
})
|
})
|
||||||
@@ -489,27 +489,104 @@ func TestStream_Skip(t *testing.T) {
|
|||||||
func TestStream_Concat(t *testing.T) {
|
func TestStream_Concat(t *testing.T) {
|
||||||
runCheckedTest(t, func(t *testing.T) {
|
runCheckedTest(t, func(t *testing.T) {
|
||||||
stream := Just(1).Concat(Just(2), Just(3))
|
stream := Just(1).Concat(Just(2), Just(3))
|
||||||
var items []interface{}
|
var items []any
|
||||||
for item := range stream.source {
|
for item := range stream.source {
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
sort.Slice(items, func(i, j int) bool {
|
sort.Slice(items, func(i, j int) bool {
|
||||||
return items[i].(int) < items[j].(int)
|
return items[i].(int) < items[j].(int)
|
||||||
})
|
})
|
||||||
assetEqual(t, []interface{}{1, 2, 3}, items)
|
assetEqual(t, []any{1, 2, 3}, items)
|
||||||
|
|
||||||
just := Just(1)
|
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) {
|
func BenchmarkParallelMapReduce(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
mapper := func(v interface{}) interface{} {
|
mapper := func(v any) any {
|
||||||
return v.(int64) * v.(int64)
|
return v.(int64) * v.(int64)
|
||||||
}
|
}
|
||||||
reducer := func(input <-chan interface{}) (interface{}, error) {
|
reducer := func(input <-chan any) (any, error) {
|
||||||
var result int64
|
var result int64
|
||||||
for v := range input {
|
for v := range input {
|
||||||
result += v.(int64)
|
result += v.(int64)
|
||||||
@@ -517,7 +594,7 @@ func BenchmarkParallelMapReduce(b *testing.B) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
From(func(input chan<- interface{}) {
|
From(func(input chan<- any) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
input <- int64(rand.Int())
|
input <- int64(rand.Int())
|
||||||
@@ -529,10 +606,10 @@ func BenchmarkParallelMapReduce(b *testing.B) {
|
|||||||
func BenchmarkMapReduce(b *testing.B) {
|
func BenchmarkMapReduce(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
mapper := func(v interface{}) interface{} {
|
mapper := func(v any) any {
|
||||||
return v.(int64) * v.(int64)
|
return v.(int64) * v.(int64)
|
||||||
}
|
}
|
||||||
reducer := func(input <-chan interface{}) (interface{}, error) {
|
reducer := func(input <-chan any) (any, error) {
|
||||||
var result int64
|
var result int64
|
||||||
for v := range input {
|
for v := range input {
|
||||||
result += v.(int64)
|
result += v.(int64)
|
||||||
@@ -540,21 +617,21 @@ func BenchmarkMapReduce(b *testing.B) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
From(func(input chan<- interface{}) {
|
From(func(input chan<- any) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
input <- int64(rand.Int())
|
input <- int64(rand.Int())
|
||||||
}
|
}
|
||||||
}).Map(mapper).Reduce(reducer)
|
}).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) {
|
if !reflect.DeepEqual(except, data) {
|
||||||
t.Errorf(" %v, want %v", data, except)
|
t.Errorf(" %v, want %v", data, except)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func equal(t *testing.T, stream Stream, data []interface{}) {
|
func equal(t *testing.T, stream Stream, data []any) {
|
||||||
items := make([]interface{}, 0)
|
items := make([]any, 0)
|
||||||
for item := range stream.source {
|
for item := range stream.source {
|
||||||
items = append(items, item)
|
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
|
// create channel with buffer size 1 to avoid goroutine leak
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
panicChan := make(chan interface{}, 1)
|
panicChan := make(chan any, 1)
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if p := recover(); p != nil {
|
if p := recover(); p != nil {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ type (
|
|||||||
hashFunc Func
|
hashFunc Func
|
||||||
replicas int
|
replicas int
|
||||||
keys []uint64
|
keys []uint64
|
||||||
ring map[uint64][]interface{}
|
ring map[uint64][]any
|
||||||
nodes map[string]lang.PlaceholderType
|
nodes map[string]lang.PlaceholderType
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
@@ -50,21 +50,21 @@ func NewCustomConsistentHash(replicas int, fn Func) *ConsistentHash {
|
|||||||
return &ConsistentHash{
|
return &ConsistentHash{
|
||||||
hashFunc: fn,
|
hashFunc: fn,
|
||||||
replicas: replicas,
|
replicas: replicas,
|
||||||
ring: make(map[uint64][]interface{}),
|
ring: make(map[uint64][]any),
|
||||||
nodes: make(map[string]lang.PlaceholderType),
|
nodes: make(map[string]lang.PlaceholderType),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the node with the number of h.replicas,
|
// Add adds the node with the number of h.replicas,
|
||||||
// the later call will overwrite the replicas of the former calls.
|
// 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)
|
h.AddWithReplicas(node, h.replicas)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddWithReplicas adds the node with the number of replicas,
|
// AddWithReplicas adds the node with the number of replicas,
|
||||||
// replicas will be truncated to h.replicas if it's larger than h.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.
|
// 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)
|
h.Remove(node)
|
||||||
|
|
||||||
if replicas > h.replicas {
|
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,
|
// 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.
|
// 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,
|
// don't need to make sure weight not larger than TopWeight,
|
||||||
// because AddWithReplicas makes sure replicas cannot be larger than h.replicas
|
// because AddWithReplicas makes sure replicas cannot be larger than h.replicas
|
||||||
replicas := h.replicas * weight / TopWeight
|
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.
|
// 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()
|
h.lock.RLock()
|
||||||
defer h.lock.RUnlock()
|
defer h.lock.RUnlock()
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ func (h *ConsistentHash) Get(v interface{}) (interface{}, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the given node from h.
|
// Remove removes the given node from h.
|
||||||
func (h *ConsistentHash) Remove(node interface{}) {
|
func (h *ConsistentHash) Remove(node any) {
|
||||||
nodeRepr := repr(node)
|
nodeRepr := repr(node)
|
||||||
|
|
||||||
h.lock.Lock()
|
h.lock.Lock()
|
||||||
@@ -177,10 +177,10 @@ func (h *ConsistentHash) removeNode(nodeRepr string) {
|
|||||||
delete(h.nodes, nodeRepr)
|
delete(h.nodes, nodeRepr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func innerRepr(node interface{}) string {
|
func innerRepr(node any) string {
|
||||||
return fmt.Sprintf("%d:%v", prime, node)
|
return fmt.Sprintf("%d:%v", prime, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func repr(node interface{}) string {
|
func repr(node any) string {
|
||||||
return lang.Repr(node)
|
return lang.Repr(node)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func TestConsistentHash(t *testing.T) {
|
|||||||
keys[key.(string)]++
|
keys[key.(string)]++
|
||||||
}
|
}
|
||||||
|
|
||||||
mi := make(map[interface{}]int, len(keys))
|
mi := make(map[any]int, len(keys))
|
||||||
for k, v := range keys {
|
for k, v := range keys {
|
||||||
mi[k] = v
|
mi[k] = v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func NewBufferPool(capability int) *BufferPool {
|
|||||||
return &BufferPool{
|
return &BufferPool{
|
||||||
capability: capability,
|
capability: capability,
|
||||||
pool: &sync.Pool{
|
pool: &sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() any {
|
||||||
return new(bytes.Buffer)
|
return new(bytes.Buffer)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ b`,
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.input, func(t *testing.T) {
|
t.Run(test.input, func(t *testing.T) {
|
||||||
tmpfile, err := fs.TempFilenameWithText(test.input)
|
tmpFile, err := fs.TempFilenameWithText(test.input)
|
||||||
assert.Nil(t, err)
|
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.Nil(t, err)
|
||||||
assert.Equal(t, test.expect, content)
|
assert.Equal(t, test.expect, content)
|
||||||
})
|
})
|
||||||
@@ -59,9 +59,9 @@ func TestReadTextLines(t *testing.T) {
|
|||||||
#a
|
#a
|
||||||
3`
|
3`
|
||||||
|
|
||||||
tmpfile, err := fs.TempFilenameWithText(text)
|
tmpFile, err := fs.TempFilenameWithText(text)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
defer os.Remove(tmpfile)
|
defer os.Remove(tmpFile)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
options []TextReadOption
|
options []TextReadOption
|
||||||
@@ -87,7 +87,7 @@ func TestReadTextLines(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(stringx.Rand(), func(t *testing.T) {
|
t.Run(stringx.Rand(), func(t *testing.T) {
|
||||||
lines, err := ReadTextLines(tmpfile, test.options...)
|
lines, err := ReadTextLines(tmpFile, test.options...)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, test.expectLines, len(lines))
|
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.
|
// Marshal marshals v into json bytes.
|
||||||
func Marshal(v interface{}) ([]byte, error) {
|
func Marshal(v any) ([]byte, error) {
|
||||||
return json.Marshal(v)
|
return json.Marshal(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalToString marshals v into a string.
|
// MarshalToString marshals v into a string.
|
||||||
func MarshalToString(v interface{}) (string, error) {
|
func MarshalToString(v any) (string, error) {
|
||||||
data, err := Marshal(v)
|
data, err := Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -24,7 +24,7 @@ func MarshalToString(v interface{}) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals data bytes into v.
|
// 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))
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
||||||
if err := unmarshalUseNumber(decoder, v); err != nil {
|
if err := unmarshalUseNumber(decoder, v); err != nil {
|
||||||
return formatError(string(data), err)
|
return formatError(string(data), err)
|
||||||
@@ -34,7 +34,7 @@ func Unmarshal(data []byte, v interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalFromString unmarshals v from str.
|
// 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))
|
decoder := json.NewDecoder(strings.NewReader(str))
|
||||||
if err := unmarshalUseNumber(decoder, v); err != nil {
|
if err := unmarshalUseNumber(decoder, v); err != nil {
|
||||||
return formatError(str, err)
|
return formatError(str, err)
|
||||||
@@ -44,7 +44,7 @@ func UnmarshalFromString(str string, v interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalFromReader unmarshals v from reader.
|
// 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
|
var buf strings.Builder
|
||||||
teeReader := io.TeeReader(reader, &buf)
|
teeReader := io.TeeReader(reader, &buf)
|
||||||
decoder := json.NewDecoder(teeReader)
|
decoder := json.NewDecoder(teeReader)
|
||||||
@@ -55,7 +55,7 @@ func UnmarshalFromReader(reader io.Reader, v interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalUseNumber(decoder *json.Decoder, v interface{}) error {
|
func unmarshalUseNumber(decoder *json.Decoder, v any) error {
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
return decoder.Decode(v)
|
return decoder.Decode(v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ var Placeholder PlaceholderType
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
// AnyType can be used to hold any type.
|
// AnyType can be used to hold any type.
|
||||||
AnyType = interface{}
|
AnyType = any
|
||||||
// PlaceholderType represents a placeholder type.
|
// PlaceholderType represents a placeholder type.
|
||||||
PlaceholderType = struct{}
|
PlaceholderType = struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repr returns the string representation of v.
|
// Repr returns the string representation of v.
|
||||||
func Repr(v interface{}) string {
|
func Repr(v any) string {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ func Repr(v interface{}) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val := reflect.ValueOf(v)
|
val := reflect.ValueOf(v)
|
||||||
if val.Kind() == reflect.Ptr && !val.IsNil() {
|
for val.Kind() == reflect.Ptr && !val.IsNil() {
|
||||||
val = val.Elem()
|
val = val.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package lang
|
package lang
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -20,7 +23,7 @@ func TestRepr(t *testing.T) {
|
|||||||
u64 uint64 = 8
|
u64 uint64 = 8
|
||||||
)
|
)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
v interface{}
|
v any
|
||||||
expect string
|
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{}
|
type mockStringable struct{}
|
||||||
|
|
||||||
func (m mockStringable) String() string {
|
func (m mockStringable) String() string {
|
||||||
|
|||||||
@@ -9,21 +9,6 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"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 (
|
const (
|
||||||
// Unknown means not initialized state.
|
// Unknown means not initialized state.
|
||||||
Unknown = iota
|
Unknown = iota
|
||||||
@@ -39,8 +24,25 @@ const (
|
|||||||
internalHitQuota = 2
|
internalHitQuota = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownCode is an error that represents unknown status code.
|
var (
|
||||||
var ErrUnknownCode = errors.New("unknown status code")
|
// 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 (
|
type (
|
||||||
// PeriodOption defines the method to customize a PeriodLimit.
|
// 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.
|
// TakeCtx requests a permit with context, it returns the permit state.
|
||||||
func (h *PeriodLimit) TakeCtx(ctx context.Context, key string) (int, error) {
|
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.quota),
|
||||||
strconv.Itoa(h.calcExpireSeconds()),
|
strconv.Itoa(h.calcExpireSeconds()),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ func TestPeriodLimit_RedisUnavailable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
|
func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
seconds = 1
|
seconds = 1
|
||||||
|
|||||||
@@ -15,10 +15,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// to be compatible with aliyun redis, we cannot use `local key = KEYS[1]` to reuse the key
|
tokenFormat = "{%s}.tokens"
|
||||||
// KEYS[1] as tokens_key
|
timestampFormat = "{%s}.ts"
|
||||||
// KEYS[2] as timestamp_key
|
pingInterval = time.Millisecond * 100
|
||||||
script = `local rate = tonumber(ARGV[1])
|
)
|
||||||
|
|
||||||
|
// 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 capacity = tonumber(ARGV[2])
|
||||||
local now = tonumber(ARGV[3])
|
local now = tonumber(ARGV[3])
|
||||||
local requested = tonumber(ARGV[4])
|
local requested = tonumber(ARGV[4])
|
||||||
@@ -45,11 +50,7 @@ end
|
|||||||
redis.call("setex", KEYS[1], ttl, new_tokens)
|
redis.call("setex", KEYS[1], ttl, new_tokens)
|
||||||
redis.call("setex", KEYS[2], ttl, now)
|
redis.call("setex", KEYS[2], ttl, now)
|
||||||
|
|
||||||
return allowed`
|
return allowed`)
|
||||||
tokenFormat = "{%s}.tokens"
|
|
||||||
timestampFormat = "{%s}.ts"
|
|
||||||
pingInterval = time.Millisecond * 100
|
|
||||||
)
|
|
||||||
|
|
||||||
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
|
// A TokenLimiter controls how frequently events are allowed to happen with in one second.
|
||||||
type TokenLimiter struct {
|
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)
|
return lim.rescueLimiter.AllowN(now, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := lim.store.EvalCtx(ctx,
|
resp, err := lim.store.ScriptRunCtx(ctx,
|
||||||
script,
|
script,
|
||||||
[]string{
|
[]string{
|
||||||
lim.tokenKey,
|
lim.tokenKey,
|
||||||
|
|||||||
@@ -70,9 +70,7 @@ func TestTokenLimit_Rescue(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_Take(t *testing.T) {
|
func TestTokenLimit_Take(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
@@ -92,9 +90,7 @@ func TestTokenLimit_Take(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenLimit_TakeBurst(t *testing.T) {
|
func TestTokenLimit_TakeBurst(t *testing.T) {
|
||||||
store, clean, err := redistest.CreateRedis()
|
store := redistest.CreateRedis(t)
|
||||||
assert.Nil(t, err)
|
|
||||||
defer clean()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
total = 100
|
total = 100
|
||||||
|
|||||||
@@ -27,19 +27,39 @@ func Close() error {
|
|||||||
return logx.Close()
|
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.
|
// Error writes v into error log.
|
||||||
func Error(ctx context.Context, v ...interface{}) {
|
func Error(ctx context.Context, v ...any) {
|
||||||
getLogger(ctx).Error(v...)
|
getLogger(ctx).Error(v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf writes v with format into error log.
|
// 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())
|
getLogger(ctx).Errorf(fmt.Errorf(format, v...).Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorv writes v into error log with json content.
|
// Errorv writes v into error log with json content.
|
||||||
// No call stack attached, because not elegant to pack the messages.
|
// 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)
|
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.
|
// 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)
|
return logx.Field(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info writes v into access log.
|
// Info writes v into access log.
|
||||||
func Info(ctx context.Context, v ...interface{}) {
|
func Info(ctx context.Context, v ...any) {
|
||||||
getLogger(ctx).Info(v...)
|
getLogger(ctx).Info(v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof writes v with format into access log.
|
// 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...)
|
getLogger(ctx).Infof(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infov writes v into access log with json content.
|
// 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)
|
getLogger(ctx).Infov(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,17 +117,17 @@ func SetUp(c LogConf) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Slow writes v into slow log.
|
// Slow writes v into slow log.
|
||||||
func Slow(ctx context.Context, v ...interface{}) {
|
func Slow(ctx context.Context, v ...any) {
|
||||||
getLogger(ctx).Slow(v...)
|
getLogger(ctx).Slow(v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowf writes v with format into slow log.
|
// 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...)
|
getLogger(ctx).Slowf(format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowv writes v into slow log with json content.
|
// 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)
|
getLogger(ctx).Slowv(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package logc
|
package logc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -11,14 +10,11 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
|
"github.com/zeromicro/go-zero/core/logx/logtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddGlobalFields(t *testing.T) {
|
func TestAddGlobalFields(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
Info(context.Background(), "hello")
|
Info(context.Background(), "hello")
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
@@ -26,7 +22,7 @@ func TestAddGlobalFields(t *testing.T) {
|
|||||||
AddGlobalFields(Field("a", "1"), Field("b", "2"))
|
AddGlobalFields(Field("a", "1"), Field("b", "2"))
|
||||||
AddGlobalFields(Field("c", "3"))
|
AddGlobalFields(Field("c", "3"))
|
||||||
Info(context.Background(), "world")
|
Info(context.Background(), "world")
|
||||||
var m map[string]interface{}
|
var m map[string]any
|
||||||
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
|
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
|
||||||
assert.Equal(t, "1", m["a"])
|
assert.Equal(t, "1", m["a"])
|
||||||
assert.Equal(t, "2", m["b"])
|
assert.Equal(t, "2", m["b"])
|
||||||
@@ -34,112 +30,95 @@ func TestAddGlobalFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAlert(t *testing.T) {
|
func TestAlert(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
Alert(context.Background(), "foo")
|
Alert(context.Background(), "foo")
|
||||||
assert.True(t, strings.Contains(buf.String(), "foo"), buf.String())
|
assert.True(t, strings.Contains(buf.String(), "foo"), buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestError(t *testing.T) {
|
func TestError(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Error(context.Background(), "foo")
|
Error(context.Background(), "foo")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorf(t *testing.T) {
|
func TestErrorf(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Errorf(context.Background(), "foo %s", "bar")
|
Errorf(context.Background(), "foo %s", "bar")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorv(t *testing.T) {
|
func TestErrorv(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Errorv(context.Background(), "foo")
|
Errorv(context.Background(), "foo")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorw(t *testing.T) {
|
func TestErrorw(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Errorw(context.Background(), "foo", Field("a", "b"))
|
Errorw(context.Background(), "foo", Field("a", "b"))
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInfo(t *testing.T) {
|
func TestInfo(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Info(context.Background(), "foo")
|
Info(context.Background(), "foo")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInfof(t *testing.T) {
|
func TestInfof(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Infof(context.Background(), "foo %s", "bar")
|
Infof(context.Background(), "foo %s", "bar")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInfov(t *testing.T) {
|
func TestInfov(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Infov(context.Background(), "foo")
|
Infov(context.Background(), "foo")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInfow(t *testing.T) {
|
func TestInfow(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Infow(context.Background(), "foo", Field("a", "b"))
|
Infow(context.Background(), "foo", Field("a", "b"))
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)))
|
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) {
|
func TestMust(t *testing.T) {
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
Must(nil)
|
Must(nil)
|
||||||
@@ -156,48 +135,28 @@ func TestMisc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSlow(t *testing.T) {
|
func TestSlow(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Slow(context.Background(), "foo")
|
Slow(context.Background(), "foo")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSlowf(t *testing.T) {
|
func TestSlowf(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Slowf(context.Background(), "foo %s", "bar")
|
Slowf(context.Background(), "foo %s", "bar")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSlowv(t *testing.T) {
|
func TestSlowv(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Slowv(context.Background(), "foo")
|
Slowv(context.Background(), "foo")
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSloww(t *testing.T) {
|
func TestSloww(t *testing.T) {
|
||||||
var buf strings.Builder
|
buf := logtest.NewCollector(t)
|
||||||
writer := logx.NewWriter(&buf)
|
|
||||||
old := logx.Reset()
|
|
||||||
logx.SetWriter(writer)
|
|
||||||
defer logx.SetWriter(old)
|
|
||||||
|
|
||||||
file, line := getFileLine()
|
file, line := getFileLine()
|
||||||
Sloww(context.Background(), "foo", Field("a", "b"))
|
Sloww(context.Background(), "foo", Field("a", "b"))
|
||||||
assert.True(t, strings.Contains(buf.String(), fmt.Sprintf("%s:%d", file, line+1)), buf.String())
|
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.
|
// A LogConf is a logging config.
|
||||||
type LogConf struct {
|
type LogConf struct {
|
||||||
ServiceName string `json:",optional"`
|
// ServiceName represents the service name.
|
||||||
Mode string `json:",default=console,options=[console,file,volume]"`
|
ServiceName string `json:",optional"`
|
||||||
Encoding string `json:",default=json,options=[json,plain]"`
|
// Mode represents the logging mode, default is `console`.
|
||||||
TimeFormat string `json:",optional"`
|
// console: log to console.
|
||||||
Path string `json:",default=logs"`
|
// file: log to file.
|
||||||
Level string `json:",default=info,options=[debug,info,error,severe]"`
|
// volume: used in k8s, prepend the hostname to the log file name.
|
||||||
Compress bool `json:",optional"`
|
Mode string `json:",default=console,options=[console,file,volume]"`
|
||||||
KeepDays int `json:",optional"`
|
// Encoding represents the encoding type, default is `json`.
|
||||||
StackCooldownMillis int `json:",default=100"`
|
// 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.
|
// 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`.
|
// 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.
|
// if the `KeepDays` limitation is reached.
|
||||||
MaxBackups int `json:",default=0"`
|
MaxBackups int `json:",default=0"`
|
||||||
// MaxSize represents how much space the writing log file takes up. 0 means no limit. The unit is `MB`.
|
// 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`
|
// Only take effect when RotationRuleType is `size`
|
||||||
MaxSize int `json:",default=0"`
|
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.
|
// daily: daily rotation.
|
||||||
// size: size limited rotation.
|
// size: size limited rotation.
|
||||||
Rotation string `json:",default=daily,options=[daily,size]"`
|
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("a", "1"), Field("b", "2"))
|
||||||
AddGlobalFields(Field("c", "3"))
|
AddGlobalFields(Field("c", "3"))
|
||||||
Info("world")
|
Info("world")
|
||||||
var m map[string]interface{}
|
var m map[string]any
|
||||||
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
|
assert.NoError(t, json.Unmarshal(buf.Bytes(), &m))
|
||||||
assert.Equal(t, "1", m["a"])
|
assert.Equal(t, "1", m["a"])
|
||||||
assert.Equal(t, "2", m["b"])
|
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.
|
// 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() {
|
logger.logOrDiscard(func() {
|
||||||
Error(v...)
|
Error(v...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf logs v with format into error log or discard it if more than once in the given duration.
|
// 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() {
|
logger.logOrDiscard(func() {
|
||||||
Errorf(format, v...)
|
Errorf(format, v...)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,35 +8,35 @@ import (
|
|||||||
// A Logger represents a logger.
|
// A Logger represents a logger.
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
// Debug logs a message at info level.
|
// Debug logs a message at info level.
|
||||||
Debug(...interface{})
|
Debug(...any)
|
||||||
// Debugf logs a message at info level.
|
// Debugf logs a message at info level.
|
||||||
Debugf(string, ...interface{})
|
Debugf(string, ...any)
|
||||||
// Debugv logs a message at info level.
|
// Debugv logs a message at info level.
|
||||||
Debugv(interface{})
|
Debugv(any)
|
||||||
// Debugw logs a message at info level.
|
// Debugw logs a message at info level.
|
||||||
Debugw(string, ...LogField)
|
Debugw(string, ...LogField)
|
||||||
// Error logs a message at error level.
|
// Error logs a message at error level.
|
||||||
Error(...interface{})
|
Error(...any)
|
||||||
// Errorf logs a message at error level.
|
// Errorf logs a message at error level.
|
||||||
Errorf(string, ...interface{})
|
Errorf(string, ...any)
|
||||||
// Errorv logs a message at error level.
|
// Errorv logs a message at error level.
|
||||||
Errorv(interface{})
|
Errorv(any)
|
||||||
// Errorw logs a message at error level.
|
// Errorw logs a message at error level.
|
||||||
Errorw(string, ...LogField)
|
Errorw(string, ...LogField)
|
||||||
// Info logs a message at info level.
|
// Info logs a message at info level.
|
||||||
Info(...interface{})
|
Info(...any)
|
||||||
// Infof logs a message at info level.
|
// Infof logs a message at info level.
|
||||||
Infof(string, ...interface{})
|
Infof(string, ...any)
|
||||||
// Infov logs a message at info level.
|
// Infov logs a message at info level.
|
||||||
Infov(interface{})
|
Infov(any)
|
||||||
// Infow logs a message at info level.
|
// Infow logs a message at info level.
|
||||||
Infow(string, ...LogField)
|
Infow(string, ...LogField)
|
||||||
// Slow logs a message at slow level.
|
// Slow logs a message at slow level.
|
||||||
Slow(...interface{})
|
Slow(...any)
|
||||||
// Slowf logs a message at slow level.
|
// Slowf logs a message at slow level.
|
||||||
Slowf(string, ...interface{})
|
Slowf(string, ...any)
|
||||||
// Slowv logs a message at slow level.
|
// Slowv logs a message at slow level.
|
||||||
Slowv(interface{})
|
Slowv(any)
|
||||||
// Sloww logs a message at slow level.
|
// Sloww logs a message at slow level.
|
||||||
Sloww(string, ...LogField)
|
Sloww(string, ...LogField)
|
||||||
// WithCallerSkip returns a new logger with the given caller skip.
|
// WithCallerSkip returns a new logger with the given caller skip.
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ var (
|
|||||||
timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
timeFormat = "2006-01-02T15:04:05.000Z07:00"
|
||||||
logLevel uint32
|
logLevel uint32
|
||||||
encoding uint32 = jsonEncodingType
|
encoding uint32 = jsonEncodingType
|
||||||
|
// maxContentLength is used to truncate the log content, 0 for not truncating.
|
||||||
|
maxContentLength uint32
|
||||||
// use uint32 for atomic operations
|
// use uint32 for atomic operations
|
||||||
disableLog uint32
|
disableLog uint32
|
||||||
disableStat uint32
|
disableStat uint32
|
||||||
@@ -32,13 +34,13 @@ type (
|
|||||||
// LogField is a key-value pair that will be added to the log entry.
|
// LogField is a key-value pair that will be added to the log entry.
|
||||||
LogField struct {
|
LogField struct {
|
||||||
Key string
|
Key string
|
||||||
Value interface{}
|
Value any
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogOption defines the method to customize the logging.
|
// LogOption defines the method to customize the logging.
|
||||||
LogOption func(options *logOptions)
|
LogOption func(options *logOptions)
|
||||||
|
|
||||||
logEntry map[string]interface{}
|
logEntry map[string]any
|
||||||
|
|
||||||
logOptions struct {
|
logOptions struct {
|
||||||
gzipEnabled bool
|
gzipEnabled bool
|
||||||
@@ -65,23 +67,31 @@ func Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Debug writes v into access log.
|
// Debug writes v into access log.
|
||||||
func Debug(v ...interface{}) {
|
func Debug(v ...any) {
|
||||||
writeDebug(fmt.Sprint(v...))
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugf writes v with format into access log.
|
// Debugf writes v with format into access log.
|
||||||
func Debugf(format string, v ...interface{}) {
|
func Debugf(format string, v ...any) {
|
||||||
writeDebug(fmt.Sprintf(format, v...))
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugv writes v into access log with json content.
|
// Debugv writes v into access log with json content.
|
||||||
func Debugv(v interface{}) {
|
func Debugv(v any) {
|
||||||
writeDebug(v)
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugw writes msg along with fields into access log.
|
// Debugw writes msg along with fields into access log.
|
||||||
func Debugw(msg string, fields ...LogField) {
|
func Debugw(msg string, fields ...LogField) {
|
||||||
writeDebug(msg, fields...)
|
if shallLog(DebugLevel) {
|
||||||
|
writeDebug(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable disables the logging.
|
// Disable disables the logging.
|
||||||
@@ -96,40 +106,52 @@ func DisableStat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Error writes v into error log.
|
// Error writes v into error log.
|
||||||
func Error(v ...interface{}) {
|
func Error(v ...any) {
|
||||||
writeError(fmt.Sprint(v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
writeError(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf writes v with format into error log.
|
// Errorf writes v with format into error log.
|
||||||
func Errorf(format string, v ...interface{}) {
|
func Errorf(format string, v ...any) {
|
||||||
writeError(fmt.Errorf(format, v...).Error())
|
if shallLog(ErrorLevel) {
|
||||||
|
writeError(fmt.Errorf(format, v...).Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorStack writes v along with call stack into error log.
|
// ErrorStack writes v along with call stack into error log.
|
||||||
func ErrorStack(v ...interface{}) {
|
func ErrorStack(v ...any) {
|
||||||
// there is newline in stack string
|
if shallLog(ErrorLevel) {
|
||||||
writeStack(fmt.Sprint(v...))
|
// there is newline in stack string
|
||||||
|
writeStack(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorStackf writes v along with call stack in format into error log.
|
// ErrorStackf writes v along with call stack in format into error log.
|
||||||
func ErrorStackf(format string, v ...interface{}) {
|
func ErrorStackf(format string, v ...any) {
|
||||||
// there is newline in stack string
|
if shallLog(ErrorLevel) {
|
||||||
writeStack(fmt.Sprintf(format, v...))
|
// there is newline in stack string
|
||||||
|
writeStack(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorv writes v into error log with json content.
|
// Errorv writes v into error log with json content.
|
||||||
// No call stack attached, because not elegant to pack the messages.
|
// No call stack attached, because not elegant to pack the messages.
|
||||||
func Errorv(v interface{}) {
|
func Errorv(v any) {
|
||||||
writeError(v)
|
if shallLog(ErrorLevel) {
|
||||||
|
writeError(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorw writes msg along with fields into error log.
|
// Errorw writes msg along with fields into error log.
|
||||||
func Errorw(msg string, fields ...LogField) {
|
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.
|
// 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) {
|
switch val := value.(type) {
|
||||||
case error:
|
case error:
|
||||||
return LogField{Key: key, Value: val.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.
|
// Info writes v into access log.
|
||||||
func Info(v ...interface{}) {
|
func Info(v ...any) {
|
||||||
writeInfo(fmt.Sprint(v...))
|
if shallLog(InfoLevel) {
|
||||||
|
writeInfo(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof writes v with format into access log.
|
// Infof writes v with format into access log.
|
||||||
func Infof(format string, v ...interface{}) {
|
func Infof(format string, v ...any) {
|
||||||
writeInfo(fmt.Sprintf(format, v...))
|
if shallLog(InfoLevel) {
|
||||||
|
writeInfo(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infov writes v into access log with json content.
|
// Infov writes v into access log with json content.
|
||||||
func Infov(v interface{}) {
|
func Infov(v any) {
|
||||||
writeInfo(v)
|
if shallLog(InfoLevel) {
|
||||||
|
writeInfo(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infow writes msg along with fields into access log.
|
// Infow writes msg along with fields into access log.
|
||||||
func Infow(msg string, fields ...LogField) {
|
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.
|
// Must checks if err is nil, otherwise logs the error and exits.
|
||||||
@@ -192,10 +222,15 @@ func Must(err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := err.Error()
|
msg := fmt.Sprintf("%+v\n\n%s", err.Error(), debug.Stack())
|
||||||
log.Print(msg)
|
log.Print(msg)
|
||||||
getWriter().Severe(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.
|
// 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() {
|
setupOnce.Do(func() {
|
||||||
setupLogLevel(c)
|
setupLogLevel(c)
|
||||||
|
|
||||||
|
if !c.Stat {
|
||||||
|
DisableStat()
|
||||||
|
}
|
||||||
|
|
||||||
if len(c.TimeFormat) > 0 {
|
if len(c.TimeFormat) > 0 {
|
||||||
timeFormat = c.TimeFormat
|
timeFormat = c.TimeFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
atomic.StoreUint32(&maxContentLength, c.MaxContentLength)
|
||||||
|
|
||||||
switch c.Encoding {
|
switch c.Encoding {
|
||||||
case plainEncoding:
|
case plainEncoding:
|
||||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||||
@@ -255,43 +296,59 @@ func SetUp(c LogConf) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Severe writes v into severe log.
|
// Severe writes v into severe log.
|
||||||
func Severe(v ...interface{}) {
|
func Severe(v ...any) {
|
||||||
writeSevere(fmt.Sprint(v...))
|
if shallLog(SevereLevel) {
|
||||||
|
writeSevere(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Severef writes v with format into severe log.
|
// Severef writes v with format into severe log.
|
||||||
func Severef(format string, v ...interface{}) {
|
func Severef(format string, v ...any) {
|
||||||
writeSevere(fmt.Sprintf(format, v...))
|
if shallLog(SevereLevel) {
|
||||||
|
writeSevere(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slow writes v into slow log.
|
// Slow writes v into slow log.
|
||||||
func Slow(v ...interface{}) {
|
func Slow(v ...any) {
|
||||||
writeSlow(fmt.Sprint(v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowf writes v with format into slow log.
|
// Slowf writes v with format into slow log.
|
||||||
func Slowf(format string, v ...interface{}) {
|
func Slowf(format string, v ...any) {
|
||||||
writeSlow(fmt.Sprintf(format, v...))
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slowv writes v into slow log with json content.
|
// Slowv writes v into slow log with json content.
|
||||||
func Slowv(v interface{}) {
|
func Slowv(v any) {
|
||||||
writeSlow(v)
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sloww writes msg along with fields into slow log.
|
// Sloww writes msg along with fields into slow log.
|
||||||
func Sloww(msg string, fields ...LogField) {
|
func Sloww(msg string, fields ...LogField) {
|
||||||
writeSlow(msg, fields...)
|
if shallLog(ErrorLevel) {
|
||||||
|
writeSlow(msg, fields...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat writes v into stat log.
|
// Stat writes v into stat log.
|
||||||
func Stat(v ...interface{}) {
|
func Stat(v ...any) {
|
||||||
writeStat(fmt.Sprint(v...))
|
if shallLogStat() && shallLog(InfoLevel) {
|
||||||
|
writeStat(fmt.Sprint(v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statf writes v with format into stat log.
|
// Statf writes v with format into stat log.
|
||||||
func Statf(format string, v ...interface{}) {
|
func Statf(format string, v ...any) {
|
||||||
writeStat(fmt.Sprintf(format, v...))
|
if shallLogStat() && shallLog(InfoLevel) {
|
||||||
|
writeStat(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCooldownMillis customizes logging on writing call stack interval.
|
// WithCooldownMillis customizes logging on writing call stack interval.
|
||||||
@@ -345,14 +402,16 @@ func createOutput(path string) (io.WriteCloser, error) {
|
|||||||
return nil, ErrLogPathNotSet
|
return nil, ErrLogPathNotSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rule RotateRule
|
||||||
switch options.rotationRule {
|
switch options.rotationRule {
|
||||||
case sizeRotationRule:
|
case sizeRotationRule:
|
||||||
return NewLogger(path, NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays,
|
rule = NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays, options.maxSize,
|
||||||
options.maxSize, options.maxBackups, options.gzipEnabled), options.gzipEnabled)
|
options.maxBackups, options.gzipEnabled)
|
||||||
default:
|
default:
|
||||||
return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays,
|
rule = DefaultRotateRule(path, backupFileDelimiter, options.keepDays, options.gzipEnabled)
|
||||||
options.gzipEnabled), options.gzipEnabled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NewLogger(path, rule, options.gzipEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWriter() Writer {
|
func getWriter() Writer {
|
||||||
@@ -414,44 +473,58 @@ func shallLogStat() bool {
|
|||||||
return atomic.LoadUint32(&disableStat) == 0
|
return atomic.LoadUint32(&disableStat) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeDebug(val interface{}, fields ...LogField) {
|
// writeDebug writes v into debug log.
|
||||||
if shallLog(DebugLevel) {
|
// Not checking shallLog here is for performance consideration.
|
||||||
getWriter().Debug(val, addCaller(fields...)...)
|
// 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) {
|
// writeError writes v into error log.
|
||||||
if shallLog(ErrorLevel) {
|
// Not checking shallLog here is for performance consideration.
|
||||||
getWriter().Error(val, addCaller(fields...)...)
|
// 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) {
|
// writeInfo writes v into info log.
|
||||||
if shallLog(InfoLevel) {
|
// Not checking shallLog here is for performance consideration.
|
||||||
getWriter().Info(val, addCaller(fields...)...)
|
// 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) {
|
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) {
|
// writeSlow writes v into slow log.
|
||||||
if shallLog(ErrorLevel) {
|
// Not checking shallLog here is for performance consideration.
|
||||||
getWriter().Slow(val, addCaller(fields...)...)
|
// 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) {
|
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) {
|
func writeStat(msg string) {
|
||||||
if shallLogStat() && shallLog(InfoLevel) {
|
getWriter().Stat(msg, addCaller()...)
|
||||||
getWriter().Stat(msg, addCaller()...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,54 +24,58 @@ var (
|
|||||||
_ Writer = (*mockWriter)(nil)
|
_ Writer = (*mockWriter)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ExitOnFatal.Set(false)
|
||||||
|
}
|
||||||
|
|
||||||
type mockWriter struct {
|
type mockWriter struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
builder strings.Builder
|
builder strings.Builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Alert(v interface{}) {
|
func (mw *mockWriter) Alert(v any) {
|
||||||
mw.lock.Lock()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelAlert, v)
|
output(&mw.builder, levelAlert, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Debug(v interface{}, fields ...LogField) {
|
func (mw *mockWriter) Debug(v any, fields ...LogField) {
|
||||||
mw.lock.Lock()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelDebug, v, fields...)
|
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()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelError, v, fields...)
|
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()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelInfo, v, fields...)
|
output(&mw.builder, levelInfo, v, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Severe(v interface{}) {
|
func (mw *mockWriter) Severe(v any) {
|
||||||
mw.lock.Lock()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelSevere, v)
|
output(&mw.builder, levelSevere, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Slow(v interface{}, fields ...LogField) {
|
func (mw *mockWriter) Slow(v any, fields ...LogField) {
|
||||||
mw.lock.Lock()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelSlow, v, fields...)
|
output(&mw.builder, levelSlow, v, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Stack(v interface{}) {
|
func (mw *mockWriter) Stack(v any) {
|
||||||
mw.lock.Lock()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelError, v)
|
output(&mw.builder, levelError, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *mockWriter) Stat(v interface{}, fields ...LogField) {
|
func (mw *mockWriter) Stat(v any, fields ...LogField) {
|
||||||
mw.lock.Lock()
|
mw.lock.Lock()
|
||||||
defer mw.lock.Unlock()
|
defer mw.lock.Unlock()
|
||||||
output(&mw.builder, levelStat, v, fields...)
|
output(&mw.builder, levelStat, v, fields...)
|
||||||
@@ -103,41 +107,41 @@ func TestField(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
f LogField
|
f LogField
|
||||||
want map[string]interface{}
|
want map[string]any
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "error",
|
name: "error",
|
||||||
f: Field("foo", errors.New("bar")),
|
f: Field("foo", errors.New("bar")),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "errors",
|
name: "errors",
|
||||||
f: Field("foo", []error{errors.New("bar"), errors.New("baz")}),
|
f: Field("foo", []error{errors.New("bar"), errors.New("baz")}),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": []interface{}{"bar", "baz"},
|
"foo": []any{"bar", "baz"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "strings",
|
name: "strings",
|
||||||
f: Field("foo", []string{"bar", "baz"}),
|
f: Field("foo", []string{"bar", "baz"}),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": []interface{}{"bar", "baz"},
|
"foo": []any{"bar", "baz"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "duration",
|
name: "duration",
|
||||||
f: Field("foo", time.Second),
|
f: Field("foo", time.Second),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": "1s",
|
"foo": "1s",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "durations",
|
name: "durations",
|
||||||
f: Field("foo", []time.Duration{time.Second, 2 * time.Second}),
|
f: Field("foo", []time.Duration{time.Second, 2 * time.Second}),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": []interface{}{"1s", "2s"},
|
"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, 1, 0, 0, 0, 0, time.UTC),
|
||||||
time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC),
|
time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC),
|
||||||
}),
|
}),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": []interface{}{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
|
"foo": []any{"2020-01-01 00:00:00 +0000 UTC", "2020-01-02 00:00:00 +0000 UTC"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "stringer",
|
name: "stringer",
|
||||||
f: Field("foo", ValStringer{val: "bar"}),
|
f: Field("foo", ValStringer{val: "bar"}),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "stringers",
|
name: "stringers",
|
||||||
f: Field("foo", []fmt.Stringer{ValStringer{val: "bar"}, ValStringer{val: "baz"}}),
|
f: Field("foo", []fmt.Stringer{ValStringer{val: "bar"}, ValStringer{val: "baz"}}),
|
||||||
want: map[string]interface{}{
|
want: map[string]any{
|
||||||
"foo": []interface{}{"bar", "baz"},
|
"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)))
|
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) {
|
func TestStructedLogAlert(t *testing.T) {
|
||||||
w := new(mockWriter)
|
w := new(mockWriter)
|
||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelAlert, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelAlert, w, func(v ...any) {
|
||||||
Alert(fmt.Sprint(v...))
|
Alert(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -223,7 +233,7 @@ func TestStructedLogDebug(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelDebug, w, func(v ...any) {
|
||||||
Debug(v...)
|
Debug(v...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -233,7 +243,7 @@ func TestStructedLogDebugf(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelDebug, w, func(v ...any) {
|
||||||
Debugf(fmt.Sprint(v...))
|
Debugf(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -243,7 +253,7 @@ func TestStructedLogDebugv(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelDebug, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelDebug, w, func(v ...any) {
|
||||||
Debugv(fmt.Sprint(v...))
|
Debugv(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -253,7 +263,7 @@ func TestStructedLogDebugw(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
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))
|
Debugw(fmt.Sprint(v...), Field("foo", time.Second))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -263,7 +273,7 @@ func TestStructedLogError(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelError, w, func(v ...any) {
|
||||||
Error(v...)
|
Error(v...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -273,7 +283,7 @@ func TestStructedLogErrorf(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelError, w, func(v ...any) {
|
||||||
Errorf("%s", fmt.Sprint(v...))
|
Errorf("%s", fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -283,7 +293,7 @@ func TestStructedLogErrorv(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelError, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelError, w, func(v ...any) {
|
||||||
Errorv(fmt.Sprint(v...))
|
Errorv(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -293,7 +303,7 @@ func TestStructedLogErrorw(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
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"))
|
Errorw(fmt.Sprint(v...), Field("foo", "bar"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -303,7 +313,7 @@ func TestStructedLogInfo(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelInfo, w, func(v ...any) {
|
||||||
Info(v...)
|
Info(v...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -313,7 +323,7 @@ func TestStructedLogInfof(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelInfo, w, func(v ...any) {
|
||||||
Infof("%s", fmt.Sprint(v...))
|
Infof("%s", fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -323,7 +333,7 @@ func TestStructedLogInfov(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelInfo, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelInfo, w, func(v ...any) {
|
||||||
Infov(fmt.Sprint(v...))
|
Infov(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -333,7 +343,7 @@ func TestStructedLogInfow(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
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"))
|
Infow(fmt.Sprint(v...), Field("foo", "bar"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -343,7 +353,7 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||||
old := atomic.LoadUint32(&encoding)
|
old := atomic.LoadUint32(&encoding)
|
||||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -359,7 +369,7 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||||
old := atomic.LoadUint32(&encoding)
|
old := atomic.LoadUint32(&encoding)
|
||||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -375,7 +385,7 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||||
old := atomic.LoadUint32(&encoding)
|
old := atomic.LoadUint32(&encoding)
|
||||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -391,7 +401,7 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||||
old := atomic.LoadUint32(&encoding)
|
old := atomic.LoadUint32(&encoding)
|
||||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -409,7 +419,7 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLogConsole(t, w, func(v ...interface{}) {
|
doTestStructedLogConsole(t, w, func(v ...any) {
|
||||||
old := atomic.LoadUint32(&encoding)
|
old := atomic.LoadUint32(&encoding)
|
||||||
atomic.StoreUint32(&encoding, plainEncodingType)
|
atomic.StoreUint32(&encoding, plainEncodingType)
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -425,7 +435,7 @@ func TestStructedLogSlow(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelSlow, w, func(v ...any) {
|
||||||
Slow(v...)
|
Slow(v...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -435,7 +445,7 @@ func TestStructedLogSlowf(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelSlow, w, func(v ...any) {
|
||||||
Slowf(fmt.Sprint(v...))
|
Slowf(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -445,7 +455,7 @@ func TestStructedLogSlowv(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelSlow, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelSlow, w, func(v ...any) {
|
||||||
Slowv(fmt.Sprint(v...))
|
Slowv(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -455,7 +465,7 @@ func TestStructedLogSloww(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
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))
|
Sloww(fmt.Sprint(v...), Field("foo", time.Second))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -465,7 +475,7 @@ func TestStructedLogStat(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelStat, w, func(v ...any) {
|
||||||
Stat(v...)
|
Stat(v...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -475,7 +485,7 @@ func TestStructedLogStatf(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelStat, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelStat, w, func(v ...any) {
|
||||||
Statf(fmt.Sprint(v...))
|
Statf(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -485,7 +495,7 @@ func TestStructedLogSevere(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelSevere, w, func(v ...any) {
|
||||||
Severe(v...)
|
Severe(v...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -495,7 +505,7 @@ func TestStructedLogSeveref(t *testing.T) {
|
|||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
doTestStructedLog(t, levelSevere, w, func(v ...interface{}) {
|
doTestStructedLog(t, levelSevere, w, func(v ...any) {
|
||||||
Severef(fmt.Sprint(v...))
|
Severef(fmt.Sprint(v...))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -507,7 +517,7 @@ func TestStructedLogWithDuration(t *testing.T) {
|
|||||||
defer writer.Store(old)
|
defer writer.Store(old)
|
||||||
|
|
||||||
WithDuration(time.Second).Info(message)
|
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 {
|
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -529,9 +539,9 @@ func TestSetLevel(t *testing.T) {
|
|||||||
|
|
||||||
func TestSetLevelTwiceWithMode(t *testing.T) {
|
func TestSetLevelTwiceWithMode(t *testing.T) {
|
||||||
testModes := []string{
|
testModes := []string{
|
||||||
"mode",
|
|
||||||
"console",
|
"console",
|
||||||
"volumn",
|
"volumn",
|
||||||
|
"mode",
|
||||||
}
|
}
|
||||||
w := new(mockWriter)
|
w := new(mockWriter)
|
||||||
old := writer.Swap(w)
|
old := writer.Swap(w)
|
||||||
@@ -574,26 +584,38 @@ func TestSetup(t *testing.T) {
|
|||||||
atomic.StoreUint32(&encoding, jsonEncodingType)
|
atomic.StoreUint32(&encoding, jsonEncodingType)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
setupOnce = sync.Once{}
|
||||||
|
MustSetup(LogConf{
|
||||||
|
ServiceName: "any",
|
||||||
|
Mode: "console",
|
||||||
|
Encoding: "json",
|
||||||
|
TimeFormat: timeFormat,
|
||||||
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "console",
|
Mode: "console",
|
||||||
TimeFormat: timeFormat,
|
TimeFormat: timeFormat,
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "file",
|
Mode: "file",
|
||||||
Path: os.TempDir(),
|
Path: os.TempDir(),
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "volume",
|
Mode: "volume",
|
||||||
Path: os.TempDir(),
|
Path: os.TempDir(),
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "console",
|
Mode: "console",
|
||||||
TimeFormat: timeFormat,
|
TimeFormat: timeFormat,
|
||||||
})
|
})
|
||||||
|
setupOnce = sync.Once{}
|
||||||
MustSetup(LogConf{
|
MustSetup(LogConf{
|
||||||
ServiceName: "any",
|
ServiceName: "any",
|
||||||
Mode: "console",
|
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"
|
const message = "hello there"
|
||||||
write(message)
|
write(message)
|
||||||
|
|
||||||
var entry map[string]interface{}
|
var entry map[string]any
|
||||||
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
|
if err := json.Unmarshal([]byte(w.String()), &entry); err != nil {
|
||||||
t.Error(err)
|
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))
|
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"
|
const message = "hello there"
|
||||||
write(message)
|
write(message)
|
||||||
assert.True(t, strings.Contains(w.String(), 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) {
|
func testSetLevelTwiceWithMode(t *testing.T, mode string, w *mockWriter) {
|
||||||
writer.Store(nil)
|
writer.Store(nil)
|
||||||
SetUp(LogConf{
|
SetUp(LogConf{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Level: "error",
|
Level: "debug",
|
||||||
Path: "/dev/null",
|
Path: "/dev/null",
|
||||||
|
Encoding: plainEncoding,
|
||||||
|
Stat: false,
|
||||||
|
TimeFormat: time.RFC3339,
|
||||||
})
|
})
|
||||||
SetUp(LogConf{
|
SetUp(LogConf{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
@@ -819,8 +844,8 @@ func (v ValStringer) String() string {
|
|||||||
return v.val
|
return v.val
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFields(t *testing.T, content string, fields map[string]interface{}) {
|
func validateFields(t *testing.T, content string, fields map[string]any) {
|
||||||
var m map[string]interface{}
|
var m map[string]any
|
||||||
if err := json.Unmarshal([]byte(content), &m); err != nil {
|
if err := json.Unmarshal([]byte(content), &m); err != nil {
|
||||||
t.Error(err)
|
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
|
```go
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
// Error logs a message at error level.
|
// Error logs a message at error level.
|
||||||
Error(...interface{})
|
Error(...any)
|
||||||
// Errorf logs a message at error level.
|
// Errorf logs a message at error level.
|
||||||
Errorf(string, ...interface{})
|
Errorf(string, ...any)
|
||||||
// Errorv logs a message at error level.
|
// Errorv logs a message at error level.
|
||||||
Errorv(interface{})
|
Errorv(any)
|
||||||
// Errorw logs a message at error level.
|
// Errorw logs a message at error level.
|
||||||
Errorw(string, ...LogField)
|
Errorw(string, ...LogField)
|
||||||
// Info logs a message at info level.
|
// Info logs a message at info level.
|
||||||
Info(...interface{})
|
Info(...any)
|
||||||
// Infof logs a message at info level.
|
// Infof logs a message at info level.
|
||||||
Infof(string, ...interface{})
|
Infof(string, ...any)
|
||||||
// Infov logs a message at info level.
|
// Infov logs a message at info level.
|
||||||
Infov(interface{})
|
Infov(any)
|
||||||
// Infow logs a message at info level.
|
// Infow logs a message at info level.
|
||||||
Infow(string, ...LogField)
|
Infow(string, ...LogField)
|
||||||
// Slow logs a message at slow level.
|
// Slow logs a message at slow level.
|
||||||
Slow(...interface{})
|
Slow(...any)
|
||||||
// Slowf logs a message at slow level.
|
// Slowf logs a message at slow level.
|
||||||
Slowf(string, ...interface{})
|
Slowf(string, ...any)
|
||||||
// Slowv logs a message at slow level.
|
// Slowv logs a message at slow level.
|
||||||
Slowv(interface{})
|
Slowv(any)
|
||||||
// Sloww logs a message at slow level.
|
// Sloww logs a message at slow level.
|
||||||
Sloww(string, ...LogField)
|
Sloww(string, ...LogField)
|
||||||
// WithContext returns a new logger with the given context.
|
// 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 {
|
if m, ok := msg.(Message); ok {
|
||||||
l.Writer.Info(Message{
|
l.Writer.Info(Message{
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
|
|||||||
@@ -51,27 +51,27 @@ type LogConf struct {
|
|||||||
```go
|
```go
|
||||||
type Logger interface {
|
type Logger interface {
|
||||||
// Error logs a message at error level.
|
// Error logs a message at error level.
|
||||||
Error(...interface{})
|
Error(...any)
|
||||||
// Errorf logs a message at error level.
|
// Errorf logs a message at error level.
|
||||||
Errorf(string, ...interface{})
|
Errorf(string, ...any)
|
||||||
// Errorv logs a message at error level.
|
// Errorv logs a message at error level.
|
||||||
Errorv(interface{})
|
Errorv(any)
|
||||||
// Errorw logs a message at error level.
|
// Errorw logs a message at error level.
|
||||||
Errorw(string, ...LogField)
|
Errorw(string, ...LogField)
|
||||||
// Info logs a message at info level.
|
// Info logs a message at info level.
|
||||||
Info(...interface{})
|
Info(...any)
|
||||||
// Infof logs a message at info level.
|
// Infof logs a message at info level.
|
||||||
Infof(string, ...interface{})
|
Infof(string, ...any)
|
||||||
// Infov logs a message at info level.
|
// Infov logs a message at info level.
|
||||||
Infov(interface{})
|
Infov(any)
|
||||||
// Infow logs a message at info level.
|
// Infow logs a message at info level.
|
||||||
Infow(string, ...LogField)
|
Infow(string, ...LogField)
|
||||||
// Slow logs a message at slow level.
|
// Slow logs a message at slow level.
|
||||||
Slow(...interface{})
|
Slow(...any)
|
||||||
// Slowf logs a message at slow level.
|
// Slowf logs a message at slow level.
|
||||||
Slowf(string, ...interface{})
|
Slowf(string, ...any)
|
||||||
// Slowv logs a message at slow level.
|
// Slowv logs a message at slow level.
|
||||||
Slowv(interface{})
|
Slowv(any)
|
||||||
// Sloww logs a message at slow level.
|
// Sloww logs a message at slow level.
|
||||||
Sloww(string, ...LogField)
|
Sloww(string, ...LogField)
|
||||||
// WithContext returns a new logger with the given context.
|
// 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 {
|
if m, ok := msg.(Message); ok {
|
||||||
l.Writer.Info(Message{
|
l.Writer.Info(Message{
|
||||||
Name: m.Name,
|
Name: m.Name,
|
||||||
|
|||||||
@@ -40,15 +40,15 @@ type richLogger struct {
|
|||||||
fields []LogField
|
fields []LogField
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Debug(v ...interface{}) {
|
func (l *richLogger) Debug(v ...any) {
|
||||||
l.debug(fmt.Sprint(v...))
|
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...))
|
l.debug(fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Debugv(v interface{}) {
|
func (l *richLogger) Debugv(v any) {
|
||||||
l.debug(v)
|
l.debug(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,31 +56,31 @@ func (l *richLogger) Debugw(msg string, fields ...LogField) {
|
|||||||
l.debug(msg, fields...)
|
l.debug(msg, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Error(v ...interface{}) {
|
func (l *richLogger) Error(v ...any) {
|
||||||
l.err(fmt.Sprint(v...))
|
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...))
|
l.err(fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Errorv(v interface{}) {
|
func (l *richLogger) Errorv(v any) {
|
||||||
l.err(fmt.Sprint(v))
|
l.err(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Errorw(msg string, fields ...LogField) {
|
func (l *richLogger) Errorw(msg string, fields ...LogField) {
|
||||||
l.err(msg, fields...)
|
l.err(msg, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Info(v ...interface{}) {
|
func (l *richLogger) Info(v ...any) {
|
||||||
l.info(fmt.Sprint(v...))
|
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...))
|
l.info(fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Infov(v interface{}) {
|
func (l *richLogger) Infov(v any) {
|
||||||
l.info(v)
|
l.info(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,15 +88,15 @@ func (l *richLogger) Infow(msg string, fields ...LogField) {
|
|||||||
l.info(msg, fields...)
|
l.info(msg, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Slow(v ...interface{}) {
|
func (l *richLogger) Slow(v ...any) {
|
||||||
l.slow(fmt.Sprint(v...))
|
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...))
|
l.slow(fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) Slowv(v interface{}) {
|
func (l *richLogger) Slowv(v any) {
|
||||||
l.slow(v)
|
l.slow(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,25 +156,25 @@ func (l *richLogger) buildFields(fields ...LogField) []LogField {
|
|||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *richLogger) debug(v interface{}, fields ...LogField) {
|
func (l *richLogger) debug(v any, fields ...LogField) {
|
||||||
if shallLog(DebugLevel) {
|
if shallLog(DebugLevel) {
|
||||||
getWriter().Debug(v, l.buildFields(fields...)...)
|
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) {
|
if shallLog(ErrorLevel) {
|
||||||
getWriter().Error(v, l.buildFields(fields...)...)
|
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) {
|
if shallLog(InfoLevel) {
|
||||||
getWriter().Info(v, l.buildFields(fields...)...)
|
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) {
|
if shallLog(ErrorLevel) {
|
||||||
getWriter().Slow(v, l.buildFields(fields...)...)
|
getWriter().Slow(v, l.buildFields(fields...)...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ func TestTraceDebug(t *testing.T) {
|
|||||||
l.WithDuration(time.Second).Debugv(testlog)
|
l.WithDuration(time.Second).Debugv(testlog)
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
w.Reset()
|
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"))
|
l.WithDuration(time.Second).Debugw(testlog, Field("foo", "bar"))
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
assert.True(t, strings.Contains(w.String(), "foo"), w.String())
|
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)
|
l.WithDuration(time.Second).Errorv(testlog)
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
w.Reset()
|
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"))
|
l.WithDuration(time.Second).Errorw(testlog, Field("basket", "ball"))
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
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)
|
l.WithDuration(time.Second).Infov(testlog)
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
w.Reset()
|
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"))
|
l.WithDuration(time.Second).Infow(testlog, Field("basket", "ball"))
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
||||||
@@ -173,6 +182,9 @@ func TestTraceInfoConsole(t *testing.T) {
|
|||||||
w.Reset()
|
w.Reset()
|
||||||
l.WithDuration(time.Second).Infov(testlog)
|
l.WithDuration(time.Second).Infov(testlog)
|
||||||
validate(t, w.String(), true, true)
|
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) {
|
func TestTraceSlow(t *testing.T) {
|
||||||
@@ -204,6 +216,9 @@ func TestTraceSlow(t *testing.T) {
|
|||||||
l.WithDuration(time.Second).Slowv(testlog)
|
l.WithDuration(time.Second).Slowv(testlog)
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
w.Reset()
|
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"))
|
l.WithDuration(time.Second).Sloww(testlog, Field("basket", "ball"))
|
||||||
validate(t, w.String(), true, true)
|
validate(t, w.String(), true, true)
|
||||||
assert.True(t, strings.Contains(w.String(), "basket"), w.String())
|
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)
|
assert.Equal(t, expectedSpan, len(val.Span) > 0, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockValue struct {
|
func validateContentType(t *testing.T, body string, expectedType any, expectedTrace, expectedSpan bool) {
|
||||||
Trace string `json:"trace"`
|
var val mockValue
|
||||||
Span string `json:"span"`
|
dec := json.NewDecoder(strings.NewReader(body))
|
||||||
Foo string `json:"foo"`
|
|
||||||
|
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"
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -237,7 +236,7 @@ func NewLogger(filename string, rule RotateRule, compress bool) (*RotateLogger,
|
|||||||
rule: rule,
|
rule: rule,
|
||||||
compress: compress,
|
compress: compress,
|
||||||
}
|
}
|
||||||
if err := l.init(); err != nil {
|
if err := l.initialize(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +280,7 @@ func (l *RotateLogger) getBackupFilename() string {
|
|||||||
return l.backup
|
return l.backup
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *RotateLogger) init() error {
|
func (l *RotateLogger) initialize() error {
|
||||||
l.backup = l.rule.BackupFileName()
|
l.backup = l.rule.BackupFileName()
|
||||||
|
|
||||||
if fileInfo, err := os.Stat(l.filename); err != nil {
|
if fileInfo, err := os.Stat(l.filename); err != nil {
|
||||||
@@ -406,7 +405,7 @@ func (l *RotateLogger) write(v []byte) {
|
|||||||
func compressLogFile(file string) {
|
func compressLogFile(file string) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
Infof("compressing log file: %s", file)
|
Infof("compressing log file: %s", file)
|
||||||
if err := gzipFile(file); err != nil {
|
if err := gzipFile(file, fileSys); err != nil {
|
||||||
Errorf("compress error: %s", err)
|
Errorf("compress error: %s", err)
|
||||||
} else {
|
} else {
|
||||||
Infof("compressed log file: %s, took %s", file, time.Since(start))
|
Infof("compressed log file: %s, took %s", file, time.Since(start))
|
||||||
@@ -421,25 +420,37 @@ func getNowDateInRFC3339Format() string {
|
|||||||
return time.Now().Format(fileTimeFormat)
|
return time.Now().Format(fileTimeFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func gzipFile(file string) error {
|
func gzipFile(file string, fsys fileSystem) (err error) {
|
||||||
in, err := os.Open(file)
|
in, err := fsys.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer func() {
|
||||||
|
e := fsys.Close(out)
|
||||||
|
if err == nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
w := gzip.NewWriter(out)
|
w := gzip.NewWriter(out)
|
||||||
if _, err = io.Copy(w, in); err != nil {
|
if _, err = fsys.Copy(w, in); err != nil {
|
||||||
return err
|
// failed to copy, no need to close w
|
||||||
} else if err = w.Close(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Remove(file)
|
return fsys.Close(w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,74 @@
|
|||||||
package logx
|
package logx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeromicro/go-zero/core/fs"
|
"github.com/zeromicro/go-zero/core/fs"
|
||||||
|
"github.com/zeromicro/go-zero/core/stringx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDailyRotateRuleMarkRotated(t *testing.T) {
|
func TestDailyRotateRuleMarkRotated(t *testing.T) {
|
||||||
var rule DailyRotateRule
|
t.Run("daily rule", func(t *testing.T) {
|
||||||
rule.MarkRotated()
|
var rule DailyRotateRule
|
||||||
assert.Equal(t, getNowDate(), rule.rotatedTime)
|
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) {
|
func TestDailyRotateRuleOutdatedFiles(t *testing.T) {
|
||||||
var rule DailyRotateRule
|
t.Run("no files", func(t *testing.T) {
|
||||||
assert.Empty(t, rule.OutdatedFiles())
|
var rule DailyRotateRule
|
||||||
rule.days = 1
|
assert.Empty(t, rule.OutdatedFiles())
|
||||||
assert.Empty(t, rule.OutdatedFiles())
|
rule.days = 1
|
||||||
rule.gzip = true
|
assert.Empty(t, rule.OutdatedFiles())
|
||||||
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) {
|
func TestDailyRotateRuleShallRotate(t *testing.T) {
|
||||||
@@ -33,20 +78,101 @@ func TestDailyRotateRuleShallRotate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
|
func TestSizeLimitRotateRuleMarkRotated(t *testing.T) {
|
||||||
var rule SizeLimitRotateRule
|
t.Run("size limit rule", func(t *testing.T) {
|
||||||
rule.MarkRotated()
|
var rule SizeLimitRotateRule
|
||||||
assert.Equal(t, getNowDateInRFC3339Format(), rule.rotatedTime)
|
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) {
|
func TestSizeLimitRotateRuleOutdatedFiles(t *testing.T) {
|
||||||
var rule SizeLimitRotateRule
|
t.Run("no files", func(t *testing.T) {
|
||||||
assert.Empty(t, rule.OutdatedFiles())
|
var rule SizeLimitRotateRule
|
||||||
rule.days = 1
|
assert.Empty(t, rule.OutdatedFiles())
|
||||||
assert.Empty(t, rule.OutdatedFiles())
|
rule.days = 1
|
||||||
rule.gzip = true
|
assert.Empty(t, rule.OutdatedFiles())
|
||||||
assert.Empty(t, rule.OutdatedFiles())
|
rule.gzip = true
|
||||||
rule.maxBackups = 0
|
assert.Empty(t, rule.OutdatedFiles())
|
||||||
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) {
|
func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
|
||||||
@@ -60,14 +186,26 @@ func TestSizeLimitRotateRuleShallRotate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRotateLoggerClose(t *testing.T) {
|
func TestRotateLoggerClose(t *testing.T) {
|
||||||
filename, err := fs.TempFilenameWithText("foo")
|
t.Run("close", func(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
filename, err := fs.TempFilenameWithText("foo")
|
||||||
if len(filename) > 0 {
|
assert.Nil(t, err)
|
||||||
defer os.Remove(filename)
|
if len(filename) > 0 {
|
||||||
}
|
defer os.Remove(filename)
|
||||||
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
}
|
||||||
assert.Nil(t, err)
|
logger, err := NewLogger(filename, new(DailyRotateRule), false)
|
||||||
assert.Nil(t, logger.Close())
|
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) {
|
func TestRotateLoggerGetBackupFilename(t *testing.T) {
|
||||||
@@ -178,7 +316,7 @@ func TestRotateLoggerWithSizeLimitRotateRuleClose(t *testing.T) {
|
|||||||
}
|
}
|
||||||
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
|
logger, err := NewLogger(filename, new(SizeLimitRotateRule), false)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Nil(t, logger.Close())
|
_ = logger.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
|
func TestRotateLoggerGetBackupWithSizeLimitRotateRuleFilename(t *testing.T) {
|
||||||
@@ -232,6 +370,23 @@ func TestRotateLoggerWithSizeLimitRotateRuleMayCompressFileTrue(t *testing.T) {
|
|||||||
assert.NotNil(t, err)
|
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) {
|
func TestRotateLoggerWithSizeLimitRotateRuleRotate(t *testing.T) {
|
||||||
filename, err := fs.TempFilenameWithText("foo")
|
filename, err := fs.TempFilenameWithText("foo")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -277,6 +432,70 @@ func TestRotateLoggerWithSizeLimitRotateRuleWrite(t *testing.T) {
|
|||||||
logger.write([]byte(`baz`))
|
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) {
|
func BenchmarkRotateLogger(b *testing.B) {
|
||||||
filename := "./test.log"
|
filename := "./test.log"
|
||||||
filename2 := "./test2.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."
|
const testlog = "Stay hungry, stay foolish."
|
||||||
|
|
||||||
|
var testobj = map[string]any{"foo": "bar"}
|
||||||
|
|
||||||
func TestCollectSysLog(t *testing.T) {
|
func TestCollectSysLog(t *testing.T) {
|
||||||
CollectSysLog()
|
CollectSysLog()
|
||||||
content := getContent(captureOutput(func() {
|
content := getContent(captureOutput(func() {
|
||||||
@@ -42,7 +44,7 @@ func captureOutput(f func()) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getContent(jsonStr string) string {
|
func getContent(jsonStr string) string {
|
||||||
var entry map[string]interface{}
|
var entry map[string]any
|
||||||
json.Unmarshal([]byte(jsonStr), &entry)
|
json.Unmarshal([]byte(jsonStr), &entry)
|
||||||
|
|
||||||
val, ok := entry[contentKey]
|
val, ok := entry[contentKey]
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package logx
|
package logx
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/zeromicro/go-zero/core/syncx"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DebugLevel logs everything
|
// DebugLevel logs everything
|
||||||
@@ -16,13 +20,13 @@ const (
|
|||||||
const (
|
const (
|
||||||
jsonEncodingType = iota
|
jsonEncodingType = iota
|
||||||
plainEncodingType
|
plainEncodingType
|
||||||
|
|
||||||
plainEncoding = "plain"
|
|
||||||
plainEncodingSep = '\t'
|
|
||||||
sizeRotationRule = "size"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
plainEncoding = "plain"
|
||||||
|
plainEncodingSep = '\t'
|
||||||
|
sizeRotationRule = "size"
|
||||||
|
|
||||||
accessFilename = "access.log"
|
accessFilename = "access.log"
|
||||||
errorFilename = "error.log"
|
errorFilename = "error.log"
|
||||||
severeFilename = "severe.log"
|
severeFilename = "severe.log"
|
||||||
@@ -53,6 +57,7 @@ const (
|
|||||||
spanKey = "span"
|
spanKey = "span"
|
||||||
timestampKey = "@timestamp"
|
timestampKey = "@timestamp"
|
||||||
traceKey = "trace"
|
traceKey = "trace"
|
||||||
|
truncatedKey = "truncated"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -60,4 +65,8 @@ var (
|
|||||||
ErrLogPathNotSet = errors.New("log path must be set")
|
ErrLogPathNotSet = errors.New("log path must be set")
|
||||||
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
|
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
|
||||||
ErrLogServiceNameNotSet = errors.New("log service name must be 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 (
|
type (
|
||||||
Writer interface {
|
Writer interface {
|
||||||
Alert(v interface{})
|
Alert(v any)
|
||||||
Close() error
|
Close() error
|
||||||
Debug(v interface{}, fields ...LogField)
|
Debug(v any, fields ...LogField)
|
||||||
Error(v interface{}, fields ...LogField)
|
Error(v any, fields ...LogField)
|
||||||
Info(v interface{}, fields ...LogField)
|
Info(v any, fields ...LogField)
|
||||||
Severe(v interface{})
|
Severe(v any)
|
||||||
Slow(v interface{}, fields ...LogField)
|
Slow(v any, fields ...LogField)
|
||||||
Stack(v interface{})
|
Stack(v any)
|
||||||
Stat(v interface{}, fields ...LogField)
|
Stat(v any, fields ...LogField)
|
||||||
}
|
}
|
||||||
|
|
||||||
atomicWriter struct {
|
atomicWriter struct {
|
||||||
@@ -171,7 +171,7 @@ func newFileWriter(c LogConf) (Writer, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *concreteWriter) Alert(v interface{}) {
|
func (w *concreteWriter) Alert(v any) {
|
||||||
output(w.errorLog, levelAlert, v)
|
output(w.errorLog, levelAlert, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,69 +195,69 @@ func (w *concreteWriter) Close() error {
|
|||||||
return w.statLog.Close()
|
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...)
|
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...)
|
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...)
|
output(w.infoLog, levelInfo, v, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *concreteWriter) Severe(v interface{}) {
|
func (w *concreteWriter) Severe(v any) {
|
||||||
output(w.severeLog, levelFatal, v)
|
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...)
|
output(w.slowLog, levelSlow, v, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *concreteWriter) Stack(v interface{}) {
|
func (w *concreteWriter) Stack(v any) {
|
||||||
output(w.stackLog, levelError, v)
|
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...)
|
output(w.statLog, levelStat, v, fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type nopWriter struct{}
|
type nopWriter struct{}
|
||||||
|
|
||||||
func (n nopWriter) Alert(_ interface{}) {
|
func (n nopWriter) Alert(_ any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n nopWriter) Close() error {
|
func (n nopWriter) Close() error {
|
||||||
return nil
|
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
|
var items []string
|
||||||
|
|
||||||
for _, field := range fields {
|
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
|
return items
|
||||||
@@ -269,15 +269,29 @@ func combineGlobalFields(fields []LogField) []LogField {
|
|||||||
return fields
|
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)
|
fields = combineGlobalFields(fields)
|
||||||
|
|
||||||
switch atomic.LoadUint32(&encoding) {
|
switch atomic.LoadUint32(&encoding) {
|
||||||
case plainEncodingType:
|
case plainEncodingType:
|
||||||
writePlainAny(writer, level, val, buildFields(fields...)...)
|
writePlainAny(writer, level, val, buildPlainFields(fields...)...)
|
||||||
default:
|
default:
|
||||||
entry := make(logEntry)
|
entry := make(logEntry)
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
@@ -316,7 +330,7 @@ func wrapLevelWithColor(level string) string {
|
|||||||
return color.WithColorPadding(level, colour)
|
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 {
|
if content, err := json.Marshal(info); err != nil {
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
} else if writer == nil {
|
} 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)
|
level = wrapLevelWithColor(level)
|
||||||
|
|
||||||
switch v := val.(type) {
|
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
|
var buf bytes.Buffer
|
||||||
buf.WriteString(getTimestamp())
|
buf.WriteString(getTimestamp())
|
||||||
buf.WriteByte(plainEncodingSep)
|
buf.WriteByte(plainEncodingSep)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -96,6 +97,15 @@ func TestConsoleWriter(t *testing.T) {
|
|||||||
w.(*concreteWriter).statLog = easyToCloseWriter{}
|
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) {
|
func TestNopWriter(t *testing.T) {
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
var w nopWriter
|
var w nopWriter
|
||||||
@@ -107,7 +117,7 @@ func TestNopWriter(t *testing.T) {
|
|||||||
w.Stack("foo")
|
w.Stack("foo")
|
||||||
w.Stat("foo")
|
w.Stat("foo")
|
||||||
w.Slow("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 {
|
type mockedEntry struct {
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
Truncated bool `json:"truncated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type easyToCloseWriter struct{}
|
type easyToCloseWriter struct{}
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ const jsonTagKey = "json"
|
|||||||
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
|
var jsonUnmarshaler = NewUnmarshaler(jsonTagKey)
|
||||||
|
|
||||||
// UnmarshalJsonBytes unmarshals content into v.
|
// 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...))
|
return unmarshalJsonBytes(content, v, getJsonUnmarshaler(opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJsonMap unmarshals content from m into v.
|
// 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)
|
return getJsonUnmarshaler(opts...).Unmarshal(m, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJsonReader unmarshals content from reader into 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...))
|
return unmarshalJsonReader(reader, v, getJsonUnmarshaler(opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +33,8 @@ func getJsonUnmarshaler(opts ...UnmarshalOption) *Unmarshaler {
|
|||||||
return jsonUnmarshaler
|
return jsonUnmarshaler
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler) error {
|
func unmarshalJsonBytes(content []byte, v any, unmarshaler *Unmarshaler) error {
|
||||||
var m map[string]interface{}
|
var m any
|
||||||
if err := jsonx.Unmarshal(content, &m); err != nil {
|
if err := jsonx.Unmarshal(content, &m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -42,8 +42,8 @@ func unmarshalJsonBytes(content []byte, v interface{}, unmarshaler *Unmarshaler)
|
|||||||
return unmarshaler.Unmarshal(m, v)
|
return unmarshaler.Unmarshal(m, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalJsonReader(reader io.Reader, v interface{}, unmarshaler *Unmarshaler) error {
|
func unmarshalJsonReader(reader io.Reader, v any, unmarshaler *Unmarshaler) error {
|
||||||
var m map[string]interface{}
|
var m any
|
||||||
if err := jsonx.UnmarshalFromReader(reader, &m); err != nil {
|
if err := jsonx.UnmarshalFromReader(reader, &m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -856,8 +856,7 @@ func TestUnmarshalBytesError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := UnmarshalJsonBytes([]byte(payload), &v)
|
err := UnmarshalJsonBytes([]byte(payload), &v)
|
||||||
assert.NotNil(t, err)
|
assert.Equal(t, errTypeMismatch, err)
|
||||||
assert.True(t, strings.Contains(err.Error(), payload))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalReaderError(t *testing.T) {
|
func TestUnmarshalReaderError(t *testing.T) {
|
||||||
@@ -867,14 +866,12 @@ func TestUnmarshalReaderError(t *testing.T) {
|
|||||||
Any string
|
Any string
|
||||||
}
|
}
|
||||||
|
|
||||||
err := UnmarshalJsonReader(reader, &v)
|
assert.Equal(t, errTypeMismatch, UnmarshalJsonReader(reader, &v))
|
||||||
assert.NotNil(t, err)
|
|
||||||
assert.True(t, strings.Contains(err.Error(), payload))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshalMap(t *testing.T) {
|
func TestUnmarshalMap(t *testing.T) {
|
||||||
t.Run("nil map and valid", func(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 {
|
var v struct {
|
||||||
Any string `json:",optional"`
|
Any string `json:",optional"`
|
||||||
}
|
}
|
||||||
@@ -885,7 +882,7 @@ func TestUnmarshalMap(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("empty map but not valid", func(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 {
|
var v struct {
|
||||||
Any string
|
Any string
|
||||||
}
|
}
|
||||||
@@ -895,7 +892,7 @@ func TestUnmarshalMap(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("empty map and valid", func(t *testing.T) {
|
t.Run("empty map and valid", func(t *testing.T) {
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
var v struct {
|
var v struct {
|
||||||
Any string `json:",optional"`
|
Any string `json:",optional"`
|
||||||
}
|
}
|
||||||
@@ -908,7 +905,7 @@ func TestUnmarshalMap(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("valid map", func(t *testing.T) {
|
t.Run("valid map", func(t *testing.T) {
|
||||||
m := map[string]interface{}{
|
m := map[string]any{
|
||||||
"Any": "foo",
|
"Any": "foo",
|
||||||
}
|
}
|
||||||
var v struct {
|
var v struct {
|
||||||
@@ -920,3 +917,26 @@ func TestUnmarshalMap(t *testing.T) {
|
|||||||
assert.Equal(t, "foo", v.Any)
|
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.
|
// 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.
|
// 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) {
|
func Marshal(val any) (map[string]map[string]any, error) {
|
||||||
ret := make(map[string]map[string]interface{})
|
ret := make(map[string]map[string]any)
|
||||||
tp := reflect.TypeOf(val)
|
tp := reflect.TypeOf(val)
|
||||||
if tp.Kind() == reflect.Ptr {
|
if tp.Kind() == reflect.Ptr {
|
||||||
tp = tp.Elem()
|
tp = tp.Elem()
|
||||||
@@ -45,7 +45,7 @@ func getTag(field reflect.StructField) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func processMember(field reflect.StructField, value reflect.Value,
|
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 key string
|
||||||
var opt *fieldOptions
|
var opt *fieldOptions
|
||||||
var err error
|
var err error
|
||||||
@@ -73,7 +73,7 @@ func processMember(field reflect.StructField, value reflect.Value,
|
|||||||
if ok {
|
if ok {
|
||||||
m[key] = val
|
m[key] = val
|
||||||
} else {
|
} else {
|
||||||
m = map[string]interface{}{
|
m = map[string]any{
|
||||||
key: val,
|
key: val,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ func TestMarshal_Range(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshal_RangeOut(t *testing.T) {
|
func TestMarshal_RangeOut(t *testing.T) {
|
||||||
tests := []interface{}{
|
tests := []any{
|
||||||
struct {
|
struct {
|
||||||
Int int `json:"int,range=[1:3]"`
|
Int int `json:"int,range=[1:3]"`
|
||||||
}{
|
}{
|
||||||
@@ -262,7 +262,7 @@ func TestMarshal_RangeOut(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshal_RangeIllegal(t *testing.T) {
|
func TestMarshal_RangeIllegal(t *testing.T) {
|
||||||
tests := []interface{}{
|
tests := []any{
|
||||||
struct {
|
struct {
|
||||||
Int int `json:"int,range=[3:1]"`
|
Int int `json:"int,range=[3:1]"`
|
||||||
}{
|
}{
|
||||||
@@ -284,7 +284,7 @@ func TestMarshal_RangeIllegal(t *testing.T) {
|
|||||||
func TestMarshal_RangeLeftEqualsToRight(t *testing.T) {
|
func TestMarshal_RangeLeftEqualsToRight(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
value interface{}
|
value any
|
||||||
err error
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalTomlBytes unmarshals TOML bytes into the given v.
|
// 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)
|
b, err := encoding.TomlToJson(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// 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)
|
b, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ var (
|
|||||||
durationType = reflect.TypeOf(time.Duration(0))
|
durationType = reflect.TypeOf(time.Duration(0))
|
||||||
cacheKeys = make(map[string][]string)
|
cacheKeys = make(map[string][]string)
|
||||||
cacheKeysLock sync.Mutex
|
cacheKeysLock sync.Mutex
|
||||||
defaultCache = make(map[string]interface{})
|
defaultCache = make(map[string]any)
|
||||||
defaultCacheLock sync.Mutex
|
defaultCacheLock sync.Mutex
|
||||||
emptyMap = map[string]interface{}{}
|
emptyMap = map[string]any{}
|
||||||
emptyValue = reflect.ValueOf(lang.Placeholder)
|
emptyValue = reflect.ValueOf(lang.Placeholder)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,6 +47,7 @@ type (
|
|||||||
UnmarshalOption func(*unmarshalOptions)
|
UnmarshalOption func(*unmarshalOptions)
|
||||||
|
|
||||||
unmarshalOptions struct {
|
unmarshalOptions struct {
|
||||||
|
fillDefault bool
|
||||||
fromString bool
|
fromString bool
|
||||||
canonicalKey func(key string) string
|
canonicalKey func(key string) string
|
||||||
}
|
}
|
||||||
@@ -66,21 +67,42 @@ func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalKey unmarshals m into v with tag key.
|
// 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)
|
return keyUnmarshaler.Unmarshal(m, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals m into v.
|
// Unmarshal unmarshals m into v.
|
||||||
func (u *Unmarshaler) Unmarshal(m map[string]interface{}, v interface{}) error {
|
func (u *Unmarshaler) Unmarshal(i any, v any) error {
|
||||||
return u.UnmarshalValuer(mapValuer(m), v)
|
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.
|
// 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, "")
|
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() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
}
|
}
|
||||||
@@ -100,7 +122,7 @@ func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapVa
|
|||||||
return nil
|
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() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
}
|
}
|
||||||
@@ -121,20 +143,22 @@ func (u *Unmarshaler) fillMapFromString(value reflect.Value, mapValue interface{
|
|||||||
return nil
|
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() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
}
|
}
|
||||||
|
|
||||||
baseType := fieldType.Elem()
|
|
||||||
baseKind := baseType.Kind()
|
|
||||||
dereffedBaseType := Deref(baseType)
|
|
||||||
dereffedBaseKind := dereffedBaseType.Kind()
|
|
||||||
refValue := reflect.ValueOf(mapValue)
|
refValue := reflect.ValueOf(mapValue)
|
||||||
|
if refValue.Kind() != reflect.Slice {
|
||||||
|
return errTypeMismatch
|
||||||
|
}
|
||||||
if refValue.IsNil() {
|
if refValue.IsNil() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseType := fieldType.Elem()
|
||||||
|
dereffedBaseType := Deref(baseType)
|
||||||
|
dereffedBaseKind := dereffedBaseType.Kind()
|
||||||
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
|
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
|
||||||
if refValue.Len() == 0 {
|
if refValue.Len() == 0 {
|
||||||
value.Set(conv)
|
value.Set(conv)
|
||||||
@@ -152,15 +176,16 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, map
|
|||||||
switch dereffedBaseKind {
|
switch dereffedBaseKind {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
target := reflect.New(dereffedBaseType)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseKind == reflect.Ptr {
|
SetValue(fieldType.Elem(), conv.Index(i), target.Elem())
|
||||||
conv.Index(i).Set(target)
|
|
||||||
} else {
|
|
||||||
conv.Index(i).Set(target.Elem())
|
|
||||||
}
|
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil {
|
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil {
|
||||||
return err
|
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,
|
func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.Value,
|
||||||
mapValue interface{}) error {
|
mapValue any) error {
|
||||||
var slice []interface{}
|
var slice []any
|
||||||
switch v := mapValue.(type) {
|
switch v := mapValue.(type) {
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
if err := jsonx.UnmarshalFromString(v.String(), &slice); err != nil {
|
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,
|
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)
|
ithVal := slice.Index(index)
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
return setValue(baseKind, ithVal, v.String())
|
return setValueFromString(baseKind, ithVal, v.String())
|
||||||
case string:
|
case string:
|
||||||
return setValue(baseKind, ithVal, v)
|
return setValueFromString(baseKind, ithVal, v)
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
return u.fillMap(ithVal.Type(), ithVal, value)
|
return u.fillMap(ithVal.Type(), ithVal, value)
|
||||||
default:
|
default:
|
||||||
// don't need to consider the difference between int, int8, int16, int32, int64,
|
// 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 := reflect.New(baseType).Elem()
|
||||||
target.Set(reflect.ValueOf(value))
|
target.Set(reflect.ValueOf(value))
|
||||||
ithVal.Set(target.Addr())
|
SetValue(ithVal.Type(), ithVal, target)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,16 +290,19 @@ func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value refle
|
|||||||
return u.fillSlice(derefedType, value, slice)
|
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)
|
mapType := reflect.MapOf(keyType, elemType)
|
||||||
valueType := reflect.TypeOf(mapValue)
|
valueType := reflect.TypeOf(mapValue)
|
||||||
if mapType == valueType {
|
if mapType == valueType {
|
||||||
return reflect.ValueOf(mapValue), nil
|
return reflect.ValueOf(mapValue), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if keyType != valueType.Key() {
|
||||||
|
return emptyValue, errTypeMismatch
|
||||||
|
}
|
||||||
|
|
||||||
refValue := reflect.ValueOf(mapValue)
|
refValue := reflect.ValueOf(mapValue)
|
||||||
targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
|
targetValue := reflect.MakeMapWithSize(mapType, refValue.Len())
|
||||||
fieldElemKind := elemType.Kind()
|
|
||||||
dereffedElemType := Deref(elemType)
|
dereffedElemType := Deref(elemType)
|
||||||
dereffedElemKind := dereffedElemType.Kind()
|
dereffedElemKind := dereffedElemType.Kind()
|
||||||
|
|
||||||
@@ -291,7 +319,7 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue inter
|
|||||||
|
|
||||||
targetValue.SetMapIndex(key, target.Elem())
|
targetValue.SetMapIndex(key, target.Elem())
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
keythMap, ok := keythData.(map[string]interface{})
|
keythMap, ok := keythData.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return emptyValue, errTypeMismatch
|
return emptyValue, errTypeMismatch
|
||||||
}
|
}
|
||||||
@@ -301,13 +329,9 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue inter
|
|||||||
return emptyValue, err
|
return emptyValue, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fieldElemKind == reflect.Ptr {
|
SetMapIndexValue(elemType, targetValue, key, target.Elem())
|
||||||
targetValue.SetMapIndex(key, target)
|
|
||||||
} else {
|
|
||||||
targetValue.SetMapIndex(key, target.Elem())
|
|
||||||
}
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
keythMap, ok := keythData.(map[string]interface{})
|
keythMap, ok := keythData.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return emptyValue, errTypeMismatch
|
return emptyValue, errTypeMismatch
|
||||||
}
|
}
|
||||||
@@ -331,10 +355,15 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue inter
|
|||||||
return emptyValue, errTypeMismatch
|
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:
|
case json.Number:
|
||||||
target := reflect.New(dereffedElemType)
|
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
|
return emptyValue, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,6 +390,26 @@ func (u *Unmarshaler) parseOptionsWithContext(field reflect.StructField, m Value
|
|||||||
return key, nil, nil
|
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)
|
optsWithContext, err := options.toOptionsWithContext(key, m, fullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
@@ -376,19 +425,51 @@ func (u *Unmarshaler) processAnonymousField(field reflect.StructField, value ref
|
|||||||
return err
|
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() {
|
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 {
|
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 filled bool
|
||||||
var required int
|
var required int
|
||||||
var requiredFilled int
|
var requiredFilled int
|
||||||
@@ -422,22 +503,7 @@ func (u *Unmarshaler) processAnonymousFieldOptional(fieldType reflect.Type, valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if filled && required != requiredFilled {
|
if filled && required != requiredFilled {
|
||||||
return fmt.Errorf("%s is not fully set", key)
|
return fmt.Errorf("%q 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 nil
|
return nil
|
||||||
@@ -460,12 +526,12 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
|||||||
vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error {
|
vp valueWithParent, opts *fieldOptionsWithContext, fullName string) error {
|
||||||
derefedFieldType := Deref(fieldType)
|
derefedFieldType := Deref(fieldType)
|
||||||
typeKind := derefedFieldType.Kind()
|
typeKind := derefedFieldType.Kind()
|
||||||
valueKind := reflect.TypeOf(vp.value).Kind()
|
|
||||||
mapValue := vp.value
|
mapValue := vp.value
|
||||||
|
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
case valueKind == reflect.Map && typeKind == reflect.Struct:
|
||||||
mv, ok := mapValue.(map[string]interface{})
|
mv, ok := mapValue.(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errTypeMismatch
|
return errTypeMismatch
|
||||||
}
|
}
|
||||||
@@ -474,6 +540,8 @@ func (u *Unmarshaler) processFieldNotFromString(fieldType reflect.Type, value re
|
|||||||
current: mapValuer(mv),
|
current: mapValuer(mv),
|
||||||
parent: vp.parent,
|
parent: vp.parent,
|
||||||
}, fullName)
|
}, fullName)
|
||||||
|
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
||||||
|
return u.fillSlice(fieldType, value, mapValue)
|
||||||
case valueKind == reflect.Map && typeKind == reflect.Map:
|
case valueKind == reflect.Map && typeKind == reflect.Map:
|
||||||
return u.fillMap(fieldType, value, mapValue)
|
return u.fillMap(fieldType, value, mapValue)
|
||||||
case valueKind == reflect.String && typeKind == reflect.Map:
|
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:
|
case valueKind == reflect.String && typeKind == reflect.Slice:
|
||||||
return u.fillSliceFromString(fieldType, value, mapValue)
|
return u.fillSliceFromString(fieldType, value, mapValue)
|
||||||
case valueKind == reflect.String && derefedFieldType == durationType:
|
case valueKind == reflect.String && derefedFieldType == durationType:
|
||||||
return fillDurationValue(fieldType.Kind(), value, mapValue.(string))
|
return fillDurationValue(fieldType, value, mapValue.(string))
|
||||||
default:
|
default:
|
||||||
return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName)
|
return u.processFieldPrimitive(fieldType, value, mapValue, opts, fullName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Unmarshaler) processFieldPrimitive(fieldType reflect.Type, value reflect.Value,
|
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()
|
typeKind := Deref(fieldType).Kind()
|
||||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
valueKind := reflect.TypeOf(mapValue).Kind()
|
||||||
|
|
||||||
switch {
|
switch v := mapValue.(type) {
|
||||||
case typeKind == reflect.Slice && valueKind == reflect.Slice:
|
case json.Number:
|
||||||
return u.fillSlice(fieldType, value, mapValue)
|
return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName)
|
||||||
case typeKind == reflect.Map && valueKind == reflect.Map:
|
|
||||||
return u.fillMap(fieldType, value, mapValue)
|
|
||||||
default:
|
default:
|
||||||
switch v := mapValue.(type) {
|
if typeKind == valueKind {
|
||||||
case json.Number:
|
if err := validateValueInOptions(mapValue, opts.options()); err != nil {
|
||||||
return u.processFieldPrimitiveWithJSONNumber(fieldType, value, v, opts, fullName)
|
return err
|
||||||
default:
|
|
||||||
if typeKind == valueKind {
|
|
||||||
if err := validateValueInOptions(mapValue, opts.options()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fillWithSameType(fieldType, value, mapValue, opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type, value reflect.Value,
|
||||||
v json.Number, opts *fieldOptionsWithContext, fullName string) error {
|
v json.Number, opts *fieldOptionsWithContext, fullName string) error {
|
||||||
fieldKind := fieldType.Kind()
|
baseType := Deref(fieldType)
|
||||||
typeKind := Deref(fieldType).Kind()
|
typeKind := baseType.Kind()
|
||||||
|
|
||||||
if err := validateJsonNumberRange(v, opts); err != nil {
|
if err := validateJsonNumberRange(v, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -528,9 +589,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fieldKind == reflect.Ptr {
|
target := reflect.New(Deref(fieldType)).Elem()
|
||||||
value = value.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch typeKind {
|
switch typeKind {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
@@ -539,7 +598,7 @@ func (u *Unmarshaler) processFieldPrimitiveWithJSONNumber(fieldType reflect.Type
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
value.SetInt(iValue)
|
target.SetInt(iValue)
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
iValue, err := v.Int64()
|
iValue, err := v.Int64()
|
||||||
if err != nil {
|
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())
|
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:
|
case reflect.Float32, reflect.Float64:
|
||||||
fValue, err := v.Float64()
|
fValue, err := v.Float64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
value.SetFloat(fValue)
|
target.SetFloat(fValue)
|
||||||
default:
|
default:
|
||||||
return newTypeMismatchError(fullName)
|
return newTypeMismatchErrorWithHint(fullName, typeKind.String(), value.Type().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetValue(fieldType, value, target)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,7 +635,7 @@ func (u *Unmarshaler) processFieldStruct(fieldType reflect.Type, value reflect.V
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
value.Set(target.Addr())
|
SetValue(fieldType, value, target)
|
||||||
} else if err := u.unmarshalWithFullName(m, value.Addr().Interface(), fullName); err != nil {
|
} else if err := u.unmarshalWithFullName(m, value.Addr().Interface(), fullName); err != nil {
|
||||||
return err
|
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,
|
func (u *Unmarshaler) processFieldTextUnmarshaler(fieldType reflect.Type, value reflect.Value,
|
||||||
mapValue interface{}) (bool, error) {
|
mapValue any) (bool, error) {
|
||||||
var tval encoding.TextUnmarshaler
|
var tval encoding.TextUnmarshaler
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if fieldType.Kind() == reflect.Ptr {
|
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 {
|
} else {
|
||||||
tval, ok = value.Addr().Interface().(encoding.TextUnmarshaler)
|
tval, ok = value.Addr().Interface().(encoding.TextUnmarshaler)
|
||||||
}
|
}
|
||||||
@@ -621,7 +688,7 @@ func (u *Unmarshaler) processFieldWithEnvValue(fieldType reflect.Type, value ref
|
|||||||
value.SetBool(val)
|
value.SetBool(val)
|
||||||
return nil
|
return nil
|
||||||
case durationType.Kind():
|
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)
|
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,
|
func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect.Value,
|
||||||
m valuerWithParent, fullName string) error {
|
m valuerWithParent, fullName string) error {
|
||||||
|
if !field.IsExported() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
key, opts, err := u.parseOptionsWithContext(field, m, fullName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -656,7 +727,14 @@ func (u *Unmarshaler) processNamedField(field reflect.StructField, value reflect
|
|||||||
|
|
||||||
valuer := createValuer(m, opts)
|
valuer := createValuer(m, opts)
|
||||||
mapValue, hasValue := getValue(valuer, canonicalKey)
|
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)
|
return u.processNamedFieldWithoutValue(field.Type, value, opts, fullName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,11 +752,11 @@ func (u *Unmarshaler) processNamedFieldWithValue(fieldType reflect.Type, value r
|
|||||||
return nil
|
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() {
|
if !value.CanSet() {
|
||||||
return fmt.Errorf("field %s is not settable", key)
|
return fmt.Errorf("field %q is not settable", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeNewValue(fieldType, value)
|
maybeNewValue(fieldType, value)
|
||||||
@@ -693,47 +771,69 @@ func (u *Unmarshaler) processNamedFieldWithValue(fieldType reflect.Type, value r
|
|||||||
return u.processFieldNotFromString(fieldType, value, vp, opts, fullName)
|
return u.processFieldNotFromString(fieldType, value, vp, opts, fullName)
|
||||||
default:
|
default:
|
||||||
if u.opts.fromString || opts.fromString() {
|
if u.opts.fromString || opts.fromString() {
|
||||||
valueKind := reflect.TypeOf(mapValue).Kind()
|
return u.processNamedFieldWithValueFromString(fieldType, value, mapValue,
|
||||||
if valueKind != reflect.String {
|
key, opts, fullName)
|
||||||
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.processFieldNotFromString(fieldType, value, vp, 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,
|
func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, value reflect.Value,
|
||||||
opts *fieldOptionsWithContext, fullName string) error {
|
opts *fieldOptionsWithContext, fullName string) error {
|
||||||
derefedType := Deref(fieldType)
|
derefedType := Deref(fieldType)
|
||||||
fieldKind := derefedType.Kind()
|
fieldKind := derefedType.Kind()
|
||||||
if defaultValue, ok := opts.getDefault(); ok {
|
if defaultValue, ok := opts.getDefault(); ok {
|
||||||
if fieldType.Kind() == reflect.Ptr {
|
|
||||||
maybeNewValue(fieldType, value)
|
|
||||||
value = value.Elem()
|
|
||||||
}
|
|
||||||
if derefedType == durationType {
|
if derefedType == durationType {
|
||||||
return fillDurationValue(fieldKind, value, defaultValue)
|
return fillDurationValue(fieldType, value, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch fieldKind {
|
switch fieldKind {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
return u.fillSliceWithDefault(derefedType, value, defaultValue)
|
return u.fillSliceWithDefault(derefedType, value, defaultValue)
|
||||||
default:
|
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 {
|
switch fieldKind {
|
||||||
case reflect.Array, reflect.Map, reflect.Slice:
|
case reflect.Array, reflect.Map, reflect.Slice:
|
||||||
if !opts.optional() {
|
if !opts.optional() {
|
||||||
@@ -765,21 +865,35 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu
|
|||||||
return nil
|
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)
|
rv := reflect.ValueOf(v)
|
||||||
if err := ValidatePtr(&rv); err != nil {
|
if err := ValidatePtr(&rv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rte := reflect.TypeOf(v).Elem()
|
valueType := reflect.TypeOf(v)
|
||||||
if rte.Kind() != reflect.Struct {
|
baseType := Deref(valueType)
|
||||||
|
if baseType.Kind() != reflect.Struct {
|
||||||
return errValueNotStruct
|
return errValueNotStruct
|
||||||
}
|
}
|
||||||
|
|
||||||
rve := rv.Elem()
|
valElem := rv.Elem()
|
||||||
numFields := rte.NumField()
|
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++ {
|
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
|
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 {
|
func WithCanonicalKeyFunc(f func(string) string) UnmarshalOption {
|
||||||
return func(opt *unmarshalOptions) {
|
return func(opt *unmarshalOptions) {
|
||||||
opt.canonicalKey = f
|
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 {
|
func createValuer(v valuerWithParent, opts *fieldOptionsWithContext) valuerWithParent {
|
||||||
if opts.inherit() {
|
if opts.inherit() {
|
||||||
return recursiveValuer{
|
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)
|
d, err := time.ParseDuration(dur)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fieldKind == reflect.Ptr {
|
SetValue(fieldType, value, reflect.ValueOf(d))
|
||||||
value.Elem().Set(reflect.ValueOf(d))
|
|
||||||
} else {
|
|
||||||
value.Set(reflect.ValueOf(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
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 {
|
opts *fieldOptionsWithContext, fullName string) error {
|
||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
@@ -841,7 +958,7 @@ func fillPrimitive(fieldType reflect.Type, value reflect.Value, mapValue interfa
|
|||||||
target := reflect.New(baseType).Elem()
|
target := reflect.New(baseType).Elem()
|
||||||
switch mapValue.(type) {
|
switch mapValue.(type) {
|
||||||
case string, json.Number:
|
case string, json.Number:
|
||||||
value.Set(target.Addr())
|
SetValue(fieldType, value, target)
|
||||||
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 {
|
if err := validateJsonNumberRange(v, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return setValue(baseType.Kind(), value, v.String())
|
return setValueFromString(baseType.Kind(), value, v.String())
|
||||||
default:
|
default:
|
||||||
return newTypeMismatchError(fullName)
|
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 {
|
opts *fieldOptionsWithContext) error {
|
||||||
if !value.CanSet() {
|
if !value.CanSet() {
|
||||||
return errValueNotSettable
|
return errValueNotSettable
|
||||||
@@ -873,7 +990,7 @@ func fillWithSameType(fieldType reflect.Type, value reflect.Value, mapValue inte
|
|||||||
baseType := Deref(fieldType)
|
baseType := Deref(fieldType)
|
||||||
target := reflect.New(baseType).Elem()
|
target := reflect.New(baseType).Elem()
|
||||||
setSameKindValue(baseType, target, mapValue)
|
setSameKindValue(baseType, target, mapValue)
|
||||||
value.Set(target.Addr())
|
SetValue(fieldType, value, target)
|
||||||
} else {
|
} else {
|
||||||
setSameKindValue(fieldType, value, mapValue)
|
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
|
// 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)
|
keys := readKeys(key)
|
||||||
return getValueWithChainedKeys(m, keys)
|
return getValueWithChainedKeys(m, keys)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getValueWithChainedKeys(m valuerWithParent, keys []string) (interface{}, bool) {
|
func getValueWithChainedKeys(m valuerWithParent, keys []string) (any, bool) {
|
||||||
switch len(keys) {
|
switch len(keys) {
|
||||||
case 0:
|
case 0:
|
||||||
return nil, false
|
return nil, false
|
||||||
@@ -896,7 +1013,7 @@ func getValueWithChainedKeys(m valuerWithParent, keys []string) (interface{}, bo
|
|||||||
return v, ok
|
return v, ok
|
||||||
default:
|
default:
|
||||||
if v, ok := m.Value(keys[0]); ok {
|
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{
|
return getValueWithChainedKeys(recursiveValuer{
|
||||||
current: mapValuer(nextm),
|
current: mapValuer(nextm),
|
||||||
parent: m,
|
parent: m,
|
||||||
@@ -930,11 +1047,16 @@ func join(elem ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newInitError(name string) error {
|
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 {
|
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 {
|
func readKeys(key string) []string {
|
||||||
@@ -955,7 +1077,7 @@ func readKeys(key string) []string {
|
|||||||
return keys
|
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) {
|
if reflect.ValueOf(value).Type().AssignableTo(targetType) {
|
||||||
target.Set(reflect.ValueOf(value))
|
target.Set(reflect.ValueOf(value))
|
||||||
} else {
|
} 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