mirror of https://github.com/stretchr/testify.git
Compare commits
578 Commits
Author | SHA1 | Date |
---|---|---|
|
5ac6528bff | |
|
d0e0f4961b | |
|
b561f16e87 | |
|
a948a8c402 | |
|
c3915e850a | |
|
16020e8cbc | |
|
75df9d50d4 | |
|
33be8f984a | |
|
a9e8aed155 | |
|
3b8bd9bf7d | |
|
1e7fb5865a | |
|
c6ac9bb91d | |
|
65f73866c0 | |
|
b1c9368f81 | |
|
5a5ac85551 | |
|
53e0c918d4 | |
|
89086b0757 | |
|
098128fd10 | |
|
f784abc221 | |
|
dfda68b86f | |
|
3cf0926564 | |
|
c60c3bd7fb | |
|
1c717c00c1 | |
|
ccb5e7f656 | |
|
ca6698b8a1 | |
|
7c367bb7bc | |
|
e6a990c21d | |
|
cfee2346d7 | |
|
f8c628e5a1 | |
|
014ae9a7a4 | |
|
30f3cef5ad | |
|
d57bac8721 | |
|
89cbdd9e7b | |
|
07bac606be | |
|
716de8dff4 | |
|
118fb83466 | |
|
7d99b2b43d | |
|
05f87c0160 | |
|
ea7129e006 | |
|
a1b9c9efe3 | |
|
8302de98b1 | |
|
89352f7958 | |
|
2780579e15 | |
|
8f049b0122 | |
|
be992afabf | |
|
fb67df6392 | |
|
55bac84354 | |
|
822223ec34 | |
|
22d3bd5def | |
|
9c174eb41c | |
|
7efaf15f33 | |
|
dce9e58ee3 | |
|
75a239b2fd | |
|
f9ccf14f6f | |
|
6555fd4da6 | |
|
a012e45d18 | |
|
3b2754b72f | |
|
2fc4e39394 | |
|
f2227519d6 | |
|
50d5b7e695 | |
|
55ebaca027 | |
|
ba3e7c34d5 | |
|
2063e81696 | |
|
7f489726a5 | |
|
f844b269df | |
|
dc100b1be3 | |
|
aade8450b3 | |
|
3380867632 | |
|
95d1f9c2ad | |
|
fed9ee68dc | |
|
5dc934f9aa | |
|
c4b8421a1f | |
|
85a526818c | |
|
16a09b7086 | |
|
f3f7181b01 | |
|
d62ca68bf5 | |
|
428847e363 | |
|
72e3b61028 | |
|
ea6964c2e9 | |
|
f17409f81f | |
|
bdb1271ed8 | |
|
e943930404 | |
|
7268a5bc0b | |
|
da63673a11 | |
|
52df55490e | |
|
7f10816c93 | |
|
176474a4c9 | |
|
28e0be5092 | |
|
4a90eff4ae | |
|
3ca01f4bc3 | |
|
bc04bb85a2 | |
|
b074924938 | |
|
f71de4a756 | |
|
592e4e3c00 | |
|
044c46a89f | |
|
84619f5c3c | |
|
9326036bf5 | |
|
1b4fca7679 | |
|
cb4e70cf8d | |
|
7af3ed34c2 | |
|
df81388b27 | |
|
6b275adbf7 | |
|
b661f0ade2 | |
|
109f4286cf | |
|
3c0c0e6443 | |
|
32766084e4 | |
|
8d4dcbbccb | |
|
8c324a0bbd | |
|
f32ff5b3cb | |
|
e33bd6fdd1 | |
|
d4a63f5b89 | |
|
a9e6121b1c | |
|
a61e9e59d6 | |
|
4ec7678c61 | |
|
42d887f28b | |
|
0e5b59666a | |
|
352d2438b9 | |
|
726249eca2 | |
|
d3dbb19355 | |
|
17b83c52e4 | |
|
a2fbbfe71b | |
|
be3fbeb943 | |
|
edd74b24a1 | |
|
740a5e83fa | |
|
404159f5fa | |
|
a155d2a49d | |
|
39442a4e4e | |
|
74e1cbebab | |
|
a71299064b | |
|
da1e1476cb | |
|
3c302f75ae | |
|
1dedc83b8f | |
|
aca1890ec1 | |
|
bfa3ee96e3 | |
|
f1b5324b90 | |
|
632a26080f | |
|
cab2acc70f | |
|
edb801534f | |
|
89ffab03a5 | |
|
8585d8de96 | |
|
e5e71998af | |
|
85fabe7c5c | |
|
bb548d0473 | |
|
814075f391 | |
|
e045612245 | |
|
5b6926d686 | |
|
9f97d67703 | |
|
bcb0d3fe49 | |
|
fb770f8238 | |
|
85d8bb6eea | |
|
e2741fa4e9 | |
|
6e59f20c0d | |
|
840f800f1a | |
|
e65c014fc9 | |
|
135b468c5a | |
|
ecdde720b4 | |
|
9f0ad86b78 | |
|
7c847e2503 | |
|
898b77d0bb | |
|
7caada5a3b | |
|
e099480d59 | |
|
4b9af26649 | |
|
a5087d7793 | |
|
bd22f81c8f | |
|
28efcd1170 | |
|
fef12e7dc3 | |
|
5ed61202ae | |
|
7b3de08425 | |
|
14ffa908e6 | |
|
c41592ba5f | |
|
c719de3088 | |
|
0e75f9941b | |
|
89c0872acd | |
|
f6ed021e60 | |
|
f7fedd9f85 | |
|
5911e38e09 | |
|
4c4d0118a6 | |
|
d25ac14e7d | |
|
d3b6816104 | |
|
a67cee3a98 | |
|
c3b0c9b4f5 | |
|
2be68b5e9e | |
|
b139300e7e | |
|
c86e139511 | |
|
9b9a3b48b1 | |
|
0feb1d9baf | |
|
648a7937c5 | |
|
24e57f1a77 | |
|
12f05f76ab | |
|
14d4b9bcc6 | |
|
92533fad9b | |
|
1f53b4e175 | |
|
74a35d55d5 | |
|
cbcc423cdf | |
|
858080fbab | |
|
db8608ed63 | |
|
331c520966 | |
|
5f48c62606 | |
|
b7c378d6bd | |
|
002647e9f8 | |
|
5105b61304 | |
|
43b2ef12b3 | |
|
056e9e6bfa | |
|
1837f62a5f | |
|
21ba5d23bb | |
|
7df1a82a31 | |
|
a4a54a4597 | |
|
65318c364a | |
|
130d340262 | |
|
855d1c6784 | |
|
82022eeb0d | |
|
4526456fa4 | |
|
737a765d89 | |
|
351d2776c6 | |
|
375474cd3c | |
|
fc1dee9921 | |
|
7f962d56e4 | |
|
f8dcfd6618 | |
|
7d383ba732 | |
|
f7fbc7da15 | |
|
8fd5aae061 | |
|
f728d3c50f | |
|
b5dec80529 | |
|
ab3b9743a7 | |
|
5dca985ff4 | |
|
e8837d5396 | |
|
f19cdfc9fe | |
|
4e56e1ee06 | |
|
307c9344b8 | |
|
413628c0f4 | |
|
4ae48e988c | |
|
2f7efa2451 | |
|
ce5c2b684b | |
|
89920137cd | |
|
4ed68e1bca | |
|
11a6452626 | |
|
c1ca192909 | |
|
34763e0df3 | |
|
1ee798c140 | |
|
838caeaac1 | |
|
975128c5e6 | |
|
882382d845 | |
|
f24f3ba986 | |
|
0e90845e22 | |
|
9e58631a03 | |
|
7a796b8f87 | |
|
a23f5db224 | |
|
a392378178 | |
|
81667ad920 | |
|
c740480fc1 | |
|
862e41010c | |
|
486eb6f08c | |
|
af4d8a66cf | |
|
78aedbf433 | |
|
95683d1a6c | |
|
0a3163c6c4 | |
|
37814a1755 | |
|
34b86428f4 | |
|
4f6e609334 | |
|
204612911e | |
|
6bfed73816 | |
|
c6ffad3f5c | |
|
b247874fc3 | |
|
945f91b9b9 | |
|
68bbf7ae46 | |
|
5ca0755b51 | |
|
b5eddf779a | |
|
e18a70d446 | |
|
f97607b898 | |
|
9f0f17fe64 | |
|
4c93d8f201 | |
|
4b2f4d2bcf | |
|
b3106d772c | |
|
437071b948 | |
|
02647d3471 | |
|
c5fc9d6b6b | |
|
75b9b6dfff | |
|
d099934742 | |
|
f36bfe3c33 | |
|
0291ba6dc2 | |
|
d2db48976a | |
|
f7b49d3ed7 | |
|
0ab3ce1249 | |
|
2b00d33aec | |
|
c029f419b8 | |
|
9acc22213e | |
|
1333b5d3bd | |
|
b747d7c5f8 | |
|
afd76b48e3 | |
|
181cea6eab | |
|
cf1284f8dd | |
|
66eef0ef3a | |
|
2fab6dffcf | |
|
b5ce165710 | |
|
c206b2e823 | |
|
1b73601ae8 | |
|
ba1076d8b3 | |
|
c31ea0312f | |
|
48391ba5eb | |
|
840cb80149 | |
|
07dc7ee5ab | |
|
c33fc8d30d | |
|
3c33e07c4c | |
|
e2b56b3a38 | |
|
41453c009a | |
|
285adcc5ce | |
|
6e7fab43fc | |
|
106ec21d14 | |
|
a409ccf19e | |
|
35864782d2 | |
|
7797738693 | |
|
083ff1c044 | |
|
1e36bfe104 | |
|
e798dc2763 | |
|
83198c2c50 | |
|
087b655c75 | |
|
7bcf74e94f | |
|
c29de71342 | |
|
f87e2b2119 | |
|
ab6dc32628 | |
|
edff5a049b | |
|
5c61ef97ae | |
|
e209ca88af | |
|
a9de4f065a | |
|
fd9e1fb0e1 | |
|
ee42bbe4ab | |
|
57bf675175 | |
|
6c59e0f73d | |
|
6241f9ab99 | |
|
dc5c261377 | |
|
6990a05d54 | |
|
bf646ea5b3 | |
|
6f81fdf1db | |
|
a2f7dbf150 | |
|
acba37e5db | |
|
eb8c41ec07 | |
|
a5830c56d3 | |
|
1962448488 | |
|
92707c0b2d | |
|
05dd0b2b35 | |
|
c26b7f39f8 | |
|
8fb4b2442e | |
|
dc8af7208c | |
|
1544508911 | |
|
54d05a4e18 | |
|
44accac0f5 | |
|
cf221cc875 | |
|
c74c0d3a7f | |
|
a9284e66a9 | |
|
a3bed97cf3 | |
|
ed4976c764 | |
|
b09b5a43a5 | |
|
415d89281b | |
|
1ebd9c5791 | |
|
6a6c303c3c | |
|
95a9d909e9 | |
|
38a7ed3d85 | |
|
404e6fa1be | |
|
87a988cffb | |
|
51595dcf94 | |
|
b8f7d52a4a | |
|
8a501b0fac | |
|
4bbffeac6c | |
|
52b38ca424 | |
|
0929293466 | |
|
67a4d91853 | |
|
5717c498e9 | |
|
408bc6703a | |
|
590942c47f | |
|
9ffb85bbec | |
|
51b7cfe385 | |
|
07d1e00890 | |
|
f50e178a9f | |
|
f654a9112b | |
|
3184a9e141 | |
|
e2b269ecc5 | |
|
6353e56395 | |
|
656132404a | |
|
46420cf544 | |
|
303198d014 | |
|
e7cc868148 | |
|
004e3cb722 | |
|
ac1463f956 | |
|
e72b029e2a | |
|
d4e7ca1687 | |
|
8329c5daa7 | |
|
3ec00f620a | |
|
15aff29f35 | |
|
d76ac5e41f | |
|
1bbde5e52a | |
|
f96052c82a | |
|
93bea66f75 | |
|
cd58006fe6 | |
|
097ec799df | |
|
f7ef284eb4 | |
|
e734bda58c | |
|
136026fb25 | |
|
1a43b8334a | |
|
484fff1ace | |
|
9d083cac4a | |
|
2adb7b54b7 | |
|
0a813b5898 | |
|
ca8e08c131 | |
|
3bf8d0aa5e | |
|
d3decad621 | |
|
35d4bf5bd4 | |
|
2566b66989 | |
|
f238e4b70a | |
|
f43aa3c488 | |
|
a41f2db807 | |
|
36f3f1ec85 | |
|
1752a4b8e5 | |
|
b1dfcec1fe | |
|
46796a5c6a | |
|
581db3dfa7 | |
|
2c1f261278 | |
|
bb468cc94d | |
|
28b7455875 | |
|
e0afeb10e3 | |
|
1454493cee | |
|
f0828adbb3 | |
|
012967472b | |
|
dfba5a4e3a | |
|
f6cbfc0d03 | |
|
961bfee4b1 | |
|
f37e428318 | |
|
89909913cc | |
|
9feda7c901 | |
|
fdf3f01101 | |
|
e8910bb335 | |
|
cb23521296 | |
|
c12dcedf28 | |
|
6be346c1f1 | |
|
9388656beb | |
|
2ca25e3fac | |
|
ec73f449b3 | |
|
0b4ff03cda | |
|
ad53dbbf0a | |
|
d583a38000 | |
|
f4b48264a4 | |
|
c106be4ce3 | |
|
02b2656991 | |
|
1c7f4ef084 | |
|
3acde138ca | |
|
08b5acc756 | |
|
8bb674980e | |
|
fbbf8a0782 | |
|
c5d499e514 | |
|
310548cda6 | |
|
b7d60e3a8c | |
|
afd4130c14 | |
|
9f1c28b404 | |
|
3a72ffb6d8 | |
|
6bf5d94027 | |
|
4a3d527660 | |
|
36e077c09a | |
|
42aafee8d7 | |
|
3ebf1ddaeb | |
|
624f997379 | |
|
5d2970ff94 | |
|
2aadfe8adc | |
|
55d8b5740c | |
|
9dfcf7c562 | |
|
518a1491c7 | |
|
ea72eb9159 | |
|
dca7be2281 | |
|
d3e61647c0 | |
|
045d838faf | |
|
17a1e1d4bf | |
|
ce229281f0 | |
|
bda8848f6f | |
|
9cc41e665b | |
|
12fe0eb94c | |
|
8c465a0c8e | |
|
3c60a0e014 | |
|
e7b6c14305 | |
|
0d3c8ce9f8 | |
|
858f37ff9b | |
|
22d5528225 | |
|
41d0ae8564 | |
|
7660131ef3 | |
|
28b40b159e | |
|
4b71b28738 | |
|
7b3a490010 | |
|
937e12391f | |
|
9a14481b90 | |
|
5b0b9669b6 | |
|
367102ea5a | |
|
59bec1f2ff | |
|
940aba6697 | |
|
f2b3a9bb9b | |
|
3b0d317f67 | |
|
7369010b35 | |
|
32124e9523 | |
|
b4bc8de52a | |
|
43bc313c8e | |
|
d893331bef | |
|
60ab6cdfeb | |
|
8e7131e0bd | |
|
8a3895e82f | |
|
1a61f16b44 | |
|
da3134049a | |
|
7c5ac23b81 | |
|
711f4df360 | |
|
7009337cb5 | |
|
37e2176163 | |
|
f1bd0923b8 | |
|
a88bf7aab8 | |
|
cabedb3967 | |
|
4a312d02f2 | |
|
d2e1501cff | |
|
4d8751d477 | |
|
db3bc60f5a | |
|
7088056203 | |
|
85f2b59c44 | |
|
221dbe5ed4 | |
|
1bb3d5a619 | |
|
555ebd3959 | |
|
0224ef258e | |
|
d84e815d44 | |
|
ae5876d09a | |
|
34c6fa2dc7 | |
|
21cb1c2932 | |
|
10a9f47426 | |
|
3f658bd5ac | |
|
78018b09cd | |
|
2894c76efe | |
|
943c6e8f43 | |
|
af4cbaf11d | |
|
2696ec2b70 | |
|
3efd733edb | |
|
363ebb24d0 | |
|
834f27f4b7 | |
|
ffdc059bfe | |
|
1ecda4918e | |
|
f1df803a70 | |
|
26d4a37fb4 | |
|
865fb2c8f5 | |
|
c45a1382af | |
|
660f15d67d | |
|
5b93e2dc01 | |
|
6697e04e8d | |
|
199de5f3a4 | |
|
8019298d9f | |
|
04af85275a | |
|
282608cc76 | |
|
2db35c88b9 | |
|
f2347ac6c9 | |
|
14d66a7ab5 | |
|
c4a7fdae49 | |
|
7023ef1c65 | |
|
82a6bb6ee5 | |
|
7d5bdf6757 | |
|
581e4986f3 | |
|
f35b8ab0b5 | |
|
e4944078a3 | |
|
38eb60e591 | |
|
adc03609b3 | |
|
26d6fa081f | |
|
1c264b1925 | |
|
2a15e200fd | |
|
ef2d015404 | |
|
89226b8388 | |
|
6c9d18aaae | |
|
c679ae2cc0 | |
|
380174f817 | |
|
33951ec724 | |
|
20dae58180 | |
|
4c1331b42c | |
|
921da254ef | |
|
6efb0c49fb | |
|
b89eecf5ca | |
|
0bfbef4e58 | |
|
bfc76300bb | |
|
662d1e61b7 | |
|
be8372ae8e | |
|
a726187e31 | |
|
8824eb48ce |
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -n "$(gofmt -l .)" ]; then
|
||||||
|
echo "Go code is not formatted:"
|
||||||
|
gofmt -d .
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
go generate ./...
|
||||||
|
if [ -n "$(git status -s -uno)" ]; then
|
||||||
|
echo "Go generate output does not match commit."
|
||||||
|
echo "Did you forget to run go generate ./... ?"
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# If GOMOD is defined we are running with Go Modules enabled, either
|
||||||
|
# automatically or via the GO111MODULE=on environment variable. Codegen only
|
||||||
|
# works with modules, so skip generation if modules is not in use.
|
||||||
|
if [[ -z "$(go env GOMOD)" ]]; then
|
||||||
|
echo "Skipping go generate because modules not enabled and required"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
go generate ./...
|
||||||
|
if [ -n "$(git diff)" ]; then
|
||||||
|
echo "Go generate had not been run"
|
||||||
|
git diff
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
go vet ./...
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Verify that the code snippets in README.md are formatted.
|
||||||
|
# The tool https://github.com/hougesen/mdsf is used.
|
||||||
|
|
||||||
|
if [ -n "$(mdsf verify --config .mdsf.json --log-level error README.md 2>&1)" ]; then
|
||||||
|
echo "Go code in the README.md is not formatted."
|
||||||
|
echo "Did you forget to run 'mdsf format --config .mdsf.json README.md'?"
|
||||||
|
mdsf format --config .mdsf.json README.md
|
||||||
|
git diff
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Format to report a bug
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- If this is a question, consider using the discussion section of this repo -->
|
||||||
|
<!-- Here: https://github.com/stretchr/testify/discussions/new?category=q-a -->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!-- A detailed description of the bug -->
|
||||||
|
|
||||||
|
## Step To Reproduce
|
||||||
|
<!-- Steps or code snippet to reproduce the behavior -->
|
||||||
|
|
||||||
|
## Expected behavior
|
||||||
|
<!-- A clear and concise description of what you expected to happen -->
|
||||||
|
|
||||||
|
## Actual behavior
|
||||||
|
<!-- What testify does -->
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Propose a new feature
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- If this is a question, consider using the discussion section of this repo -->
|
||||||
|
<!-- Here: https://github.com/stretchr/testify/discussions/new?category=q-a -->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!-- A clear and concise description of what feature you are proposing -->
|
||||||
|
|
||||||
|
## Proposed solution
|
||||||
|
<!-- Optionally a suggested implementation -->
|
||||||
|
|
||||||
|
## Use case
|
||||||
|
<!-- What is the motivation? What workarounds have you used? -->
|
|
@ -0,0 +1,10 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: gomod
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
|
@ -0,0 +1,15 @@
|
||||||
|
## Summary
|
||||||
|
<!-- High-level, one sentence summary of what this PR accomplishes -->
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
<!-- * Description of change 1 -->
|
||||||
|
<!-- * Description of change 2 -->
|
||||||
|
<!-- ... -->
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
<!-- Why were the changes necessary. -->
|
||||||
|
|
||||||
|
<!-- ## Example usage (if applicable) -->
|
||||||
|
|
||||||
|
## Related issues
|
||||||
|
<!-- Put `Closes #XXXX` for each issue number this PR fixes/closes -->
|
|
@ -0,0 +1,41 @@
|
||||||
|
name: All builds
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go_version:
|
||||||
|
- stable
|
||||||
|
- oldstable
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go_version }}
|
||||||
|
- run: npm install -g mdsf-cli
|
||||||
|
- run: ./.ci.gogenerate.sh
|
||||||
|
- run: ./.ci.gofmt.sh
|
||||||
|
- run: ./.ci.readme.fmt.sh
|
||||||
|
- run: ./.ci.govet.sh
|
||||||
|
- run: go test -v -race ./...
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go_version:
|
||||||
|
- "1.17"
|
||||||
|
- "1.18"
|
||||||
|
- "1.19"
|
||||||
|
- "1.20"
|
||||||
|
- "1.21"
|
||||||
|
- "1.22"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go_version }}
|
||||||
|
- run: go test -v -race ./...
|
|
@ -0,0 +1,21 @@
|
||||||
|
name: Create release from new tag
|
||||||
|
|
||||||
|
# this flow will be run only when new tags are pushed that match our pattern
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create GitHub release from tag
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
generate_release_notes: true
|
|
@ -22,3 +22,9 @@ _testmain.go
|
||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Output of "go test -c"
|
||||||
|
/assert/assert.test
|
||||||
|
/require/require.test
|
||||||
|
/suite/suite.test
|
||||||
|
/mock/mock.test
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/hougesen/mdsf/main/schemas/v0.8.2/mdsf.schema.json",
|
||||||
|
"format_finished_document": false,
|
||||||
|
"languages": {
|
||||||
|
"go": [
|
||||||
|
[
|
||||||
|
"gofmt",
|
||||||
|
"goimports"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ -n "$(gofmt -l .)" ]; then
|
|
||||||
echo "Go code is not formatted:"
|
|
||||||
gofmt -d .
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [[ "$TRAVIS_GO_VERSION" =~ ^1\.[45](\..*)?$ ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
go get github.com/ernesto-jimenez/gogen/imports
|
|
||||||
go generate ./...
|
|
||||||
if [ -n "$(git diff)" ]; then
|
|
||||||
echo "Go generate had not been run"
|
|
||||||
git diff
|
|
||||||
exit 1
|
|
||||||
fi
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cd "$(dirname $0)"
|
|
||||||
DIRS=". assert require mock _codegen"
|
|
||||||
set -e
|
|
||||||
for subdir in $DIRS; do
|
|
||||||
pushd $subdir
|
|
||||||
go vet
|
|
||||||
popd
|
|
||||||
done
|
|
15
.travis.yml
15
.travis.yml
|
@ -1,15 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.7
|
|
||||||
- 1.8
|
|
||||||
- 1.9
|
|
||||||
- tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./.travis.gogenerate.sh
|
|
||||||
- ./.travis.gofmt.sh
|
|
||||||
- ./.travis.govet.sh
|
|
||||||
- go test -v -race $(go list ./... | grep -v vendor)
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Contributing to Testify
|
||||||
|
|
||||||
|
So you'd like to contribute to Testify? First of all, thank you! Testify is widely used, so each
|
||||||
|
contribution has a significant impact within the Golang community! Below you'll find everything you
|
||||||
|
need to know to get up to speed on the project.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
The Testify maintainers generally attempt to follow widely accepted practices within the Golang
|
||||||
|
community. That being said, the first priority is always to make sure that the package is useful to
|
||||||
|
the community. A few general guidelines are listed here:
|
||||||
|
|
||||||
|
*Keep it simple (whenever practical)* - Try not to expand the API unless the new surface area
|
||||||
|
provides meaningful benefits. For example, don't add functions because they might be useful to
|
||||||
|
someone, someday. Add what is useful to specific users, today.
|
||||||
|
|
||||||
|
*Ease of use is paramount* - This means good documentation and package organization. It also means
|
||||||
|
that we should try hard to use meaningful, descriptive function names, avoid breaking the API
|
||||||
|
unnecessarily, and try not to surprise the user.
|
||||||
|
|
||||||
|
*Quality isn't an afterthought* - Testify is a testing library, so it seems reasonable that we
|
||||||
|
should have a decent test suite. This is doubly important because a bug in Testify doesn't just mean
|
||||||
|
a bug in our users' code, it means a bug in our users' tests, which means a potentially unnoticed
|
||||||
|
and hard-to-find bug in our users' code.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
We welcome pull requests! Please include the following in the description:
|
||||||
|
|
||||||
|
* Motivation, why your change is important or helpful
|
||||||
|
* Example usage (if applicable)
|
||||||
|
* Whether you intend to add / change behavior or fix a bug
|
||||||
|
|
||||||
|
Please be aware that the maintainers may ask for changes. This isn't a commentary on the quality of
|
||||||
|
your idea or your code. Testify is the result of many contributions from many individuals, so we
|
||||||
|
need to enforce certain practices and patterns to keep the package easy for others to understand.
|
||||||
|
Essentially, we recognize that there are often many good ways to do a given thing, but we have to
|
||||||
|
pick one and stick with it.
|
||||||
|
|
||||||
|
See `MAINTAINERS.md` for a list of users who can approve / merge your changes.
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
If you find a bug or think of a useful feature you'd like to see added to Testify, the best thing
|
||||||
|
you can do is make the necessary changes and open a pull request (see above). If that isn't an
|
||||||
|
option, or if you'd like to discuss your change before you write the code, open an issue!
|
||||||
|
|
||||||
|
Please provide enough context in the issue description that other members of the community can
|
||||||
|
easily understand what it is that you'd like to see.
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Emeritus
|
||||||
|
|
||||||
|
We would like to acknowledge previous testify maintainers and their huge contributions to our collective success:
|
||||||
|
|
||||||
|
* @matryer
|
||||||
|
* @glesica
|
||||||
|
* @ernesto-jimenez
|
||||||
|
* @mvdkleijn
|
||||||
|
* @georgelesica-wf
|
||||||
|
* @bencampbell-wf
|
||||||
|
|
||||||
|
We thank these members for their service to this community.
|
|
@ -1,27 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/davecgh/go-spew"
|
|
||||||
packages = ["spew"]
|
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pmezard/go-difflib"
|
|
||||||
packages = ["difflib"]
|
|
||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/stretchr/objx"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "facf9a85c22f48d2f52f2380e4efce1768749a89"
|
|
||||||
version = "v0.1"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "448ddae4702c6aded2555faafd390c537789bb1c483f70b0431e6634f73f2090"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
16
Gopkg.toml
16
Gopkg.toml
|
@ -1,16 +0,0 @@
|
||||||
[prune]
|
|
||||||
unused-packages = true
|
|
||||||
non-go = true
|
|
||||||
go-tests = true
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/davecgh/go-spew"
|
|
||||||
version = "~1.1.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/pmezard/go-difflib"
|
|
||||||
version = "~1.0.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/stretchr/objx"
|
|
||||||
version = "~0.1.0"
|
|
35
LICENSE
35
LICENSE
|
@ -1,22 +1,21 @@
|
||||||
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
|
MIT License
|
||||||
|
|
||||||
Please consider promoting this project if you find it useful.
|
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
obtaining a copy of this software and associated documentation
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
files (the "Software"), to deal in the Software without restriction,
|
in the Software without restriction, including without limitation the rights
|
||||||
including without limitation the rights to use, copy, modify, merge,
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
and to permit persons to whom the Software is furnished to do so,
|
furnished to do so, subject to the following conditions:
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
The above copyright notice and this permission notice shall be included in all
|
||||||
in all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Testify Maintainers
|
||||||
|
|
||||||
|
The individuals listed below are active in the project and have the ability to approve and merge
|
||||||
|
pull requests.
|
||||||
|
|
||||||
|
* @boyan-soubachov
|
||||||
|
* @dolmen
|
||||||
|
* @MovieStoreGuy
|
||||||
|
* @brackendawson
|
||||||
|
|
||||||
|
## Approvers
|
||||||
|
|
||||||
|
The individuals listed below are active in the project and have the ability to approve pull
|
||||||
|
requests.
|
||||||
|
|
||||||
|
* @arjunmahishi
|
||||||
|
* @ccoVeille
|
125
README.md
125
README.md
|
@ -1,7 +1,11 @@
|
||||||
Testify - Thou Shalt Write Tests
|
Testify - Thou Shalt Write Tests
|
||||||
================================
|
================================
|
||||||
|
|
||||||
[](https://travis-ci.org/stretchr/testify) [](https://goreportcard.com/report/github.com/stretchr/testify) [](https://godoc.org/github.com/stretchr/testify)
|
> [!NOTE]
|
||||||
|
> Testify is being maintained at v1, no breaking changes will be accepted in this repo.
|
||||||
|
> [See discussion about v2](https://github.com/stretchr/testify/discussions/1560).
|
||||||
|
|
||||||
|
[](https://github.com/stretchr/testify/actions/workflows/main.yml) [](https://goreportcard.com/report/github.com/stretchr/testify) [](https://pkg.go.dev/github.com/stretchr/testify)
|
||||||
|
|
||||||
Go code (golang) set of packages that provide many tools for testifying that your code will behave as you intend.
|
Go code (golang) set of packages that provide many tools for testifying that your code will behave as you intend.
|
||||||
|
|
||||||
|
@ -14,14 +18,12 @@ Features include:
|
||||||
Get started:
|
Get started:
|
||||||
|
|
||||||
* Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date)
|
* Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date)
|
||||||
* For an introduction to writing test code in Go, see http://golang.org/doc/code.html#Testing
|
* For an introduction to writing test code in Go, see https://go.dev/doc/code#Testing
|
||||||
* Check out the API Documentation http://godoc.org/github.com/stretchr/testify
|
* Check out the API Documentation https://pkg.go.dev/github.com/stretchr/testify
|
||||||
* To make your testing life easier, check out our other project, [gorc](http://github.com/stretchr/gorc)
|
* Use [testifylint](https://github.com/Antonboom/testifylint) (via [golangci-lint](https://golangci-lint.run/)) to avoid common mistakes
|
||||||
* A little about [Test-Driven Development (TDD)](http://en.wikipedia.org/wiki/Test-driven_development)
|
* A little about [Test-Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development)
|
||||||
|
|
||||||
|
[`assert`](https://pkg.go.dev/github.com/stretchr/testify/assert "API documentation") package
|
||||||
|
|
||||||
[`assert`](http://godoc.org/github.com/stretchr/testify/assert "API documentation") package
|
|
||||||
-------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
The `assert` package provides some helpful methods that allow you to write better test code in Go.
|
The `assert` package provides some helpful methods that allow you to write better test code in Go.
|
||||||
|
@ -37,11 +39,11 @@ package yours
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSomething(t *testing.T) {
|
func TestSomething(t *testing.T) {
|
||||||
|
|
||||||
// assert equality
|
// assert equality
|
||||||
assert.Equal(t, 123, 123, "they should be equal")
|
assert.Equal(t, 123, 123, "they should be equal")
|
||||||
|
|
||||||
|
@ -53,13 +55,10 @@ func TestSomething(t *testing.T) {
|
||||||
|
|
||||||
// assert for not nil (good when you expect something)
|
// assert for not nil (good when you expect something)
|
||||||
if assert.NotNil(t, object) {
|
if assert.NotNil(t, object) {
|
||||||
|
|
||||||
// now we know that object isn't nil, we are safe to make
|
// now we know that object isn't nil, we are safe to make
|
||||||
// further assertions without causing any errors
|
// further assertions without causing any errors
|
||||||
assert.Equal(t, "Something", object.Value)
|
assert.Equal(t, "Something", object.Value)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -73,6 +72,7 @@ package yours
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -90,7 +90,6 @@ func TestSomething(t *testing.T) {
|
||||||
|
|
||||||
// assert for not nil (good when you expect something)
|
// assert for not nil (good when you expect something)
|
||||||
if assert.NotNil(object) {
|
if assert.NotNil(object) {
|
||||||
|
|
||||||
// now we know that object isn't nil, we are safe to make
|
// now we know that object isn't nil, we are safe to make
|
||||||
// further assertions without causing any errors
|
// further assertions without causing any errors
|
||||||
assert.Equal("Something", object.Value)
|
assert.Equal("Something", object.Value)
|
||||||
|
@ -98,25 +97,28 @@ func TestSomething(t *testing.T) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
[`require`](http://godoc.org/github.com/stretchr/testify/require "API documentation") package
|
[`require`](https://pkg.go.dev/github.com/stretchr/testify/require "API documentation") package
|
||||||
---------------------------------------------------------------------------------------------
|
---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
The `require` package provides same global functions as the `assert` package, but instead of returning a boolean result they terminate current test.
|
The `require` package provides same global functions as the `assert` package, but instead of returning a boolean result they terminate current test.
|
||||||
|
These functions must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test.
|
||||||
|
Otherwise race conditions may occur.
|
||||||
|
|
||||||
See [t.FailNow](http://golang.org/pkg/testing/#T.FailNow) for details.
|
See [t.FailNow](https://pkg.go.dev/testing#T.FailNow) for details.
|
||||||
|
|
||||||
[`mock`](http://godoc.org/github.com/stretchr/testify/mock "API documentation") package
|
[`mock`](https://pkg.go.dev/github.com/stretchr/testify/mock "API documentation") package
|
||||||
----------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code.
|
The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code.
|
||||||
|
|
||||||
An example test function that tests a piece of code that relies on an external object `testObj`, can setup expectations (testify) and assert that they indeed happened:
|
An example test function that tests a piece of code that relies on an external object `testObj`, can set up expectations (testify) and assert that they indeed happened:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package yours
|
package yours
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -126,7 +128,7 @@ import (
|
||||||
|
|
||||||
// MyMockedObject is a mocked object that implements an interface
|
// MyMockedObject is a mocked object that implements an interface
|
||||||
// that describes an object that the code I am testing relies on.
|
// that describes an object that the code I am testing relies on.
|
||||||
type MyMockedObject struct{
|
type MyMockedObject struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,10 +140,8 @@ type MyMockedObject struct{
|
||||||
//
|
//
|
||||||
// NOTE: This method is not being tested here, code that uses this object is.
|
// NOTE: This method is not being tested here, code that uses this object is.
|
||||||
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
|
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
|
||||||
|
|
||||||
args := m.Called(number)
|
args := m.Called(number)
|
||||||
return args.Bool(0), args.Error(1)
|
return args.Bool(0), args.Error(1)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -151,30 +151,74 @@ func (m *MyMockedObject) DoSomething(number int) (bool, error) {
|
||||||
// TestSomething is an example of how to use our test object to
|
// TestSomething is an example of how to use our test object to
|
||||||
// make assertions about some target code we are testing.
|
// make assertions about some target code we are testing.
|
||||||
func TestSomething(t *testing.T) {
|
func TestSomething(t *testing.T) {
|
||||||
|
|
||||||
// create an instance of our test object
|
// create an instance of our test object
|
||||||
testObj := new(MyMockedObject)
|
testObj := new(MyMockedObject)
|
||||||
|
|
||||||
// setup expectations
|
// set up expectations
|
||||||
testObj.On("DoSomething", 123).Return(true, nil)
|
testObj.On("DoSomething", 123).Return(true, nil)
|
||||||
|
|
||||||
// call the code we are testing
|
// call the code we are testing
|
||||||
targetFuncThatDoesSomethingWithObj(testObj)
|
targetFuncThatDoesSomethingWithObj(testObj)
|
||||||
|
|
||||||
|
// assert that the expectations were met
|
||||||
|
testObj.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSomethingWithPlaceholder is a second example of how to use our test object to
|
||||||
|
// make assertions about some target code we are testing.
|
||||||
|
// This time using a placeholder. Placeholders might be used when the
|
||||||
|
// data being passed in is normally dynamically generated and cannot be
|
||||||
|
// predicted beforehand (eg. containing hashes that are time sensitive)
|
||||||
|
func TestSomethingWithPlaceholder(t *testing.T) {
|
||||||
|
// create an instance of our test object
|
||||||
|
testObj := new(MyMockedObject)
|
||||||
|
|
||||||
|
// set up expectations with a placeholder in the argument list
|
||||||
|
testObj.On("DoSomething", mock.Anything).Return(true, nil)
|
||||||
|
|
||||||
|
// call the code we are testing
|
||||||
|
targetFuncThatDoesSomethingWithObj(testObj)
|
||||||
|
|
||||||
// assert that the expectations were met
|
// assert that the expectations were met
|
||||||
testObj.AssertExpectations(t)
|
testObj.AssertExpectations(t)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSomethingElse2 is a third example that shows how you can use
|
||||||
|
// the Unset method to cleanup handlers and then add new ones.
|
||||||
|
func TestSomethingElse2(t *testing.T) {
|
||||||
|
// create an instance of our test object
|
||||||
|
testObj := new(MyMockedObject)
|
||||||
|
|
||||||
|
// set up expectations with a placeholder in the argument list
|
||||||
|
mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
|
||||||
|
|
||||||
|
// call the code we are testing
|
||||||
|
targetFuncThatDoesSomethingWithObj(testObj)
|
||||||
|
|
||||||
|
// assert that the expectations were met
|
||||||
|
testObj.AssertExpectations(t)
|
||||||
|
|
||||||
|
// remove the handler now so we can add another one that takes precedence
|
||||||
|
mockCall.Unset()
|
||||||
|
|
||||||
|
// return false now instead of true
|
||||||
|
testObj.On("DoSomething", mock.Anything).Return(false, nil)
|
||||||
|
|
||||||
|
testObj.AssertExpectations(t)
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information on how to write mock code, check out the [API documentation for the `mock` package](http://godoc.org/github.com/stretchr/testify/mock).
|
For more information on how to write mock code, check out the [API documentation for the `mock` package](https://pkg.go.dev/github.com/stretchr/testify/mock).
|
||||||
|
|
||||||
You can use the [mockery tool](http://github.com/vektra/mockery) to autogenerate the mock code against an interface as well, making using mocks much quicker.
|
You can use the [mockery tool](https://vektra.github.io/mockery/latest/) to autogenerate the mock code against an interface as well, making using mocks much quicker.
|
||||||
|
|
||||||
[`suite`](http://godoc.org/github.com/stretchr/testify/suite "API documentation") package
|
[`suite`](https://pkg.go.dev/github.com/stretchr/testify/suite "API documentation") package
|
||||||
-----------------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------------------
|
||||||
|
> [!WARNING]
|
||||||
|
> The suite package does not support parallel tests. See [#934](https://github.com/stretchr/testify/issues/934).
|
||||||
|
|
||||||
The `suite` package provides functionality that you might be used to from more common object oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
|
The `suite` package provides functionality that you might be used to from more common object-oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
|
||||||
|
|
||||||
An example suite is shown below:
|
An example suite is shown below:
|
||||||
|
|
||||||
|
@ -182,6 +226,7 @@ An example suite is shown below:
|
||||||
// Basic imports
|
// Basic imports
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
@ -215,7 +260,7 @@ func TestExampleTestSuite(t *testing.T) {
|
||||||
|
|
||||||
For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go)
|
For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go)
|
||||||
|
|
||||||
For more information on writing suites, check out the [API documentation for the `suite` package](http://godoc.org/github.com/stretchr/testify/suite).
|
For more information on writing suites, check out the [API documentation for the `suite` package](https://pkg.go.dev/github.com/stretchr/testify/suite).
|
||||||
|
|
||||||
`Suite` object has assertion methods:
|
`Suite` object has assertion methods:
|
||||||
|
|
||||||
|
@ -223,6 +268,7 @@ For more information on writing suites, check out the [API documentation for the
|
||||||
// Basic imports
|
// Basic imports
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -264,8 +310,10 @@ To install Testify, use `go get`:
|
||||||
This will then make the following packages available to you:
|
This will then make the following packages available to you:
|
||||||
|
|
||||||
github.com/stretchr/testify/assert
|
github.com/stretchr/testify/assert
|
||||||
|
github.com/stretchr/testify/require
|
||||||
github.com/stretchr/testify/mock
|
github.com/stretchr/testify/mock
|
||||||
github.com/stretchr/testify/http
|
github.com/stretchr/testify/suite
|
||||||
|
github.com/stretchr/testify/http (deprecated)
|
||||||
|
|
||||||
Import the `testify/assert` package into your code using this template:
|
Import the `testify/assert` package into your code using this template:
|
||||||
|
|
||||||
|
@ -274,13 +322,12 @@ package yours
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSomething(t *testing.T) {
|
func TestSomething(t *testing.T) {
|
||||||
|
|
||||||
assert.True(t, true, "True is true!")
|
assert.True(t, true, "True is true!")
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -293,9 +340,27 @@ To update Testify to the latest version, use `go get -u github.com/stretchr/test
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
|
Supported go versions
|
||||||
|
==================
|
||||||
|
|
||||||
|
We currently support the most recent major Go versions from 1.19 onward.
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
============
|
============
|
||||||
|
|
||||||
Please feel free to submit issues, fork the repository and send pull requests!
|
Please feel free to submit issues, fork the repository and send pull requests!
|
||||||
|
|
||||||
When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it.
|
When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it.
|
||||||
|
|
||||||
|
Code generation is used. [Look for `Code generated with`](https://github.com/search?q=repo%3Astretchr%2Ftestify%20%22Code%20generated%20with%22&type=code) at the top of some files. Run `go generate ./...` to update generated files.
|
||||||
|
|
||||||
|
We also chat on the [Gophers Slack](https://gophers.slack.com) group in the `#testify` and `#testify-dev` channels.
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
This project is licensed under the terms of the MIT license.
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
_codegen
|
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/stretchr/testify/_codegen
|
||||||
|
|
||||||
|
go 1.11
|
||||||
|
|
||||||
|
require github.com/ernesto-jimenez/gogen v0.0.0-20180125220232-d7d4131e6607
|
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/ernesto-jimenez/gogen v0.0.0-20180125220232-d7d4131e6607 h1:cTavhURetDkezJCvxFggiyLeP40Mrk/TtVg2+ycw1Es=
|
||||||
|
github.com/ernesto-jimenez/gogen v0.0.0-20180125220232-d7d4131e6607/go.mod h1:Cg4fM0vhYWOZdgM7RIOSTRNIc8/VT7CXClC3Ni86lu4=
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -101,13 +100,15 @@ func parseTemplates() (*template.Template, *template.Template, error) {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if *tmplFile != "" {
|
if *tmplFile != "" {
|
||||||
f, err := ioutil.ReadFile(*tmplFile)
|
f, err := os.ReadFile(*tmplFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
funcTemplate = string(f)
|
funcTemplate = string(f)
|
||||||
}
|
}
|
||||||
tmpl, err := template.New("function").Parse(funcTemplate)
|
tmpl, err := template.New("function").Funcs(template.FuncMap{
|
||||||
|
"replace": strings.ReplaceAll,
|
||||||
|
}).Parse(funcTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -181,7 +182,7 @@ func parsePackageSource(pkg string) (*types.Scope, *doc.Package, error) {
|
||||||
files := make(map[string]*ast.File)
|
files := make(map[string]*ast.File)
|
||||||
fileList := make([]*ast.File, len(pd.GoFiles))
|
fileList := make([]*ast.File, len(pd.GoFiles))
|
||||||
for i, fname := range pd.GoFiles {
|
for i, fname := range pd.GoFiles {
|
||||||
src, err := ioutil.ReadFile(path.Join(pd.SrcRoot, pd.ImportPath, fname))
|
src, err := os.ReadFile(path.Join(pd.Dir, fname))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -194,7 +195,7 @@ func parsePackageSource(pkg string) (*types.Scope, *doc.Package, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := types.Config{
|
cfg := types.Config{
|
||||||
Importer: importer.Default(),
|
Importer: importer.For("source", nil),
|
||||||
}
|
}
|
||||||
info := types.Info{
|
info := types.Info{
|
||||||
Defs: make(map[*ast.Ident]types.Object),
|
Defs: make(map[*ast.Ident]types.Object),
|
||||||
|
@ -287,7 +288,7 @@ func (f *testFunc) CommentFormat() string {
|
||||||
search := fmt.Sprintf("%s", f.DocInfo.Name)
|
search := fmt.Sprintf("%s", f.DocInfo.Name)
|
||||||
replace := fmt.Sprintf("%sf", f.DocInfo.Name)
|
replace := fmt.Sprintf("%sf", f.DocInfo.Name)
|
||||||
comment := strings.Replace(f.Comment(), search, replace, -1)
|
comment := strings.Replace(f.Comment(), search, replace, -1)
|
||||||
exp := regexp.MustCompile(replace + `\(((\(\)|[^)])+)\)`)
|
exp := regexp.MustCompile(replace + `\(((\(\)|[^\n])+)\)`)
|
||||||
return exp.ReplaceAllString(comment, replace+`($1, "error message %s", "formatted")`)
|
return exp.ReplaceAllString(comment, replace+`($1, "error message %s", "formatted")`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,10 +298,8 @@ func (f *testFunc) CommentWithoutT(receiver string) string {
|
||||||
return strings.Replace(f.Comment(), search, replace, -1)
|
return strings.Replace(f.Comment(), search, replace, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var headerTemplate = `/*
|
// Standard header https://go.dev/s/generatedcode.
|
||||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
var headerTemplate = `// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
|
||||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
|
||||||
*/
|
|
||||||
|
|
||||||
package {{.Name}}
|
package {{.Name}}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,495 @@
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated: CompareType has only ever been for internal use and has accidentally been published since v1.6.0. Do not use it.
|
||||||
|
type CompareType = compareResult
|
||||||
|
|
||||||
|
type compareResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
compareLess compareResult = iota - 1
|
||||||
|
compareEqual
|
||||||
|
compareGreater
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
intType = reflect.TypeOf(int(1))
|
||||||
|
int8Type = reflect.TypeOf(int8(1))
|
||||||
|
int16Type = reflect.TypeOf(int16(1))
|
||||||
|
int32Type = reflect.TypeOf(int32(1))
|
||||||
|
int64Type = reflect.TypeOf(int64(1))
|
||||||
|
|
||||||
|
uintType = reflect.TypeOf(uint(1))
|
||||||
|
uint8Type = reflect.TypeOf(uint8(1))
|
||||||
|
uint16Type = reflect.TypeOf(uint16(1))
|
||||||
|
uint32Type = reflect.TypeOf(uint32(1))
|
||||||
|
uint64Type = reflect.TypeOf(uint64(1))
|
||||||
|
|
||||||
|
uintptrType = reflect.TypeOf(uintptr(1))
|
||||||
|
|
||||||
|
float32Type = reflect.TypeOf(float32(1))
|
||||||
|
float64Type = reflect.TypeOf(float64(1))
|
||||||
|
|
||||||
|
stringType = reflect.TypeOf("")
|
||||||
|
|
||||||
|
timeType = reflect.TypeOf(time.Time{})
|
||||||
|
bytesType = reflect.TypeOf([]byte{})
|
||||||
|
)
|
||||||
|
|
||||||
|
func compare(obj1, obj2 interface{}, kind reflect.Kind) (compareResult, bool) {
|
||||||
|
obj1Value := reflect.ValueOf(obj1)
|
||||||
|
obj2Value := reflect.ValueOf(obj2)
|
||||||
|
|
||||||
|
// throughout this switch we try and avoid calling .Convert() if possible,
|
||||||
|
// as this has a pretty big performance impact
|
||||||
|
switch kind {
|
||||||
|
case reflect.Int:
|
||||||
|
{
|
||||||
|
intobj1, ok := obj1.(int)
|
||||||
|
if !ok {
|
||||||
|
intobj1 = obj1Value.Convert(intType).Interface().(int)
|
||||||
|
}
|
||||||
|
intobj2, ok := obj2.(int)
|
||||||
|
if !ok {
|
||||||
|
intobj2 = obj2Value.Convert(intType).Interface().(int)
|
||||||
|
}
|
||||||
|
if intobj1 > intobj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if intobj1 == intobj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if intobj1 < intobj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int8:
|
||||||
|
{
|
||||||
|
int8obj1, ok := obj1.(int8)
|
||||||
|
if !ok {
|
||||||
|
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
|
||||||
|
}
|
||||||
|
int8obj2, ok := obj2.(int8)
|
||||||
|
if !ok {
|
||||||
|
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
|
||||||
|
}
|
||||||
|
if int8obj1 > int8obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int8obj1 == int8obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int8obj1 < int8obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
{
|
||||||
|
int16obj1, ok := obj1.(int16)
|
||||||
|
if !ok {
|
||||||
|
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
|
||||||
|
}
|
||||||
|
int16obj2, ok := obj2.(int16)
|
||||||
|
if !ok {
|
||||||
|
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
|
||||||
|
}
|
||||||
|
if int16obj1 > int16obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int16obj1 == int16obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int16obj1 < int16obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
{
|
||||||
|
int32obj1, ok := obj1.(int32)
|
||||||
|
if !ok {
|
||||||
|
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
|
||||||
|
}
|
||||||
|
int32obj2, ok := obj2.(int32)
|
||||||
|
if !ok {
|
||||||
|
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
|
||||||
|
}
|
||||||
|
if int32obj1 > int32obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int32obj1 == int32obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int32obj1 < int32obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
{
|
||||||
|
int64obj1, ok := obj1.(int64)
|
||||||
|
if !ok {
|
||||||
|
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
|
||||||
|
}
|
||||||
|
int64obj2, ok := obj2.(int64)
|
||||||
|
if !ok {
|
||||||
|
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
|
||||||
|
}
|
||||||
|
if int64obj1 > int64obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int64obj1 == int64obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int64obj1 < int64obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint:
|
||||||
|
{
|
||||||
|
uintobj1, ok := obj1.(uint)
|
||||||
|
if !ok {
|
||||||
|
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
|
||||||
|
}
|
||||||
|
uintobj2, ok := obj2.(uint)
|
||||||
|
if !ok {
|
||||||
|
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
|
||||||
|
}
|
||||||
|
if uintobj1 > uintobj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uintobj1 == uintobj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uintobj1 < uintobj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint8:
|
||||||
|
{
|
||||||
|
uint8obj1, ok := obj1.(uint8)
|
||||||
|
if !ok {
|
||||||
|
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
|
||||||
|
}
|
||||||
|
uint8obj2, ok := obj2.(uint8)
|
||||||
|
if !ok {
|
||||||
|
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
|
||||||
|
}
|
||||||
|
if uint8obj1 > uint8obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint8obj1 == uint8obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint8obj1 < uint8obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
{
|
||||||
|
uint16obj1, ok := obj1.(uint16)
|
||||||
|
if !ok {
|
||||||
|
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
|
||||||
|
}
|
||||||
|
uint16obj2, ok := obj2.(uint16)
|
||||||
|
if !ok {
|
||||||
|
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
|
||||||
|
}
|
||||||
|
if uint16obj1 > uint16obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint16obj1 == uint16obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint16obj1 < uint16obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
{
|
||||||
|
uint32obj1, ok := obj1.(uint32)
|
||||||
|
if !ok {
|
||||||
|
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
|
||||||
|
}
|
||||||
|
uint32obj2, ok := obj2.(uint32)
|
||||||
|
if !ok {
|
||||||
|
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
|
||||||
|
}
|
||||||
|
if uint32obj1 > uint32obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint32obj1 == uint32obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint32obj1 < uint32obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
{
|
||||||
|
uint64obj1, ok := obj1.(uint64)
|
||||||
|
if !ok {
|
||||||
|
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
|
||||||
|
}
|
||||||
|
uint64obj2, ok := obj2.(uint64)
|
||||||
|
if !ok {
|
||||||
|
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
|
||||||
|
}
|
||||||
|
if uint64obj1 > uint64obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint64obj1 == uint64obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint64obj1 < uint64obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float32:
|
||||||
|
{
|
||||||
|
float32obj1, ok := obj1.(float32)
|
||||||
|
if !ok {
|
||||||
|
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
|
||||||
|
}
|
||||||
|
float32obj2, ok := obj2.(float32)
|
||||||
|
if !ok {
|
||||||
|
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
|
||||||
|
}
|
||||||
|
if float32obj1 > float32obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if float32obj1 == float32obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if float32obj1 < float32obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float64:
|
||||||
|
{
|
||||||
|
float64obj1, ok := obj1.(float64)
|
||||||
|
if !ok {
|
||||||
|
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
|
||||||
|
}
|
||||||
|
float64obj2, ok := obj2.(float64)
|
||||||
|
if !ok {
|
||||||
|
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
|
||||||
|
}
|
||||||
|
if float64obj1 > float64obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if float64obj1 == float64obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if float64obj1 < float64obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
{
|
||||||
|
stringobj1, ok := obj1.(string)
|
||||||
|
if !ok {
|
||||||
|
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
|
||||||
|
}
|
||||||
|
stringobj2, ok := obj2.(string)
|
||||||
|
if !ok {
|
||||||
|
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
|
||||||
|
}
|
||||||
|
if stringobj1 > stringobj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if stringobj1 == stringobj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if stringobj1 < stringobj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for known struct types we can check for compare results.
|
||||||
|
case reflect.Struct:
|
||||||
|
{
|
||||||
|
// All structs enter here. We're not interested in most types.
|
||||||
|
if !obj1Value.CanConvert(timeType) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// time.Time can be compared!
|
||||||
|
timeObj1, ok := obj1.(time.Time)
|
||||||
|
if !ok {
|
||||||
|
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeObj2, ok := obj2.(time.Time)
|
||||||
|
if !ok {
|
||||||
|
timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeObj1.Before(timeObj2) {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
if timeObj1.Equal(timeObj2) {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
{
|
||||||
|
// We only care about the []byte type.
|
||||||
|
if !obj1Value.CanConvert(bytesType) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// []byte can be compared!
|
||||||
|
bytesObj1, ok := obj1.([]byte)
|
||||||
|
if !ok {
|
||||||
|
bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
|
||||||
|
|
||||||
|
}
|
||||||
|
bytesObj2, ok := obj2.([]byte)
|
||||||
|
if !ok {
|
||||||
|
bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareResult(bytes.Compare(bytesObj1, bytesObj2)), true
|
||||||
|
}
|
||||||
|
case reflect.Uintptr:
|
||||||
|
{
|
||||||
|
uintptrObj1, ok := obj1.(uintptr)
|
||||||
|
if !ok {
|
||||||
|
uintptrObj1 = obj1Value.Convert(uintptrType).Interface().(uintptr)
|
||||||
|
}
|
||||||
|
uintptrObj2, ok := obj2.(uintptr)
|
||||||
|
if !ok {
|
||||||
|
uintptrObj2 = obj2Value.Convert(uintptrType).Interface().(uintptr)
|
||||||
|
}
|
||||||
|
if uintptrObj1 > uintptrObj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uintptrObj1 == uintptrObj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uintptrObj1 < uintptrObj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareEqual, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greater asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// assert.Greater(t, 2, 1)
|
||||||
|
// assert.Greater(t, float64(2), float64(1))
|
||||||
|
// assert.Greater(t, "b", "a")
|
||||||
|
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
failMessage := fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareGreater}, failMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.GreaterOrEqual(t, 2, 1)
|
||||||
|
// assert.GreaterOrEqual(t, 2, 2)
|
||||||
|
// assert.GreaterOrEqual(t, "b", "a")
|
||||||
|
// assert.GreaterOrEqual(t, "b", "b")
|
||||||
|
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
failMessage := fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareGreater, compareEqual}, failMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// assert.Less(t, 1, 2)
|
||||||
|
// assert.Less(t, float64(1), float64(2))
|
||||||
|
// assert.Less(t, "a", "b")
|
||||||
|
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
failMessage := fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareLess}, failMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.LessOrEqual(t, 1, 2)
|
||||||
|
// assert.LessOrEqual(t, 2, 2)
|
||||||
|
// assert.LessOrEqual(t, "a", "b")
|
||||||
|
// assert.LessOrEqual(t, "b", "b")
|
||||||
|
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
failMessage := fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2)
|
||||||
|
return compareTwoValues(t, e1, e2, []compareResult{compareLess, compareEqual}, failMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positive asserts that the specified element is positive
|
||||||
|
//
|
||||||
|
// assert.Positive(t, 1)
|
||||||
|
// assert.Positive(t, 1.23)
|
||||||
|
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
zero := reflect.Zero(reflect.TypeOf(e))
|
||||||
|
failMessage := fmt.Sprintf("\"%v\" is not positive", e)
|
||||||
|
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareGreater}, failMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative asserts that the specified element is negative
|
||||||
|
//
|
||||||
|
// assert.Negative(t, -1)
|
||||||
|
// assert.Negative(t, -1.23)
|
||||||
|
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
zero := reflect.Zero(reflect.TypeOf(e))
|
||||||
|
failMessage := fmt.Sprintf("\"%v\" is not negative", e)
|
||||||
|
return compareTwoValues(t, e, zero.Interface(), []compareResult{compareLess}, failMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
e1Kind := reflect.ValueOf(e1).Kind()
|
||||||
|
e2Kind := reflect.ValueOf(e2).Kind()
|
||||||
|
if e1Kind != e2Kind {
|
||||||
|
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
compareResult, isComparable := compare(e1, e2, e1Kind)
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf(`Can not compare type "%T"`, e1), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containsValue(allowedComparesResults, compareResult) {
|
||||||
|
return Fail(t, failMessage, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsValue(values []compareResult, value compareResult) bool {
|
||||||
|
for _, v := range values {
|
||||||
|
if v == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,472 @@
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompare(t *testing.T) {
|
||||||
|
type customString string
|
||||||
|
type customInt int
|
||||||
|
type customInt8 int8
|
||||||
|
type customInt16 int16
|
||||||
|
type customInt32 int32
|
||||||
|
type customInt64 int64
|
||||||
|
type customUInt uint
|
||||||
|
type customUInt8 uint8
|
||||||
|
type customUInt16 uint16
|
||||||
|
type customUInt32 uint32
|
||||||
|
type customUInt64 uint64
|
||||||
|
type customFloat32 float32
|
||||||
|
type customFloat64 float64
|
||||||
|
type customUintptr uintptr
|
||||||
|
type customTime time.Time
|
||||||
|
type customBytes []byte
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
less interface{}
|
||||||
|
greater interface{}
|
||||||
|
cType string
|
||||||
|
}{
|
||||||
|
{less: customString("a"), greater: customString("b"), cType: "string"},
|
||||||
|
{less: "a", greater: "b", cType: "string"},
|
||||||
|
{less: customInt(1), greater: customInt(2), cType: "int"},
|
||||||
|
{less: int(1), greater: int(2), cType: "int"},
|
||||||
|
{less: customInt8(1), greater: customInt8(2), cType: "int8"},
|
||||||
|
{less: int8(1), greater: int8(2), cType: "int8"},
|
||||||
|
{less: customInt16(1), greater: customInt16(2), cType: "int16"},
|
||||||
|
{less: int16(1), greater: int16(2), cType: "int16"},
|
||||||
|
{less: customInt32(1), greater: customInt32(2), cType: "int32"},
|
||||||
|
{less: int32(1), greater: int32(2), cType: "int32"},
|
||||||
|
{less: customInt64(1), greater: customInt64(2), cType: "int64"},
|
||||||
|
{less: int64(1), greater: int64(2), cType: "int64"},
|
||||||
|
{less: customUInt(1), greater: customUInt(2), cType: "uint"},
|
||||||
|
{less: uint8(1), greater: uint8(2), cType: "uint8"},
|
||||||
|
{less: customUInt8(1), greater: customUInt8(2), cType: "uint8"},
|
||||||
|
{less: uint16(1), greater: uint16(2), cType: "uint16"},
|
||||||
|
{less: customUInt16(1), greater: customUInt16(2), cType: "uint16"},
|
||||||
|
{less: uint32(1), greater: uint32(2), cType: "uint32"},
|
||||||
|
{less: customUInt32(1), greater: customUInt32(2), cType: "uint32"},
|
||||||
|
{less: uint64(1), greater: uint64(2), cType: "uint64"},
|
||||||
|
{less: customUInt64(1), greater: customUInt64(2), cType: "uint64"},
|
||||||
|
{less: float32(1.23), greater: float32(2.34), cType: "float32"},
|
||||||
|
{less: customFloat32(1.23), greater: customFloat32(2.23), cType: "float32"},
|
||||||
|
{less: float64(1.23), greater: float64(2.34), cType: "float64"},
|
||||||
|
{less: customFloat64(1.23), greater: customFloat64(2.34), cType: "float64"},
|
||||||
|
{less: uintptr(1), greater: uintptr(2), cType: "uintptr"},
|
||||||
|
{less: customUintptr(1), greater: customUintptr(2), cType: "uint64"},
|
||||||
|
{less: time.Now(), greater: time.Now().Add(time.Hour), cType: "time.Time"},
|
||||||
|
{less: time.Date(2024, 0, 0, 0, 0, 0, 0, time.Local), greater: time.Date(2263, 0, 0, 0, 0, 0, 0, time.Local), cType: "time.Time"},
|
||||||
|
{less: customTime(time.Now()), greater: customTime(time.Now().Add(time.Hour)), cType: "time.Time"},
|
||||||
|
{less: []byte{1, 1}, greater: []byte{1, 2}, cType: "[]byte"},
|
||||||
|
{less: customBytes([]byte{1, 1}), greater: customBytes([]byte{1, 2}), cType: "[]byte"},
|
||||||
|
} {
|
||||||
|
resLess, isComparable := compare(currCase.less, currCase.greater, reflect.ValueOf(currCase.less).Kind())
|
||||||
|
if !isComparable {
|
||||||
|
t.Error("object should be comparable for type " + currCase.cType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resLess != compareLess {
|
||||||
|
t.Errorf("object less (%v) should be less than greater (%v) for type "+currCase.cType,
|
||||||
|
currCase.less, currCase.greater)
|
||||||
|
}
|
||||||
|
|
||||||
|
resGreater, isComparable := compare(currCase.greater, currCase.less, reflect.ValueOf(currCase.less).Kind())
|
||||||
|
if !isComparable {
|
||||||
|
t.Error("object are comparable for type " + currCase.cType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resGreater != compareGreater {
|
||||||
|
t.Errorf("object greater should be greater than less for type " + currCase.cType)
|
||||||
|
}
|
||||||
|
|
||||||
|
resEqual, isComparable := compare(currCase.less, currCase.less, reflect.ValueOf(currCase.less).Kind())
|
||||||
|
if !isComparable {
|
||||||
|
t.Error("object are comparable for type " + currCase.cType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resEqual != 0 {
|
||||||
|
t.Errorf("objects should be equal for type " + currCase.cType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type outputT struct {
|
||||||
|
buf *bytes.Buffer
|
||||||
|
helpers map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements TestingT
|
||||||
|
func (t *outputT) Errorf(format string, args ...interface{}) {
|
||||||
|
s := fmt.Sprintf(format, args...)
|
||||||
|
t.buf.WriteString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *outputT) Helper() {
|
||||||
|
if t.helpers == nil {
|
||||||
|
t.helpers = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
t.helpers[callerName(1)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// callerName gives the function name (qualified with a package path)
|
||||||
|
// for the caller after skip frames (where 0 means the current function).
|
||||||
|
func callerName(skip int) string {
|
||||||
|
// Make room for the skip PC.
|
||||||
|
var pc [1]uintptr
|
||||||
|
n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName
|
||||||
|
if n == 0 {
|
||||||
|
panic("testing: zero callers found")
|
||||||
|
}
|
||||||
|
frames := runtime.CallersFrames(pc[:n])
|
||||||
|
frame, _ := frames.Next()
|
||||||
|
return frame.Function
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGreater(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !Greater(mockT, 2, 1) {
|
||||||
|
t.Error("Greater should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Greater(mockT, 1, 1) {
|
||||||
|
t.Error("Greater should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Greater(mockT, 1, 2) {
|
||||||
|
t.Error("Greater should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
less interface{}
|
||||||
|
greater interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{less: "a", greater: "b", msg: `"a" is not greater than "b"`},
|
||||||
|
{less: int(1), greater: int(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: int8(1), greater: int8(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: int16(1), greater: int16(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: int32(1), greater: int32(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: int64(1), greater: int64(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: uint8(1), greater: uint8(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: uint16(1), greater: uint16(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: uint32(1), greater: uint32(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: uint64(1), greater: uint64(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than "2.34"`},
|
||||||
|
{less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than "2.34"`},
|
||||||
|
{less: uintptr(1), greater: uintptr(2), msg: `"1" is not greater than "2"`},
|
||||||
|
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than "0001-01-01 01:00:00 +0000 UTC"`},
|
||||||
|
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than "[1 2]"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, Greater(out, currCase.less, currCase.greater))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Greater")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGreaterOrEqual(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !GreaterOrEqual(mockT, 2, 1) {
|
||||||
|
t.Error("GreaterOrEqual should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !GreaterOrEqual(mockT, 1, 1) {
|
||||||
|
t.Error("GreaterOrEqual should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if GreaterOrEqual(mockT, 1, 2) {
|
||||||
|
t.Error("GreaterOrEqual should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
less interface{}
|
||||||
|
greater interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{less: "a", greater: "b", msg: `"a" is not greater than or equal to "b"`},
|
||||||
|
{less: int(1), greater: int(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: int8(1), greater: int8(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: int16(1), greater: int16(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: int32(1), greater: int32(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: int64(1), greater: int64(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: uint8(1), greater: uint8(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: uint16(1), greater: uint16(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: uint32(1), greater: uint32(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: uint64(1), greater: uint64(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than or equal to "2.34"`},
|
||||||
|
{less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than or equal to "2.34"`},
|
||||||
|
{less: uintptr(1), greater: uintptr(2), msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than or equal to "0001-01-01 01:00:00 +0000 UTC"`},
|
||||||
|
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than or equal to "[1 2]"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, GreaterOrEqual(out, currCase.less, currCase.greater))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
Contains(t, out.helpers, "github.com/stretchr/testify/assert.GreaterOrEqual")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLess(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !Less(mockT, 1, 2) {
|
||||||
|
t.Error("Less should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Less(mockT, 1, 1) {
|
||||||
|
t.Error("Less should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Less(mockT, 2, 1) {
|
||||||
|
t.Error("Less should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
less interface{}
|
||||||
|
greater interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{less: "a", greater: "b", msg: `"b" is not less than "a"`},
|
||||||
|
{less: int(1), greater: int(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: int8(1), greater: int8(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: int16(1), greater: int16(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: int32(1), greater: int32(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: int64(1), greater: int64(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: uint8(1), greater: uint8(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: uint16(1), greater: uint16(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: uint32(1), greater: uint32(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: uint64(1), greater: uint64(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than "1.23"`},
|
||||||
|
{less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than "1.23"`},
|
||||||
|
{less: uintptr(1), greater: uintptr(2), msg: `"2" is not less than "1"`},
|
||||||
|
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than "0001-01-01 00:00:00 +0000 UTC"`},
|
||||||
|
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than "[1 1]"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, Less(out, currCase.greater, currCase.less))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Less")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLessOrEqual(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !LessOrEqual(mockT, 1, 2) {
|
||||||
|
t.Error("LessOrEqual should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !LessOrEqual(mockT, 1, 1) {
|
||||||
|
t.Error("LessOrEqual should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if LessOrEqual(mockT, 2, 1) {
|
||||||
|
t.Error("LessOrEqual should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
less interface{}
|
||||||
|
greater interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{less: "a", greater: "b", msg: `"b" is not less than or equal to "a"`},
|
||||||
|
{less: int(1), greater: int(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: int8(1), greater: int8(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: int16(1), greater: int16(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: int32(1), greater: int32(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: int64(1), greater: int64(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: uint8(1), greater: uint8(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: uint16(1), greater: uint16(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: uint32(1), greater: uint32(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: uint64(1), greater: uint64(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than or equal to "1.23"`},
|
||||||
|
{less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than or equal to "1.23"`},
|
||||||
|
{less: uintptr(1), greater: uintptr(2), msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than or equal to "0001-01-01 00:00:00 +0000 UTC"`},
|
||||||
|
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than or equal to "[1 1]"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, LessOrEqual(out, currCase.greater, currCase.less))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
Contains(t, out.helpers, "github.com/stretchr/testify/assert.LessOrEqual")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPositive(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !Positive(mockT, 1) {
|
||||||
|
t.Error("Positive should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Positive(mockT, 1.23) {
|
||||||
|
t.Error("Positive should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Positive(mockT, -1) {
|
||||||
|
t.Error("Positive should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Positive(mockT, -1.23) {
|
||||||
|
t.Error("Positive should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
e interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{e: int(-1), msg: `"-1" is not positive`},
|
||||||
|
{e: int8(-1), msg: `"-1" is not positive`},
|
||||||
|
{e: int16(-1), msg: `"-1" is not positive`},
|
||||||
|
{e: int32(-1), msg: `"-1" is not positive`},
|
||||||
|
{e: int64(-1), msg: `"-1" is not positive`},
|
||||||
|
{e: float32(-1.23), msg: `"-1.23" is not positive`},
|
||||||
|
{e: float64(-1.23), msg: `"-1.23" is not positive`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, Positive(out, currCase.e))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Positive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNegative(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !Negative(mockT, -1) {
|
||||||
|
t.Error("Negative should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Negative(mockT, -1.23) {
|
||||||
|
t.Error("Negative should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Negative(mockT, 1) {
|
||||||
|
t.Error("Negative should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if Negative(mockT, 1.23) {
|
||||||
|
t.Error("Negative should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
e interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{e: int(1), msg: `"1" is not negative`},
|
||||||
|
{e: int8(1), msg: `"1" is not negative`},
|
||||||
|
{e: int16(1), msg: `"1" is not negative`},
|
||||||
|
{e: int32(1), msg: `"1" is not negative`},
|
||||||
|
{e: int64(1), msg: `"1" is not negative`},
|
||||||
|
{e: float32(1.23), msg: `"1.23" is not negative`},
|
||||||
|
{e: float64(1.23), msg: `"1.23" is not negative`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, Negative(out, currCase.e))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Negative")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_compareTwoValuesDifferentValuesTypes(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
v1 interface{}
|
||||||
|
v2 interface{}
|
||||||
|
compareResult bool
|
||||||
|
}{
|
||||||
|
{v1: 123, v2: "abc"},
|
||||||
|
{v1: "abc", v2: 123456},
|
||||||
|
{v1: float64(12), v2: "123"},
|
||||||
|
{v1: "float(12)", v2: float64(1)},
|
||||||
|
} {
|
||||||
|
result := compareTwoValues(mockT, currCase.v1, currCase.v2, []compareResult{compareLess, compareEqual, compareGreater}, "testFailMessage")
|
||||||
|
False(t, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_compareTwoValuesNotComparableValues(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
type CompareStruct struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
v1 interface{}
|
||||||
|
v2 interface{}
|
||||||
|
}{
|
||||||
|
{v1: CompareStruct{}, v2: CompareStruct{}},
|
||||||
|
{v1: map[string]int{}, v2: map[string]int{}},
|
||||||
|
{v1: make([]int, 5), v2: make([]int, 5)},
|
||||||
|
} {
|
||||||
|
result := compareTwoValues(mockT, currCase.v1, currCase.v2, []compareResult{compareLess, compareEqual, compareGreater}, "testFailMessage")
|
||||||
|
False(t, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_compareTwoValuesCorrectCompareResult(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
v1 interface{}
|
||||||
|
v2 interface{}
|
||||||
|
allowedResults []compareResult
|
||||||
|
}{
|
||||||
|
{v1: 1, v2: 2, allowedResults: []compareResult{compareLess}},
|
||||||
|
{v1: 1, v2: 2, allowedResults: []compareResult{compareLess, compareEqual}},
|
||||||
|
{v1: 2, v2: 2, allowedResults: []compareResult{compareGreater, compareEqual}},
|
||||||
|
{v1: 2, v2: 2, allowedResults: []compareResult{compareEqual}},
|
||||||
|
{v1: 2, v2: 1, allowedResults: []compareResult{compareEqual, compareGreater}},
|
||||||
|
{v1: 2, v2: 1, allowedResults: []compareResult{compareGreater}},
|
||||||
|
} {
|
||||||
|
result := compareTwoValues(mockT, currCase.v1, currCase.v2, currCase.allowedResults, "testFailMessage")
|
||||||
|
True(t, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_containsValue(t *testing.T) {
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
values []compareResult
|
||||||
|
value compareResult
|
||||||
|
result bool
|
||||||
|
}{
|
||||||
|
{values: []compareResult{compareGreater}, value: compareGreater, result: true},
|
||||||
|
{values: []compareResult{compareGreater, compareLess}, value: compareGreater, result: true},
|
||||||
|
{values: []compareResult{compareGreater, compareLess}, value: compareLess, result: true},
|
||||||
|
{values: []compareResult{compareGreater, compareLess}, value: compareEqual, result: false},
|
||||||
|
} {
|
||||||
|
result := containsValue(currCase.values, currCase.value)
|
||||||
|
Equal(t, currCase.result, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComparingMsgAndArgsForwarding(t *testing.T) {
|
||||||
|
msgAndArgs := []interface{}{"format %s %x", "this", 0xc001}
|
||||||
|
expectedOutput := "format this c001\n"
|
||||||
|
funcs := []func(t TestingT){
|
||||||
|
func(t TestingT) { Greater(t, 1, 2, msgAndArgs...) },
|
||||||
|
func(t TestingT) { GreaterOrEqual(t, 1, 2, msgAndArgs...) },
|
||||||
|
func(t TestingT) { Less(t, 2, 1, msgAndArgs...) },
|
||||||
|
func(t TestingT) { LessOrEqual(t, 2, 1, msgAndArgs...) },
|
||||||
|
func(t TestingT) { Positive(t, 0, msgAndArgs...) },
|
||||||
|
func(t TestingT) { Negative(t, 0, msgAndArgs...) },
|
||||||
|
}
|
||||||
|
for _, f := range funcs {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
f(out)
|
||||||
|
Contains(t, out.buf.String(), expectedOutput)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,4 @@
|
||||||
/*
|
// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
|
||||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
|
||||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
|
||||||
*/
|
|
||||||
|
|
||||||
package assert
|
package assert
|
||||||
|
|
||||||
|
@ -13,6 +10,9 @@ import (
|
||||||
|
|
||||||
// Conditionf uses a Comparison to assert a complex condition.
|
// Conditionf uses a Comparison to assert a complex condition.
|
||||||
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
|
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Condition(t, comp, append([]interface{}{msg}, args...)...)
|
return Condition(t, comp, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,18 @@ func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bo
|
||||||
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||||
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||||
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
|
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
// DirExistsf checks whether a directory exists in the given path. It also fails
|
||||||
|
// if the path is a file rather a directory or there is an error checking whether it exists.
|
||||||
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return DirExists(t, path, append([]interface{}{msg}, args...)...)
|
return DirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +44,9 @@ func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
//
|
//
|
||||||
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||||
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +55,9 @@ func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string
|
||||||
//
|
//
|
||||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Empty(t, object, append([]interface{}{msg}, args...)...)
|
return Empty(t, object, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +69,9 @@ func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boo
|
||||||
// referenced values (as opposed to the memory addresses). Function equality
|
// referenced values (as opposed to the memory addresses). Function equality
|
||||||
// cannot be determined and will always fail.
|
// cannot be determined and will always fail.
|
||||||
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,14 +81,37 @@ func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, ar
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||||
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
// EqualExportedValuesf asserts that the types of two objects are equal and their public
|
||||||
// and equal.
|
// fields are also equal. This is useful for comparing structs that have private fields
|
||||||
|
// that could potentially differ.
|
||||||
//
|
//
|
||||||
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
|
// type S struct {
|
||||||
|
// Exported int
|
||||||
|
// notExported int
|
||||||
|
// }
|
||||||
|
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
|
||||||
|
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
|
||||||
|
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EqualValuesf asserts that two objects are equal or convertible to the larger
|
||||||
|
// type and equal.
|
||||||
|
//
|
||||||
|
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
|
||||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,23 +122,101 @@ func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg stri
|
||||||
// assert.Equal(t, expectedErrorf, err)
|
// assert.Equal(t, expectedErrorf, err)
|
||||||
// }
|
// }
|
||||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Error(t, err, append([]interface{}{msg}, args...)...)
|
return Error(t, err, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
|
||||||
|
// This is a wrapper for errors.As.
|
||||||
|
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
|
||||||
|
// and that the error contains the specified substring.
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted")
|
||||||
|
func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
|
||||||
|
// This is a wrapper for errors.Is.
|
||||||
|
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eventuallyf asserts that given condition will be met in waitFor time,
|
||||||
|
// periodically checking target function each tick.
|
||||||
|
//
|
||||||
|
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||||
|
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventuallyWithTf asserts that given condition will be met in waitFor time,
|
||||||
|
// periodically checking target function each tick. In contrast to Eventually,
|
||||||
|
// it supplies a CollectT to the condition function, so that the condition
|
||||||
|
// function can use the CollectT to call other assertions.
|
||||||
|
// The condition is considered "met" if no errors are raised in a tick.
|
||||||
|
// The supplied CollectT collects all errors from one tick (if there are any).
|
||||||
|
// If the condition is not met before waitFor, the collected errors of
|
||||||
|
// the last tick are copied to t.
|
||||||
|
//
|
||||||
|
// externalValue := false
|
||||||
|
// go func() {
|
||||||
|
// time.Sleep(8*time.Second)
|
||||||
|
// externalValue = true
|
||||||
|
// }()
|
||||||
|
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
|
||||||
|
// // add assertions as needed; any assertion failure will fail the current tick
|
||||||
|
// assert.True(c, externalValue, "expected 'externalValue' to be true")
|
||||||
|
// }, 10*time.Second, 1*time.Second, "external state has not changed to 'true'; still false")
|
||||||
|
func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Exactlyf asserts that two objects are equal in value and type.
|
// Exactlyf asserts that two objects are equal in value and type.
|
||||||
//
|
//
|
||||||
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
|
||||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failf reports a failure through
|
// Failf reports a failure through
|
||||||
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
|
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FailNowf fails test
|
// FailNowf fails test
|
||||||
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
|
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,31 +224,69 @@ func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}
|
||||||
//
|
//
|
||||||
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||||
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return False(t, value, append([]interface{}{msg}, args...)...)
|
return False(t, value, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
// FileExistsf checks whether a file exists in the given path. It also fails if
|
||||||
|
// the path points to a directory or there is an error when trying to check the file.
|
||||||
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Greaterf asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||||
|
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
|
||||||
|
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||||
|
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||||
|
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||||
// body that contains a string.
|
// body that contains a string.
|
||||||
//
|
//
|
||||||
// assert.HTTPBodyContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||||
// body that does not contain a string.
|
// body that does not contain a string.
|
||||||
//
|
//
|
||||||
// assert.HTTPBodyNotContainsf(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,8 +294,11 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u
|
||||||
//
|
//
|
||||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,56 +306,143 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string,
|
||||||
//
|
//
|
||||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||||
|
//
|
||||||
|
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||||
//
|
//
|
||||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementsf asserts that an object is implemented by the specified interface.
|
// Implementsf asserts that an object is implemented by the specified interface.
|
||||||
//
|
//
|
||||||
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||||
//
|
//
|
||||||
// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
|
||||||
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||||
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||||
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||||
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||||
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDecreasingf asserts that the collection is decreasing
|
||||||
|
//
|
||||||
|
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
|
||||||
|
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||||
|
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||||
|
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIncreasingf asserts that the collection is increasing
|
||||||
|
//
|
||||||
|
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
|
||||||
|
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||||
|
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||||
|
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonDecreasingf asserts that the collection is not decreasing
|
||||||
|
//
|
||||||
|
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||||
|
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonIncreasingf asserts that the collection is not increasing
|
||||||
|
//
|
||||||
|
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||||
|
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// IsTypef asserts that the specified objects are of the same type.
|
// IsTypef asserts that the specified objects are of the same type.
|
||||||
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
|
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +450,9 @@ func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg strin
|
||||||
//
|
//
|
||||||
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||||
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,16 +461,78 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int
|
||||||
//
|
//
|
||||||
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||||
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lessf asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||||
|
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
|
||||||
|
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||||
|
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqualf asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||||
|
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negativef asserts that the specified element is negative
|
||||||
|
//
|
||||||
|
// assert.Negativef(t, -1, "error message %s", "formatted")
|
||||||
|
// assert.Negativef(t, -1.23, "error message %s", "formatted")
|
||||||
|
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Negative(t, e, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
|
||||||
|
// periodically checking the target function each tick.
|
||||||
|
//
|
||||||
|
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||||
|
func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Never(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Nilf asserts that the specified object is nil.
|
// Nilf asserts that the specified object is nil.
|
||||||
//
|
//
|
||||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||||
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Nil(t, object, append([]interface{}{msg}, args...)...)
|
return Nil(t, object, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoDirExistsf checks whether a directory does not exist in the given path.
|
||||||
|
// It fails if the path points to an existing _directory_ only.
|
||||||
|
func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NoDirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||||
//
|
//
|
||||||
// actualObj, err := SomeFunction()
|
// actualObj, err := SomeFunction()
|
||||||
|
@ -230,9 +540,21 @@ func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool
|
||||||
// assert.Equal(t, expectedObj, actualObj)
|
// assert.Equal(t, expectedObj, actualObj)
|
||||||
// }
|
// }
|
||||||
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NoError(t, err, append([]interface{}{msg}, args...)...)
|
return NoError(t, err, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoFileExistsf checks whether a file does not exist in a given path. It fails
|
||||||
|
// if the path points to an existing _file_ only.
|
||||||
|
func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NoFileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||||
// specified substring or element.
|
// specified substring or element.
|
||||||
//
|
//
|
||||||
|
@ -240,9 +562,29 @@ func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||||
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||||
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||||
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
|
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotElementsMatchf asserts that the specified listA(array, slice...) is NOT equal to specified
|
||||||
|
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||||
|
// the number of appearances of each of them in both lists should not match.
|
||||||
|
// This is an inverse of ElementsMatch.
|
||||||
|
//
|
||||||
|
// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 1, 2, 3], "error message %s", "formatted") -> false
|
||||||
|
//
|
||||||
|
// assert.NotElementsMatchf(t, [1, 1, 2, 3], [1, 2, 3], "error message %s", "formatted") -> true
|
||||||
|
//
|
||||||
|
// assert.NotElementsMatchf(t, [1, 2, 3], [1, 2, 4], "error message %s", "formatted") -> true
|
||||||
|
func NotElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||||
// a slice or a channel with len == 0.
|
// a slice or a channel with len == 0.
|
||||||
//
|
//
|
||||||
|
@ -250,6 +592,9 @@ func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, a
|
||||||
// assert.Equal(t, "two", obj[1])
|
// assert.Equal(t, "two", obj[1])
|
||||||
// }
|
// }
|
||||||
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
|
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,13 +605,57 @@ func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{})
|
||||||
// Pointer variable equality is determined based on the equality of the
|
// Pointer variable equality is determined based on the equality of the
|
||||||
// referenced values (as opposed to the memory addresses).
|
// referenced values (as opposed to the memory addresses).
|
||||||
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||||
|
//
|
||||||
|
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
|
||||||
|
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotErrorAsf asserts that none of the errors in err's chain matches target,
|
||||||
|
// but if so, sets target to that error value.
|
||||||
|
func NotErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotErrorIsf asserts that none of the errors in err's chain matches target.
|
||||||
|
// This is a wrapper for errors.Is.
|
||||||
|
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotImplementsf asserts that an object does not implement the specified interface.
|
||||||
|
//
|
||||||
|
// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||||
|
func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotImplements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// NotNilf asserts that the specified object is not nil.
|
// NotNilf asserts that the specified object is not nil.
|
||||||
//
|
//
|
||||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||||
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotNil(t, object, append([]interface{}{msg}, args...)...)
|
return NotNil(t, object, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,27 +663,54 @@ func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bo
|
||||||
//
|
//
|
||||||
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||||
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
|
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||||
//
|
//
|
||||||
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
// NotSamef asserts that two pointers do not reference the same object.
|
||||||
// elements given in the specified subset(array, slice...).
|
|
||||||
//
|
//
|
||||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotSubsetf asserts that the specified list(array, slice...) or map does NOT
|
||||||
|
// contain all elements given in the specified subset list(array, slice...) or
|
||||||
|
// map.
|
||||||
|
//
|
||||||
|
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted")
|
||||||
|
// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted")
|
||||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
|
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotZerof asserts that i is not the zero value for its type.
|
// NotZerof asserts that i is not the zero value for its type.
|
||||||
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return NotZero(t, i, append([]interface{}{msg}, args...)...)
|
return NotZero(t, i, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,30 +718,79 @@ func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||||
//
|
//
|
||||||
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||||
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Panics(t, f, append([]interface{}{msg}, args...)...)
|
return Panics(t, f, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
|
||||||
|
// panics, and that the recovered panic value is an error that satisfies the
|
||||||
|
// EqualError comparison.
|
||||||
|
//
|
||||||
|
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||||
|
func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||||
// the recovered panic value equals the expected panic value.
|
// the recovered panic value equals the expected panic value.
|
||||||
//
|
//
|
||||||
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||||
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
|
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Positivef asserts that the specified element is positive
|
||||||
|
//
|
||||||
|
// assert.Positivef(t, 1, "error message %s", "formatted")
|
||||||
|
// assert.Positivef(t, 1.23, "error message %s", "formatted")
|
||||||
|
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Positive(t, e, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Regexpf asserts that a specified regexp matches a string.
|
// Regexpf asserts that a specified regexp matches a string.
|
||||||
//
|
//
|
||||||
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
// Samef asserts that two pointers reference the same object.
|
||||||
// elements given in the specified subset(array, slice...).
|
|
||||||
//
|
//
|
||||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subsetf asserts that the specified list(array, slice...) or map contains all
|
||||||
|
// elements given in the specified subset list(array, slice...) or map.
|
||||||
|
//
|
||||||
|
// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted")
|
||||||
|
// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted")
|
||||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
|
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,6 +798,9 @@ func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args
|
||||||
//
|
//
|
||||||
// assert.Truef(t, myBool, "error message %s", "formatted")
|
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||||
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return True(t, value, append([]interface{}{msg}, args...)...)
|
return True(t, value, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,10 +808,34 @@ func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||||
//
|
//
|
||||||
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||||
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithinRangef asserts that a time is within a time range (inclusive).
|
||||||
|
//
|
||||||
|
// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted")
|
||||||
|
func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||||
|
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Zerof asserts that i is the zero value for its type.
|
// Zerof asserts that i is the zero value for its type.
|
||||||
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
return Zero(t, i, append([]interface{}{msg}, args...)...)
|
return Zero(t, i, append([]interface{}{msg}, args...)...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{{.CommentFormat}}
|
{{.CommentFormat}}
|
||||||
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
|
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||||
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
|
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
||||||
{{.CommentWithoutT "a"}}
|
{{.CommentWithoutT "a"}}
|
||||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||||
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isOrdered checks that collection contains orderable elements.
|
||||||
|
func isOrdered(t TestingT, object interface{}, allowedComparesResults []compareResult, failMessage string, msgAndArgs ...interface{}) bool {
|
||||||
|
objKind := reflect.TypeOf(object).Kind()
|
||||||
|
if objKind != reflect.Slice && objKind != reflect.Array {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
objValue := reflect.ValueOf(object)
|
||||||
|
objLen := objValue.Len()
|
||||||
|
|
||||||
|
if objLen <= 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
value := objValue.Index(0)
|
||||||
|
valueInterface := value.Interface()
|
||||||
|
firstValueKind := value.Kind()
|
||||||
|
|
||||||
|
for i := 1; i < objLen; i++ {
|
||||||
|
prevValue := value
|
||||||
|
prevValueInterface := valueInterface
|
||||||
|
|
||||||
|
value = objValue.Index(i)
|
||||||
|
valueInterface = value.Interface()
|
||||||
|
|
||||||
|
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
|
||||||
|
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf(`Can not compare type "%T" and "%T"`, value, prevValue), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containsValue(allowedComparesResults, compareResult) {
|
||||||
|
return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIncreasing asserts that the collection is increasing
|
||||||
|
//
|
||||||
|
// assert.IsIncreasing(t, []int{1, 2, 3})
|
||||||
|
// assert.IsIncreasing(t, []float{1, 2})
|
||||||
|
// assert.IsIncreasing(t, []string{"a", "b"})
|
||||||
|
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []compareResult{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonIncreasing asserts that the collection is not increasing
|
||||||
|
//
|
||||||
|
// assert.IsNonIncreasing(t, []int{2, 1, 1})
|
||||||
|
// assert.IsNonIncreasing(t, []float{2, 1})
|
||||||
|
// assert.IsNonIncreasing(t, []string{"b", "a"})
|
||||||
|
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []compareResult{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDecreasing asserts that the collection is decreasing
|
||||||
|
//
|
||||||
|
// assert.IsDecreasing(t, []int{2, 1, 0})
|
||||||
|
// assert.IsDecreasing(t, []float{2, 1})
|
||||||
|
// assert.IsDecreasing(t, []string{"b", "a"})
|
||||||
|
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []compareResult{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonDecreasing asserts that the collection is not decreasing
|
||||||
|
//
|
||||||
|
// assert.IsNonDecreasing(t, []int{1, 1, 2})
|
||||||
|
// assert.IsNonDecreasing(t, []float{1, 2})
|
||||||
|
// assert.IsNonDecreasing(t, []string{"a", "b"})
|
||||||
|
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []compareResult{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsIncreasing(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !IsIncreasing(mockT, []int{1, 2}) {
|
||||||
|
t.Error("IsIncreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsIncreasing(mockT, []int{1, 2, 3, 4, 5}) {
|
||||||
|
t.Error("IsIncreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsIncreasing(mockT, []int{1, 1}) {
|
||||||
|
t.Error("IsIncreasing should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsIncreasing(mockT, []int{2, 1}) {
|
||||||
|
t.Error("IsIncreasing should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
collection interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{collection: []string{"b", "a"}, msg: `"b" is not less than "a"`},
|
||||||
|
{collection: []int{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []int{2, 1, 3, 4, 5, 6, 7}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []int{-1, 0, 2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []int8{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []int16{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []int32{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []int64{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []uint8{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []uint16{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []uint32{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []uint64{2, 1}, msg: `"2" is not less than "1"`},
|
||||||
|
{collection: []float32{2.34, 1.23}, msg: `"2.34" is not less than "1.23"`},
|
||||||
|
{collection: []float64{2.34, 1.23}, msg: `"2.34" is not less than "1.23"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, IsIncreasing(out, currCase.collection))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsNonIncreasing(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !IsNonIncreasing(mockT, []int{2, 1}) {
|
||||||
|
t.Error("IsNonIncreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsNonIncreasing(mockT, []int{5, 4, 4, 3, 2, 1}) {
|
||||||
|
t.Error("IsNonIncreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsNonIncreasing(mockT, []int{1, 1}) {
|
||||||
|
t.Error("IsNonIncreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsNonIncreasing(mockT, []int{1, 2}) {
|
||||||
|
t.Error("IsNonIncreasing should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
collection interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{collection: []string{"a", "b"}, msg: `"a" is not greater than or equal to "b"`},
|
||||||
|
{collection: []int{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []int{1, 2, 7, 6, 5, 4, 3}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []int{5, 4, 3, 1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []int8{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []int16{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []int32{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []int64{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []uint8{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []uint16{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []uint32{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []uint64{1, 2}, msg: `"1" is not greater than or equal to "2"`},
|
||||||
|
{collection: []float32{1.23, 2.34}, msg: `"1.23" is not greater than or equal to "2.34"`},
|
||||||
|
{collection: []float64{1.23, 2.34}, msg: `"1.23" is not greater than or equal to "2.34"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, IsNonIncreasing(out, currCase.collection))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsDecreasing(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !IsDecreasing(mockT, []int{2, 1}) {
|
||||||
|
t.Error("IsDecreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsDecreasing(mockT, []int{5, 4, 3, 2, 1}) {
|
||||||
|
t.Error("IsDecreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsDecreasing(mockT, []int{1, 1}) {
|
||||||
|
t.Error("IsDecreasing should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsDecreasing(mockT, []int{1, 2}) {
|
||||||
|
t.Error("IsDecreasing should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
collection interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{collection: []string{"a", "b"}, msg: `"a" is not greater than "b"`},
|
||||||
|
{collection: []int{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []int{1, 2, 7, 6, 5, 4, 3}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []int{5, 4, 3, 1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []int8{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []int16{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []int32{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []int64{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []uint8{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []uint16{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []uint32{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []uint64{1, 2}, msg: `"1" is not greater than "2"`},
|
||||||
|
{collection: []float32{1.23, 2.34}, msg: `"1.23" is not greater than "2.34"`},
|
||||||
|
{collection: []float64{1.23, 2.34}, msg: `"1.23" is not greater than "2.34"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, IsDecreasing(out, currCase.collection))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsNonDecreasing(t *testing.T) {
|
||||||
|
mockT := new(testing.T)
|
||||||
|
|
||||||
|
if !IsNonDecreasing(mockT, []int{1, 2}) {
|
||||||
|
t.Error("IsNonDecreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsNonDecreasing(mockT, []int{1, 1, 2, 3, 4, 5}) {
|
||||||
|
t.Error("IsNonDecreasing should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !IsNonDecreasing(mockT, []int{1, 1}) {
|
||||||
|
t.Error("IsNonDecreasing should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsNonDecreasing(mockT, []int{2, 1}) {
|
||||||
|
t.Error("IsNonDecreasing should return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check error report
|
||||||
|
for _, currCase := range []struct {
|
||||||
|
collection interface{}
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{collection: []string{"b", "a"}, msg: `"b" is not less than or equal to "a"`},
|
||||||
|
{collection: []int{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []int{2, 1, 3, 4, 5, 6, 7}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []int{-1, 0, 2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []int8{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []int16{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []int32{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []int64{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []uint8{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []uint16{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []uint32{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []uint64{2, 1}, msg: `"2" is not less than or equal to "1"`},
|
||||||
|
{collection: []float32{2.34, 1.23}, msg: `"2.34" is not less than or equal to "1.23"`},
|
||||||
|
{collection: []float64{2.34, 1.23}, msg: `"2.34" is not less than or equal to "1.23"`},
|
||||||
|
} {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
False(t, IsNonDecreasing(out, currCase.collection))
|
||||||
|
Contains(t, out.buf.String(), currCase.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderingMsgAndArgsForwarding(t *testing.T) {
|
||||||
|
msgAndArgs := []interface{}{"format %s %x", "this", 0xc001}
|
||||||
|
expectedOutput := "format this c001\n"
|
||||||
|
collection := []int{1, 2, 1}
|
||||||
|
funcs := []func(t TestingT){
|
||||||
|
func(t TestingT) { IsIncreasing(t, collection, msgAndArgs...) },
|
||||||
|
func(t TestingT) { IsNonIncreasing(t, collection, msgAndArgs...) },
|
||||||
|
func(t TestingT) { IsDecreasing(t, collection, msgAndArgs...) },
|
||||||
|
func(t TestingT) { IsNonDecreasing(t, collection, msgAndArgs...) },
|
||||||
|
}
|
||||||
|
for _, f := range funcs {
|
||||||
|
out := &outputT{buf: bytes.NewBuffer(nil)}
|
||||||
|
f(out)
|
||||||
|
Contains(t, out.buf.String(), expectedOutput)
|
||||||
|
}
|
||||||
|
}
|
1366
assert/assertions.go
1366
assert/assertions.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,9 @@
|
||||||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||||
//
|
//
|
||||||
// Example Usage
|
// # Example Usage
|
||||||
//
|
//
|
||||||
// The following is a complete example using assert in a standard test function:
|
// The following is a complete example using assert in a standard test function:
|
||||||
|
//
|
||||||
// import (
|
// import (
|
||||||
// "testing"
|
// "testing"
|
||||||
// "github.com/stretchr/testify/assert"
|
// "github.com/stretchr/testify/assert"
|
||||||
|
@ -33,7 +34,7 @@
|
||||||
// assert.Equal(a, b, "The two words should be the same.")
|
// assert.Equal(a, b, "The two words should be the same.")
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Assertions
|
// # Assertions
|
||||||
//
|
//
|
||||||
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
||||||
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
||||||
|
|
|
@ -13,4 +13,4 @@ func New(t TestingT) *Assertions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs
|
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs"
|
||||||
|
|
|
@ -154,6 +154,30 @@ func TestNotEqualWrapper(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNotEqualValuesWrapper(t *testing.T) {
|
||||||
|
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
|
||||||
|
if !assert.NotEqualValues("Hello World", "Hello World!") {
|
||||||
|
t.Error("NotEqualValues should return true")
|
||||||
|
}
|
||||||
|
if !assert.NotEqualValues(123, 1234) {
|
||||||
|
t.Error("NotEqualValues should return true")
|
||||||
|
}
|
||||||
|
if !assert.NotEqualValues(123.5, 123.55) {
|
||||||
|
t.Error("NotEqualValues should return true")
|
||||||
|
}
|
||||||
|
if !assert.NotEqualValues([]byte("Hello World"), []byte("Hello World!")) {
|
||||||
|
t.Error("NotEqualValues should return true")
|
||||||
|
}
|
||||||
|
if !assert.NotEqualValues(nil, new(AssertionTesterConformingObject)) {
|
||||||
|
t.Error("NotEqualValues should return true")
|
||||||
|
}
|
||||||
|
if assert.NotEqualValues(10, uint(10)) {
|
||||||
|
t.Error("NotEqualValues should return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContainsWrapper(t *testing.T) {
|
func TestContainsWrapper(t *testing.T) {
|
||||||
|
|
||||||
assert := New(new(testing.T))
|
assert := New(new(testing.T))
|
||||||
|
@ -212,13 +236,13 @@ func TestConditionWrapper(t *testing.T) {
|
||||||
|
|
||||||
func TestDidPanicWrapper(t *testing.T) {
|
func TestDidPanicWrapper(t *testing.T) {
|
||||||
|
|
||||||
if funcDidPanic, _ := didPanic(func() {
|
if funcDidPanic, _, _ := didPanic(func() {
|
||||||
panic("Panic!")
|
panic("Panic!")
|
||||||
}); !funcDidPanic {
|
}); !funcDidPanic {
|
||||||
t.Error("didPanic should return true")
|
t.Error("didPanic should return true")
|
||||||
}
|
}
|
||||||
|
|
||||||
if funcDidPanic, _ := didPanic(func() {
|
if funcDidPanic, _, _ := didPanic(func() {
|
||||||
}); funcDidPanic {
|
}); funcDidPanic {
|
||||||
t.Error("didPanic should return false")
|
t.Error("didPanic should return false")
|
||||||
}
|
}
|
||||||
|
@ -291,6 +315,25 @@ func TestErrorWrapper(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestErrorContainsWrapper(t *testing.T) {
|
||||||
|
assert := New(t)
|
||||||
|
mockAssert := New(new(testing.T))
|
||||||
|
|
||||||
|
// start with a nil error
|
||||||
|
var err error
|
||||||
|
assert.False(mockAssert.ErrorContains(err, ""),
|
||||||
|
"ErrorContains should return false for nil arg")
|
||||||
|
|
||||||
|
// now set an error
|
||||||
|
err = errors.New("some error: another error")
|
||||||
|
assert.False(mockAssert.ErrorContains(err, "different error"),
|
||||||
|
"ErrorContains should return false for different error string")
|
||||||
|
assert.True(mockAssert.ErrorContains(err, "some error"),
|
||||||
|
"ErrorContains should return true")
|
||||||
|
assert.True(mockAssert.ErrorContains(err, "another error"),
|
||||||
|
"ErrorContains should return true")
|
||||||
|
}
|
||||||
|
|
||||||
func TestEqualErrorWrapper(t *testing.T) {
|
func TestEqualErrorWrapper(t *testing.T) {
|
||||||
assert := New(t)
|
assert := New(t)
|
||||||
mockAssert := New(new(testing.T))
|
mockAssert := New(new(testing.T))
|
||||||
|
@ -503,7 +546,7 @@ func TestRegexpWrapper(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
False(t, assert.Regexp(tc.rx, tc.str), "Expected \"%s\" to not match \"%s\"", tc.rx, tc.str)
|
False(t, assert.Regexp(tc.rx, tc.str), "Expected %q to not match %q", tc.rx, tc.str)
|
||||||
False(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
|
False(t, assert.Regexp(regexp.MustCompile(tc.rx), tc.str))
|
||||||
True(t, assert.NotRegexp(tc.rx, tc.str))
|
True(t, assert.NotRegexp(tc.rx, tc.str))
|
||||||
True(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))
|
True(t, assert.NotRegexp(regexp.MustCompile(tc.rx), tc.str))
|
||||||
|
@ -609,3 +652,101 @@ func TestJSONEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
|
||||||
t.Error("JSONEq should return false")
|
t.Error("JSONEq should return false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_EqualYAMLString(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if !assert.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) {
|
||||||
|
t.Error("YAMLEq should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_EquivalentButNotEqual(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if !assert.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||||
|
t.Error("YAMLEq should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_HashOfArraysAndHashes(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
expected := `
|
||||||
|
numeric: 1.5
|
||||||
|
array:
|
||||||
|
- foo: bar
|
||||||
|
- 1
|
||||||
|
- "string"
|
||||||
|
- ["nested", "array", 5.5]
|
||||||
|
hash:
|
||||||
|
nested: hash
|
||||||
|
nested_slice: [this, is, nested]
|
||||||
|
string: "foo"
|
||||||
|
`
|
||||||
|
|
||||||
|
actual := `
|
||||||
|
numeric: 1.5
|
||||||
|
hash:
|
||||||
|
nested: hash
|
||||||
|
nested_slice: [this, is, nested]
|
||||||
|
string: "foo"
|
||||||
|
array:
|
||||||
|
- foo: bar
|
||||||
|
- 1
|
||||||
|
- "string"
|
||||||
|
- ["nested", "array", 5.5]
|
||||||
|
`
|
||||||
|
if !assert.YAMLEq(expected, actual) {
|
||||||
|
t.Error("YAMLEq should return true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_Array(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if !assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) {
|
||||||
|
t.Error("YAMLEq should return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_HashAndArrayNotEquivalent(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) {
|
||||||
|
t.Error("YAMLEq should return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_HashesNotEquivalent(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if assert.YAMLEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) {
|
||||||
|
t.Error("YAMLEq should return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ActualIsSimpleString(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if assert.YAMLEq(`{"foo": "bar"}`, "Simple String") {
|
||||||
|
t.Error("YAMLEq should return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ExpectedIsSimpleString(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if assert.YAMLEq("Simple String", `{"foo": "bar", "hello": "world"}`) {
|
||||||
|
t.Error("YAMLEq should return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ExpectedAndActualSimpleString(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if !assert.YAMLEq("Simple String", "Simple String") {
|
||||||
|
t.Error("YAMLEq should return true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
|
||||||
|
assert := New(new(testing.T))
|
||||||
|
if assert.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`) {
|
||||||
|
t.Error("YAMLEq should return false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,10 +12,11 @@ import (
|
||||||
// an error if building a new request fails.
|
// an error if building a new request fails.
|
||||||
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
|
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
req, err := http.NewRequest(method, url, http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
req.URL.RawQuery = values.Encode()
|
||||||
handler(w, req)
|
handler(w, req)
|
||||||
return w.Code, nil
|
return w.Code, nil
|
||||||
}
|
}
|
||||||
|
@ -26,15 +27,17 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
code, err := httpCode(handler, method, url, values)
|
code, err := httpCode(handler, method, url, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
|
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
|
||||||
if !isSuccessCode {
|
if !isSuccessCode {
|
||||||
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
|
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return isSuccessCode
|
return isSuccessCode
|
||||||
|
@ -46,15 +49,17 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
code, err := httpCode(handler, method, url, values)
|
code, err := httpCode(handler, method, url, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
||||||
if !isRedirectCode {
|
if !isRedirectCode {
|
||||||
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
|
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return isRedirectCode
|
return isRedirectCode
|
||||||
|
@ -66,25 +71,52 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
code, err := httpCode(handler, method, url, values)
|
code, err := httpCode(handler, method, url, values)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isErrorCode := code >= http.StatusBadRequest
|
isErrorCode := code >= http.StatusBadRequest
|
||||||
if !isErrorCode {
|
if !isErrorCode {
|
||||||
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
|
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return isErrorCode
|
return isErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPStatusCode asserts that a specified handler returns a specified status code.
|
||||||
|
//
|
||||||
|
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
code, err := httpCode(handler, method, url, values)
|
||||||
|
if err != nil {
|
||||||
|
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
successful := code == statuscode
|
||||||
|
if !successful {
|
||||||
|
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return successful
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
||||||
// empty string if building a new request fails.
|
// empty string if building a new request fails.
|
||||||
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
if len(values) > 0 {
|
||||||
|
url += "?" + values.Encode()
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(method, url, http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -95,15 +127,18 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s
|
||||||
// HTTPBodyContains asserts that a specified handler returns a
|
// HTTPBodyContains asserts that a specified handler returns a
|
||||||
// body that contains a string.
|
// body that contains a string.
|
||||||
//
|
//
|
||||||
// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
body := HTTPBody(handler, method, url, values)
|
body := HTTPBody(handler, method, url, values)
|
||||||
|
|
||||||
contains := strings.Contains(body, fmt.Sprint(str))
|
contains := strings.Contains(body, fmt.Sprint(str))
|
||||||
if !contains {
|
if !contains {
|
||||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
Fail(t, fmt.Sprintf("Expected response body for %q to contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return contains
|
return contains
|
||||||
|
@ -112,15 +147,18 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
|
||||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||||
// body that does not contain a string.
|
// body that does not contain a string.
|
||||||
//
|
//
|
||||||
// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||||
//
|
//
|
||||||
// Returns whether the assertion was successful (true) or not (false).
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
body := HTTPBody(handler, method, url, values)
|
body := HTTPBody(handler, method, url, values)
|
||||||
|
|
||||||
contains := strings.Contains(body, fmt.Sprint(str))
|
contains := strings.Contains(body, fmt.Sprint(str))
|
||||||
if contains {
|
if contains {
|
||||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
Fail(t, fmt.Sprintf("Expected response body for %q to NOT contain %q but found %q", url+"?"+values.Encode(), str, body), msgAndArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return !contains
|
return !contains
|
||||||
|
|
|
@ -2,6 +2,7 @@ package assert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -11,6 +12,12 @@ func httpOK(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func httpReadBody(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, _ = io.Copy(io.Discard, r.Body)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte("hello"))
|
||||||
|
}
|
||||||
|
|
||||||
func httpRedirect(w http.ResponseWriter, r *http.Request) {
|
func httpRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusTemporaryRedirect)
|
w.WriteHeader(http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +26,10 @@ func httpError(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func httpStatusCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHTTPSuccess(t *testing.T) {
|
func TestHTTPSuccess(t *testing.T) {
|
||||||
assert := New(t)
|
assert := New(t)
|
||||||
|
|
||||||
|
@ -30,17 +41,33 @@ func TestHTTPSuccess(t *testing.T) {
|
||||||
assert.Equal(HTTPSuccess(mockT2, httpRedirect, "GET", "/", nil), false)
|
assert.Equal(HTTPSuccess(mockT2, httpRedirect, "GET", "/", nil), false)
|
||||||
assert.True(mockT2.Failed())
|
assert.True(mockT2.Failed())
|
||||||
|
|
||||||
mockT3 := new(testing.T)
|
mockT3 := new(mockTestingT)
|
||||||
assert.Equal(HTTPSuccess(mockT3, httpError, "GET", "/", nil), false)
|
assert.Equal(HTTPSuccess(
|
||||||
|
mockT3, httpError, "GET", "/", nil,
|
||||||
|
"was not expecting a failure here",
|
||||||
|
), false)
|
||||||
assert.True(mockT3.Failed())
|
assert.True(mockT3.Failed())
|
||||||
|
assert.Contains(mockT3.errorString(), "was not expecting a failure here")
|
||||||
|
|
||||||
|
mockT4 := new(testing.T)
|
||||||
|
assert.Equal(HTTPSuccess(mockT4, httpStatusCode, "GET", "/", nil), false)
|
||||||
|
assert.True(mockT4.Failed())
|
||||||
|
|
||||||
|
mockT5 := new(testing.T)
|
||||||
|
assert.Equal(HTTPSuccess(mockT5, httpReadBody, "POST", "/", nil), true)
|
||||||
|
assert.False(mockT5.Failed())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPRedirect(t *testing.T) {
|
func TestHTTPRedirect(t *testing.T) {
|
||||||
assert := New(t)
|
assert := New(t)
|
||||||
|
|
||||||
mockT1 := new(testing.T)
|
mockT1 := new(mockTestingT)
|
||||||
assert.Equal(HTTPRedirect(mockT1, httpOK, "GET", "/", nil), false)
|
assert.Equal(HTTPRedirect(
|
||||||
|
mockT1, httpOK, "GET", "/", nil,
|
||||||
|
"was expecting a 3xx status code. Got 200.",
|
||||||
|
), false)
|
||||||
assert.True(mockT1.Failed())
|
assert.True(mockT1.Failed())
|
||||||
|
assert.Contains(mockT1.errorString(), "was expecting a 3xx status code. Got 200.")
|
||||||
|
|
||||||
mockT2 := new(testing.T)
|
mockT2 := new(testing.T)
|
||||||
assert.Equal(HTTPRedirect(mockT2, httpRedirect, "GET", "/", nil), true)
|
assert.Equal(HTTPRedirect(mockT2, httpRedirect, "GET", "/", nil), true)
|
||||||
|
@ -49,6 +76,10 @@ func TestHTTPRedirect(t *testing.T) {
|
||||||
mockT3 := new(testing.T)
|
mockT3 := new(testing.T)
|
||||||
assert.Equal(HTTPRedirect(mockT3, httpError, "GET", "/", nil), false)
|
assert.Equal(HTTPRedirect(mockT3, httpError, "GET", "/", nil), false)
|
||||||
assert.True(mockT3.Failed())
|
assert.True(mockT3.Failed())
|
||||||
|
|
||||||
|
mockT4 := new(testing.T)
|
||||||
|
assert.Equal(HTTPRedirect(mockT4, httpStatusCode, "GET", "/", nil), false)
|
||||||
|
assert.True(mockT4.Failed())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPError(t *testing.T) {
|
func TestHTTPError(t *testing.T) {
|
||||||
|
@ -58,13 +89,45 @@ func TestHTTPError(t *testing.T) {
|
||||||
assert.Equal(HTTPError(mockT1, httpOK, "GET", "/", nil), false)
|
assert.Equal(HTTPError(mockT1, httpOK, "GET", "/", nil), false)
|
||||||
assert.True(mockT1.Failed())
|
assert.True(mockT1.Failed())
|
||||||
|
|
||||||
mockT2 := new(testing.T)
|
mockT2 := new(mockTestingT)
|
||||||
assert.Equal(HTTPError(mockT2, httpRedirect, "GET", "/", nil), false)
|
assert.Equal(HTTPError(
|
||||||
|
mockT2, httpRedirect, "GET", "/", nil,
|
||||||
|
"Expected this request to error out. But it didn't",
|
||||||
|
), false)
|
||||||
assert.True(mockT2.Failed())
|
assert.True(mockT2.Failed())
|
||||||
|
assert.Contains(mockT2.errorString(), "Expected this request to error out. But it didn't")
|
||||||
|
|
||||||
mockT3 := new(testing.T)
|
mockT3 := new(testing.T)
|
||||||
assert.Equal(HTTPError(mockT3, httpError, "GET", "/", nil), true)
|
assert.Equal(HTTPError(mockT3, httpError, "GET", "/", nil), true)
|
||||||
assert.False(mockT3.Failed())
|
assert.False(mockT3.Failed())
|
||||||
|
|
||||||
|
mockT4 := new(testing.T)
|
||||||
|
assert.Equal(HTTPError(mockT4, httpStatusCode, "GET", "/", nil), false)
|
||||||
|
assert.True(mockT4.Failed())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPStatusCode(t *testing.T) {
|
||||||
|
assert := New(t)
|
||||||
|
|
||||||
|
mockT1 := new(testing.T)
|
||||||
|
assert.Equal(HTTPStatusCode(mockT1, httpOK, "GET", "/", nil, http.StatusSwitchingProtocols), false)
|
||||||
|
assert.True(mockT1.Failed())
|
||||||
|
|
||||||
|
mockT2 := new(testing.T)
|
||||||
|
assert.Equal(HTTPStatusCode(mockT2, httpRedirect, "GET", "/", nil, http.StatusSwitchingProtocols), false)
|
||||||
|
assert.True(mockT2.Failed())
|
||||||
|
|
||||||
|
mockT3 := new(mockTestingT)
|
||||||
|
assert.Equal(HTTPStatusCode(
|
||||||
|
mockT3, httpError, "GET", "/", nil, http.StatusSwitchingProtocols,
|
||||||
|
"Expected the status code to be %d", http.StatusSwitchingProtocols,
|
||||||
|
), false)
|
||||||
|
assert.True(mockT3.Failed())
|
||||||
|
assert.Contains(mockT3.errorString(), "Expected the status code to be 101")
|
||||||
|
|
||||||
|
mockT4 := new(testing.T)
|
||||||
|
assert.Equal(HTTPStatusCode(mockT4, httpStatusCode, "GET", "/", nil, http.StatusSwitchingProtocols), true)
|
||||||
|
assert.False(mockT4.Failed())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPStatusesWrapper(t *testing.T) {
|
func TestHTTPStatusesWrapper(t *testing.T) {
|
||||||
|
@ -86,20 +149,55 @@ func TestHTTPStatusesWrapper(t *testing.T) {
|
||||||
|
|
||||||
func httpHelloName(w http.ResponseWriter, r *http.Request) {
|
func httpHelloName(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.FormValue("name")
|
name := r.FormValue("name")
|
||||||
w.Write([]byte(fmt.Sprintf("Hello, %s!", name)))
|
_, _ = fmt.Fprintf(w, "Hello, %s!", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPRequestWithNoParams(t *testing.T) {
|
||||||
|
var got *http.Request
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
got = r
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
True(t, HTTPSuccess(t, handler, "GET", "/url", nil))
|
||||||
|
|
||||||
|
Empty(t, got.URL.Query())
|
||||||
|
Equal(t, "/url", got.URL.RequestURI())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPRequestWithParams(t *testing.T) {
|
||||||
|
var got *http.Request
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
got = r
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
params := url.Values{}
|
||||||
|
params.Add("id", "12345")
|
||||||
|
|
||||||
|
True(t, HTTPSuccess(t, handler, "GET", "/url", params))
|
||||||
|
|
||||||
|
Equal(t, url.Values{"id": []string{"12345"}}, got.URL.Query())
|
||||||
|
Equal(t, "/url?id=12345", got.URL.String())
|
||||||
|
Equal(t, "/url?id=12345", got.URL.RequestURI())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpBody(t *testing.T) {
|
func TestHttpBody(t *testing.T) {
|
||||||
assert := New(t)
|
assert := New(t)
|
||||||
mockT := new(testing.T)
|
mockT := new(mockTestingT)
|
||||||
|
|
||||||
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
||||||
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
assert.True(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
||||||
assert.False(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
assert.False(HTTPBodyContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
||||||
|
|
||||||
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
||||||
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
assert.False(HTTPBodyNotContains(
|
||||||
|
mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World",
|
||||||
|
"Expected the request body to not contain 'World'. But it did.",
|
||||||
|
))
|
||||||
assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
||||||
|
assert.Contains(mockT.errorString(), "Expected the request body to not contain 'World'. But it did.")
|
||||||
|
|
||||||
|
assert.True(HTTPBodyContains(mockT, httpReadBody, "GET", "/", nil, "hello"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHttpBodyWrappers(t *testing.T) {
|
func TestHttpBodyWrappers(t *testing.T) {
|
||||||
|
@ -113,5 +211,4 @@ func TestHttpBodyWrappers(t *testing.T) {
|
||||||
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
|
||||||
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
|
||||||
assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
// This package exists just to isolate tests that reference the [unsafe] package.
|
||||||
|
//
|
||||||
|
// The tests in this package are totally safe.
|
||||||
|
package unsafetests
|
|
@ -0,0 +1,34 @@
|
||||||
|
package unsafetests_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ignoreTestingT struct{}
|
||||||
|
|
||||||
|
var _ assert.TestingT = ignoreTestingT{}
|
||||||
|
|
||||||
|
func (ignoreTestingT) Helper() {}
|
||||||
|
|
||||||
|
func (ignoreTestingT) Errorf(format string, args ...interface{}) {
|
||||||
|
// Run the formatting, but ignore the result
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
_ = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnsafePointers(t *testing.T) {
|
||||||
|
var ignore ignoreTestingT
|
||||||
|
|
||||||
|
assert.True(t, assert.Nil(t, unsafe.Pointer(nil), "unsafe.Pointer(nil) is nil"))
|
||||||
|
assert.False(t, assert.NotNil(ignore, unsafe.Pointer(nil), "unsafe.Pointer(nil) is nil"))
|
||||||
|
|
||||||
|
assert.True(t, assert.Nil(t, unsafe.Pointer((*int)(nil)), "unsafe.Pointer((*int)(nil)) is nil"))
|
||||||
|
assert.False(t, assert.NotNil(ignore, unsafe.Pointer((*int)(nil)), "unsafe.Pointer((*int)(nil)) is nil"))
|
||||||
|
|
||||||
|
assert.False(t, assert.Nil(ignore, unsafe.Pointer(new(int)), "unsafe.Pointer(new(int)) is NOT nil"))
|
||||||
|
assert.True(t, assert.NotNil(t, unsafe.Pointer(new(int)), "unsafe.Pointer(new(int)) is NOT nil"))
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
//go:build testify_yaml_custom && !testify_yaml_fail && !testify_yaml_default
|
||||||
|
// +build testify_yaml_custom,!testify_yaml_fail,!testify_yaml_default
|
||||||
|
|
||||||
|
// Package yaml is an implementation of YAML functions that calls a pluggable implementation.
|
||||||
|
//
|
||||||
|
// This implementation is selected with the testify_yaml_custom build tag.
|
||||||
|
//
|
||||||
|
// go test -tags testify_yaml_custom
|
||||||
|
//
|
||||||
|
// This implementation can be used at build time to replace the default implementation
|
||||||
|
// to avoid linking with [gopkg.in/yaml.v3].
|
||||||
|
//
|
||||||
|
// In your test package:
|
||||||
|
//
|
||||||
|
// import assertYaml "github.com/stretchr/testify/assert/yaml"
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// assertYaml.Unmarshal = func (in []byte, out interface{}) error {
|
||||||
|
// // ...
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
var Unmarshal func(in []byte, out interface{}) error
|
|
@ -0,0 +1,37 @@
|
||||||
|
//go:build !testify_yaml_fail && !testify_yaml_custom
|
||||||
|
// +build !testify_yaml_fail,!testify_yaml_custom
|
||||||
|
|
||||||
|
// Package yaml is just an indirection to handle YAML deserialization.
|
||||||
|
//
|
||||||
|
// This package is just an indirection that allows the builder to override the
|
||||||
|
// indirection with an alternative implementation of this package that uses
|
||||||
|
// another implementation of YAML deserialization. This allows to not either not
|
||||||
|
// use YAML deserialization at all, or to use another implementation than
|
||||||
|
// [gopkg.in/yaml.v3] (for example for license compatibility reasons, see [PR #1120]).
|
||||||
|
//
|
||||||
|
// Alternative implementations are selected using build tags:
|
||||||
|
//
|
||||||
|
// - testify_yaml_fail: [Unmarshal] always fails with an error
|
||||||
|
// - testify_yaml_custom: [Unmarshal] is a variable. Caller must initialize it
|
||||||
|
// before calling any of [github.com/stretchr/testify/assert.YAMLEq] or
|
||||||
|
// [github.com/stretchr/testify/assert.YAMLEqf].
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// go test -tags testify_yaml_fail
|
||||||
|
//
|
||||||
|
// You can check with "go list" which implementation is linked:
|
||||||
|
//
|
||||||
|
// go list -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
|
||||||
|
// go list -tags testify_yaml_fail -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
|
||||||
|
// go list -tags testify_yaml_custom -f '{{.Imports}}' github.com/stretchr/testify/assert/yaml
|
||||||
|
//
|
||||||
|
// [PR #1120]: https://github.com/stretchr/testify/pull/1120
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import goyaml "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
// Unmarshal is just a wrapper of [gopkg.in/yaml.v3.Unmarshal].
|
||||||
|
func Unmarshal(in []byte, out interface{}) error {
|
||||||
|
return goyaml.Unmarshal(in, out)
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
//go:build testify_yaml_fail && !testify_yaml_custom && !testify_yaml_default
|
||||||
|
// +build testify_yaml_fail,!testify_yaml_custom,!testify_yaml_default
|
||||||
|
|
||||||
|
// Package yaml is an implementation of YAML functions that always fail.
|
||||||
|
//
|
||||||
|
// This implementation can be used at build time to replace the default implementation
|
||||||
|
// to avoid linking with [gopkg.in/yaml.v3]:
|
||||||
|
//
|
||||||
|
// go test -tags testify_yaml_fail
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var errNotImplemented = errors.New("YAML functions are not available (see https://pkg.go.dev/github.com/stretchr/testify/assert/yaml)")
|
||||||
|
|
||||||
|
func Unmarshal([]byte, interface{}) error {
|
||||||
|
return errNotImplemented
|
||||||
|
}
|
17
doc.go
17
doc.go
|
@ -4,19 +4,12 @@
|
||||||
//
|
//
|
||||||
// The assert package provides a comprehensive set of assertion functions that tie in to the Go testing system.
|
// The assert package provides a comprehensive set of assertion functions that tie in to the Go testing system.
|
||||||
//
|
//
|
||||||
// The http package contains tools to make it easier to test http activity using the Go testing system.
|
|
||||||
//
|
|
||||||
// The mock package provides a system by which it is possible to mock your objects and verify calls are happening as expected.
|
// The mock package provides a system by which it is possible to mock your objects and verify calls are happening as expected.
|
||||||
//
|
//
|
||||||
// The suite package provides a basic structure for using structs as testing suites, and methods on those structs as tests. It includes setup/teardown functionality in the way of interfaces.
|
// The suite package provides a basic structure for using structs as testing suites, and methods on those structs as tests. It includes setup/teardown functionality in the way of interfaces.
|
||||||
|
//
|
||||||
|
// A [golangci-lint] compatible linter for testify is available called [testifylint].
|
||||||
|
//
|
||||||
|
// [golangci-lint]: https://golangci-lint.run/
|
||||||
|
// [testifylint]: https://github.com/Antonboom/testifylint
|
||||||
package testify
|
package testify
|
||||||
|
|
||||||
// blank imports help docs.
|
|
||||||
import (
|
|
||||||
// assert package
|
|
||||||
_ "github.com/stretchr/testify/assert"
|
|
||||||
// http package
|
|
||||||
_ "github.com/stretchr/testify/http"
|
|
||||||
// mock package
|
|
||||||
_ "github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
module github.com/stretchr/testify
|
||||||
|
|
||||||
|
// This should match the minimum supported version that is tested in
|
||||||
|
// .github/workflows/main.yml
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
github.com/pmezard/go-difflib v1.0.0
|
||||||
|
github.com/stretchr/objx v0.5.2
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Break dependency cycle with objx.
|
||||||
|
// See https://github.com/stretchr/objx/pull/140
|
||||||
|
exclude github.com/stretchr/testify v1.8.2
|
|
@ -0,0 +1,18 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,2 +1,2 @@
|
||||||
// Package http DEPRECATED USE net/http/httptest
|
// Deprecated: Use [net/http/httptest] instead.
|
||||||
package http
|
package http
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestResponseWriter DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
|
// Deprecated: Use [net/http/httptest] instead.
|
||||||
type TestResponseWriter struct {
|
type TestResponseWriter struct {
|
||||||
|
|
||||||
// StatusCode is the last int written by the call to WriteHeader(int)
|
// StatusCode is the last int written by the call to WriteHeader(int)
|
||||||
|
@ -17,7 +17,7 @@ type TestResponseWriter struct {
|
||||||
header http.Header
|
header http.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
|
// Deprecated: Use [net/http/httptest] instead.
|
||||||
func (rw *TestResponseWriter) Header() http.Header {
|
func (rw *TestResponseWriter) Header() http.Header {
|
||||||
|
|
||||||
if rw.header == nil {
|
if rw.header == nil {
|
||||||
|
@ -27,7 +27,7 @@ func (rw *TestResponseWriter) Header() http.Header {
|
||||||
return rw.header
|
return rw.header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
|
// Deprecated: Use [net/http/httptest] instead.
|
||||||
func (rw *TestResponseWriter) Write(bytes []byte) (int, error) {
|
func (rw *TestResponseWriter) Write(bytes []byte) (int, error) {
|
||||||
|
|
||||||
// assume 200 success if no header has been set
|
// assume 200 success if no header has been set
|
||||||
|
@ -36,14 +36,14 @@ func (rw *TestResponseWriter) Write(bytes []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add these bytes to the output string
|
// add these bytes to the output string
|
||||||
rw.Output = rw.Output + string(bytes)
|
rw.Output += string(bytes)
|
||||||
|
|
||||||
// return normal values
|
// return normal values
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteHeader DEPRECATED: We recommend you use http://golang.org/pkg/net/http/httptest instead.
|
// Deprecated: Use [net/http/httptest] instead.
|
||||||
func (rw *TestResponseWriter) WriteHeader(i int) {
|
func (rw *TestResponseWriter) WriteHeader(i int) {
|
||||||
rw.StatusCode = i
|
rw.StatusCode = i
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRoundTripper DEPRECATED USE net/http/httptest
|
// Deprecated: Use [net/http/httptest] instead.
|
||||||
type TestRoundTripper struct {
|
type TestRoundTripper struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoundTrip DEPRECATED USE net/http/httptest
|
// Deprecated: Use [net/http/httptest] instead.
|
||||||
func (t *TestRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (t *TestRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
args := t.Called(req)
|
args := t.Called(req)
|
||||||
return args.Get(0).(*http.Response), args.Error(1)
|
return args.Get(0).(*http.Response), args.Error(1)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Package mock provides a system by which it is possible to mock your objects
|
// Package mock provides a system by which it is possible to mock your objects
|
||||||
// and verify calls are happening as expected.
|
// and verify calls are happening as expected.
|
||||||
//
|
//
|
||||||
// Example Usage
|
// # Example Usage
|
||||||
//
|
//
|
||||||
// The mock package provides an object, Mock, that tracks activity on another object. It is usually
|
// The mock package provides an object, Mock, that tracks activity on another object. It is usually
|
||||||
// embedded into a test object as shown below:
|
// embedded into a test object as shown below:
|
||||||
|
|
677
mock/mock.go
677
mock/mock.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,9 @@
|
||||||
package testify
|
package testify
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestImports(t *testing.T) {
|
func TestImports(t *testing.T) {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// Package require implements the same assertions as the `assert` package but
|
// Package require implements the same assertions as the `assert` package but
|
||||||
// stops test execution when a test fails.
|
// stops test execution when a test fails.
|
||||||
//
|
//
|
||||||
// Example Usage
|
// # Example Usage
|
||||||
//
|
//
|
||||||
// The following is a complete example using require in a standard test function:
|
// The following is a complete example using require in a standard test function:
|
||||||
|
//
|
||||||
// import (
|
// import (
|
||||||
// "testing"
|
// "testing"
|
||||||
// "github.com/stretchr/testify/require"
|
// "github.com/stretchr/testify/require"
|
||||||
|
@ -18,10 +19,12 @@
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Assertions
|
// # Assertions
|
||||||
//
|
//
|
||||||
// The `require` package have same global functions as in the `assert` package,
|
// The `require` package have same global functions as in the `assert` package,
|
||||||
// but instead of returning a boolean result they call `t.FailNow()`.
|
// but instead of returning a boolean result they call `t.FailNow()`.
|
||||||
|
// A consequence of this is that it must be called from the goroutine running
|
||||||
|
// the test function, not from other goroutines created during the test.
|
||||||
//
|
//
|
||||||
// Every assertion function also takes an optional string message as the final argument,
|
// Every assertion function also takes an optional string message as the final argument,
|
||||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||||
|
|
|
@ -13,4 +13,4 @@ func New(t TestingT) *Assertions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl -include-format-funcs
|
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs"
|
||||||
|
|
|
@ -196,6 +196,18 @@ func TestErrorWrapper(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestErrorContainsWrapper(t *testing.T) {
|
||||||
|
require := New(t)
|
||||||
|
require.ErrorContains(errors.New("some error: another error"), "some error")
|
||||||
|
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
mockRequire.ErrorContains(errors.New("some error: another error"), "different error")
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEqualErrorWrapper(t *testing.T) {
|
func TestEqualErrorWrapper(t *testing.T) {
|
||||||
require := New(t)
|
require := New(t)
|
||||||
require.EqualError(errors.New("some error"), "some error")
|
require.EqualError(errors.New("some error"), "some error")
|
||||||
|
@ -383,3 +395,129 @@ func TestJSONEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
|
||||||
t.Error("Check should fail")
|
t.Error("Check should fail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_EqualYAMLString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_EquivalentButNotEqual(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_HashOfArraysAndHashes(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
expected := `
|
||||||
|
numeric: 1.5
|
||||||
|
array:
|
||||||
|
- foo: bar
|
||||||
|
- 1
|
||||||
|
- "string"
|
||||||
|
- ["nested", "array", 5.5]
|
||||||
|
hash:
|
||||||
|
nested: hash
|
||||||
|
nested_slice: [this, is, nested]
|
||||||
|
string: "foo"
|
||||||
|
`
|
||||||
|
|
||||||
|
actual := `
|
||||||
|
numeric: 1.5
|
||||||
|
hash:
|
||||||
|
nested: hash
|
||||||
|
nested_slice: [this, is, nested]
|
||||||
|
string: "foo"
|
||||||
|
array:
|
||||||
|
- foo: bar
|
||||||
|
- 1
|
||||||
|
- "string"
|
||||||
|
- ["nested", "array", 5.5]
|
||||||
|
`
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(expected, actual)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_Array(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_HashAndArrayNotEquivalent(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_HashesNotEquivalent(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ActualIsSimpleString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(`{"foo": "bar"}`, "Simple String")
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ExpectedIsSimpleString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq("Simple String", `{"foo": "bar", "hello": "world"}`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ExpectedAndActualSimpleString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq("Simple String", "Simple String")
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEqWrapper_ArraysOfDifferentOrder(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
mockRequire := New(mockT)
|
||||||
|
|
||||||
|
mockRequire.YAMLEq(`["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1829
require/require.go
1829
require/require.go
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{{.Comment}}
|
{{ replace .Comment "assert." "require."}}
|
||||||
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
|
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
|
||||||
if !assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) {
|
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||||
|
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
||||||
{{.CommentWithoutT "a"}}
|
{{.CommentWithoutT "a"}}
|
||||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
|
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
|
||||||
|
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||||
{{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
{{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,24 @@ type TestingT interface {
|
||||||
FailNow()
|
FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl -include-format-funcs
|
type tHelper = interface {
|
||||||
|
Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
|
||||||
|
// for table driven tests.
|
||||||
|
type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{})
|
||||||
|
|
||||||
|
// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
|
||||||
|
// for table driven tests.
|
||||||
|
type ValueAssertionFunc func(TestingT, interface{}, ...interface{})
|
||||||
|
|
||||||
|
// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
|
||||||
|
// for table driven tests.
|
||||||
|
type BoolAssertionFunc func(TestingT, bool, ...interface{})
|
||||||
|
|
||||||
|
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
|
||||||
|
// for table driven tests.
|
||||||
|
type ErrorAssertionFunc func(TestingT, error, ...interface{})
|
||||||
|
|
||||||
|
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs"
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package require
|
package require
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AssertionTesterInterface defines an interface to be used for testing assertion methods
|
// AssertionTesterInterface defines an interface to be used for testing assertion methods
|
||||||
|
@ -207,6 +210,17 @@ func TestError(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestErrorContains(t *testing.T) {
|
||||||
|
|
||||||
|
ErrorContains(t, errors.New("some error: another error"), "some error")
|
||||||
|
|
||||||
|
mockT := new(MockT)
|
||||||
|
ErrorContains(mockT, errors.New("some error"), "different error")
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEqualError(t *testing.T) {
|
func TestEqualError(t *testing.T) {
|
||||||
|
|
||||||
EqualError(t, errors.New("some error"), "some error")
|
EqualError(t, errors.New("some error"), "some error")
|
||||||
|
@ -367,3 +381,332 @@ func TestJSONEq_ArraysOfDifferentOrder(t *testing.T) {
|
||||||
t.Error("Check should fail")
|
t.Error("Check should fail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_EqualYAMLString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_EquivalentButNotEqual(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_HashOfArraysAndHashes(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
expected := `
|
||||||
|
numeric: 1.5
|
||||||
|
array:
|
||||||
|
- foo: bar
|
||||||
|
- 1
|
||||||
|
- "string"
|
||||||
|
- ["nested", "array", 5.5]
|
||||||
|
hash:
|
||||||
|
nested: hash
|
||||||
|
nested_slice: [this, is, nested]
|
||||||
|
string: "foo"
|
||||||
|
`
|
||||||
|
|
||||||
|
actual := `
|
||||||
|
numeric: 1.5
|
||||||
|
hash:
|
||||||
|
nested: hash
|
||||||
|
nested_slice: [this, is, nested]
|
||||||
|
string: "foo"
|
||||||
|
array:
|
||||||
|
- foo: bar
|
||||||
|
- 1
|
||||||
|
- "string"
|
||||||
|
- ["nested", "array", 5.5]
|
||||||
|
`
|
||||||
|
YAMLEq(mockT, expected, actual)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_Array(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`)
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_HashAndArrayNotEquivalent(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_HashesNotEquivalent(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, `{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_ActualIsSimpleString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, `{"foo": "bar"}`, "Simple String")
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_ExpectedIsSimpleString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, "Simple String", `{"foo": "bar", "hello": "world"}`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_ExpectedAndActualSimpleString(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, "Simple String", "Simple String")
|
||||||
|
if mockT.Failed {
|
||||||
|
t.Error("Check should pass")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestYAMLEq_ArraysOfDifferentOrder(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
YAMLEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`)
|
||||||
|
if !mockT.Failed {
|
||||||
|
t.Error("Check should fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleComparisonAssertionFunc() {
|
||||||
|
t := &testing.T{} // provided by test
|
||||||
|
|
||||||
|
adder := func(x, y int) int {
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
expect int
|
||||||
|
assertion ComparisonAssertionFunc
|
||||||
|
}{
|
||||||
|
{"2+2=4", args{2, 2}, 4, Equal},
|
||||||
|
{"2+2!=5", args{2, 2}, 5, NotEqual},
|
||||||
|
{"2+3==5", args{2, 3}, 5, Exactly},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.assertion(t, tt.expect, adder(tt.args.x, tt.args.y))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComparisonAssertionFunc(t *testing.T) {
|
||||||
|
type iface interface {
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expect interface{}
|
||||||
|
got interface{}
|
||||||
|
assertion ComparisonAssertionFunc
|
||||||
|
}{
|
||||||
|
{"implements", (*iface)(nil), t, Implements},
|
||||||
|
{"isType", (*testing.T)(nil), t, IsType},
|
||||||
|
{"equal", t, t, Equal},
|
||||||
|
{"equalValues", t, t, EqualValues},
|
||||||
|
{"exactly", t, t, Exactly},
|
||||||
|
{"notEqual", t, nil, NotEqual},
|
||||||
|
{"NotEqualValues", t, nil, NotEqualValues},
|
||||||
|
{"notContains", []int{1, 2, 3}, 4, NotContains},
|
||||||
|
{"subset", []int{1, 2, 3, 4}, []int{2, 3}, Subset},
|
||||||
|
{"notSubset", []int{1, 2, 3, 4}, []int{0, 3}, NotSubset},
|
||||||
|
{"elementsMatch", []byte("abc"), []byte("bac"), ElementsMatch},
|
||||||
|
{"regexp", "^t.*y$", "testify", Regexp},
|
||||||
|
{"notRegexp", "^t.*y$", "Testify", NotRegexp},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.assertion(t, tt.expect, tt.got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleValueAssertionFunc() {
|
||||||
|
t := &testing.T{} // provided by test
|
||||||
|
|
||||||
|
dumbParse := func(input string) interface{} {
|
||||||
|
var x interface{}
|
||||||
|
json.Unmarshal([]byte(input), &x)
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
arg string
|
||||||
|
assertion ValueAssertionFunc
|
||||||
|
}{
|
||||||
|
{"true is not nil", "true", NotNil},
|
||||||
|
{"empty string is nil", "", Nil},
|
||||||
|
{"zero is not nil", "0", NotNil},
|
||||||
|
{"zero is zero", "0", Zero},
|
||||||
|
{"false is zero", "false", Zero},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.assertion(t, dumbParse(tt.arg))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueAssertionFunc(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value interface{}
|
||||||
|
assertion ValueAssertionFunc
|
||||||
|
}{
|
||||||
|
{"notNil", true, NotNil},
|
||||||
|
{"nil", nil, Nil},
|
||||||
|
{"empty", []int{}, Empty},
|
||||||
|
{"notEmpty", []int{1}, NotEmpty},
|
||||||
|
{"zero", false, Zero},
|
||||||
|
{"notZero", 42, NotZero},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.assertion(t, tt.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleBoolAssertionFunc() {
|
||||||
|
t := &testing.T{} // provided by test
|
||||||
|
|
||||||
|
isOkay := func(x int) bool {
|
||||||
|
return x >= 42
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
arg int
|
||||||
|
assertion BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{"-1 is bad", -1, False},
|
||||||
|
{"42 is good", 42, True},
|
||||||
|
{"41 is bad", 41, False},
|
||||||
|
{"45 is cool", 45, True},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.assertion(t, isOkay(tt.arg))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolAssertionFunc(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value bool
|
||||||
|
assertion BoolAssertionFunc
|
||||||
|
}{
|
||||||
|
{"true", true, True},
|
||||||
|
{"false", false, False},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.assertion(t, tt.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleErrorAssertionFunc() {
|
||||||
|
t := &testing.T{} // provided by test
|
||||||
|
|
||||||
|
dumbParseNum := func(input string, v interface{}) error {
|
||||||
|
return json.Unmarshal([]byte(input), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
arg string
|
||||||
|
assertion ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{"1.2 is number", "1.2", NoError},
|
||||||
|
{"1.2.3 not number", "1.2.3", Error},
|
||||||
|
{"true is not number", "true", Error},
|
||||||
|
{"3 is number", "3", NoError},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var x float64
|
||||||
|
tt.assertion(t, dumbParseNum(tt.arg, &x))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorAssertionFunc(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
assertion ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{"noError", nil, NoError},
|
||||||
|
{"error", errors.New("whoops"), Error},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.assertion(t, tt.err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventuallyWithTFalse(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
|
||||||
|
condition := func(collect *assert.CollectT) {
|
||||||
|
True(collect, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)
|
||||||
|
True(t, mockT.Failed, "Check should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventuallyWithTTrue(t *testing.T) {
|
||||||
|
mockT := new(MockT)
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
condition := func(collect *assert.CollectT) {
|
||||||
|
defer func() {
|
||||||
|
counter += 1
|
||||||
|
}()
|
||||||
|
True(collect, counter == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)
|
||||||
|
False(t, mockT.Failed, "Check should pass")
|
||||||
|
Equal(t, 2, counter, "Condition is expected to be called 2 times")
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
// or individual tests (depending on which interface(s) you
|
// or individual tests (depending on which interface(s) you
|
||||||
// implement).
|
// implement).
|
||||||
//
|
//
|
||||||
|
// The suite package does not support parallel tests. See [issue 934].
|
||||||
|
//
|
||||||
// A testing suite is usually built by first extending the built-in
|
// A testing suite is usually built by first extending the built-in
|
||||||
// suite functionality from suite.Suite in testify. Alternatively,
|
// suite functionality from suite.Suite in testify. Alternatively,
|
||||||
// you could reproduce that logic on your own if you wanted (you
|
// you could reproduce that logic on your own if you wanted (you
|
||||||
|
@ -29,6 +31,7 @@
|
||||||
// Suite object has assertion methods.
|
// Suite object has assertion methods.
|
||||||
//
|
//
|
||||||
// A crude example:
|
// A crude example:
|
||||||
|
//
|
||||||
// // Basic imports
|
// // Basic imports
|
||||||
// import (
|
// import (
|
||||||
// "testing"
|
// "testing"
|
||||||
|
@ -62,4 +65,6 @@
|
||||||
// func TestExampleTestSuite(t *testing.T) {
|
// func TestExampleTestSuite(t *testing.T) {
|
||||||
// suite.Run(t, new(ExampleTestSuite))
|
// suite.Run(t, new(ExampleTestSuite))
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
// [issue 934]: https://github.com/stretchr/testify/issues/934
|
||||||
package suite
|
package suite
|
||||||
|
|
|
@ -7,6 +7,7 @@ import "testing"
|
||||||
type TestingSuite interface {
|
type TestingSuite interface {
|
||||||
T() *testing.T
|
T() *testing.T
|
||||||
SetT(*testing.T)
|
SetT(*testing.T)
|
||||||
|
SetS(suite TestingSuite)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupAllSuite has a SetupSuite method, which will run before the
|
// SetupAllSuite has a SetupSuite method, which will run before the
|
||||||
|
@ -44,3 +45,22 @@ type BeforeTest interface {
|
||||||
type AfterTest interface {
|
type AfterTest interface {
|
||||||
AfterTest(suiteName, testName string)
|
AfterTest(suiteName, testName string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithStats implements HandleStats, a function that will be executed
|
||||||
|
// when a test suite is finished. The stats contain information about
|
||||||
|
// the execution of that suite and its tests.
|
||||||
|
type WithStats interface {
|
||||||
|
HandleStats(suiteName string, stats *SuiteInformation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupSubTest has a SetupSubTest method, which will run before each
|
||||||
|
// subtest in the suite.
|
||||||
|
type SetupSubTest interface {
|
||||||
|
SetupSubTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TearDownSubTest has a TearDownSubTest method, which will run after
|
||||||
|
// each subtest in the suite have been run.
|
||||||
|
type TearDownSubTest interface {
|
||||||
|
TearDownSubTest()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package suite
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// SuiteInformation stats stores stats for the whole suite execution.
|
||||||
|
type SuiteInformation struct {
|
||||||
|
Start, End time.Time
|
||||||
|
TestStats map[string]*TestInformation
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInformation stores information about the execution of each test.
|
||||||
|
type TestInformation struct {
|
||||||
|
TestName string
|
||||||
|
Start, End time.Time
|
||||||
|
Passed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSuiteInformation() *SuiteInformation {
|
||||||
|
testStats := make(map[string]*TestInformation)
|
||||||
|
|
||||||
|
return &SuiteInformation{
|
||||||
|
TestStats: testStats,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SuiteInformation) start(testName string) {
|
||||||
|
s.TestStats[testName] = &TestInformation{
|
||||||
|
TestName: testName,
|
||||||
|
Start: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SuiteInformation) end(testName string, passed bool) {
|
||||||
|
s.TestStats[testName].End = time.Now()
|
||||||
|
s.TestStats[testName].Passed = passed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SuiteInformation) Passed() bool {
|
||||||
|
for _, stats := range s.TestStats {
|
||||||
|
if !stats.Passed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package suite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPassedReturnsTrueWhenAllTestsPass(t *testing.T) {
|
||||||
|
sinfo := newSuiteInformation()
|
||||||
|
sinfo.TestStats = map[string]*TestInformation{
|
||||||
|
"Test1": {TestName: "Test1", Passed: true},
|
||||||
|
"Test2": {TestName: "Test2", Passed: true},
|
||||||
|
"Test3": {TestName: "Test3", Passed: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, sinfo.Passed())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassedReturnsFalseWhenSomeTestFails(t *testing.T) {
|
||||||
|
sinfo := newSuiteInformation()
|
||||||
|
sinfo.TestStats = map[string]*TestInformation{
|
||||||
|
"Test1": {TestName: "Test1", Passed: true},
|
||||||
|
"Test2": {TestName: "Test2", Passed: false},
|
||||||
|
"Test3": {TestName: "Test3", Passed: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.False(t, sinfo.Passed())
|
||||||
|
}
|
179
suite/suite.go
179
suite/suite.go
|
@ -6,7 +6,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime/debug"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -19,26 +22,43 @@ var matchMethod = flag.String("testify.m", "", "regular expression to select tes
|
||||||
// retrieving the current *testing.T context.
|
// retrieving the current *testing.T context.
|
||||||
type Suite struct {
|
type Suite struct {
|
||||||
*assert.Assertions
|
*assert.Assertions
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
require *require.Assertions
|
require *require.Assertions
|
||||||
t *testing.T
|
t *testing.T
|
||||||
|
|
||||||
|
// Parent suite to have access to the implemented methods of parent struct
|
||||||
|
s TestingSuite
|
||||||
}
|
}
|
||||||
|
|
||||||
// T retrieves the current *testing.T context.
|
// T retrieves the current *testing.T context.
|
||||||
func (suite *Suite) T() *testing.T {
|
func (suite *Suite) T() *testing.T {
|
||||||
|
suite.mu.RLock()
|
||||||
|
defer suite.mu.RUnlock()
|
||||||
return suite.t
|
return suite.t
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetT sets the current *testing.T context.
|
// SetT sets the current *testing.T context.
|
||||||
func (suite *Suite) SetT(t *testing.T) {
|
func (suite *Suite) SetT(t *testing.T) {
|
||||||
|
suite.mu.Lock()
|
||||||
|
defer suite.mu.Unlock()
|
||||||
suite.t = t
|
suite.t = t
|
||||||
suite.Assertions = assert.New(t)
|
suite.Assertions = assert.New(t)
|
||||||
suite.require = require.New(t)
|
suite.require = require.New(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetS needs to set the current test suite as parent
|
||||||
|
// to get access to the parent methods
|
||||||
|
func (suite *Suite) SetS(s TestingSuite) {
|
||||||
|
suite.s = s
|
||||||
|
}
|
||||||
|
|
||||||
// Require returns a require context for suite.
|
// Require returns a require context for suite.
|
||||||
func (suite *Suite) Require() *require.Assertions {
|
func (suite *Suite) Require() *require.Assertions {
|
||||||
|
suite.mu.Lock()
|
||||||
|
defer suite.mu.Unlock()
|
||||||
if suite.require == nil {
|
if suite.require == nil {
|
||||||
suite.require = require.New(suite.T())
|
panic("'Require' must not be called before 'Run' or 'SetT'")
|
||||||
}
|
}
|
||||||
return suite.require
|
return suite.require
|
||||||
}
|
}
|
||||||
|
@ -49,66 +69,172 @@ func (suite *Suite) Require() *require.Assertions {
|
||||||
// assert.Assertions with require.Assertions), this method is provided so you
|
// assert.Assertions with require.Assertions), this method is provided so you
|
||||||
// can call `suite.Assert().NoError()`.
|
// can call `suite.Assert().NoError()`.
|
||||||
func (suite *Suite) Assert() *assert.Assertions {
|
func (suite *Suite) Assert() *assert.Assertions {
|
||||||
|
suite.mu.Lock()
|
||||||
|
defer suite.mu.Unlock()
|
||||||
if suite.Assertions == nil {
|
if suite.Assertions == nil {
|
||||||
suite.Assertions = assert.New(suite.T())
|
panic("'Assert' must not be called before 'Run' or 'SetT'")
|
||||||
}
|
}
|
||||||
return suite.Assertions
|
return suite.Assertions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func recoverAndFailOnPanic(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
r := recover()
|
||||||
|
failOnPanic(t, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func failOnPanic(t *testing.T, r interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
if r != nil {
|
||||||
|
t.Errorf("test panicked: %v\n%s", r, debug.Stack())
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run provides suite functionality around golang subtests. It should be
|
||||||
|
// called in place of t.Run(name, func(t *testing.T)) in test suite code.
|
||||||
|
// The passed-in func will be executed as a subtest with a fresh instance of t.
|
||||||
|
// Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
|
||||||
|
func (suite *Suite) Run(name string, subtest func()) bool {
|
||||||
|
oldT := suite.T()
|
||||||
|
|
||||||
|
return oldT.Run(name, func(t *testing.T) {
|
||||||
|
suite.SetT(t)
|
||||||
|
defer suite.SetT(oldT)
|
||||||
|
|
||||||
|
defer recoverAndFailOnPanic(t)
|
||||||
|
|
||||||
|
if setupSubTest, ok := suite.s.(SetupSubTest); ok {
|
||||||
|
setupSubTest.SetupSubTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
|
||||||
|
defer tearDownSubTest.TearDownSubTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
subtest()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Run takes a testing suite and runs all of the tests attached
|
// Run takes a testing suite and runs all of the tests attached
|
||||||
// to it.
|
// to it.
|
||||||
func Run(t *testing.T, suite TestingSuite) {
|
func Run(t *testing.T, suite TestingSuite) {
|
||||||
|
defer recoverAndFailOnPanic(t)
|
||||||
|
|
||||||
suite.SetT(t)
|
suite.SetT(t)
|
||||||
|
suite.SetS(suite)
|
||||||
|
|
||||||
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
|
var suiteSetupDone bool
|
||||||
setupAllSuite.SetupSuite()
|
|
||||||
}
|
var stats *SuiteInformation
|
||||||
defer func() {
|
if _, ok := suite.(WithStats); ok {
|
||||||
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
stats = newSuiteInformation()
|
||||||
tearDownAllSuite.TearDownSuite()
|
}
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
methodFinder := reflect.TypeOf(suite)
|
|
||||||
tests := []testing.InternalTest{}
|
tests := []testing.InternalTest{}
|
||||||
for index := 0; index < methodFinder.NumMethod(); index++ {
|
methodFinder := reflect.TypeOf(suite)
|
||||||
method := methodFinder.Method(index)
|
suiteName := methodFinder.Elem().Name()
|
||||||
|
|
||||||
|
for i := 0; i < methodFinder.NumMethod(); i++ {
|
||||||
|
method := methodFinder.Method(i)
|
||||||
|
|
||||||
ok, err := methodFilter(method.Name)
|
ok, err := methodFilter(method.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
|
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if ok {
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !suiteSetupDone {
|
||||||
|
if stats != nil {
|
||||||
|
stats.Start = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
|
||||||
|
setupAllSuite.SetupSuite()
|
||||||
|
}
|
||||||
|
|
||||||
|
suiteSetupDone = true
|
||||||
|
}
|
||||||
|
|
||||||
test := testing.InternalTest{
|
test := testing.InternalTest{
|
||||||
Name: method.Name,
|
Name: method.Name,
|
||||||
F: func(t *testing.T) {
|
F: func(t *testing.T) {
|
||||||
parentT := suite.T()
|
parentT := suite.T()
|
||||||
suite.SetT(t)
|
suite.SetT(t)
|
||||||
|
defer recoverAndFailOnPanic(t)
|
||||||
|
defer func() {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
r := recover()
|
||||||
|
|
||||||
|
if stats != nil {
|
||||||
|
passed := !t.Failed() && r == nil
|
||||||
|
stats.end(method.Name, passed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterTestSuite, ok := suite.(AfterTest); ok {
|
||||||
|
afterTestSuite.AfterTest(suiteName, method.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
|
||||||
|
tearDownTestSuite.TearDownTest()
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.SetT(parentT)
|
||||||
|
failOnPanic(t, r)
|
||||||
|
}()
|
||||||
|
|
||||||
if setupTestSuite, ok := suite.(SetupTestSuite); ok {
|
if setupTestSuite, ok := suite.(SetupTestSuite); ok {
|
||||||
setupTestSuite.SetupTest()
|
setupTestSuite.SetupTest()
|
||||||
}
|
}
|
||||||
if beforeTestSuite, ok := suite.(BeforeTest); ok {
|
if beforeTestSuite, ok := suite.(BeforeTest); ok {
|
||||||
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
|
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
if afterTestSuite, ok := suite.(AfterTest); ok {
|
if stats != nil {
|
||||||
afterTestSuite.AfterTest(methodFinder.Elem().Name(), method.Name)
|
stats.start(method.Name)
|
||||||
}
|
}
|
||||||
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
|
|
||||||
tearDownTestSuite.TearDownTest()
|
|
||||||
}
|
|
||||||
suite.SetT(parentT)
|
|
||||||
}()
|
|
||||||
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
|
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tests = append(tests, test)
|
tests = append(tests, test)
|
||||||
}
|
}
|
||||||
|
if suiteSetupDone {
|
||||||
|
defer func() {
|
||||||
|
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
||||||
|
tearDownAllSuite.TearDownSuite()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if suiteWithStats, measureStats := suite.(WithStats); measureStats {
|
||||||
|
stats.End = time.Now()
|
||||||
|
suiteWithStats.HandleStats(suiteName, stats)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
runTests(t, tests)
|
runTests(t, tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filtering method according to set regular expression
|
||||||
|
// specified command-line argument -m
|
||||||
|
func methodFilter(name string) (bool, error) {
|
||||||
|
if ok, _ := regexp.MatchString("^Test", name); !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return regexp.MatchString(*matchMethod, name)
|
||||||
|
}
|
||||||
|
|
||||||
func runTests(t testing.TB, tests []testing.InternalTest) {
|
func runTests(t testing.TB, tests []testing.InternalTest) {
|
||||||
|
if len(tests) == 0 {
|
||||||
|
t.Log("warning: no tests to run")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
r, ok := t.(runner)
|
r, ok := t.(runner)
|
||||||
if !ok { // backwards compatibility with Go 1.6 and below
|
if !ok { // backwards compatibility with Go 1.6 and below
|
||||||
if !testing.RunTests(allTestsFilter, tests) {
|
if !testing.RunTests(allTestsFilter, tests) {
|
||||||
|
@ -122,15 +248,6 @@ func runTests(t testing.TB, tests []testing.InternalTest) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtering method according to set regular expression
|
|
||||||
// specified command-line argument -m
|
|
||||||
func methodFilter(name string) (bool, error) {
|
|
||||||
if ok, _ := regexp.MatchString("^Test", name); !ok {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return regexp.MatchString(*matchMethod, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
type runner interface {
|
type runner interface {
|
||||||
Run(name string, f func(t *testing.T)) bool
|
Run(name string, f func(t *testing.T)) bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package suite
|
package suite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"flag"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -16,20 +21,20 @@ import (
|
||||||
type SuiteRequireTwice struct{ Suite }
|
type SuiteRequireTwice struct{ Suite }
|
||||||
|
|
||||||
// TestSuiteRequireTwice checks for regressions of issue #149 where
|
// TestSuiteRequireTwice checks for regressions of issue #149 where
|
||||||
// suite.requirements was not initialised in suite.SetT()
|
// suite.requirements was not initialized in suite.SetT()
|
||||||
// A regression would result on these tests panicking rather than failing.
|
// A regression would result on these tests panicking rather than failing.
|
||||||
func TestSuiteRequireTwice(t *testing.T) {
|
func TestSuiteRequireTwice(t *testing.T) {
|
||||||
ok := testing.RunTests(
|
ok := testing.RunTests(
|
||||||
allTestsFilter,
|
allTestsFilter,
|
||||||
[]testing.InternalTest{{
|
[]testing.InternalTest{{
|
||||||
Name: "TestSuiteRequireTwice",
|
Name: t.Name() + "/SuiteRequireTwice",
|
||||||
F: func(t *testing.T) {
|
F: func(t *testing.T) {
|
||||||
suite := new(SuiteRequireTwice)
|
suite := new(SuiteRequireTwice)
|
||||||
Run(t, suite)
|
Run(t, suite)
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
)
|
)
|
||||||
assert.Equal(t, false, ok)
|
assert.False(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SuiteRequireTwice) TestRequireOne() {
|
func (s *SuiteRequireTwice) TestRequireOne() {
|
||||||
|
@ -42,6 +47,99 @@ func (s *SuiteRequireTwice) TestRequireTwo() {
|
||||||
r.Equal(1, 2)
|
r.Equal(1, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type panickingSuite struct {
|
||||||
|
Suite
|
||||||
|
panicInSetupSuite bool
|
||||||
|
panicInSetupTest bool
|
||||||
|
panicInBeforeTest bool
|
||||||
|
panicInTest bool
|
||||||
|
panicInAfterTest bool
|
||||||
|
panicInTearDownTest bool
|
||||||
|
panicInTearDownSuite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *panickingSuite) SetupSuite() {
|
||||||
|
if s.panicInSetupSuite {
|
||||||
|
panic("oops in setup suite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *panickingSuite) SetupTest() {
|
||||||
|
if s.panicInSetupTest {
|
||||||
|
panic("oops in setup test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *panickingSuite) BeforeTest(_, _ string) {
|
||||||
|
if s.panicInBeforeTest {
|
||||||
|
panic("oops in before test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *panickingSuite) Test() {
|
||||||
|
if s.panicInTest {
|
||||||
|
panic("oops in test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *panickingSuite) AfterTest(_, _ string) {
|
||||||
|
if s.panicInAfterTest {
|
||||||
|
panic("oops in after test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *panickingSuite) TearDownTest() {
|
||||||
|
if s.panicInTearDownTest {
|
||||||
|
panic("oops in tear down test")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *panickingSuite) TearDownSuite() {
|
||||||
|
if s.panicInTearDownSuite {
|
||||||
|
panic("oops in tear down suite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuiteRecoverPanic(t *testing.T) {
|
||||||
|
ok := true
|
||||||
|
panickingTests := []testing.InternalTest{
|
||||||
|
{
|
||||||
|
Name: t.Name() + "/InSetupSuite",
|
||||||
|
F: func(t *testing.T) { Run(t, &panickingSuite{panicInSetupSuite: true}) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: t.Name() + "/InSetupTest",
|
||||||
|
F: func(t *testing.T) { Run(t, &panickingSuite{panicInSetupTest: true}) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: t.Name() + "InBeforeTest",
|
||||||
|
F: func(t *testing.T) { Run(t, &panickingSuite{panicInBeforeTest: true}) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: t.Name() + "/InTest",
|
||||||
|
F: func(t *testing.T) { Run(t, &panickingSuite{panicInTest: true}) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: t.Name() + "/InAfterTest",
|
||||||
|
F: func(t *testing.T) { Run(t, &panickingSuite{panicInAfterTest: true}) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: t.Name() + "/InTearDownTest",
|
||||||
|
F: func(t *testing.T) { Run(t, &panickingSuite{panicInTearDownTest: true}) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: t.Name() + "/InTearDownSuite",
|
||||||
|
F: func(t *testing.T) { Run(t, &panickingSuite{panicInTearDownSuite: true}) },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
ok = testing.RunTests(allTestsFilter, panickingTests)
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
|
||||||
// This suite is intended to store values to make sure that only
|
// This suite is intended to store values to make sure that only
|
||||||
// testing-suite-related methods are run. It's also a fully
|
// testing-suite-related methods are run. It's also a fully
|
||||||
// functional example of a testing suite, using setup/teardown methods
|
// functional example of a testing suite, using setup/teardown methods
|
||||||
|
@ -59,7 +157,13 @@ type SuiteTester struct {
|
||||||
TearDownTestRunCount int
|
TearDownTestRunCount int
|
||||||
TestOneRunCount int
|
TestOneRunCount int
|
||||||
TestTwoRunCount int
|
TestTwoRunCount int
|
||||||
|
TestSubtestRunCount int
|
||||||
NonTestMethodRunCount int
|
NonTestMethodRunCount int
|
||||||
|
SetupSubTestRunCount int
|
||||||
|
TearDownSubTestRunCount int
|
||||||
|
|
||||||
|
SetupSubTestNames []string
|
||||||
|
TearDownSubTestNames []string
|
||||||
|
|
||||||
SuiteNameBefore []string
|
SuiteNameBefore []string
|
||||||
TestNameBefore []string
|
TestNameBefore []string
|
||||||
|
@ -71,15 +175,6 @@ type SuiteTester struct {
|
||||||
TimeAfter []time.Time
|
TimeAfter []time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type SuiteSkipTester struct {
|
|
||||||
// Include our basic suite logic.
|
|
||||||
Suite
|
|
||||||
|
|
||||||
// Keep counts of how many times each method is run.
|
|
||||||
SetupSuiteRunCount int
|
|
||||||
TearDownSuiteRunCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SetupSuite method will be run by testify once, at the very
|
// The SetupSuite method will be run by testify once, at the very
|
||||||
// start of the testing suite, before any tests are run.
|
// start of the testing suite, before any tests are run.
|
||||||
func (suite *SuiteTester) SetupSuite() {
|
func (suite *SuiteTester) SetupSuite() {
|
||||||
|
@ -98,21 +193,12 @@ func (suite *SuiteTester) AfterTest(suiteName, testName string) {
|
||||||
suite.TimeAfter = append(suite.TimeAfter, time.Now())
|
suite.TimeAfter = append(suite.TimeAfter, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SuiteSkipTester) SetupSuite() {
|
|
||||||
suite.SetupSuiteRunCount++
|
|
||||||
suite.T().Skip()
|
|
||||||
}
|
|
||||||
|
|
||||||
// The TearDownSuite method will be run by testify once, at the very
|
// The TearDownSuite method will be run by testify once, at the very
|
||||||
// end of the testing suite, after all tests have been run.
|
// end of the testing suite, after all tests have been run.
|
||||||
func (suite *SuiteTester) TearDownSuite() {
|
func (suite *SuiteTester) TearDownSuite() {
|
||||||
suite.TearDownSuiteRunCount++
|
suite.TearDownSuiteRunCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *SuiteSkipTester) TearDownSuite() {
|
|
||||||
suite.TearDownSuiteRunCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SetupTest method will be run before every test in the suite.
|
// The SetupTest method will be run before every test in the suite.
|
||||||
func (suite *SuiteTester) SetupTest() {
|
func (suite *SuiteTester) SetupTest() {
|
||||||
suite.SetupTestRunCount++
|
suite.SetupTestRunCount++
|
||||||
|
@ -153,6 +239,61 @@ func (suite *SuiteTester) NonTestMethod() {
|
||||||
suite.NonTestMethodRunCount++
|
suite.NonTestMethodRunCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *SuiteTester) TestSubtest() {
|
||||||
|
suite.TestSubtestRunCount++
|
||||||
|
|
||||||
|
for _, t := range []struct {
|
||||||
|
testName string
|
||||||
|
}{
|
||||||
|
{"first"},
|
||||||
|
{"second"},
|
||||||
|
} {
|
||||||
|
suiteT := suite.T()
|
||||||
|
suite.Run(t.testName, func() {
|
||||||
|
// We should get a different *testing.T for subtests, so that
|
||||||
|
// go test recognizes them as proper subtests for output formatting
|
||||||
|
// and running individual subtests
|
||||||
|
subTestT := suite.T()
|
||||||
|
suite.NotEqual(subTestT, suiteT)
|
||||||
|
})
|
||||||
|
suite.Equal(suiteT, suite.T())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SuiteTester) TearDownSubTest() {
|
||||||
|
suite.TearDownSubTestNames = append(suite.TearDownSubTestNames, suite.T().Name())
|
||||||
|
suite.TearDownSubTestRunCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SuiteTester) SetupSubTest() {
|
||||||
|
suite.SetupSubTestNames = append(suite.SetupSubTestNames, suite.T().Name())
|
||||||
|
suite.SetupSubTestRunCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
type SuiteSkipTester struct {
|
||||||
|
// Include our basic suite logic.
|
||||||
|
Suite
|
||||||
|
|
||||||
|
// Keep counts of how many times each method is run.
|
||||||
|
SetupSuiteRunCount int
|
||||||
|
TearDownSuiteRunCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SuiteSkipTester) SetupSuite() {
|
||||||
|
suite.SetupSuiteRunCount++
|
||||||
|
suite.T().Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SuiteSkipTester) TestNothing() {
|
||||||
|
// SetupSuite is only called when at least one test satisfies
|
||||||
|
// test filter. For this suite to be set up (and then tore down)
|
||||||
|
// it is necessary to add at least one test method.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SuiteSkipTester) TearDownSuite() {
|
||||||
|
suite.TearDownSuiteRunCount++
|
||||||
|
}
|
||||||
|
|
||||||
// TestRunSuite will be run by the 'go test' command, so within it, we
|
// TestRunSuite will be run by the 'go test' command, so within it, we
|
||||||
// can run our suite using the Run(*testing.T, TestingSuite) function.
|
// can run our suite using the Run(*testing.T, TestingSuite) function.
|
||||||
func TestRunSuite(t *testing.T) {
|
func TestRunSuite(t *testing.T) {
|
||||||
|
@ -165,21 +306,29 @@ func TestRunSuite(t *testing.T) {
|
||||||
|
|
||||||
// The suite was only run once, so the SetupSuite and TearDownSuite
|
// The suite was only run once, so the SetupSuite and TearDownSuite
|
||||||
// methods should have each been run only once.
|
// methods should have each been run only once.
|
||||||
assert.Equal(t, suiteTester.SetupSuiteRunCount, 1)
|
assert.Equal(t, 1, suiteTester.SetupSuiteRunCount)
|
||||||
assert.Equal(t, suiteTester.TearDownSuiteRunCount, 1)
|
assert.Equal(t, 1, suiteTester.TearDownSuiteRunCount)
|
||||||
|
|
||||||
assert.Equal(t, len(suiteTester.SuiteNameAfter), 3)
|
assert.Len(t, suiteTester.SuiteNameAfter, 4)
|
||||||
assert.Equal(t, len(suiteTester.SuiteNameBefore), 3)
|
assert.Len(t, suiteTester.SuiteNameBefore, 4)
|
||||||
assert.Equal(t, len(suiteTester.TestNameAfter), 3)
|
assert.Len(t, suiteTester.TestNameAfter, 4)
|
||||||
assert.Equal(t, len(suiteTester.TestNameBefore), 3)
|
assert.Len(t, suiteTester.TestNameBefore, 4)
|
||||||
|
|
||||||
assert.Contains(t, suiteTester.TestNameAfter, "TestOne")
|
assert.Contains(t, suiteTester.TestNameAfter, "TestOne")
|
||||||
assert.Contains(t, suiteTester.TestNameAfter, "TestTwo")
|
assert.Contains(t, suiteTester.TestNameAfter, "TestTwo")
|
||||||
assert.Contains(t, suiteTester.TestNameAfter, "TestSkip")
|
assert.Contains(t, suiteTester.TestNameAfter, "TestSkip")
|
||||||
|
assert.Contains(t, suiteTester.TestNameAfter, "TestSubtest")
|
||||||
|
|
||||||
assert.Contains(t, suiteTester.TestNameBefore, "TestOne")
|
assert.Contains(t, suiteTester.TestNameBefore, "TestOne")
|
||||||
assert.Contains(t, suiteTester.TestNameBefore, "TestTwo")
|
assert.Contains(t, suiteTester.TestNameBefore, "TestTwo")
|
||||||
assert.Contains(t, suiteTester.TestNameBefore, "TestSkip")
|
assert.Contains(t, suiteTester.TestNameBefore, "TestSkip")
|
||||||
|
assert.Contains(t, suiteTester.TestNameBefore, "TestSubtest")
|
||||||
|
|
||||||
|
assert.Contains(t, suiteTester.SetupSubTestNames, "TestRunSuite/TestSubtest/first")
|
||||||
|
assert.Contains(t, suiteTester.SetupSubTestNames, "TestRunSuite/TestSubtest/second")
|
||||||
|
|
||||||
|
assert.Contains(t, suiteTester.TearDownSubTestNames, "TestRunSuite/TestSubtest/first")
|
||||||
|
assert.Contains(t, suiteTester.TearDownSubTestNames, "TestRunSuite/TestSubtest/second")
|
||||||
|
|
||||||
for _, suiteName := range suiteTester.SuiteNameAfter {
|
for _, suiteName := range suiteTester.SuiteNameAfter {
|
||||||
assert.Equal(t, "SuiteTester", suiteName)
|
assert.Equal(t, "SuiteTester", suiteName)
|
||||||
|
@ -197,19 +346,23 @@ func TestRunSuite(t *testing.T) {
|
||||||
assert.False(t, when.IsZero())
|
assert.False(t, when.IsZero())
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are three test methods (TestOne, TestTwo, and TestSkip), so
|
// There are four test methods (TestOne, TestTwo, TestSkip, and TestSubtest), so
|
||||||
// the SetupTest and TearDownTest methods (which should be run once for
|
// the SetupTest and TearDownTest methods (which should be run once for
|
||||||
// each test) should have been run three times.
|
// each test) should have been run four times.
|
||||||
assert.Equal(t, suiteTester.SetupTestRunCount, 3)
|
assert.Equal(t, 4, suiteTester.SetupTestRunCount)
|
||||||
assert.Equal(t, suiteTester.TearDownTestRunCount, 3)
|
assert.Equal(t, 4, suiteTester.TearDownTestRunCount)
|
||||||
|
|
||||||
// Each test should have been run once.
|
// Each test should have been run once.
|
||||||
assert.Equal(t, suiteTester.TestOneRunCount, 1)
|
assert.Equal(t, 1, suiteTester.TestOneRunCount)
|
||||||
assert.Equal(t, suiteTester.TestTwoRunCount, 1)
|
assert.Equal(t, 1, suiteTester.TestTwoRunCount)
|
||||||
|
assert.Equal(t, 1, suiteTester.TestSubtestRunCount)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, suiteTester.TearDownSubTestRunCount)
|
||||||
|
assert.Equal(t, 2, suiteTester.SetupSubTestRunCount)
|
||||||
|
|
||||||
// Methods that don't match the test method identifier shouldn't
|
// Methods that don't match the test method identifier shouldn't
|
||||||
// have been run at all.
|
// have been run at all.
|
||||||
assert.Equal(t, suiteTester.NonTestMethodRunCount, 0)
|
assert.Equal(t, 0, suiteTester.NonTestMethodRunCount)
|
||||||
|
|
||||||
suiteSkipTester := new(SuiteSkipTester)
|
suiteSkipTester := new(SuiteSkipTester)
|
||||||
Run(t, suiteSkipTester)
|
Run(t, suiteSkipTester)
|
||||||
|
@ -217,11 +370,38 @@ func TestRunSuite(t *testing.T) {
|
||||||
// The suite was only run once, so the SetupSuite and TearDownSuite
|
// The suite was only run once, so the SetupSuite and TearDownSuite
|
||||||
// methods should have each been run only once, even though SetupSuite
|
// methods should have each been run only once, even though SetupSuite
|
||||||
// called Skip()
|
// called Skip()
|
||||||
assert.Equal(t, suiteSkipTester.SetupSuiteRunCount, 1)
|
assert.Equal(t, 1, suiteSkipTester.SetupSuiteRunCount)
|
||||||
assert.Equal(t, suiteSkipTester.TearDownSuiteRunCount, 1)
|
assert.Equal(t, 1, suiteSkipTester.TearDownSuiteRunCount)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This suite has no Test... methods. It's setup and teardown must be skipped.
|
||||||
|
type SuiteSetupSkipTester struct {
|
||||||
|
Suite
|
||||||
|
|
||||||
|
setUp bool
|
||||||
|
toreDown bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SuiteSetupSkipTester) SetupSuite() {
|
||||||
|
s.setUp = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SuiteSetupSkipTester) NonTestMethod() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SuiteSetupSkipTester) TearDownSuite() {
|
||||||
|
s.toreDown = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkippingSuiteSetup(t *testing.T) {
|
||||||
|
suiteTester := new(SuiteSetupSkipTester)
|
||||||
|
Run(t, suiteTester)
|
||||||
|
assert.False(t, suiteTester.setUp)
|
||||||
|
assert.False(t, suiteTester.toreDown)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSuiteGetters(t *testing.T) {
|
func TestSuiteGetters(t *testing.T) {
|
||||||
suite := new(SuiteTester)
|
suite := new(SuiteTester)
|
||||||
suite.SetT(t)
|
suite.SetT(t)
|
||||||
|
@ -260,7 +440,7 @@ func (sc *StdoutCapture) StopCapture() (string, error) {
|
||||||
}
|
}
|
||||||
os.Stdout.Close()
|
os.Stdout.Close()
|
||||||
os.Stdout = sc.oldStdout
|
os.Stdout = sc.oldStdout
|
||||||
bytes, err := ioutil.ReadAll(sc.readPipe)
|
bytes, err := io.ReadAll(sc.readPipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -271,7 +451,7 @@ func TestSuiteLogging(t *testing.T) {
|
||||||
suiteLoggingTester := new(SuiteLoggingTester)
|
suiteLoggingTester := new(SuiteLoggingTester)
|
||||||
capture := StdoutCapture{}
|
capture := StdoutCapture{}
|
||||||
internalTest := testing.InternalTest{
|
internalTest := testing.InternalTest{
|
||||||
Name: "SomeTest",
|
Name: t.Name() + "/SuiteLoggingTester",
|
||||||
F: func(subT *testing.T) {
|
F: func(subT *testing.T) {
|
||||||
Run(subT, suiteLoggingTester)
|
Run(subT, suiteLoggingTester)
|
||||||
},
|
},
|
||||||
|
@ -292,3 +472,277 @@ func TestSuiteLogging(t *testing.T) {
|
||||||
assert.NotContains(t, output, "TESTLOGPASS")
|
assert.NotContains(t, output, "TESTLOGPASS")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CallOrderSuite struct {
|
||||||
|
Suite
|
||||||
|
callOrder []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CallOrderSuite) call(method string) {
|
||||||
|
time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
|
||||||
|
s.callOrder = append(s.callOrder, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuiteCallOrder(t *testing.T) {
|
||||||
|
Run(t, new(CallOrderSuite))
|
||||||
|
}
|
||||||
|
func (s *CallOrderSuite) SetupSuite() {
|
||||||
|
s.call("SetupSuite")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CallOrderSuite) TearDownSuite() {
|
||||||
|
s.call("TearDownSuite")
|
||||||
|
assert.Equal(s.T(), "SetupSuite;SetupTest;Test A;SetupSubTest;SubTest A1;TearDownSubTest;SetupSubTest;SubTest A2;TearDownSubTest;TearDownTest;SetupTest;Test B;SetupSubTest;SubTest B1;TearDownSubTest;SetupSubTest;SubTest B2;TearDownSubTest;TearDownTest;TearDownSuite", strings.Join(s.callOrder, ";"))
|
||||||
|
}
|
||||||
|
func (s *CallOrderSuite) SetupTest() {
|
||||||
|
s.call("SetupTest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CallOrderSuite) TearDownTest() {
|
||||||
|
s.call("TearDownTest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CallOrderSuite) SetupSubTest() {
|
||||||
|
s.call("SetupSubTest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CallOrderSuite) TearDownSubTest() {
|
||||||
|
s.call("TearDownSubTest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CallOrderSuite) Test_A() {
|
||||||
|
s.call("Test A")
|
||||||
|
s.Run("SubTest A1", func() {
|
||||||
|
s.call("SubTest A1")
|
||||||
|
})
|
||||||
|
s.Run("SubTest A2", func() {
|
||||||
|
s.call("SubTest A2")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CallOrderSuite) Test_B() {
|
||||||
|
s.call("Test B")
|
||||||
|
s.Run("SubTest B1", func() {
|
||||||
|
s.call("SubTest B1")
|
||||||
|
})
|
||||||
|
s.Run("SubTest B2", func() {
|
||||||
|
s.call("SubTest B2")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type suiteWithStats struct {
|
||||||
|
Suite
|
||||||
|
wasCalled bool
|
||||||
|
stats *SuiteInformation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suiteWithStats) HandleStats(suiteName string, stats *SuiteInformation) {
|
||||||
|
s.wasCalled = true
|
||||||
|
s.stats = stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suiteWithStats) TestSomething() {
|
||||||
|
s.Equal(1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suiteWithStats) TestPanic() {
|
||||||
|
panic("oops")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuiteWithStats(t *testing.T) {
|
||||||
|
suiteWithStats := new(suiteWithStats)
|
||||||
|
|
||||||
|
suiteSuccess := testing.RunTests(allTestsFilter, []testing.InternalTest{
|
||||||
|
{
|
||||||
|
Name: t.Name() + "/suiteWithStats",
|
||||||
|
F: func(t *testing.T) {
|
||||||
|
Run(t, suiteWithStats)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.False(t, suiteSuccess, "suiteWithStats should report test failure because of panic in TestPanic")
|
||||||
|
|
||||||
|
assert.True(t, suiteWithStats.wasCalled)
|
||||||
|
assert.NotZero(t, suiteWithStats.stats.Start)
|
||||||
|
assert.NotZero(t, suiteWithStats.stats.End)
|
||||||
|
assert.False(t, suiteWithStats.stats.Passed())
|
||||||
|
|
||||||
|
testStats := suiteWithStats.stats.TestStats
|
||||||
|
|
||||||
|
assert.NotZero(t, testStats["TestSomething"].Start)
|
||||||
|
assert.NotZero(t, testStats["TestSomething"].End)
|
||||||
|
assert.True(t, testStats["TestSomething"].Passed)
|
||||||
|
|
||||||
|
assert.NotZero(t, testStats["TestPanic"].Start)
|
||||||
|
assert.NotZero(t, testStats["TestPanic"].End)
|
||||||
|
assert.False(t, testStats["TestPanic"].Passed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailfastSuite will test the behavior when running with the failfast flag
|
||||||
|
// It logs calls in the callOrder slice which we then use to assert the correct calls were made
|
||||||
|
type FailfastSuite struct {
|
||||||
|
Suite
|
||||||
|
callOrder []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailfastSuite) call(method string) {
|
||||||
|
s.callOrder = append(s.callOrder, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailfastSuite(t *testing.T) {
|
||||||
|
// This test suite is run twice. Once normally and once with the -failfast flag by TestFailfastSuiteFailFastOn
|
||||||
|
// If you need to debug it run this test directly with the failfast flag set on/off as you need
|
||||||
|
failFast := flag.Lookup("test.failfast").Value.(flag.Getter).Get().(bool)
|
||||||
|
s := new(FailfastSuite)
|
||||||
|
ok := testing.RunTests(
|
||||||
|
allTestsFilter,
|
||||||
|
[]testing.InternalTest{{
|
||||||
|
Name: t.Name() + "/FailfastSuite",
|
||||||
|
F: func(t *testing.T) {
|
||||||
|
Run(t, s)
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
assert.False(t, ok)
|
||||||
|
var expect []string
|
||||||
|
if failFast {
|
||||||
|
// Test A Fails and because we are running with failfast Test B never runs and we proceed straight to TearDownSuite
|
||||||
|
expect = []string{"SetupSuite", "SetupTest", "Test A Fails", "TearDownTest", "TearDownSuite"}
|
||||||
|
} else {
|
||||||
|
// Test A Fails and because we are running without failfast we continue and run Test B and then proceed to TearDownSuite
|
||||||
|
expect = []string{"SetupSuite", "SetupTest", "Test A Fails", "TearDownTest", "SetupTest", "Test B Passes", "TearDownTest", "TearDownSuite"}
|
||||||
|
}
|
||||||
|
callOrderAssert(t, expect, s.callOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tHelper interface {
|
||||||
|
Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// callOrderAssert is a help with confirms that asserts that expect
|
||||||
|
// matches one or more times in callOrder. This makes it compatible
|
||||||
|
// with go test flag -count=X where X > 1.
|
||||||
|
func callOrderAssert(t *testing.T, expect, callOrder []string) {
|
||||||
|
var ti interface{} = t
|
||||||
|
if h, ok := ti.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
callCount := len(callOrder)
|
||||||
|
expectCount := len(expect)
|
||||||
|
if callCount > expectCount && callCount%expectCount == 0 {
|
||||||
|
// Command line flag -count=X where X > 1.
|
||||||
|
for len(callOrder) >= expectCount {
|
||||||
|
assert.Equal(t, expect, callOrder[:expectCount])
|
||||||
|
callOrder = callOrder[expectCount:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, expect, callOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailfastSuiteFailFastOn(t *testing.T) {
|
||||||
|
// To test this with failfast on (and isolated from other intended test failures in our test suite) we launch it in its own process
|
||||||
|
cmd := exec.Command("go", "test", "-v", "-race", "-run", "TestFailfastSuite", "-failfast")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
t.Log("Running go test -v -race -run TestFailfastSuite -failfast")
|
||||||
|
err := cmd.Run()
|
||||||
|
t.Log(out.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (s *FailfastSuite) SetupSuite() {
|
||||||
|
s.call("SetupSuite")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailfastSuite) TearDownSuite() {
|
||||||
|
s.call("TearDownSuite")
|
||||||
|
}
|
||||||
|
func (s *FailfastSuite) SetupTest() {
|
||||||
|
s.call("SetupTest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailfastSuite) TearDownTest() {
|
||||||
|
s.call("TearDownTest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailfastSuite) Test_A_Fails() {
|
||||||
|
s.call("Test A Fails")
|
||||||
|
s.T().Error("Test A meant to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FailfastSuite) Test_B_Passes() {
|
||||||
|
s.call("Test B Passes")
|
||||||
|
s.Require().True(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type subtestPanicSuite struct {
|
||||||
|
Suite
|
||||||
|
inTearDownSuite bool
|
||||||
|
inTearDownTest bool
|
||||||
|
inTearDownSubTest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *subtestPanicSuite) TearDownSuite() {
|
||||||
|
s.inTearDownSuite = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *subtestPanicSuite) TearDownTest() {
|
||||||
|
s.inTearDownTest = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *subtestPanicSuite) TearDownSubTest() {
|
||||||
|
s.inTearDownSubTest = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *subtestPanicSuite) TestSubtestPanic() {
|
||||||
|
ok := s.Run("subtest", func() {
|
||||||
|
panic("panic")
|
||||||
|
})
|
||||||
|
s.False(ok, "subtest failure is expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubtestPanic(t *testing.T) {
|
||||||
|
suite := new(subtestPanicSuite)
|
||||||
|
ok := testing.RunTests(
|
||||||
|
allTestsFilter,
|
||||||
|
[]testing.InternalTest{{
|
||||||
|
Name: t.Name() + "/subtestPanicSuite",
|
||||||
|
F: func(t *testing.T) {
|
||||||
|
Run(t, suite)
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
)
|
||||||
|
assert.False(t, ok, "TestSubtestPanic/subtest should make the testsuite fail")
|
||||||
|
assert.True(t, suite.inTearDownSubTest)
|
||||||
|
assert.True(t, suite.inTearDownTest)
|
||||||
|
assert.True(t, suite.inTearDownSuite)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unInitializedSuite struct {
|
||||||
|
Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUnInitializedSuites asserts the behavior of the suite methods when the
|
||||||
|
// suite is not initialized
|
||||||
|
func TestUnInitializedSuites(t *testing.T) {
|
||||||
|
t.Run("should panic on Require", func(t *testing.T) {
|
||||||
|
suite := new(unInitializedSuite)
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
suite.Require().True(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should panic on Assert", func(t *testing.T) {
|
||||||
|
suite := new(unInitializedSuite)
|
||||||
|
|
||||||
|
assert.Panics(t, func() {
|
||||||
|
suite.Assert().True(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
ISC License
|
|
||||||
|
|
||||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
@ -1,152 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
|
||||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build !js,!appengine,!safe,!disableunsafe
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = false
|
|
||||||
|
|
||||||
// ptrSize is the size of a pointer on the current arch.
|
|
||||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
|
||||||
// internal reflect.Value fields. These values are valid before golang
|
|
||||||
// commit ecccf07e7f9d which changed the format. The are also valid
|
|
||||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
|
||||||
// the original format. Code in the init function updates these offsets
|
|
||||||
// as necessary.
|
|
||||||
offsetPtr = uintptr(ptrSize)
|
|
||||||
offsetScalar = uintptr(0)
|
|
||||||
offsetFlag = uintptr(ptrSize * 2)
|
|
||||||
|
|
||||||
// flagKindWidth and flagKindShift indicate various bits that the
|
|
||||||
// reflect package uses internally to track kind information.
|
|
||||||
//
|
|
||||||
// flagRO indicates whether or not the value field of a reflect.Value is
|
|
||||||
// read-only.
|
|
||||||
//
|
|
||||||
// flagIndir indicates whether the value field of a reflect.Value is
|
|
||||||
// the actual data or a pointer to the data.
|
|
||||||
//
|
|
||||||
// These values are valid before golang commit 90a7c3c86944 which
|
|
||||||
// changed their positions. Code in the init function updates these
|
|
||||||
// flags as necessary.
|
|
||||||
flagKindWidth = uintptr(5)
|
|
||||||
flagKindShift = uintptr(flagKindWidth - 1)
|
|
||||||
flagRO = uintptr(1 << 0)
|
|
||||||
flagIndir = uintptr(1 << 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Older versions of reflect.Value stored small integers directly in the
|
|
||||||
// ptr field (which is named val in the older versions). Versions
|
|
||||||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
|
||||||
// scalar for this purpose which unfortunately came before the flag
|
|
||||||
// field, so the offset of the flag field is different for those
|
|
||||||
// versions.
|
|
||||||
//
|
|
||||||
// This code constructs a new reflect.Value from a known small integer
|
|
||||||
// and checks if the size of the reflect.Value struct indicates it has
|
|
||||||
// the scalar field. When it does, the offsets are updated accordingly.
|
|
||||||
vv := reflect.ValueOf(0xf00)
|
|
||||||
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
|
||||||
offsetScalar = ptrSize * 2
|
|
||||||
offsetFlag = ptrSize * 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit 90a7c3c86944 changed the flag positions such that the low
|
|
||||||
// order bits are the kind. This code extracts the kind from the flags
|
|
||||||
// field and ensures it's the correct type. When it's not, the flag
|
|
||||||
// order has been changed to the newer format, so the flags are updated
|
|
||||||
// accordingly.
|
|
||||||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
|
||||||
upfv := *(*uintptr)(upf)
|
|
||||||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
|
||||||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
|
||||||
flagKindShift = 0
|
|
||||||
flagRO = 1 << 5
|
|
||||||
flagIndir = 1 << 6
|
|
||||||
|
|
||||||
// Commit adf9b30e5594 modified the flags to separate the
|
|
||||||
// flagRO flag into two bits which specifies whether or not the
|
|
||||||
// field is embedded. This causes flagIndir to move over a bit
|
|
||||||
// and means that flagRO is the combination of either of the
|
|
||||||
// original flagRO bit and the new bit.
|
|
||||||
//
|
|
||||||
// This code detects the change by extracting what used to be
|
|
||||||
// the indirect bit to ensure it's set. When it's not, the flag
|
|
||||||
// order has been changed to the newer format, so the flags are
|
|
||||||
// updated accordingly.
|
|
||||||
if upfv&flagIndir == 0 {
|
|
||||||
flagRO = 3 << 5
|
|
||||||
flagIndir = 1 << 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
|
||||||
// the typical safety restrictions preventing access to unaddressable and
|
|
||||||
// unexported data. It works by digging the raw pointer to the underlying
|
|
||||||
// value out of the protected value and generating a new unprotected (unsafe)
|
|
||||||
// reflect.Value to it.
|
|
||||||
//
|
|
||||||
// This allows us to check for implementations of the Stringer and error
|
|
||||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
|
||||||
// inaccessible values such as unexported struct fields.
|
|
||||||
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
|
||||||
indirects := 1
|
|
||||||
vt := v.Type()
|
|
||||||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
|
||||||
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
|
||||||
if rvf&flagIndir != 0 {
|
|
||||||
vt = reflect.PtrTo(v.Type())
|
|
||||||
indirects++
|
|
||||||
} else if offsetScalar != 0 {
|
|
||||||
// The value is in the scalar field when it's not one of the
|
|
||||||
// reference types.
|
|
||||||
switch vt.Kind() {
|
|
||||||
case reflect.Uintptr:
|
|
||||||
case reflect.Chan:
|
|
||||||
case reflect.Func:
|
|
||||||
case reflect.Map:
|
|
||||||
case reflect.Ptr:
|
|
||||||
case reflect.UnsafePointer:
|
|
||||||
default:
|
|
||||||
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
|
||||||
offsetScalar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pv := reflect.NewAt(vt, upv)
|
|
||||||
rv = pv
|
|
||||||
for i := 0; i < indirects; i++ {
|
|
||||||
rv = rv.Elem()
|
|
||||||
}
|
|
||||||
return rv
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
|
||||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build js appengine safe disableunsafe
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
|
||||||
// that bypasses the typical safety restrictions preventing access to
|
|
||||||
// unaddressable and unexported data. However, doing this relies on access to
|
|
||||||
// the unsafe package. This is a stub version which simply returns the passed
|
|
||||||
// reflect.Value when the unsafe package is not available.
|
|
||||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
|
||||||
return v
|
|
||||||
}
|
|
|
@ -1,341 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
|
||||||
// the technique used in the fmt package.
|
|
||||||
var (
|
|
||||||
panicBytes = []byte("(PANIC=")
|
|
||||||
plusBytes = []byte("+")
|
|
||||||
iBytes = []byte("i")
|
|
||||||
trueBytes = []byte("true")
|
|
||||||
falseBytes = []byte("false")
|
|
||||||
interfaceBytes = []byte("(interface {})")
|
|
||||||
commaNewlineBytes = []byte(",\n")
|
|
||||||
newlineBytes = []byte("\n")
|
|
||||||
openBraceBytes = []byte("{")
|
|
||||||
openBraceNewlineBytes = []byte("{\n")
|
|
||||||
closeBraceBytes = []byte("}")
|
|
||||||
asteriskBytes = []byte("*")
|
|
||||||
colonBytes = []byte(":")
|
|
||||||
colonSpaceBytes = []byte(": ")
|
|
||||||
openParenBytes = []byte("(")
|
|
||||||
closeParenBytes = []byte(")")
|
|
||||||
spaceBytes = []byte(" ")
|
|
||||||
pointerChainBytes = []byte("->")
|
|
||||||
nilAngleBytes = []byte("<nil>")
|
|
||||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
|
||||||
maxShortBytes = []byte("<max>")
|
|
||||||
circularBytes = []byte("<already shown>")
|
|
||||||
circularShortBytes = []byte("<shown>")
|
|
||||||
invalidAngleBytes = []byte("<invalid>")
|
|
||||||
openBracketBytes = []byte("[")
|
|
||||||
closeBracketBytes = []byte("]")
|
|
||||||
percentBytes = []byte("%")
|
|
||||||
precisionBytes = []byte(".")
|
|
||||||
openAngleBytes = []byte("<")
|
|
||||||
closeAngleBytes = []byte(">")
|
|
||||||
openMapBytes = []byte("map[")
|
|
||||||
closeMapBytes = []byte("]")
|
|
||||||
lenEqualsBytes = []byte("len=")
|
|
||||||
capEqualsBytes = []byte("cap=")
|
|
||||||
)
|
|
||||||
|
|
||||||
// hexDigits is used to map a decimal value to a hex digit.
|
|
||||||
var hexDigits = "0123456789abcdef"
|
|
||||||
|
|
||||||
// catchPanic handles any panics that might occur during the handleMethods
|
|
||||||
// calls.
|
|
||||||
func catchPanic(w io.Writer, v reflect.Value) {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
w.Write(panicBytes)
|
|
||||||
fmt.Fprintf(w, "%v", err)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMethods attempts to call the Error and String methods on the underlying
|
|
||||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
|
||||||
//
|
|
||||||
// It handles panics in any called methods by catching and displaying the error
|
|
||||||
// as the formatted value.
|
|
||||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
|
||||||
// We need an interface to check if the type implements the error or
|
|
||||||
// Stringer interface. However, the reflect package won't give us an
|
|
||||||
// interface on certain things like unexported struct fields in order
|
|
||||||
// to enforce visibility rules. We use unsafe, when it's available,
|
|
||||||
// to bypass these restrictions since this package does not mutate the
|
|
||||||
// values.
|
|
||||||
if !v.CanInterface() {
|
|
||||||
if UnsafeDisabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose whether or not to do error and Stringer interface lookups against
|
|
||||||
// the base type or a pointer to the base type depending on settings.
|
|
||||||
// Technically calling one of these methods with a pointer receiver can
|
|
||||||
// mutate the value, however, types which choose to satisify an error or
|
|
||||||
// Stringer interface with a pointer receiver should not be mutating their
|
|
||||||
// state inside these interface methods.
|
|
||||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
if v.CanAddr() {
|
|
||||||
v = v.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it an error or Stringer?
|
|
||||||
switch iface := v.Interface().(type) {
|
|
||||||
case error:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
return true
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// printBool outputs a boolean value as true or false to Writer w.
|
|
||||||
func printBool(w io.Writer, val bool) {
|
|
||||||
if val {
|
|
||||||
w.Write(trueBytes)
|
|
||||||
} else {
|
|
||||||
w.Write(falseBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printInt outputs a signed integer value to Writer w.
|
|
||||||
func printInt(w io.Writer, val int64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printUint outputs an unsigned integer value to Writer w.
|
|
||||||
func printUint(w io.Writer, val uint64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printFloat outputs a floating point value using the specified precision,
|
|
||||||
// which is expected to be 32 or 64bit, to Writer w.
|
|
||||||
func printFloat(w io.Writer, val float64, precision int) {
|
|
||||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printComplex outputs a complex value using the specified float precision
|
|
||||||
// for the real and imaginary parts to Writer w.
|
|
||||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
|
||||||
r := real(c)
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
|
||||||
i := imag(c)
|
|
||||||
if i >= 0 {
|
|
||||||
w.Write(plusBytes)
|
|
||||||
}
|
|
||||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
|
||||||
w.Write(iBytes)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
|
||||||
// prefix to Writer w.
|
|
||||||
func printHexPtr(w io.Writer, p uintptr) {
|
|
||||||
// Null pointer.
|
|
||||||
num := uint64(p)
|
|
||||||
if num == 0 {
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
|
||||||
buf := make([]byte, 18)
|
|
||||||
|
|
||||||
// It's simpler to construct the hex string right to left.
|
|
||||||
base := uint64(16)
|
|
||||||
i := len(buf) - 1
|
|
||||||
for num >= base {
|
|
||||||
buf[i] = hexDigits[num%base]
|
|
||||||
num /= base
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
buf[i] = hexDigits[num]
|
|
||||||
|
|
||||||
// Add '0x' prefix.
|
|
||||||
i--
|
|
||||||
buf[i] = 'x'
|
|
||||||
i--
|
|
||||||
buf[i] = '0'
|
|
||||||
|
|
||||||
// Strip unused leading bytes.
|
|
||||||
buf = buf[i:]
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
|
||||||
// elements to be sorted.
|
|
||||||
type valuesSorter struct {
|
|
||||||
values []reflect.Value
|
|
||||||
strings []string // either nil or same len and values
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
|
||||||
// surrogate keys on which the data should be sorted. It uses flags in
|
|
||||||
// ConfigState to decide if and how to populate those surrogate keys.
|
|
||||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
|
||||||
vs := &valuesSorter{values: values, cs: cs}
|
|
||||||
if canSortSimply(vs.values[0].Kind()) {
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
if !cs.DisableMethods {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
if !handleMethods(cs, &b, vs.values[i]) {
|
|
||||||
vs.strings = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
vs.strings[i] = b.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vs.strings == nil && cs.SpewKeys {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
|
|
||||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
|
||||||
// directly, or whether it should be considered for sorting by surrogate keys
|
|
||||||
// (if the ConfigState allows it).
|
|
||||||
func canSortSimply(kind reflect.Kind) bool {
|
|
||||||
// This switch parallels valueSortLess, except for the default case.
|
|
||||||
switch kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
return true
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return true
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return true
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return true
|
|
||||||
case reflect.String:
|
|
||||||
return true
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return true
|
|
||||||
case reflect.Array:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of values in the slice. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Len() int {
|
|
||||||
return len(s.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps the values at the passed indices. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Swap(i, j int) {
|
|
||||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
|
||||||
if s.strings != nil {
|
|
||||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// valueSortLess returns whether the first value should sort before the second
|
|
||||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
|
||||||
// implementation.
|
|
||||||
func valueSortLess(a, b reflect.Value) bool {
|
|
||||||
switch a.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return !a.Bool() && b.Bool()
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return a.Int() < b.Int()
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return a.Float() < b.Float()
|
|
||||||
case reflect.String:
|
|
||||||
return a.String() < b.String()
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Array:
|
|
||||||
// Compare the contents of both arrays.
|
|
||||||
l := a.Len()
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
av := a.Index(i)
|
|
||||||
bv := b.Index(i)
|
|
||||||
if av.Interface() == bv.Interface() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return valueSortLess(av, bv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.String() < b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less returns whether the value at index i should sort before the
|
|
||||||
// value at index j. It is part of the sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Less(i, j int) bool {
|
|
||||||
if s.strings == nil {
|
|
||||||
return valueSortLess(s.values[i], s.values[j])
|
|
||||||
}
|
|
||||||
return s.strings[i] < s.strings[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortValues is a sort function that handles both native types and any type that
|
|
||||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
|
||||||
// their Value.String() value to ensure display stability.
|
|
||||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
|
||||||
if len(values) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sort.Sort(newValuesSorter(values, cs))
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigState houses the configuration options used by spew to format and
|
|
||||||
// display values. There is a global instance, Config, that is used to control
|
|
||||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
|
||||||
// provides methods equivalent to the top-level functions.
|
|
||||||
//
|
|
||||||
// The zero value for ConfigState provides no indentation. You would typically
|
|
||||||
// want to set it to a space or a tab.
|
|
||||||
//
|
|
||||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
|
||||||
// with default settings. See the documentation of NewDefaultConfig for default
|
|
||||||
// values.
|
|
||||||
type ConfigState struct {
|
|
||||||
// Indent specifies the string to use for each indentation level. The
|
|
||||||
// global config instance that all top-level functions use set this to a
|
|
||||||
// single space by default. If you would like more indentation, you might
|
|
||||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
|
||||||
Indent string
|
|
||||||
|
|
||||||
// MaxDepth controls the maximum number of levels to descend into nested
|
|
||||||
// data structures. The default, 0, means there is no limit.
|
|
||||||
//
|
|
||||||
// NOTE: Circular data structures are properly detected, so it is not
|
|
||||||
// necessary to set this value unless you specifically want to limit deeply
|
|
||||||
// nested data structures.
|
|
||||||
MaxDepth int
|
|
||||||
|
|
||||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
|
||||||
// invoked for types that implement them.
|
|
||||||
DisableMethods bool
|
|
||||||
|
|
||||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
|
||||||
// error and Stringer interfaces on types which only accept a pointer
|
|
||||||
// receiver when the current type is not a pointer.
|
|
||||||
//
|
|
||||||
// NOTE: This might be an unsafe action since calling one of these methods
|
|
||||||
// with a pointer receiver could technically mutate the value, however,
|
|
||||||
// in practice, types which choose to satisify an error or Stringer
|
|
||||||
// interface with a pointer receiver should not be mutating their state
|
|
||||||
// inside these interface methods. As a result, this option relies on
|
|
||||||
// access to the unsafe package, so it will not have any effect when
|
|
||||||
// running in environments without access to the unsafe package such as
|
|
||||||
// Google App Engine or with the "safe" build tag specified.
|
|
||||||
DisablePointerMethods bool
|
|
||||||
|
|
||||||
// DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
// pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
DisablePointerAddresses bool
|
|
||||||
|
|
||||||
// DisableCapacities specifies whether to disable the printing of capacities
|
|
||||||
// for arrays, slices, maps and channels. This is useful when diffing
|
|
||||||
// data structures in tests.
|
|
||||||
DisableCapacities bool
|
|
||||||
|
|
||||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
|
||||||
// a custom error or Stringer interface is invoked. The default, false,
|
|
||||||
// means it will print the results of invoking the custom error or Stringer
|
|
||||||
// interface and return immediately instead of continuing to recurse into
|
|
||||||
// the internals of the data type.
|
|
||||||
//
|
|
||||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
|
||||||
// via the DisableMethods or DisablePointerMethods options.
|
|
||||||
ContinueOnMethod bool
|
|
||||||
|
|
||||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
|
||||||
// this to have a more deterministic, diffable output. Note that only
|
|
||||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
|
||||||
// that support the error or Stringer interfaces (if methods are
|
|
||||||
// enabled) are supported, with other types sorted according to the
|
|
||||||
// reflect.Value.String() output which guarantees display stability.
|
|
||||||
SortKeys bool
|
|
||||||
|
|
||||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
|
||||||
// be spewed to strings and sorted by those strings. This is only
|
|
||||||
// considered if SortKeys is true.
|
|
||||||
SpewKeys bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
|
||||||
// The configuration can be changed by modifying the contents of spew.Config.
|
|
||||||
var Config = ConfigState{Indent: " "}
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the formatted string as a value that satisfies error. See NewFormatter
|
|
||||||
// for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
c.Printf, c.Println, or c.Printf.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(c, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(c, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by modifying the public members
|
|
||||||
of c. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) Dump(a ...interface{}) {
|
|
||||||
fdump(c, os.Stdout, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(c, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a spew Formatter interface using
|
|
||||||
// the ConfigState associated with s.
|
|
||||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = newFormatter(c, arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
|
||||||
//
|
|
||||||
// Indent: " "
|
|
||||||
// MaxDepth: 0
|
|
||||||
// DisableMethods: false
|
|
||||||
// DisablePointerMethods: false
|
|
||||||
// ContinueOnMethod: false
|
|
||||||
// SortKeys: false
|
|
||||||
func NewDefaultConfig() *ConfigState {
|
|
||||||
return &ConfigState{Indent: " "}
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
|
||||||
debugging.
|
|
||||||
|
|
||||||
A quick overview of the additional features spew provides over the built-in
|
|
||||||
printing facilities for Go data types are as follows:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output (only when using
|
|
||||||
Dump style)
|
|
||||||
|
|
||||||
There are two different approaches spew allows for dumping Go data structures:
|
|
||||||
|
|
||||||
* Dump style which prints with newlines, customizable indentation,
|
|
||||||
and additional debug information such as types and all pointer addresses
|
|
||||||
used to indirect to the final value
|
|
||||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
|
||||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
|
||||||
similar to the default %v while providing the additional functionality
|
|
||||||
outlined above and passing unsupported format verbs such as %x and %q
|
|
||||||
along to fmt
|
|
||||||
|
|
||||||
Quick Start
|
|
||||||
|
|
||||||
This section demonstrates how to quickly get started with spew. See the
|
|
||||||
sections below for further details on formatting and configuration options.
|
|
||||||
|
|
||||||
To dump a variable with full newlines, indentation, type, and pointer
|
|
||||||
information use Dump, Fdump, or Sdump:
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
|
||||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
|
||||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
|
||||||
%#+v (adds types and pointer addresses):
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
Configuration Options
|
|
||||||
|
|
||||||
Configuration of spew is handled by fields in the ConfigState type. For
|
|
||||||
convenience, all of the top-level functions use a global state available
|
|
||||||
via the spew.Config global.
|
|
||||||
|
|
||||||
It is also possible to create a ConfigState instance that provides methods
|
|
||||||
equivalent to the top-level functions. This allows concurrent configuration
|
|
||||||
options. See the ConfigState documentation for more details.
|
|
||||||
|
|
||||||
The following configuration options are available:
|
|
||||||
* Indent
|
|
||||||
String to use for each indentation level for Dump functions.
|
|
||||||
It is a single space by default. A popular alternative is "\t".
|
|
||||||
|
|
||||||
* MaxDepth
|
|
||||||
Maximum number of levels to descend into nested data structures.
|
|
||||||
There is no limit by default.
|
|
||||||
|
|
||||||
* DisableMethods
|
|
||||||
Disables invocation of error and Stringer interface methods.
|
|
||||||
Method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerMethods
|
|
||||||
Disables invocation of error and Stringer interface methods on types
|
|
||||||
which only accept pointer receivers from non-pointer variables.
|
|
||||||
Pointer method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerAddresses
|
|
||||||
DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
|
|
||||||
* DisableCapacities
|
|
||||||
DisableCapacities specifies whether to disable the printing of
|
|
||||||
capacities for arrays, slices, maps and channels. This is useful when
|
|
||||||
diffing data structures in tests.
|
|
||||||
|
|
||||||
* ContinueOnMethod
|
|
||||||
Enables recursion into types after invoking error and Stringer interface
|
|
||||||
methods. Recursion after method invocation is disabled by default.
|
|
||||||
|
|
||||||
* SortKeys
|
|
||||||
Specifies map keys should be sorted before being printed. Use
|
|
||||||
this to have a more deterministic, diffable output. Note that
|
|
||||||
only native types (bool, int, uint, floats, uintptr and string)
|
|
||||||
and types which implement error or Stringer interfaces are
|
|
||||||
supported with other types sorted according to the
|
|
||||||
reflect.Value.String() output which guarantees display
|
|
||||||
stability. Natural map order is used by default.
|
|
||||||
|
|
||||||
* SpewKeys
|
|
||||||
Specifies that, as a last resort attempt, map keys should be
|
|
||||||
spewed to strings and sorted by those strings. This is only
|
|
||||||
considered if SortKeys is true.
|
|
||||||
|
|
||||||
Dump Usage
|
|
||||||
|
|
||||||
Simply call spew.Dump with a list of variables you want to dump:
|
|
||||||
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
|
||||||
io.Writer. For example, to dump to standard error:
|
|
||||||
|
|
||||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
|
||||||
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Sample Dump Output
|
|
||||||
|
|
||||||
See the Dump example for details on the setup of the types and variables being
|
|
||||||
shown here.
|
|
||||||
|
|
||||||
(main.Foo) {
|
|
||||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
|
||||||
flag: (main.Flag) flagTwo,
|
|
||||||
data: (uintptr) <nil>
|
|
||||||
}),
|
|
||||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
(string) (len=3) "one": (bool) true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
|
||||||
command as shown.
|
|
||||||
([]uint8) (len=32 cap=32) {
|
|
||||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
|
||||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
|
||||||
00000020 31 32 |12|
|
|
||||||
}
|
|
||||||
|
|
||||||
Custom Formatter
|
|
||||||
|
|
||||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
|
||||||
so that it integrates cleanly with standard fmt package printing functions. The
|
|
||||||
formatter is useful for inline printing of smaller data types similar to the
|
|
||||||
standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Custom Formatter Usage
|
|
||||||
|
|
||||||
The simplest way to make use of the spew custom formatter is to call one of the
|
|
||||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
|
||||||
functions have syntax you are most likely already familiar with:
|
|
||||||
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Println(myVar, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
See the Index for the full list convenience functions.
|
|
||||||
|
|
||||||
Sample Formatter Output
|
|
||||||
|
|
||||||
Double pointer to a uint8:
|
|
||||||
%v: <**>5
|
|
||||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
|
||||||
%#v: (**uint8)5
|
|
||||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
|
||||||
|
|
||||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
|
||||||
%v: <*>{1 <*><shown>}
|
|
||||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
|
||||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
|
||||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
|
||||||
|
|
||||||
See the Printf example for details on the setup of variables being shown
|
|
||||||
here.
|
|
||||||
|
|
||||||
Errors
|
|
||||||
|
|
||||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
|
||||||
detects them and handles them internally by printing the panic information
|
|
||||||
inline with the output. Since spew is intended to provide deep pretty printing
|
|
||||||
capabilities on structures, it intentionally does not return any errors.
|
|
||||||
*/
|
|
||||||
package spew
|
|
|
@ -1,509 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
|
||||||
// convert cgo types to uint8 slices for hexdumping.
|
|
||||||
uint8Type = reflect.TypeOf(uint8(0))
|
|
||||||
|
|
||||||
// cCharRE is a regular expression that matches a cgo char.
|
|
||||||
// It is used to detect character arrays to hexdump them.
|
|
||||||
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
|
||||||
|
|
||||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
|
||||||
// char. It is used to detect unsigned character arrays to hexdump
|
|
||||||
// them.
|
|
||||||
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
|
||||||
|
|
||||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
|
||||||
// It is used to detect uint8_t arrays to hexdump them.
|
|
||||||
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
|
||||||
)
|
|
||||||
|
|
||||||
// dumpState contains information about the state of a dump operation.
|
|
||||||
type dumpState struct {
|
|
||||||
w io.Writer
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
ignoreNextIndent bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// indent performs indentation according to the depth level and cs.Indent
|
|
||||||
// option.
|
|
||||||
func (d *dumpState) indent() {
|
|
||||||
if d.ignoreNextIndent {
|
|
||||||
d.ignoreNextIndent = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range d.pointers {
|
|
||||||
if depth >= d.depth {
|
|
||||||
delete(d.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by dereferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
d.pointers[addr] = d.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type information.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
d.w.Write([]byte(ve.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
|
|
||||||
// Display pointer information.
|
|
||||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
d.w.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(d.w, addr)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
switch {
|
|
||||||
case nilFound == true:
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound == true:
|
|
||||||
d.w.Write(circularBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.ignoreNextType = true
|
|
||||||
d.dump(ve)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
|
||||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
|
||||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
|
||||||
// Determine whether this type should be hex dumped or not. Also,
|
|
||||||
// for types which should be hexdumped, try to use the underlying data
|
|
||||||
// first, then fall back to trying to convert them to a uint8 slice.
|
|
||||||
var buf []uint8
|
|
||||||
doConvert := false
|
|
||||||
doHexDump := false
|
|
||||||
numEntries := v.Len()
|
|
||||||
if numEntries > 0 {
|
|
||||||
vt := v.Index(0).Type()
|
|
||||||
vts := vt.String()
|
|
||||||
switch {
|
|
||||||
// C types that need to be converted.
|
|
||||||
case cCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUnsignedCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUint8tCharRE.MatchString(vts):
|
|
||||||
doConvert = true
|
|
||||||
|
|
||||||
// Try to use existing uint8 slices and fall back to converting
|
|
||||||
// and copying if that fails.
|
|
||||||
case vt.Kind() == reflect.Uint8:
|
|
||||||
// We need an addressable interface to convert the type
|
|
||||||
// to a byte slice. However, the reflect package won't
|
|
||||||
// give us an interface on certain things like
|
|
||||||
// unexported struct fields in order to enforce
|
|
||||||
// visibility rules. We use unsafe, when available, to
|
|
||||||
// bypass these restrictions since this package does not
|
|
||||||
// mutate the values.
|
|
||||||
vs := v
|
|
||||||
if !vs.CanInterface() || !vs.CanAddr() {
|
|
||||||
vs = unsafeReflectValue(vs)
|
|
||||||
}
|
|
||||||
if !UnsafeDisabled {
|
|
||||||
vs = vs.Slice(0, numEntries)
|
|
||||||
|
|
||||||
// Use the existing uint8 slice if it can be
|
|
||||||
// type asserted.
|
|
||||||
iface := vs.Interface()
|
|
||||||
if slice, ok := iface.([]uint8); ok {
|
|
||||||
buf = slice
|
|
||||||
doHexDump = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The underlying data needs to be converted if it can't
|
|
||||||
// be type asserted to a uint8 slice.
|
|
||||||
doConvert = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy and convert the underlying type if needed.
|
|
||||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
|
||||||
// Convert and copy each element into a uint8 byte
|
|
||||||
// slice.
|
|
||||||
buf = make([]uint8, numEntries)
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
vv := v.Index(i)
|
|
||||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
|
||||||
}
|
|
||||||
doHexDump = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hexdump the entire slice as needed.
|
|
||||||
if doHexDump {
|
|
||||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
|
||||||
str := indent + hex.Dump(buf)
|
|
||||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
|
||||||
str = strings.TrimRight(str, d.cs.Indent)
|
|
||||||
d.w.Write([]byte(str))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively call dump for each item.
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
d.dump(d.unpackValue(v.Index(i)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
|
||||||
// value to figure out what kind of object we are dealing with and formats it
|
|
||||||
// appropriately. It is a recursive function, however circular data structures
|
|
||||||
// are detected and handled properly.
|
|
||||||
func (d *dumpState) dump(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
d.w.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
d.indent()
|
|
||||||
d.dumpPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !d.ignoreNextType {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write([]byte(v.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.ignoreNextType = false
|
|
||||||
|
|
||||||
// Display length and capacity if the built-in len and cap functions
|
|
||||||
// work with the value's kind and the len/cap itself is non-zero.
|
|
||||||
valueLen, valueCap := 0, 0
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
|
||||||
valueLen, valueCap = v.Len(), v.Cap()
|
|
||||||
case reflect.Map, reflect.String:
|
|
||||||
valueLen = v.Len()
|
|
||||||
}
|
|
||||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(lenEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueLen), 10)
|
|
||||||
}
|
|
||||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.w.Write(capEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueCap), 10)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
|
||||||
// is enabled
|
|
||||||
if !d.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(d.w, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(d.w, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(d.w, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(d.w, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(d.w, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(d.w, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(d.w, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.dumpSlice(v)
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if d.cs.SortKeys {
|
|
||||||
sortValues(keys, d.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
d.dump(d.unpackValue(key))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
numFields := v.NumField()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
d.indent()
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
d.w.Write([]byte(vtf.Name))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.Field(i)))
|
|
||||||
if i < (numFields - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(d.w, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(d.w, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it in case any new
|
|
||||||
// types are added.
|
|
||||||
default:
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fdump is a helper function to consolidate the logic from the various public
|
|
||||||
// methods which take varying writers and config states.
|
|
||||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
|
||||||
for _, arg := range a {
|
|
||||||
if arg == nil {
|
|
||||||
w.Write(interfaceBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
w.Write(newlineBytes)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d := dumpState{w: w, cs: cs}
|
|
||||||
d.pointers = make(map[uintptr]int)
|
|
||||||
d.dump(reflect.ValueOf(arg))
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(&Config, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(&Config, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by an exported package global,
|
|
||||||
spew.Config. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func Dump(a ...interface{}) {
|
|
||||||
fdump(&Config, os.Stdout, a...)
|
|
||||||
}
|
|
|
@ -1,419 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
|
||||||
const supportedFlags = "0-+# "
|
|
||||||
|
|
||||||
// formatState implements the fmt.Formatter interface and contains information
|
|
||||||
// about the state of a formatting operation. The NewFormatter function can
|
|
||||||
// be used to get a new Formatter which can be used directly as arguments
|
|
||||||
// in standard fmt package printing calls.
|
|
||||||
type formatState struct {
|
|
||||||
value interface{}
|
|
||||||
fs fmt.State
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDefaultFormat recreates the original format string without precision
|
|
||||||
// and width information to pass in to fmt.Sprintf in the case of an
|
|
||||||
// unrecognized type. Unless new types are added to the language, this
|
|
||||||
// function won't ever be called.
|
|
||||||
func (f *formatState) buildDefaultFormat() (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune('v')
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructOrigFormat recreates the original format string including precision
|
|
||||||
// and width information to pass along to the standard fmt package. This allows
|
|
||||||
// automatic deferral of all format strings this package doesn't support.
|
|
||||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if width, ok := f.fs.Width(); ok {
|
|
||||||
buf.WriteString(strconv.Itoa(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
if precision, ok := f.fs.Precision(); ok {
|
|
||||||
buf.Write(precisionBytes)
|
|
||||||
buf.WriteString(strconv.Itoa(precision))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune(verb)
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
|
||||||
// ensures that types for values which have been unpacked from an interface
|
|
||||||
// are displayed when the show types flag is also set.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface {
|
|
||||||
f.ignoreNextType = false
|
|
||||||
if !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (f *formatState) formatPtr(v reflect.Value) {
|
|
||||||
// Display nil if top level pointer is nil.
|
|
||||||
showTypes := f.fs.Flag('#')
|
|
||||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range f.pointers {
|
|
||||||
if depth >= f.depth {
|
|
||||||
delete(f.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to possibly show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by derferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
f.pointers[addr] = f.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type or indirection level depending on flags.
|
|
||||||
if showTypes && !f.ignoreNextType {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
f.fs.Write([]byte(ve.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
} else {
|
|
||||||
if nilFound || cycleFound {
|
|
||||||
indirects += strings.Count(ve.Type().String(), "*")
|
|
||||||
}
|
|
||||||
f.fs.Write(openAngleBytes)
|
|
||||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
|
||||||
f.fs.Write(closeAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display pointer information depending on flags.
|
|
||||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(f.fs, addr)
|
|
||||||
}
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
switch {
|
|
||||||
case nilFound == true:
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound == true:
|
|
||||||
f.fs.Write(circularShortBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(ve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// format is the main workhorse for providing the Formatter interface. It
|
|
||||||
// uses the passed reflect value to figure out what kind of object we are
|
|
||||||
// dealing with and formats it appropriately. It is a recursive function,
|
|
||||||
// however circular data structures are detected and handled properly.
|
|
||||||
func (f *formatState) format(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
f.fs.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
f.formatPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write([]byte(v.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = false
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods
|
|
||||||
// flag is enabled.
|
|
||||||
if !f.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(f.fs, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(f.fs, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(f.fs, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(f.fs, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(f.fs, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(f.fs, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(f.fs, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
f.fs.Write(openBracketBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.Index(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBracketBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
f.fs.Write([]byte(v.String()))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
f.fs.Write(openMapBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if f.cs.SortKeys {
|
|
||||||
sortValues(keys, f.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(key))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.MapIndex(key)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeMapBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
numFields := v.NumField()
|
|
||||||
f.fs.Write(openBraceBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
|
||||||
f.fs.Write([]byte(vtf.Name))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
}
|
|
||||||
f.format(f.unpackValue(v.Field(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(f.fs, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it if any get added.
|
|
||||||
default:
|
|
||||||
format := f.buildDefaultFormat()
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(f.fs, format, v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(f.fs, format, v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
|
||||||
// details.
|
|
||||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
|
||||||
f.fs = fs
|
|
||||||
|
|
||||||
// Use standard formatting for verbs that are not v.
|
|
||||||
if verb != 'v' {
|
|
||||||
format := f.constructOrigFormat(verb)
|
|
||||||
fmt.Fprintf(fs, format, f.value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.value == nil {
|
|
||||||
if fs.Flag('#') {
|
|
||||||
fs.Write(interfaceBytes)
|
|
||||||
}
|
|
||||||
fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.format(reflect.ValueOf(f.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFormatter is a helper function to consolidate the logic from the various
|
|
||||||
// public methods which take varying config states.
|
|
||||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
|
||||||
fs := &formatState{value: v, cs: cs}
|
|
||||||
fs.pointers = make(map[uintptr]int)
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
Printf, Println, or Fprintf.
|
|
||||||
*/
|
|
||||||
func NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(&Config, v)
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the formatted string as a value that satisfies error. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a default spew Formatter interface.
|
|
||||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = NewFormatter(arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2013, Patrick Mezard
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
The names of its contributors may not be used to endorse or promote
|
|
||||||
products derived from this software without specific prior written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
||||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,772 +0,0 @@
|
||||||
// Package difflib is a partial port of Python difflib module.
|
|
||||||
//
|
|
||||||
// It provides tools to compare sequences of strings and generate textual diffs.
|
|
||||||
//
|
|
||||||
// The following class and functions have been ported:
|
|
||||||
//
|
|
||||||
// - SequenceMatcher
|
|
||||||
//
|
|
||||||
// - unified_diff
|
|
||||||
//
|
|
||||||
// - context_diff
|
|
||||||
//
|
|
||||||
// Getting unified diffs was the main goal of the port. Keep in mind this code
|
|
||||||
// is mostly suitable to output text differences in a human friendly way, there
|
|
||||||
// are no guarantees generated diffs are consumable by patch(1).
|
|
||||||
package difflib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateRatio(matches, length int) float64 {
|
|
||||||
if length > 0 {
|
|
||||||
return 2.0 * float64(matches) / float64(length)
|
|
||||||
}
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Match struct {
|
|
||||||
A int
|
|
||||||
B int
|
|
||||||
Size int
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpCode struct {
|
|
||||||
Tag byte
|
|
||||||
I1 int
|
|
||||||
I2 int
|
|
||||||
J1 int
|
|
||||||
J2 int
|
|
||||||
}
|
|
||||||
|
|
||||||
// SequenceMatcher compares sequence of strings. The basic
|
|
||||||
// algorithm predates, and is a little fancier than, an algorithm
|
|
||||||
// published in the late 1980's by Ratcliff and Obershelp under the
|
|
||||||
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
|
||||||
// the longest contiguous matching subsequence that contains no "junk"
|
|
||||||
// elements (R-O doesn't address junk). The same idea is then applied
|
|
||||||
// recursively to the pieces of the sequences to the left and to the right
|
|
||||||
// of the matching subsequence. This does not yield minimal edit
|
|
||||||
// sequences, but does tend to yield matches that "look right" to people.
|
|
||||||
//
|
|
||||||
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
|
||||||
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
|
||||||
// longest *contiguous* & junk-free matching subsequence. That's what
|
|
||||||
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
|
||||||
// notion, pairing up elements that appear uniquely in each sequence.
|
|
||||||
// That, and the method here, appear to yield more intuitive difference
|
|
||||||
// reports than does diff. This method appears to be the least vulnerable
|
|
||||||
// to synching up on blocks of "junk lines", though (like blank lines in
|
|
||||||
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
|
||||||
// because this is the only method of the 3 that has a *concept* of
|
|
||||||
// "junk" <wink>.
|
|
||||||
//
|
|
||||||
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
|
||||||
// case. SequenceMatcher is quadratic time for the worst case and has
|
|
||||||
// expected-case behavior dependent in a complicated way on how many
|
|
||||||
// elements the sequences have in common; best case time is linear.
|
|
||||||
type SequenceMatcher struct {
|
|
||||||
a []string
|
|
||||||
b []string
|
|
||||||
b2j map[string][]int
|
|
||||||
IsJunk func(string) bool
|
|
||||||
autoJunk bool
|
|
||||||
bJunk map[string]struct{}
|
|
||||||
matchingBlocks []Match
|
|
||||||
fullBCount map[string]int
|
|
||||||
bPopular map[string]struct{}
|
|
||||||
opCodes []OpCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMatcher(a, b []string) *SequenceMatcher {
|
|
||||||
m := SequenceMatcher{autoJunk: true}
|
|
||||||
m.SetSeqs(a, b)
|
|
||||||
return &m
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
|
||||||
isJunk func(string) bool) *SequenceMatcher {
|
|
||||||
|
|
||||||
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
|
||||||
m.SetSeqs(a, b)
|
|
||||||
return &m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set two sequences to be compared.
|
|
||||||
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
|
||||||
m.SetSeq1(a)
|
|
||||||
m.SetSeq2(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the first sequence to be compared. The second sequence to be compared is
|
|
||||||
// not changed.
|
|
||||||
//
|
|
||||||
// SequenceMatcher computes and caches detailed information about the second
|
|
||||||
// sequence, so if you want to compare one sequence S against many sequences,
|
|
||||||
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
|
||||||
// sequences.
|
|
||||||
//
|
|
||||||
// See also SetSeqs() and SetSeq2().
|
|
||||||
func (m *SequenceMatcher) SetSeq1(a []string) {
|
|
||||||
if &a == &m.a {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.a = a
|
|
||||||
m.matchingBlocks = nil
|
|
||||||
m.opCodes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the second sequence to be compared. The first sequence to be compared is
|
|
||||||
// not changed.
|
|
||||||
func (m *SequenceMatcher) SetSeq2(b []string) {
|
|
||||||
if &b == &m.b {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.b = b
|
|
||||||
m.matchingBlocks = nil
|
|
||||||
m.opCodes = nil
|
|
||||||
m.fullBCount = nil
|
|
||||||
m.chainB()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SequenceMatcher) chainB() {
|
|
||||||
// Populate line -> index mapping
|
|
||||||
b2j := map[string][]int{}
|
|
||||||
for i, s := range m.b {
|
|
||||||
indices := b2j[s]
|
|
||||||
indices = append(indices, i)
|
|
||||||
b2j[s] = indices
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge junk elements
|
|
||||||
m.bJunk = map[string]struct{}{}
|
|
||||||
if m.IsJunk != nil {
|
|
||||||
junk := m.bJunk
|
|
||||||
for s, _ := range b2j {
|
|
||||||
if m.IsJunk(s) {
|
|
||||||
junk[s] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s, _ := range junk {
|
|
||||||
delete(b2j, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge remaining popular elements
|
|
||||||
popular := map[string]struct{}{}
|
|
||||||
n := len(m.b)
|
|
||||||
if m.autoJunk && n >= 200 {
|
|
||||||
ntest := n/100 + 1
|
|
||||||
for s, indices := range b2j {
|
|
||||||
if len(indices) > ntest {
|
|
||||||
popular[s] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s, _ := range popular {
|
|
||||||
delete(b2j, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.bPopular = popular
|
|
||||||
m.b2j = b2j
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SequenceMatcher) isBJunk(s string) bool {
|
|
||||||
_, ok := m.bJunk[s]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
|
||||||
//
|
|
||||||
// If IsJunk is not defined:
|
|
||||||
//
|
|
||||||
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
|
||||||
// alo <= i <= i+k <= ahi
|
|
||||||
// blo <= j <= j+k <= bhi
|
|
||||||
// and for all (i',j',k') meeting those conditions,
|
|
||||||
// k >= k'
|
|
||||||
// i <= i'
|
|
||||||
// and if i == i', j <= j'
|
|
||||||
//
|
|
||||||
// In other words, of all maximal matching blocks, return one that
|
|
||||||
// starts earliest in a, and of all those maximal matching blocks that
|
|
||||||
// start earliest in a, return the one that starts earliest in b.
|
|
||||||
//
|
|
||||||
// If IsJunk is defined, first the longest matching block is
|
|
||||||
// determined as above, but with the additional restriction that no
|
|
||||||
// junk element appears in the block. Then that block is extended as
|
|
||||||
// far as possible by matching (only) junk elements on both sides. So
|
|
||||||
// the resulting block never matches on junk except as identical junk
|
|
||||||
// happens to be adjacent to an "interesting" match.
|
|
||||||
//
|
|
||||||
// If no blocks match, return (alo, blo, 0).
|
|
||||||
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
|
||||||
// CAUTION: stripping common prefix or suffix would be incorrect.
|
|
||||||
// E.g.,
|
|
||||||
// ab
|
|
||||||
// acab
|
|
||||||
// Longest matching block is "ab", but if common prefix is
|
|
||||||
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
|
||||||
// strip, so ends up claiming that ab is changed to acab by
|
|
||||||
// inserting "ca" in the middle. That's minimal but unintuitive:
|
|
||||||
// "it's obvious" that someone inserted "ac" at the front.
|
|
||||||
// Windiff ends up at the same place as diff, but by pairing up
|
|
||||||
// the unique 'b's and then matching the first two 'a's.
|
|
||||||
besti, bestj, bestsize := alo, blo, 0
|
|
||||||
|
|
||||||
// find longest junk-free match
|
|
||||||
// during an iteration of the loop, j2len[j] = length of longest
|
|
||||||
// junk-free match ending with a[i-1] and b[j]
|
|
||||||
j2len := map[int]int{}
|
|
||||||
for i := alo; i != ahi; i++ {
|
|
||||||
// look at all instances of a[i] in b; note that because
|
|
||||||
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
|
||||||
newj2len := map[int]int{}
|
|
||||||
for _, j := range m.b2j[m.a[i]] {
|
|
||||||
// a[i] matches b[j]
|
|
||||||
if j < blo {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if j >= bhi {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
k := j2len[j-1] + 1
|
|
||||||
newj2len[j] = k
|
|
||||||
if k > bestsize {
|
|
||||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j2len = newj2len
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend the best by non-junk elements on each end. In particular,
|
|
||||||
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
|
||||||
// the inner loop above, but also means "the best" match so far
|
|
||||||
// doesn't contain any junk *or* popular non-junk elements.
|
|
||||||
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
|
||||||
m.a[besti-1] == m.b[bestj-1] {
|
|
||||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
|
||||||
}
|
|
||||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
|
||||||
!m.isBJunk(m.b[bestj+bestsize]) &&
|
|
||||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
|
||||||
bestsize += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have a wholly interesting match (albeit possibly
|
|
||||||
// empty!), we may as well suck up the matching junk on each
|
|
||||||
// side of it too. Can't think of a good reason not to, and it
|
|
||||||
// saves post-processing the (possibly considerable) expense of
|
|
||||||
// figuring out what to do with it. In the case of an empty
|
|
||||||
// interesting match, this is clearly the right thing to do,
|
|
||||||
// because no other kind of match is possible in the regions.
|
|
||||||
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
|
||||||
m.a[besti-1] == m.b[bestj-1] {
|
|
||||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
|
||||||
}
|
|
||||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
|
||||||
m.isBJunk(m.b[bestj+bestsize]) &&
|
|
||||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
|
||||||
bestsize += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return Match{A: besti, B: bestj, Size: bestsize}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return list of triples describing matching subsequences.
|
|
||||||
//
|
|
||||||
// Each triple is of the form (i, j, n), and means that
|
|
||||||
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
|
||||||
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
|
||||||
// adjacent triples in the list, and the second is not the last triple in the
|
|
||||||
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
|
||||||
// adjacent equal blocks.
|
|
||||||
//
|
|
||||||
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
|
||||||
// triple with n==0.
|
|
||||||
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
|
||||||
if m.matchingBlocks != nil {
|
|
||||||
return m.matchingBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
|
||||||
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
|
||||||
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
|
||||||
i, j, k := match.A, match.B, match.Size
|
|
||||||
if match.Size > 0 {
|
|
||||||
if alo < i && blo < j {
|
|
||||||
matched = matchBlocks(alo, i, blo, j, matched)
|
|
||||||
}
|
|
||||||
matched = append(matched, match)
|
|
||||||
if i+k < ahi && j+k < bhi {
|
|
||||||
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
|
||||||
|
|
||||||
// It's possible that we have adjacent equal blocks in the
|
|
||||||
// matching_blocks list now.
|
|
||||||
nonAdjacent := []Match{}
|
|
||||||
i1, j1, k1 := 0, 0, 0
|
|
||||||
for _, b := range matched {
|
|
||||||
// Is this block adjacent to i1, j1, k1?
|
|
||||||
i2, j2, k2 := b.A, b.B, b.Size
|
|
||||||
if i1+k1 == i2 && j1+k1 == j2 {
|
|
||||||
// Yes, so collapse them -- this just increases the length of
|
|
||||||
// the first block by the length of the second, and the first
|
|
||||||
// block so lengthened remains the block to compare against.
|
|
||||||
k1 += k2
|
|
||||||
} else {
|
|
||||||
// Not adjacent. Remember the first block (k1==0 means it's
|
|
||||||
// the dummy we started with), and make the second block the
|
|
||||||
// new block to compare against.
|
|
||||||
if k1 > 0 {
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
|
||||||
}
|
|
||||||
i1, j1, k1 = i2, j2, k2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if k1 > 0 {
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
|
||||||
}
|
|
||||||
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
|
||||||
m.matchingBlocks = nonAdjacent
|
|
||||||
return m.matchingBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return list of 5-tuples describing how to turn a into b.
|
|
||||||
//
|
|
||||||
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
|
||||||
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
|
||||||
// tuple preceding it, and likewise for j1 == the previous j2.
|
|
||||||
//
|
|
||||||
// The tags are characters, with these meanings:
|
|
||||||
//
|
|
||||||
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
|
||||||
//
|
|
||||||
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
|
||||||
//
|
|
||||||
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
|
||||||
//
|
|
||||||
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
|
||||||
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
|
||||||
if m.opCodes != nil {
|
|
||||||
return m.opCodes
|
|
||||||
}
|
|
||||||
i, j := 0, 0
|
|
||||||
matching := m.GetMatchingBlocks()
|
|
||||||
opCodes := make([]OpCode, 0, len(matching))
|
|
||||||
for _, m := range matching {
|
|
||||||
// invariant: we've pumped out correct diffs to change
|
|
||||||
// a[:i] into b[:j], and the next matching block is
|
|
||||||
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
|
||||||
// out a diff to change a[i:ai] into b[j:bj], pump out
|
|
||||||
// the matching block, and move (i,j) beyond the match
|
|
||||||
ai, bj, size := m.A, m.B, m.Size
|
|
||||||
tag := byte(0)
|
|
||||||
if i < ai && j < bj {
|
|
||||||
tag = 'r'
|
|
||||||
} else if i < ai {
|
|
||||||
tag = 'd'
|
|
||||||
} else if j < bj {
|
|
||||||
tag = 'i'
|
|
||||||
}
|
|
||||||
if tag > 0 {
|
|
||||||
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
|
||||||
}
|
|
||||||
i, j = ai+size, bj+size
|
|
||||||
// the list of matching blocks is terminated by a
|
|
||||||
// sentinel with size 0
|
|
||||||
if size > 0 {
|
|
||||||
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.opCodes = opCodes
|
|
||||||
return m.opCodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Isolate change clusters by eliminating ranges with no changes.
|
|
||||||
//
|
|
||||||
// Return a generator of groups with up to n lines of context.
|
|
||||||
// Each group is in the same format as returned by GetOpCodes().
|
|
||||||
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
|
||||||
if n < 0 {
|
|
||||||
n = 3
|
|
||||||
}
|
|
||||||
codes := m.GetOpCodes()
|
|
||||||
if len(codes) == 0 {
|
|
||||||
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
|
|
||||||
}
|
|
||||||
// Fixup leading and trailing groups if they show no changes.
|
|
||||||
if codes[0].Tag == 'e' {
|
|
||||||
c := codes[0]
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
|
||||||
}
|
|
||||||
if codes[len(codes)-1].Tag == 'e' {
|
|
||||||
c := codes[len(codes)-1]
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
|
||||||
}
|
|
||||||
nn := n + n
|
|
||||||
groups := [][]OpCode{}
|
|
||||||
group := []OpCode{}
|
|
||||||
for _, c := range codes {
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
// End the current group and start a new one whenever
|
|
||||||
// there is a large range with no changes.
|
|
||||||
if c.Tag == 'e' && i2-i1 > nn {
|
|
||||||
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
|
|
||||||
j1, min(j2, j1+n)})
|
|
||||||
groups = append(groups, group)
|
|
||||||
group = []OpCode{}
|
|
||||||
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
|
||||||
}
|
|
||||||
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
|
||||||
}
|
|
||||||
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
|
||||||
groups = append(groups, group)
|
|
||||||
}
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a measure of the sequences' similarity (float in [0,1]).
|
|
||||||
//
|
|
||||||
// Where T is the total number of elements in both sequences, and
|
|
||||||
// M is the number of matches, this is 2.0*M / T.
|
|
||||||
// Note that this is 1 if the sequences are identical, and 0 if
|
|
||||||
// they have nothing in common.
|
|
||||||
//
|
|
||||||
// .Ratio() is expensive to compute if you haven't already computed
|
|
||||||
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
|
||||||
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
|
||||||
// upper bound.
|
|
||||||
func (m *SequenceMatcher) Ratio() float64 {
|
|
||||||
matches := 0
|
|
||||||
for _, m := range m.GetMatchingBlocks() {
|
|
||||||
matches += m.Size
|
|
||||||
}
|
|
||||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an upper bound on ratio() relatively quickly.
|
|
||||||
//
|
|
||||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
|
||||||
// is faster to compute.
|
|
||||||
func (m *SequenceMatcher) QuickRatio() float64 {
|
|
||||||
// viewing a and b as multisets, set matches to the cardinality
|
|
||||||
// of their intersection; this counts the number of matches
|
|
||||||
// without regard to order, so is clearly an upper bound
|
|
||||||
if m.fullBCount == nil {
|
|
||||||
m.fullBCount = map[string]int{}
|
|
||||||
for _, s := range m.b {
|
|
||||||
m.fullBCount[s] = m.fullBCount[s] + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// avail[x] is the number of times x appears in 'b' less the
|
|
||||||
// number of times we've seen it in 'a' so far ... kinda
|
|
||||||
avail := map[string]int{}
|
|
||||||
matches := 0
|
|
||||||
for _, s := range m.a {
|
|
||||||
n, ok := avail[s]
|
|
||||||
if !ok {
|
|
||||||
n = m.fullBCount[s]
|
|
||||||
}
|
|
||||||
avail[s] = n - 1
|
|
||||||
if n > 0 {
|
|
||||||
matches += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an upper bound on ratio() very quickly.
|
|
||||||
//
|
|
||||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
|
||||||
// is faster to compute than either .Ratio() or .QuickRatio().
|
|
||||||
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
|
||||||
la, lb := len(m.a), len(m.b)
|
|
||||||
return calculateRatio(min(la, lb), la+lb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert range to the "ed" format
|
|
||||||
func formatRangeUnified(start, stop int) string {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
beginning := start + 1 // lines start numbering with one
|
|
||||||
length := stop - start
|
|
||||||
if length == 1 {
|
|
||||||
return fmt.Sprintf("%d", beginning)
|
|
||||||
}
|
|
||||||
if length == 0 {
|
|
||||||
beginning -= 1 // empty ranges begin at line just before the range
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d,%d", beginning, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unified diff parameters
|
|
||||||
type UnifiedDiff struct {
|
|
||||||
A []string // First sequence lines
|
|
||||||
FromFile string // First file name
|
|
||||||
FromDate string // First file time
|
|
||||||
B []string // Second sequence lines
|
|
||||||
ToFile string // Second file name
|
|
||||||
ToDate string // Second file time
|
|
||||||
Eol string // Headers end of line, defaults to LF
|
|
||||||
Context int // Number of context lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare two sequences of lines; generate the delta as a unified diff.
|
|
||||||
//
|
|
||||||
// Unified diffs are a compact way of showing line changes and a few
|
|
||||||
// lines of context. The number of context lines is set by 'n' which
|
|
||||||
// defaults to three.
|
|
||||||
//
|
|
||||||
// By default, the diff control lines (those with ---, +++, or @@) are
|
|
||||||
// created with a trailing newline. This is helpful so that inputs
|
|
||||||
// created from file.readlines() result in diffs that are suitable for
|
|
||||||
// file.writelines() since both the inputs and outputs have trailing
|
|
||||||
// newlines.
|
|
||||||
//
|
|
||||||
// For inputs that do not have trailing newlines, set the lineterm
|
|
||||||
// argument to "" so that the output will be uniformly newline free.
|
|
||||||
//
|
|
||||||
// The unidiff format normally has a header for filenames and modification
|
|
||||||
// times. Any or all of these may be specified using strings for
|
|
||||||
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
|
||||||
// The modification times are normally expressed in the ISO 8601 format.
|
|
||||||
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
|
||||||
buf := bufio.NewWriter(writer)
|
|
||||||
defer buf.Flush()
|
|
||||||
wf := func(format string, args ...interface{}) error {
|
|
||||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ws := func(s string) error {
|
|
||||||
_, err := buf.WriteString(s)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(diff.Eol) == 0 {
|
|
||||||
diff.Eol = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
started := false
|
|
||||||
m := NewMatcher(diff.A, diff.B)
|
|
||||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
|
||||||
if !started {
|
|
||||||
started = true
|
|
||||||
fromDate := ""
|
|
||||||
if len(diff.FromDate) > 0 {
|
|
||||||
fromDate = "\t" + diff.FromDate
|
|
||||||
}
|
|
||||||
toDate := ""
|
|
||||||
if len(diff.ToDate) > 0 {
|
|
||||||
toDate = "\t" + diff.ToDate
|
|
||||||
}
|
|
||||||
if diff.FromFile != "" || diff.ToFile != "" {
|
|
||||||
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first, last := g[0], g[len(g)-1]
|
|
||||||
range1 := formatRangeUnified(first.I1, last.I2)
|
|
||||||
range2 := formatRangeUnified(first.J1, last.J2)
|
|
||||||
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range g {
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
if c.Tag == 'e' {
|
|
||||||
for _, line := range diff.A[i1:i2] {
|
|
||||||
if err := ws(" " + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.Tag == 'r' || c.Tag == 'd' {
|
|
||||||
for _, line := range diff.A[i1:i2] {
|
|
||||||
if err := ws("-" + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.Tag == 'r' || c.Tag == 'i' {
|
|
||||||
for _, line := range diff.B[j1:j2] {
|
|
||||||
if err := ws("+" + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like WriteUnifiedDiff but returns the diff a string.
|
|
||||||
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err := WriteUnifiedDiff(w, diff)
|
|
||||||
return string(w.Bytes()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert range to the "ed" format.
|
|
||||||
func formatRangeContext(start, stop int) string {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
beginning := start + 1 // lines start numbering with one
|
|
||||||
length := stop - start
|
|
||||||
if length == 0 {
|
|
||||||
beginning -= 1 // empty ranges begin at line just before the range
|
|
||||||
}
|
|
||||||
if length <= 1 {
|
|
||||||
return fmt.Sprintf("%d", beginning)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContextDiff UnifiedDiff
|
|
||||||
|
|
||||||
// Compare two sequences of lines; generate the delta as a context diff.
|
|
||||||
//
|
|
||||||
// Context diffs are a compact way of showing line changes and a few
|
|
||||||
// lines of context. The number of context lines is set by diff.Context
|
|
||||||
// which defaults to three.
|
|
||||||
//
|
|
||||||
// By default, the diff control lines (those with *** or ---) are
|
|
||||||
// created with a trailing newline.
|
|
||||||
//
|
|
||||||
// For inputs that do not have trailing newlines, set the diff.Eol
|
|
||||||
// argument to "" so that the output will be uniformly newline free.
|
|
||||||
//
|
|
||||||
// The context diff format normally has a header for filenames and
|
|
||||||
// modification times. Any or all of these may be specified using
|
|
||||||
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
|
|
||||||
// The modification times are normally expressed in the ISO 8601 format.
|
|
||||||
// If not specified, the strings default to blanks.
|
|
||||||
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
|
|
||||||
buf := bufio.NewWriter(writer)
|
|
||||||
defer buf.Flush()
|
|
||||||
var diffErr error
|
|
||||||
wf := func(format string, args ...interface{}) {
|
|
||||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
|
||||||
if diffErr == nil && err != nil {
|
|
||||||
diffErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws := func(s string) {
|
|
||||||
_, err := buf.WriteString(s)
|
|
||||||
if diffErr == nil && err != nil {
|
|
||||||
diffErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(diff.Eol) == 0 {
|
|
||||||
diff.Eol = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := map[byte]string{
|
|
||||||
'i': "+ ",
|
|
||||||
'd': "- ",
|
|
||||||
'r': "! ",
|
|
||||||
'e': " ",
|
|
||||||
}
|
|
||||||
|
|
||||||
started := false
|
|
||||||
m := NewMatcher(diff.A, diff.B)
|
|
||||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
|
||||||
if !started {
|
|
||||||
started = true
|
|
||||||
fromDate := ""
|
|
||||||
if len(diff.FromDate) > 0 {
|
|
||||||
fromDate = "\t" + diff.FromDate
|
|
||||||
}
|
|
||||||
toDate := ""
|
|
||||||
if len(diff.ToDate) > 0 {
|
|
||||||
toDate = "\t" + diff.ToDate
|
|
||||||
}
|
|
||||||
if diff.FromFile != "" || diff.ToFile != "" {
|
|
||||||
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
|
||||||
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
first, last := g[0], g[len(g)-1]
|
|
||||||
ws("***************" + diff.Eol)
|
|
||||||
|
|
||||||
range1 := formatRangeContext(first.I1, last.I2)
|
|
||||||
wf("*** %s ****%s", range1, diff.Eol)
|
|
||||||
for _, c := range g {
|
|
||||||
if c.Tag == 'r' || c.Tag == 'd' {
|
|
||||||
for _, cc := range g {
|
|
||||||
if cc.Tag == 'i' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, line := range diff.A[cc.I1:cc.I2] {
|
|
||||||
ws(prefix[cc.Tag] + line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
range2 := formatRangeContext(first.J1, last.J2)
|
|
||||||
wf("--- %s ----%s", range2, diff.Eol)
|
|
||||||
for _, c := range g {
|
|
||||||
if c.Tag == 'r' || c.Tag == 'i' {
|
|
||||||
for _, cc := range g {
|
|
||||||
if cc.Tag == 'd' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, line := range diff.B[cc.J1:cc.J2] {
|
|
||||||
ws(prefix[cc.Tag] + line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diffErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like WriteContextDiff but returns the diff a string.
|
|
||||||
func GetContextDiffString(diff ContextDiff) (string, error) {
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err := WriteContextDiff(w, diff)
|
|
||||||
return string(w.Bytes()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split a string on "\n" while preserving them. The output can be used
|
|
||||||
// as input for UnifiedDiff and ContextDiff structures.
|
|
||||||
func SplitLines(s string) []string {
|
|
||||||
lines := strings.SplitAfter(s, "\n")
|
|
||||||
lines[len(lines)-1] += "\n"
|
|
||||||
return lines
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2014 Stretchr, Inc.
|
|
||||||
Copyright (c) 2017-2018 objx contributors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,171 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// arrayAccesRegexString is the regex used to extract the array number
|
|
||||||
// from the access path
|
|
||||||
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
|
|
||||||
|
|
||||||
// arrayAccesRegex is the compiled arrayAccesRegexString
|
|
||||||
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
|
|
||||||
|
|
||||||
// Get gets the value using the specified selector and
|
|
||||||
// returns it inside a new Obj object.
|
|
||||||
//
|
|
||||||
// If it cannot find the value, Get will return a nil
|
|
||||||
// value inside an instance of Obj.
|
|
||||||
//
|
|
||||||
// Get can only operate directly on map[string]interface{} and []interface.
|
|
||||||
//
|
|
||||||
// Example
|
|
||||||
//
|
|
||||||
// To access the title of the third chapter of the second book, do:
|
|
||||||
//
|
|
||||||
// o.Get("books[1].chapters[2].title")
|
|
||||||
func (m Map) Get(selector string) *Value {
|
|
||||||
rawObj := access(m, selector, nil, false, false)
|
|
||||||
return &Value{data: rawObj}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets the value using the specified selector and
|
|
||||||
// returns the object on which Set was called.
|
|
||||||
//
|
|
||||||
// Set can only operate directly on map[string]interface{} and []interface
|
|
||||||
//
|
|
||||||
// Example
|
|
||||||
//
|
|
||||||
// To set the title of the third chapter of the second book, do:
|
|
||||||
//
|
|
||||||
// o.Set("books[1].chapters[2].title","Time to Go")
|
|
||||||
func (m Map) Set(selector string, value interface{}) Map {
|
|
||||||
access(m, selector, value, true, false)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// access accesses the object using the selector and performs the
|
|
||||||
// appropriate action.
|
|
||||||
func access(current, selector, value interface{}, isSet, panics bool) interface{} {
|
|
||||||
|
|
||||||
switch selector.(type) {
|
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
||||||
|
|
||||||
if array, ok := current.([]interface{}); ok {
|
|
||||||
index := intFromInterface(selector)
|
|
||||||
|
|
||||||
if index >= len(array) {
|
|
||||||
if panics {
|
|
||||||
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return array[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case string:
|
|
||||||
|
|
||||||
selStr := selector.(string)
|
|
||||||
selSegs := strings.SplitN(selStr, PathSeparator, 2)
|
|
||||||
thisSel := selSegs[0]
|
|
||||||
index := -1
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if strings.Contains(thisSel, "[") {
|
|
||||||
arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)
|
|
||||||
|
|
||||||
if len(arrayMatches) > 0 {
|
|
||||||
// Get the key into the map
|
|
||||||
thisSel = arrayMatches[1]
|
|
||||||
|
|
||||||
// Get the index into the array at the key
|
|
||||||
index, err = strconv.Atoi(arrayMatches[2])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// This should never happen. If it does, something has gone
|
|
||||||
// seriously wrong. Panic.
|
|
||||||
panic("objx: Array index is not an integer. Must use array[int].")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if curMap, ok := current.(Map); ok {
|
|
||||||
current = map[string]interface{}(curMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the object in question
|
|
||||||
switch current.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
curMSI := current.(map[string]interface{})
|
|
||||||
if len(selSegs) <= 1 && isSet {
|
|
||||||
curMSI[thisSel] = value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
current = curMSI[thisSel]
|
|
||||||
default:
|
|
||||||
current = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if current == nil && panics {
|
|
||||||
panic(fmt.Sprintf("objx: '%v' invalid on object.", selector))
|
|
||||||
}
|
|
||||||
|
|
||||||
// do we need to access the item of an array?
|
|
||||||
if index > -1 {
|
|
||||||
if array, ok := current.([]interface{}); ok {
|
|
||||||
if index < len(array) {
|
|
||||||
current = array[index]
|
|
||||||
} else {
|
|
||||||
if panics {
|
|
||||||
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
|
|
||||||
}
|
|
||||||
current = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(selSegs) > 1 {
|
|
||||||
current = access(current, selSegs[1], value, isSet, panics)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
// intFromInterface converts an interface object to the largest
|
|
||||||
// representation of an unsigned integer using a type switch and
|
|
||||||
// assertions
|
|
||||||
func intFromInterface(selector interface{}) int {
|
|
||||||
var value int
|
|
||||||
switch selector.(type) {
|
|
||||||
case int:
|
|
||||||
value = selector.(int)
|
|
||||||
case int8:
|
|
||||||
value = int(selector.(int8))
|
|
||||||
case int16:
|
|
||||||
value = int(selector.(int16))
|
|
||||||
case int32:
|
|
||||||
value = int(selector.(int32))
|
|
||||||
case int64:
|
|
||||||
value = int(selector.(int64))
|
|
||||||
case uint:
|
|
||||||
value = int(selector.(uint))
|
|
||||||
case uint8:
|
|
||||||
value = int(selector.(uint8))
|
|
||||||
case uint16:
|
|
||||||
value = int(selector.(uint16))
|
|
||||||
case uint32:
|
|
||||||
value = int(selector.(uint32))
|
|
||||||
case uint64:
|
|
||||||
value = int(selector.(uint64))
|
|
||||||
default:
|
|
||||||
panic("objx: array access argument is not an integer type (this should never happen)")
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PathSeparator is the character used to separate the elements
|
|
||||||
// of the keypath.
|
|
||||||
//
|
|
||||||
// For example, `location.address.city`
|
|
||||||
PathSeparator string = "."
|
|
||||||
|
|
||||||
// SignatureSeparator is the character that is used to
|
|
||||||
// separate the Base64 string from the security signature.
|
|
||||||
SignatureSeparator = "_"
|
|
||||||
)
|
|
|
@ -1,108 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSON converts the contained object to a JSON string
|
|
||||||
// representation
|
|
||||||
func (m Map) JSON() (string, error) {
|
|
||||||
result, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
err = errors.New("objx: JSON encode failed with: " + err.Error())
|
|
||||||
}
|
|
||||||
return string(result), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustJSON converts the contained object to a JSON string
|
|
||||||
// representation and panics if there is an error
|
|
||||||
func (m Map) MustJSON() string {
|
|
||||||
result, err := m.JSON()
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation
|
|
||||||
func (m Map) Base64() (string, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
jsonData, err := m.JSON()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
|
|
||||||
_, err = encoder.Write([]byte(jsonData))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
_ = encoder.Close()
|
|
||||||
|
|
||||||
return buf.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustBase64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation and panics
|
|
||||||
// if there is an error
|
|
||||||
func (m Map) MustBase64() string {
|
|
||||||
result, err := m.Base64()
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedBase64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation and signs it
|
|
||||||
// using the provided key.
|
|
||||||
func (m Map) SignedBase64(key string) (string, error) {
|
|
||||||
base64, err := m.Base64()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := HashWithKey(base64, key)
|
|
||||||
return base64 + SignatureSeparator + sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustSignedBase64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation and signs it
|
|
||||||
// using the provided key and panics if there is an error
|
|
||||||
func (m Map) MustSignedBase64(key string) string {
|
|
||||||
result, err := m.SignedBase64(key)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
URL Query
|
|
||||||
------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// URLValues creates a url.Values object from an Obj. This
|
|
||||||
// function requires that the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) URLValues() url.Values {
|
|
||||||
vals := make(url.Values)
|
|
||||||
for k, v := range m {
|
|
||||||
//TODO: can this be done without sprintf?
|
|
||||||
vals.Set(k, fmt.Sprintf("%v", v))
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLQuery gets an encoded URL query representing the given
|
|
||||||
// Obj. This function requires that the wrapped object be a
|
|
||||||
// map[string]interface{}
|
|
||||||
func (m Map) URLQuery() (string, error) {
|
|
||||||
return m.URLValues().Encode(), nil
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
Objx - Go package for dealing with maps, slices, JSON and other data.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
|
|
||||||
Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes
|
|
||||||
a powerful `Get` method (among others) that allows you to easily and quickly get
|
|
||||||
access to data within the map, without having to worry too much about type assertions,
|
|
||||||
missing data, default values etc.
|
|
||||||
|
|
||||||
Pattern
|
|
||||||
|
|
||||||
Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy.
|
|
||||||
Call one of the `objx.` functions to create your `objx.Map` to get going:
|
|
||||||
|
|
||||||
m, err := objx.FromJSON(json)
|
|
||||||
|
|
||||||
NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong,
|
|
||||||
the rest will be optimistic and try to figure things out without panicking.
|
|
||||||
|
|
||||||
Use `Get` to access the value you're interested in. You can use dot and array
|
|
||||||
notation too:
|
|
||||||
|
|
||||||
m.Get("places[0].latlng")
|
|
||||||
|
|
||||||
Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type.
|
|
||||||
|
|
||||||
if m.Get("code").IsStr() { // Your code... }
|
|
||||||
|
|
||||||
Or you can just assume the type, and use one of the strong type methods to extract the real value:
|
|
||||||
|
|
||||||
m.Get("code").Int()
|
|
||||||
|
|
||||||
If there's no value there (or if it's the wrong type) then a default value will be returned,
|
|
||||||
or you can be explicit about the default value.
|
|
||||||
|
|
||||||
Get("code").Int(-1)
|
|
||||||
|
|
||||||
If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating,
|
|
||||||
manipulating and selecting that data. You can find out more by exploring the index below.
|
|
||||||
|
|
||||||
Reading data
|
|
||||||
|
|
||||||
A simple example of how to use Objx:
|
|
||||||
|
|
||||||
// Use MustFromJSON to make an objx.Map from some JSON
|
|
||||||
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
|
|
||||||
|
|
||||||
// Get the details
|
|
||||||
name := m.Get("name").Str()
|
|
||||||
age := m.Get("age").Int()
|
|
||||||
|
|
||||||
// Get their nickname (or use their name if they don't have one)
|
|
||||||
nickname := m.Get("nickname").Str(name)
|
|
||||||
|
|
||||||
Ranging
|
|
||||||
|
|
||||||
Since `objx.Map` is a `map[string]interface{}` you can treat it as such.
|
|
||||||
For example, to `range` the data, do what you would expect:
|
|
||||||
|
|
||||||
m := objx.MustFromJSON(json)
|
|
||||||
for key, value := range m {
|
|
||||||
// Your code...
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
package objx
|
|
|
@ -1,193 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MSIConvertable is an interface that defines methods for converting your
|
|
||||||
// custom types to a map[string]interface{} representation.
|
|
||||||
type MSIConvertable interface {
|
|
||||||
// MSI gets a map[string]interface{} (msi) representing the
|
|
||||||
// object.
|
|
||||||
MSI() map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map provides extended functionality for working with
|
|
||||||
// untyped data, in particular map[string]interface (msi).
|
|
||||||
type Map map[string]interface{}
|
|
||||||
|
|
||||||
// Value returns the internal value instance
|
|
||||||
func (m Map) Value() *Value {
|
|
||||||
return &Value{data: m}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nil represents a nil Map.
|
|
||||||
var Nil = New(nil)
|
|
||||||
|
|
||||||
// New creates a new Map containing the map[string]interface{} in the data argument.
|
|
||||||
// If the data argument is not a map[string]interface, New attempts to call the
|
|
||||||
// MSI() method on the MSIConvertable interface to create one.
|
|
||||||
func New(data interface{}) Map {
|
|
||||||
if _, ok := data.(map[string]interface{}); !ok {
|
|
||||||
if converter, ok := data.(MSIConvertable); ok {
|
|
||||||
data = converter.MSI()
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Map(data.(map[string]interface{}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MSI creates a map[string]interface{} and puts it inside a new Map.
|
|
||||||
//
|
|
||||||
// The arguments follow a key, value pattern.
|
|
||||||
//
|
|
||||||
// Panics
|
|
||||||
//
|
|
||||||
// Panics if any key argument is non-string or if there are an odd number of arguments.
|
|
||||||
//
|
|
||||||
// Example
|
|
||||||
//
|
|
||||||
// To easily create Maps:
|
|
||||||
//
|
|
||||||
// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
|
|
||||||
//
|
|
||||||
// // creates an Map equivalent to
|
|
||||||
// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}})
|
|
||||||
func MSI(keyAndValuePairs ...interface{}) Map {
|
|
||||||
newMap := make(map[string]interface{})
|
|
||||||
keyAndValuePairsLen := len(keyAndValuePairs)
|
|
||||||
if keyAndValuePairsLen%2 != 0 {
|
|
||||||
panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < keyAndValuePairsLen; i = i + 2 {
|
|
||||||
key := keyAndValuePairs[i]
|
|
||||||
value := keyAndValuePairs[i+1]
|
|
||||||
|
|
||||||
// make sure the key is a string
|
|
||||||
keyString, keyStringOK := key.(string)
|
|
||||||
if !keyStringOK {
|
|
||||||
panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.")
|
|
||||||
}
|
|
||||||
newMap[keyString] = value
|
|
||||||
}
|
|
||||||
return New(newMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****** Conversion Constructors
|
|
||||||
|
|
||||||
// MustFromJSON creates a new Map containing the data specified in the
|
|
||||||
// jsonString.
|
|
||||||
//
|
|
||||||
// Panics if the JSON is invalid.
|
|
||||||
func MustFromJSON(jsonString string) Map {
|
|
||||||
o, err := FromJSON(jsonString)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromJSON failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromJSON creates a new Map containing the data specified in the
|
|
||||||
// jsonString.
|
|
||||||
//
|
|
||||||
// Returns an error if the JSON is invalid.
|
|
||||||
func FromJSON(jsonString string) (Map, error) {
|
|
||||||
var data interface{}
|
|
||||||
err := json.Unmarshal([]byte(jsonString), &data)
|
|
||||||
if err != nil {
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
return New(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by Base64
|
|
||||||
func FromBase64(base64String string) (Map, error) {
|
|
||||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
|
|
||||||
decoded, err := ioutil.ReadAll(decoder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromJSON(string(decoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFromBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string and panics if there is an error.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by Base64
|
|
||||||
func MustFromBase64(base64String string) Map {
|
|
||||||
result, err := FromBase64(base64String)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromBase64 failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromSignedBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by SignedBase64
|
|
||||||
func FromSignedBase64(base64String, key string) (Map, error) {
|
|
||||||
parts := strings.Split(base64String, SignatureSeparator)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, errors.New("objx: Signed base64 string is malformed")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := HashWithKey(parts[0], key)
|
|
||||||
if parts[1] != sig {
|
|
||||||
return nil, errors.New("objx: Signature for base64 data does not match")
|
|
||||||
}
|
|
||||||
return FromBase64(parts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFromSignedBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string and panics if there is an error.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by Base64
|
|
||||||
func MustFromSignedBase64(base64String, key string) Map {
|
|
||||||
result, err := FromSignedBase64(base64String, key)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromURLQuery generates a new Obj by parsing the specified
|
|
||||||
// query.
|
|
||||||
//
|
|
||||||
// For queries with multiple values, the first value is selected.
|
|
||||||
func FromURLQuery(query string) (Map, error) {
|
|
||||||
vals, err := url.ParseQuery(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := make(map[string]interface{})
|
|
||||||
for k, vals := range vals {
|
|
||||||
m[k] = vals[0]
|
|
||||||
}
|
|
||||||
return New(m), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFromURLQuery generates a new Obj by parsing the specified
|
|
||||||
// query.
|
|
||||||
//
|
|
||||||
// For queries with multiple values, the first value is selected.
|
|
||||||
//
|
|
||||||
// Panics if it encounters an error
|
|
||||||
func MustFromURLQuery(query string) Map {
|
|
||||||
o, err := FromURLQuery(query)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromURLQuery failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
// Exclude returns a new Map with the keys in the specified []string
|
|
||||||
// excluded.
|
|
||||||
func (m Map) Exclude(exclude []string) Map {
|
|
||||||
excluded := make(Map)
|
|
||||||
for k, v := range m {
|
|
||||||
var shouldInclude = true
|
|
||||||
for _, toExclude := range exclude {
|
|
||||||
if k == toExclude {
|
|
||||||
shouldInclude = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if shouldInclude {
|
|
||||||
excluded[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return excluded
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a shallow copy of the Obj.
|
|
||||||
func (m Map) Copy() Map {
|
|
||||||
copied := make(map[string]interface{})
|
|
||||||
for k, v := range m {
|
|
||||||
copied[k] = v
|
|
||||||
}
|
|
||||||
return New(copied)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge blends the specified map with a copy of this map and returns the result.
|
|
||||||
//
|
|
||||||
// Keys that appear in both will be selected from the specified map.
|
|
||||||
// This method requires that the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) Merge(merge Map) Map {
|
|
||||||
return m.Copy().MergeHere(merge)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeHere blends the specified map with this map and returns the current map.
|
|
||||||
//
|
|
||||||
// Keys that appear in both will be selected from the specified map. The original map
|
|
||||||
// will be modified. This method requires that
|
|
||||||
// the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) MergeHere(merge Map) Map {
|
|
||||||
for k, v := range merge {
|
|
||||||
m[k] = v
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform builds a new Obj giving the transformer a chance
|
|
||||||
// to change the keys and values as it goes. This method requires that
|
|
||||||
// the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map {
|
|
||||||
newMap := make(map[string]interface{})
|
|
||||||
for k, v := range m {
|
|
||||||
modifiedKey, modifiedVal := transformer(k, v)
|
|
||||||
newMap[modifiedKey] = modifiedVal
|
|
||||||
}
|
|
||||||
return New(newMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransformKeys builds a new map using the specified key mapping.
|
|
||||||
//
|
|
||||||
// Unspecified keys will be unaltered.
|
|
||||||
// This method requires that the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) TransformKeys(mapping map[string]string) Map {
|
|
||||||
return m.Transform(func(key string, value interface{}) (string, interface{}) {
|
|
||||||
if newKey, ok := mapping[key]; ok {
|
|
||||||
return newKey, value
|
|
||||||
}
|
|
||||||
return key, value
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HashWithKey hashes the specified string using the security
|
|
||||||
// key.
|
|
||||||
func HashWithKey(data, key string) string {
|
|
||||||
hash := sha1.New()
|
|
||||||
_, err := hash.Write([]byte(data + ":" + key))
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(hash.Sum(nil))
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
// Has gets whether there is something at the specified selector
|
|
||||||
// or not.
|
|
||||||
//
|
|
||||||
// If m is nil, Has will always return false.
|
|
||||||
func (m Map) Has(selector string) bool {
|
|
||||||
if m == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !m.Get(selector).IsNil()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNil gets whether the data is nil or not.
|
|
||||||
func (v *Value) IsNil() bool {
|
|
||||||
return v == nil || v.data == nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,56 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Value provides methods for extracting interface{} data in various
|
|
||||||
// types.
|
|
||||||
type Value struct {
|
|
||||||
// data contains the raw data being managed by this Value
|
|
||||||
data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data returns the raw data contained by this Value
|
|
||||||
func (v *Value) Data() interface{} {
|
|
||||||
return v.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the value always as a string
|
|
||||||
func (v *Value) String() string {
|
|
||||||
switch {
|
|
||||||
case v.IsStr():
|
|
||||||
return v.Str()
|
|
||||||
case v.IsBool():
|
|
||||||
return strconv.FormatBool(v.Bool())
|
|
||||||
case v.IsFloat32():
|
|
||||||
return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32)
|
|
||||||
case v.IsFloat64():
|
|
||||||
return strconv.FormatFloat(v.Float64(), 'f', -1, 64)
|
|
||||||
case v.IsInt():
|
|
||||||
return strconv.FormatInt(int64(v.Int()), 10)
|
|
||||||
case v.IsInt():
|
|
||||||
return strconv.FormatInt(int64(v.Int()), 10)
|
|
||||||
case v.IsInt8():
|
|
||||||
return strconv.FormatInt(int64(v.Int8()), 10)
|
|
||||||
case v.IsInt16():
|
|
||||||
return strconv.FormatInt(int64(v.Int16()), 10)
|
|
||||||
case v.IsInt32():
|
|
||||||
return strconv.FormatInt(int64(v.Int32()), 10)
|
|
||||||
case v.IsInt64():
|
|
||||||
return strconv.FormatInt(v.Int64(), 10)
|
|
||||||
case v.IsUint():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint()), 10)
|
|
||||||
case v.IsUint8():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint8()), 10)
|
|
||||||
case v.IsUint16():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint16()), 10)
|
|
||||||
case v.IsUint32():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint32()), 10)
|
|
||||||
case v.IsUint64():
|
|
||||||
return strconv.FormatUint(v.Uint64(), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%#v", v.Data())
|
|
||||||
}
|
|
Loading…
Reference in New Issue