Merge pull request #448 from kubernetes-csi/dependabot/go_modules/github.com/onsi/gomega-1.27.6

chore(deps): bump github.com/onsi/gomega from 1.10.1 to 1.27.6
This commit is contained in:
Kubernetes Prow Robot 2023-05-09 18:07:53 -07:00 committed by GitHub
commit 5a9bdfb448
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 3842 additions and 1958 deletions

7
go.mod
View File

@ -4,10 +4,10 @@ go 1.19
require ( require (
github.com/container-storage-interface/spec v1.5.0 github.com/container-storage-interface/spec v1.5.0
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.3
github.com/kubernetes-csi/csi-lib-utils v0.9.0 github.com/kubernetes-csi/csi-lib-utils v0.9.0
github.com/onsi/ginkgo v1.14.0 github.com/onsi/ginkgo v1.14.0
github.com/onsi/gomega v1.10.1 github.com/onsi/gomega v1.27.6
github.com/pborman/uuid v1.2.0 github.com/pborman/uuid v1.2.0
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
golang.org/x/net v0.9.0 golang.org/x/net v0.9.0
@ -38,7 +38,7 @@ require (
github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.5.5 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.1.0 // indirect github.com/google/gofuzz v1.1.0 // indirect
github.com/google/uuid v1.1.2 // indirect github.com/google/uuid v1.1.2 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect github.com/googleapis/gnostic v0.5.5 // indirect
@ -80,7 +80,6 @@ require (
golang.org/x/term v0.7.0 // indirect golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect

14
go.sum
View File

@ -224,6 +224,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -265,8 +266,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -284,8 +286,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -303,6 +306,7 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -462,10 +466,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
@ -945,10 +951,10 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=

View File

@ -386,8 +386,14 @@ func (u *Unmarshaler) unmarshalMessage(m protoreflect.Message, in []byte) error
} }
func isSingularWellKnownValue(fd protoreflect.FieldDescriptor) bool { func isSingularWellKnownValue(fd protoreflect.FieldDescriptor) bool {
if fd.Cardinality() == protoreflect.Repeated {
return false
}
if md := fd.Message(); md != nil { if md := fd.Message(); md != nil {
return md.FullName() == "google.protobuf.Value" && fd.Cardinality() != protoreflect.Repeated return md.FullName() == "google.protobuf.Value"
}
if ed := fd.Enum(); ed != nil {
return ed.FullName() == "google.protobuf.NullValue"
} }
return false return false
} }

View File

@ -13,21 +13,21 @@
// //
// The primary features of cmp are: // The primary features of cmp are:
// //
// • When the default behavior of equality does not suit the needs of the test, // - When the default behavior of equality does not suit the test's needs,
// custom equality functions can override the equality operation. // custom equality functions can override the equality operation.
// For example, an equality function may report floats as equal so long as they // For example, an equality function may report floats as equal so long as
// are within some tolerance of each other. // they are within some tolerance of each other.
// //
// • Types that have an Equal method may use that method to determine equality. // - Types with an Equal method may use that method to determine equality.
// This allows package authors to determine the equality operation for the types // This allows package authors to determine the equality operation
// that they define. // for the types that they define.
// //
// If no custom equality functions are used and no Equal method is defined, // - If no custom equality functions are used and no Equal method is defined,
// equality is determined by recursively comparing the primitive kinds on both // equality is determined by recursively comparing the primitive kinds on
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported // both values, much like reflect.DeepEqual. Unlike reflect.DeepEqual,
// fields are not compared by default; they result in panics unless suppressed // unexported fields are not compared by default; they result in panics
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly // unless suppressed by using an Ignore option (see cmpopts.IgnoreUnexported)
// compared using the Exporter option. // or explicitly compared using the Exporter option.
package cmp package cmp
import ( import (
@ -36,33 +36,34 @@ import (
"strings" "strings"
"github.com/google/go-cmp/cmp/internal/diff" "github.com/google/go-cmp/cmp/internal/diff"
"github.com/google/go-cmp/cmp/internal/flags"
"github.com/google/go-cmp/cmp/internal/function" "github.com/google/go-cmp/cmp/internal/function"
"github.com/google/go-cmp/cmp/internal/value" "github.com/google/go-cmp/cmp/internal/value"
) )
// TODO(≥go1.18): Use any instead of interface{}.
// Equal reports whether x and y are equal by recursively applying the // Equal reports whether x and y are equal by recursively applying the
// following rules in the given order to x and y and all of their sub-values: // following rules in the given order to x and y and all of their sub-values:
// //
// Let S be the set of all Ignore, Transformer, and Comparer options that // - Let S be the set of all Ignore, Transformer, and Comparer options that
// remain after applying all path filters, value filters, and type filters. // remain after applying all path filters, value filters, and type filters.
// If at least one Ignore exists in S, then the comparison is ignored. // If at least one Ignore exists in S, then the comparison is ignored.
// If the number of Transformer and Comparer options in S is greater than one, // If the number of Transformer and Comparer options in S is non-zero,
// then Equal panics because it is ambiguous which option to use. // then Equal panics because it is ambiguous which option to use.
// If S contains a single Transformer, then use that to transform the current // If S contains a single Transformer, then use that to transform
// values and recursively call Equal on the output values. // the current values and recursively call Equal on the output values.
// If S contains a single Comparer, then use that to compare the current values. // If S contains a single Comparer, then use that to compare the current values.
// Otherwise, evaluation proceeds to the next rule. // Otherwise, evaluation proceeds to the next rule.
// //
// If the values have an Equal method of the form "(T) Equal(T) bool" or // - If the values have an Equal method of the form "(T) Equal(T) bool" or
// "(T) Equal(I) bool" where T is assignable to I, then use the result of // "(T) Equal(I) bool" where T is assignable to I, then use the result of
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and // x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
// evaluation proceeds to the next rule. // evaluation proceeds to the next rule.
// //
// Lastly, try to compare x and y based on their basic kinds. // - Lastly, try to compare x and y based on their basic kinds.
// Simple kinds like booleans, integers, floats, complex numbers, strings, and // Simple kinds like booleans, integers, floats, complex numbers, strings,
// channels are compared using the equivalent of the == operator in Go. // and channels are compared using the equivalent of the == operator in Go.
// Functions are only equal if they are both nil, otherwise they are unequal. // Functions are only equal if they are both nil, otherwise they are unequal.
// //
// Structs are equal if recursively calling Equal on all fields report equal. // Structs are equal if recursively calling Equal on all fields report equal.
// If a struct contains unexported fields, Equal panics unless an Ignore option // If a struct contains unexported fields, Equal panics unless an Ignore option
@ -143,7 +144,7 @@ func rootStep(x, y interface{}) PathStep {
// so that they have the same parent type. // so that they have the same parent type.
var t reflect.Type var t reflect.Type
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
t = reflect.TypeOf((*interface{})(nil)).Elem() t = anyType
if vx.IsValid() { if vx.IsValid() {
vvx := reflect.New(t).Elem() vvx := reflect.New(t).Elem()
vvx.Set(vx) vvx.Set(vx)
@ -319,7 +320,6 @@ func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
} }
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
v = sanitizeValue(v, f.Type().In(0))
if !s.dynChecker.Next() { if !s.dynChecker.Next() {
return f.Call([]reflect.Value{v})[0] return f.Call([]reflect.Value{v})[0]
} }
@ -343,8 +343,6 @@ func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
} }
func (s *state) callTTBFunc(f, x, y reflect.Value) bool { func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
x = sanitizeValue(x, f.Type().In(0))
y = sanitizeValue(y, f.Type().In(1))
if !s.dynChecker.Next() { if !s.dynChecker.Next() {
return f.Call([]reflect.Value{x, y})[0].Bool() return f.Call([]reflect.Value{x, y})[0].Bool()
} }
@ -372,19 +370,6 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
ret = f.Call(vs)[0] ret = f.Call(vs)[0]
} }
// sanitizeValue converts nil interfaces of type T to those of type R,
// assuming that T is assignable to R.
// Otherwise, it returns the input value as is.
func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value {
// TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/22143).
if !flags.AtLeastGo110 {
if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t {
return reflect.New(t).Elem()
}
}
return v
}
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
var addr bool var addr bool
var vax, vay reflect.Value // Addressable versions of vx and vy var vax, vay reflect.Value // Addressable versions of vx and vy
@ -654,7 +639,9 @@ type dynChecker struct{ curr, next int }
// Next increments the state and reports whether a check should be performed. // Next increments the state and reports whether a check should be performed.
// //
// Checks occur every Nth function call, where N is a triangular number: // Checks occur every Nth function call, where N is a triangular number:
//
// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ... // 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
//
// See https://en.wikipedia.org/wiki/Triangular_number // See https://en.wikipedia.org/wiki/Triangular_number
// //
// This sequence ensures that the cost of checks drops significantly as // This sequence ensures that the cost of checks drops significantly as

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build purego
// +build purego // +build purego
package cmp package cmp

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !purego
// +build !purego // +build !purego
package cmp package cmp

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !cmp_debug
// +build !cmp_debug // +build !cmp_debug
package diff package diff

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build cmp_debug
// +build cmp_debug // +build cmp_debug
package diff package diff

View File

@ -127,9 +127,9 @@ var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
// This function returns an edit-script, which is a sequence of operations // This function returns an edit-script, which is a sequence of operations
// needed to convert one list into the other. The following invariants for // needed to convert one list into the other. The following invariants for
// the edit-script are maintained: // the edit-script are maintained:
// eq == (es.Dist()==0) // - eq == (es.Dist()==0)
// nx == es.LenX() // - nx == es.LenX()
// ny == es.LenY() // - ny == es.LenY()
// //
// This algorithm is not guaranteed to be an optimal solution (i.e., one that // This algorithm is not guaranteed to be an optimal solution (i.e., one that
// produces an edit-script with a minimal Levenshtein distance). This algorithm // produces an edit-script with a minimal Levenshtein distance). This algorithm
@ -169,12 +169,13 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// A diagonal edge is equivalent to a matching symbol between both X and Y. // A diagonal edge is equivalent to a matching symbol between both X and Y.
// Invariants: // Invariants:
// 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx // - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
// 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny // - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
// //
// In general: // In general:
// • fwdFrontier.X < revFrontier.X // - fwdFrontier.X < revFrontier.X
// • fwdFrontier.Y < revFrontier.Y // - fwdFrontier.Y < revFrontier.Y
//
// Unless, it is time for the algorithm to terminate. // Unless, it is time for the algorithm to terminate.
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
revPath := path{-1, point{nx, ny}, make(EditScript, 0)} revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
@ -195,19 +196,21 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// computing sub-optimal edit-scripts between two lists. // computing sub-optimal edit-scripts between two lists.
// //
// The algorithm is approximately as follows: // The algorithm is approximately as follows:
// • Searching for differences switches back-and-forth between // - Searching for differences switches back-and-forth between
// a search that starts at the beginning (the top-left corner), and // a search that starts at the beginning (the top-left corner), and
// a search that starts at the end (the bottom-right corner). The goal of // a search that starts at the end (the bottom-right corner).
// the search is connect with the search from the opposite corner. // The goal of the search is connect with the search
// • As we search, we build a path in a greedy manner, where the first // from the opposite corner.
// match seen is added to the path (this is sub-optimal, but provides a // - As we search, we build a path in a greedy manner,
// decent result in practice). When matches are found, we try the next pair // where the first match seen is added to the path (this is sub-optimal,
// of symbols in the lists and follow all matches as far as possible. // but provides a decent result in practice). When matches are found,
// • When searching for matches, we search along a diagonal going through // we try the next pair of symbols in the lists and follow all matches
// through the "frontier" point. If no matches are found, we advance the // as far as possible.
// frontier towards the opposite corner. // - When searching for matches, we search along a diagonal going through
// • This algorithm terminates when either the X coordinates or the // through the "frontier" point. If no matches are found,
// Y coordinates of the forward and reverse frontier points ever intersect. // we advance the frontier towards the opposite corner.
// - This algorithm terminates when either the X coordinates or the
// Y coordinates of the forward and reverse frontier points ever intersect.
// This algorithm is correct even if searching only in the forward direction // This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed // or in the reverse direction. We do both because it is commonly observed
@ -389,6 +392,7 @@ type point struct{ X, Y int }
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy } func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
// zigzag maps a consecutive sequence of integers to a zig-zag sequence. // zigzag maps a consecutive sequence of integers to a zig-zag sequence.
//
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...] // [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
func zigzag(x int) int { func zigzag(x int) int {
if x&1 != 0 { if x&1 != 0 {

View File

@ -1,10 +0,0 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.10
package flags
// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
const AtLeastGo110 = false

View File

@ -1,10 +0,0 @@
// Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.10
package flags
// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
const AtLeastGo110 = true

View File

@ -9,6 +9,8 @@ import (
"strconv" "strconv"
) )
var anyType = reflect.TypeOf((*interface{})(nil)).Elem()
// TypeString is nearly identical to reflect.Type.String, // TypeString is nearly identical to reflect.Type.String,
// but has an additional option to specify that full type names be used. // but has an additional option to specify that full type names be used.
func TypeString(t reflect.Type, qualified bool) string { func TypeString(t reflect.Type, qualified bool) string {
@ -20,6 +22,11 @@ func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte
// of the same name and within the same package, // of the same name and within the same package,
// but declared within the namespace of different functions. // but declared within the namespace of different functions.
// Use the "any" alias instead of "interface{}" for better readability.
if t == anyType {
return append(b, "any"...)
}
// Named type. // Named type.
if t.Name() != "" { if t.Name() != "" {
if qualified && t.PkgPath() != "" { if qualified && t.PkgPath() != "" {

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build purego
// +build purego // +build purego
package value package value

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !purego
// +build !purego // +build !purego
package value package value

View File

@ -1,48 +0,0 @@
// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package value
import (
"math"
"reflect"
)
// IsZero reports whether v is the zero value.
// This does not rely on Interface and so can be used on unexported fields.
func IsZero(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool() == false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return math.Float64bits(v.Float()) == 0
case reflect.Complex64, reflect.Complex128:
return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0
case reflect.String:
return v.String() == ""
case reflect.UnsafePointer:
return v.Pointer() == 0
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
return v.IsNil()
case reflect.Array:
for i := 0; i < v.Len(); i++ {
if !IsZero(v.Index(i)) {
return false
}
}
return true
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if !IsZero(v.Field(i)) {
return false
}
}
return true
}
return false
}

View File

@ -33,6 +33,7 @@ type Option interface {
} }
// applicableOption represents the following types: // applicableOption represents the following types:
//
// Fundamental: ignore | validator | *comparer | *transformer // Fundamental: ignore | validator | *comparer | *transformer
// Grouping: Options // Grouping: Options
type applicableOption interface { type applicableOption interface {
@ -43,6 +44,7 @@ type applicableOption interface {
} }
// coreOption represents the following types: // coreOption represents the following types:
//
// Fundamental: ignore | validator | *comparer | *transformer // Fundamental: ignore | validator | *comparer | *transformer
// Filters: *pathFilter | *valuesFilter // Filters: *pathFilter | *valuesFilter
type coreOption interface { type coreOption interface {
@ -336,9 +338,9 @@ func (tr transformer) String() string {
// both implement T. // both implement T.
// //
// The equality function must be: // The equality function must be:
// Symmetric: equal(x, y) == equal(y, x) // - Symmetric: equal(x, y) == equal(y, x)
// Deterministic: equal(x, y) == equal(x, y) // - Deterministic: equal(x, y) == equal(x, y)
// Pure: equal(x, y) does not modify x or y // - Pure: equal(x, y) does not modify x or y
func Comparer(f interface{}) Option { func Comparer(f interface{}) Option {
v := reflect.ValueOf(f) v := reflect.ValueOf(f)
if !function.IsType(v.Type(), function.Equal) || v.IsNil() { if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
@ -430,7 +432,7 @@ func AllowUnexported(types ...interface{}) Option {
} }
// Result represents the comparison result for a single node and // Result represents the comparison result for a single node and
// is provided by cmp when calling Result (see Reporter). // is provided by cmp when calling Report (see Reporter).
type Result struct { type Result struct {
_ [0]func() // Make Result incomparable _ [0]func() // Make Result incomparable
flags resultFlags flags resultFlags

View File

@ -41,13 +41,13 @@ type PathStep interface {
// The type of each valid value is guaranteed to be identical to Type. // The type of each valid value is guaranteed to be identical to Type.
// //
// In some cases, one or both may be invalid or have restrictions: // In some cases, one or both may be invalid or have restrictions:
// For StructField, both are not interface-able if the current field // - For StructField, both are not interface-able if the current field
// is unexported and the struct type is not explicitly permitted by // is unexported and the struct type is not explicitly permitted by
// an Exporter to traverse unexported fields. // an Exporter to traverse unexported fields.
// For SliceIndex, one may be invalid if an element is missing from // - For SliceIndex, one may be invalid if an element is missing from
// either the x or y slice. // either the x or y slice.
// For MapIndex, one may be invalid if an entry is missing from // - For MapIndex, one may be invalid if an entry is missing from
// either the x or y map. // either the x or y map.
// //
// The provided values must not be mutated. // The provided values must not be mutated.
Values() (vx, vy reflect.Value) Values() (vx, vy reflect.Value)
@ -94,6 +94,7 @@ func (pa Path) Index(i int) PathStep {
// The simplified path only contains struct field accesses. // The simplified path only contains struct field accesses.
// //
// For example: // For example:
//
// MyMap.MySlices.MyField // MyMap.MySlices.MyField
func (pa Path) String() string { func (pa Path) String() string {
var ss []string var ss []string
@ -108,6 +109,7 @@ func (pa Path) String() string {
// GoString returns the path to a specific node using Go syntax. // GoString returns the path to a specific node using Go syntax.
// //
// For example: // For example:
//
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField // (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
func (pa Path) GoString() string { func (pa Path) GoString() string {
var ssPre, ssPost []string var ssPre, ssPost []string
@ -159,7 +161,7 @@ func (ps pathStep) String() string {
if ps.typ == nil { if ps.typ == nil {
return "<nil>" return "<nil>"
} }
s := ps.typ.String() s := value.TypeString(ps.typ, false)
if s == "" || strings.ContainsAny(s, "{}\n") { if s == "" || strings.ContainsAny(s, "{}\n") {
return "root" // Type too simple or complex to print return "root" // Type too simple or complex to print
} }
@ -178,7 +180,7 @@ type structField struct {
unexported bool unexported bool
mayForce bool // Forcibly allow visibility mayForce bool // Forcibly allow visibility
paddr bool // Was parent addressable? paddr bool // Was parent addressable?
pvx, pvy reflect.Value // Parent values (always addressible) pvx, pvy reflect.Value // Parent values (always addressable)
field reflect.StructField // Field information field reflect.StructField // Field information
} }
@ -282,7 +284,7 @@ type typeAssertion struct {
func (ta TypeAssertion) Type() reflect.Type { return ta.typ } func (ta TypeAssertion) Type() reflect.Type { return ta.typ }
func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }
func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) }
// Transform is a transformation from the parent type to the current type. // Transform is a transformation from the parent type to the current type.
type Transform struct{ *transform } type Transform struct{ *transform }
@ -315,7 +317,7 @@ func (tf Transform) Option() Option { return tf.trans }
// pops the address from the stack. Thus, when traversing into a pointer from // pops the address from the stack. Thus, when traversing into a pointer from
// reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles // reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
// by checking whether the pointer has already been visited. The cycle detection // by checking whether the pointer has already been visited. The cycle detection
// uses a seperate stack for the x and y values. // uses a separate stack for the x and y values.
// //
// If a cycle is detected we need to determine whether the two pointers // If a cycle is detected we need to determine whether the two pointers
// should be considered equal. The definition of equality chosen by Equal // should be considered equal. The definition of equality chosen by Equal

View File

@ -7,8 +7,6 @@ package cmp
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/google/go-cmp/cmp/internal/value"
) )
// numContextRecords is the number of surrounding equal records to print. // numContextRecords is the number of surrounding equal records to print.
@ -116,7 +114,10 @@ func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out
} }
// For leaf nodes, format the value based on the reflect.Values alone. // For leaf nodes, format the value based on the reflect.Values alone.
if v.MaxDepth == 0 { // As a special case, treat equal []byte as a leaf nodes.
isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType
isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0
if v.MaxDepth == 0 || isEqualBytes {
switch opts.DiffMode { switch opts.DiffMode {
case diffUnknown, diffIdentical: case diffUnknown, diffIdentical:
// Format Equal. // Format Equal.
@ -245,11 +246,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, pt
var isZero bool var isZero bool
switch opts.DiffMode { switch opts.DiffMode {
case diffIdentical: case diffIdentical:
isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero()
case diffRemoved: case diffRemoved:
isZero = value.IsZero(r.Value.ValueX) isZero = r.Value.ValueX.IsZero()
case diffInserted: case diffInserted:
isZero = value.IsZero(r.Value.ValueY) isZero = r.Value.ValueY.IsZero()
} }
if isZero { if isZero {
continue continue

View File

@ -16,6 +16,13 @@ import (
"github.com/google/go-cmp/cmp/internal/value" "github.com/google/go-cmp/cmp/internal/value"
) )
var (
anyType = reflect.TypeOf((*interface{})(nil)).Elem()
stringType = reflect.TypeOf((*string)(nil)).Elem()
bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
byteType = reflect.TypeOf((*byte)(nil)).Elem()
)
type formatValueOptions struct { type formatValueOptions struct {
// AvoidStringer controls whether to avoid calling custom stringer // AvoidStringer controls whether to avoid calling custom stringer
// methods like error.Error or fmt.Stringer.String. // methods like error.Error or fmt.Stringer.String.
@ -184,7 +191,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
} }
for i := 0; i < v.NumField(); i++ { for i := 0; i < v.NumField(); i++ {
vv := v.Field(i) vv := v.Field(i)
if value.IsZero(vv) { if vv.IsZero() {
continue // Elide fields with zero values continue // Elide fields with zero values
} }
if len(list) == maxLen { if len(list) == maxLen {
@ -205,12 +212,13 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
} }
// Check whether this is a []byte of text data. // Check whether this is a []byte of text data.
if t.Elem() == reflect.TypeOf(byte(0)) { if t.Elem() == byteType {
b := v.Bytes() b := v.Bytes()
isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) } isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) }
if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 {
out = opts.formatString("", string(b)) out = opts.formatString("", string(b))
return opts.WithTypeMode(emitType).FormatType(t, out) skipType = true
return opts.FormatType(t, out)
} }
} }
@ -281,7 +289,12 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
} }
defer ptrs.Pop() defer ptrs.Pop()
skipType = true // Let the underlying value print the type instead // Skip the name only if this is an unnamed pointer type.
// Otherwise taking the address of a value does not reproduce
// the named pointer type.
if v.Type().Name() == "" {
skipType = true // Let the underlying value print the type instead
}
out = opts.FormatValue(v.Elem(), t.Kind(), ptrs) out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
out = &textWrap{Prefix: "&", Value: out} out = &textWrap{Prefix: "&", Value: out}
@ -292,7 +305,6 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
} }
// Interfaces accept different concrete types, // Interfaces accept different concrete types,
// so configure the underlying value to explicitly print the type. // so configure the underlying value to explicitly print the type.
skipType = true // Print the concrete type instead
return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs) return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs)
default: default:
panic(fmt.Sprintf("%v kind not handled", v.Kind())) panic(fmt.Sprintf("%v kind not handled", v.Kind()))

View File

@ -7,6 +7,7 @@ package cmp
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -79,7 +80,7 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
} }
// Use specialized string diffing for longer slices or strings. // Use specialized string diffing for longer slices or strings.
const minLength = 64 const minLength = 32
return vx.Len() >= minLength && vy.Len() >= minLength return vx.Len() >= minLength && vy.Len() >= minLength
} }
@ -96,15 +97,16 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
} }
// Auto-detect the type of the data. // Auto-detect the type of the data.
var isLinedText, isText, isBinary bool
var sx, sy string var sx, sy string
var ssx, ssy []string
var isString, isMostlyText, isPureLinedText, isBinary bool
switch { switch {
case t.Kind() == reflect.String: case t.Kind() == reflect.String:
sx, sy = vx.String(), vy.String() sx, sy = vx.String(), vy.String()
isText = true // Initial estimate, verify later isString = true
case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)): case t.Kind() == reflect.Slice && t.Elem() == byteType:
sx, sy = string(vx.Bytes()), string(vy.Bytes()) sx, sy = string(vx.Bytes()), string(vy.Bytes())
isBinary = true // Initial estimate, verify later isString = true
case t.Kind() == reflect.Array: case t.Kind() == reflect.Array:
// Arrays need to be addressable for slice operations to work. // Arrays need to be addressable for slice operations to work.
vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem() vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem()
@ -112,13 +114,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
vy2.Set(vy) vy2.Set(vy)
vx, vy = vx2, vy2 vx, vy = vx2, vy2
} }
if isText || isBinary { if isString {
var numLines, lastLineIdx, maxLineLen int var numTotalRunes, numValidRunes, numLines, lastLineIdx, maxLineLen int
isBinary = !utf8.ValidString(sx) || !utf8.ValidString(sy)
for i, r := range sx + sy { for i, r := range sx + sy {
if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError { numTotalRunes++
isBinary = true if (unicode.IsPrint(r) || unicode.IsSpace(r)) && r != utf8.RuneError {
break numValidRunes++
} }
if r == '\n' { if r == '\n' {
if maxLineLen < i-lastLineIdx { if maxLineLen < i-lastLineIdx {
@ -128,8 +129,29 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
numLines++ numLines++
} }
} }
isText = !isBinary isPureText := numValidRunes == numTotalRunes
isLinedText = isText && numLines >= 4 && maxLineLen <= 1024 isMostlyText = float64(numValidRunes) > math.Floor(0.90*float64(numTotalRunes))
isPureLinedText = isPureText && numLines >= 4 && maxLineLen <= 1024
isBinary = !isMostlyText
// Avoid diffing by lines if it produces a significantly more complex
// edit script than diffing by bytes.
if isPureLinedText {
ssx = strings.Split(sx, "\n")
ssy = strings.Split(sy, "\n")
esLines := diff.Difference(len(ssx), len(ssy), func(ix, iy int) diff.Result {
return diff.BoolResult(ssx[ix] == ssy[iy])
})
esBytes := diff.Difference(len(sx), len(sy), func(ix, iy int) diff.Result {
return diff.BoolResult(sx[ix] == sy[iy])
})
efficiencyLines := float64(esLines.Dist()) / float64(len(esLines))
efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes))
quotedLength := len(strconv.Quote(sx + sy))
unquotedLength := len(sx) + len(sy)
escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength)
isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1
}
} }
// Format the string into printable records. // Format the string into printable records.
@ -138,9 +160,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
switch { switch {
// If the text appears to be multi-lined text, // If the text appears to be multi-lined text,
// then perform differencing across individual lines. // then perform differencing across individual lines.
case isLinedText: case isPureLinedText:
ssx := strings.Split(sx, "\n")
ssy := strings.Split(sy, "\n")
list = opts.formatDiffSlice( list = opts.formatDiffSlice(
reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line", reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line",
func(v reflect.Value, d diffMode) textRecord { func(v reflect.Value, d diffMode) textRecord {
@ -154,12 +174,13 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
// differences in a string literal. This format is more readable, // differences in a string literal. This format is more readable,
// but has edge-cases where differences are visually indistinguishable. // but has edge-cases where differences are visually indistinguishable.
// This format is avoided under the following conditions: // This format is avoided under the following conditions:
// A line starts with `"""` // - A line starts with `"""`
// A line starts with "..." // - A line starts with "..."
// A line contains non-printable characters // - A line contains non-printable characters
// Adjacent different lines differ only by whitespace // - Adjacent different lines differ only by whitespace
// //
// For example: // For example:
//
// """ // """
// ... // 3 identical lines // ... // 3 identical lines
// foo // foo
@ -214,7 +235,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"} var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
switch t.Kind() { switch t.Kind() {
case reflect.String: case reflect.String:
if t != reflect.TypeOf(string("")) { if t != stringType {
out = opts.FormatType(t, out) out = opts.FormatType(t, out)
} }
case reflect.Slice: case reflect.Slice:
@ -229,7 +250,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
// If the text appears to be single-lined text, // If the text appears to be single-lined text,
// then perform differencing in approximately fixed-sized chunks. // then perform differencing in approximately fixed-sized chunks.
// The output is printed as quoted strings. // The output is printed as quoted strings.
case isText: case isMostlyText:
list = opts.formatDiffSlice( list = opts.formatDiffSlice(
reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte", reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte",
func(v reflect.Value, d diffMode) textRecord { func(v reflect.Value, d diffMode) textRecord {
@ -237,7 +258,6 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
return textRecord{Diff: d, Value: textLine(s)} return textRecord{Diff: d, Value: textLine(s)}
}, },
) )
delim = ""
// If the text appears to be binary data, // If the text appears to be binary data,
// then perform differencing in approximately fixed-sized chunks. // then perform differencing in approximately fixed-sized chunks.
@ -299,7 +319,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
// Wrap the output with appropriate type information. // Wrap the output with appropriate type information.
var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"} var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
if !isText { if !isMostlyText {
// The "{...}" byte-sequence literal is not valid Go syntax for strings. // The "{...}" byte-sequence literal is not valid Go syntax for strings.
// Emit the type for extra clarity (e.g. "string{...}"). // Emit the type for extra clarity (e.g. "string{...}").
if t.Kind() == reflect.String { if t.Kind() == reflect.String {
@ -310,12 +330,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
switch t.Kind() { switch t.Kind() {
case reflect.String: case reflect.String:
out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf(string("")) { if t != stringType {
out = opts.FormatType(t, out) out = opts.FormatType(t, out)
} }
case reflect.Slice: case reflect.Slice:
out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
if t != reflect.TypeOf([]byte(nil)) { if t != bytesType {
out = opts.FormatType(t, out) out = opts.FormatType(t, out)
} }
} }
@ -338,8 +358,11 @@ func (opts formatOptions) formatDiffSlice(
vx, vy reflect.Value, chunkSize int, name string, vx, vy reflect.Value, chunkSize int, name string,
makeRec func(reflect.Value, diffMode) textRecord, makeRec func(reflect.Value, diffMode) textRecord,
) (list textList) { ) (list textList) {
es := diff.Difference(vx.Len(), vy.Len(), func(ix int, iy int) diff.Result { eq := func(ix, iy int) bool {
return diff.BoolResult(vx.Index(ix).Interface() == vy.Index(iy).Interface()) return vx.Index(ix).Interface() == vy.Index(iy).Interface()
}
es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
return diff.BoolResult(eq(ix, iy))
}) })
appendChunks := func(v reflect.Value, d diffMode) int { appendChunks := func(v reflect.Value, d diffMode) int {
@ -364,6 +387,7 @@ func (opts formatOptions) formatDiffSlice(
groups := coalesceAdjacentEdits(name, es) groups := coalesceAdjacentEdits(name, es)
groups = coalesceInterveningIdentical(groups, chunkSize/4) groups = coalesceInterveningIdentical(groups, chunkSize/4)
groups = cleanupSurroundingIdentical(groups, eq)
maxGroup := diffStats{Name: name} maxGroup := diffStats{Name: name}
for i, ds := range groups { for i, ds := range groups {
if maxLen >= 0 && numDiffs >= maxLen { if maxLen >= 0 && numDiffs >= maxLen {
@ -416,25 +440,35 @@ func (opts formatOptions) formatDiffSlice(
// coalesceAdjacentEdits coalesces the list of edits into groups of adjacent // coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
// equal or unequal counts. // equal or unequal counts.
//
// Example:
//
// Input: "..XXY...Y"
// Output: [
// {NumIdentical: 2},
// {NumRemoved: 2, NumInserted 1},
// {NumIdentical: 3},
// {NumInserted: 1},
// ]
func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) {
var prevCase int // Arbitrary index into which case last occurred var prevMode byte
lastStats := func(i int) *diffStats { lastStats := func(mode byte) *diffStats {
if prevCase != i { if prevMode != mode {
groups = append(groups, diffStats{Name: name}) groups = append(groups, diffStats{Name: name})
prevCase = i prevMode = mode
} }
return &groups[len(groups)-1] return &groups[len(groups)-1]
} }
for _, e := range es { for _, e := range es {
switch e { switch e {
case diff.Identity: case diff.Identity:
lastStats(1).NumIdentical++ lastStats('=').NumIdentical++
case diff.UniqueX: case diff.UniqueX:
lastStats(2).NumRemoved++ lastStats('!').NumRemoved++
case diff.UniqueY: case diff.UniqueY:
lastStats(2).NumInserted++ lastStats('!').NumInserted++
case diff.Modified: case diff.Modified:
lastStats(2).NumModified++ lastStats('!').NumModified++
} }
} }
return groups return groups
@ -444,6 +478,34 @@ func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats)
// equal groups into adjacent unequal groups that currently result in a // equal groups into adjacent unequal groups that currently result in a
// dual inserted/removed printout. This acts as a high-pass filter to smooth // dual inserted/removed printout. This acts as a high-pass filter to smooth
// out high-frequency changes within the windowSize. // out high-frequency changes within the windowSize.
//
// Example:
//
// WindowSize: 16,
// Input: [
// {NumIdentical: 61}, // group 0
// {NumRemoved: 3, NumInserted: 1}, // group 1
// {NumIdentical: 6}, // ├── coalesce
// {NumInserted: 2}, // ├── coalesce
// {NumIdentical: 1}, // ├── coalesce
// {NumRemoved: 9}, // └── coalesce
// {NumIdentical: 64}, // group 2
// {NumRemoved: 3, NumInserted: 1}, // group 3
// {NumIdentical: 6}, // ├── coalesce
// {NumInserted: 2}, // ├── coalesce
// {NumIdentical: 1}, // ├── coalesce
// {NumRemoved: 7}, // ├── coalesce
// {NumIdentical: 1}, // ├── coalesce
// {NumRemoved: 2}, // └── coalesce
// {NumIdentical: 63}, // group 4
// ]
// Output: [
// {NumIdentical: 61},
// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3},
// {NumIdentical: 64},
// {NumIdentical: 8, NumRemoved: 12, NumInserted: 3},
// {NumIdentical: 63},
// ]
func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats {
groups, groupsOrig := groups[:0], groups groups, groupsOrig := groups[:0], groups
for i, ds := range groupsOrig { for i, ds := range groupsOrig {
@ -463,3 +525,90 @@ func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStat
} }
return groups return groups
} }
// cleanupSurroundingIdentical scans through all unequal groups, and
// moves any leading sequence of equal elements to the preceding equal group and
// moves and trailing sequence of equal elements to the succeeding equal group.
//
// This is necessary since coalesceInterveningIdentical may coalesce edit groups
// together such that leading/trailing spans of equal elements becomes possible.
// Note that this can occur even with an optimal diffing algorithm.
//
// Example:
//
// Input: [
// {NumIdentical: 61},
// {NumIdentical: 1 , NumRemoved: 11, NumInserted: 2}, // assume 3 leading identical elements
// {NumIdentical: 67},
// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, // assume 10 trailing identical elements
// {NumIdentical: 54},
// ]
// Output: [
// {NumIdentical: 64}, // incremented by 3
// {NumRemoved: 9},
// {NumIdentical: 67},
// {NumRemoved: 9},
// {NumIdentical: 64}, // incremented by 10
// ]
func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats {
var ix, iy int // indexes into sequence x and y
for i, ds := range groups {
// Handle equal group.
if ds.NumDiff() == 0 {
ix += ds.NumIdentical
iy += ds.NumIdentical
continue
}
// Handle unequal group.
nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified
ny := ds.NumIdentical + ds.NumInserted + ds.NumModified
var numLeadingIdentical, numTrailingIdentical int
for j := 0; j < nx && j < ny && eq(ix+j, iy+j); j++ {
numLeadingIdentical++
}
for j := 0; j < nx && j < ny && eq(ix+nx-1-j, iy+ny-1-j); j++ {
numTrailingIdentical++
}
if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 {
if numLeadingIdentical > 0 {
// Remove leading identical span from this group and
// insert it into the preceding group.
if i-1 >= 0 {
groups[i-1].NumIdentical += numLeadingIdentical
} else {
// No preceding group exists, so prepend a new group,
// but do so after we finish iterating over all groups.
defer func() {
groups = append([]diffStats{{Name: groups[0].Name, NumIdentical: numLeadingIdentical}}, groups...)
}()
}
// Increment indexes since the preceding group would have handled this.
ix += numLeadingIdentical
iy += numLeadingIdentical
}
if numTrailingIdentical > 0 {
// Remove trailing identical span from this group and
// insert it into the succeeding group.
if i+1 < len(groups) {
groups[i+1].NumIdentical += numTrailingIdentical
} else {
// No succeeding group exists, so append a new group,
// but do so after we finish iterating over all groups.
defer func() {
groups = append(groups, diffStats{Name: groups[len(groups)-1].Name, NumIdentical: numTrailingIdentical})
}()
}
// Do not increment indexes since the succeeding group will handle this.
}
// Update this group since some identical elements were removed.
nx -= numIdentical
ny -= numIdentical
groups[i] = diffStats{Name: ds.Name, NumRemoved: nx, NumInserted: ny}
}
ix += nx
iy += ny
}
return groups
}

View File

@ -393,6 +393,7 @@ func (s diffStats) Append(ds diffStats) diffStats {
// String prints a humanly-readable summary of coalesced records. // String prints a humanly-readable summary of coalesced records.
// //
// Example: // Example:
//
// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields" // diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
func (s diffStats) String() string { func (s diffStats) String() string {
var ss []string var ss []string

View File

@ -3,3 +3,5 @@
. .
.idea .idea
gomega.iml gomega.iml
TODO.md
.vscode

View File

@ -1,17 +0,0 @@
language: go
go:
- 1.13.x
- 1.14.x
- gotip
env:
- GO111MODULE=on
install:
- go get -v ./...
- go build ./...
- go get github.com/onsi/ginkgo
- go install github.com/onsi/ginkgo/ginkgo
script: make test

View File

@ -1,3 +1,382 @@
## 1.27.6
### Fixes
- Allow collections matchers to work correctly when expected has nil elements [60e7cf3]
### Maintenance
- updates MatchError godoc comment to also accept a Gomega matcher (#654) [67b869d]
## 1.27.5
### Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.9.1 to 2.9.2 (#653) [a215021]
- Bump github.com/go-task/slim-sprig (#652) [a26fed8]
## 1.27.4
### Fixes
- improve error formatting and remove duplication of error message in Eventually/Consistently [854f075]
### Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.9.0 to 2.9.1 (#650) [ccebd9b]
## 1.27.3
### Fixes
- format.Object now always includes err.Error() when passed an error [86d97ef]
- Fix HaveExactElements to work inside ContainElement or other collection matchers (#648) [636757e]
### Maintenance
- Bump github.com/golang/protobuf from 1.5.2 to 1.5.3 (#649) [cc16689]
- Bump github.com/onsi/ginkgo/v2 from 2.8.4 to 2.9.0 (#646) [e783366]
## 1.27.2
### Fixes
- improve poll progress message when polling a consistently that has been passing [28a319b]
### Maintenance
- bump ginkgo
- remove tools.go hack as Ginkgo 2.8.2 automatically pulls in the cli dependencies [81443b3]
## 1.27.1
### Maintenance
- Bump golang.org/x/net from 0.6.0 to 0.7.0 (#640) [bc686cd]
## 1.27.0
### Features
- Add HaveExactElements matcher (#634) [9d50783]
- update Gomega docs to discuss GinkgoHelper() [be32774]
### Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.8.0 to 2.8.1 (#639) [296a68b]
- Bump golang.org/x/net from 0.5.0 to 0.6.0 (#638) [c2b098b]
- Bump github-pages from 227 to 228 in /docs (#636) [a9069ab]
- test: update matrix for Go 1.20 (#635) [6bd25c8]
- Bump github.com/onsi/ginkgo/v2 from 2.7.0 to 2.8.0 (#631) [5445f8b]
- Bump webrick from 1.7.0 to 1.8.1 in /docs (#630) [03e93bb]
- codeql: add ruby language (#626) [63c7d21]
- dependabot: add bundler package-ecosystem for docs (#625) [d92f963]
## 1.26.0
### Features
- When a polled function returns an error, keep track of the actual and report on the matcher state of the last non-errored actual [21f3090]
- improve eventually failure message output [c530fb3]
### Fixes
- fix several documentation spelling issues [e2eff1f]
## 1.25.0
### Features
- add `MustPassRepeatedly(int)` to asyncAssertion (#619) [4509f72]
- compare unwrapped errors using DeepEqual (#617) [aaeaa5d]
### Maintenance
- Bump golang.org/x/net from 0.4.0 to 0.5.0 (#614) [c7cfea4]
- Bump github.com/onsi/ginkgo/v2 from 2.6.1 to 2.7.0 (#615) [71b8adb]
- Docs: Fix typo "MUltiple" -> "Multiple" (#616) [9351dda]
- clean up go.sum [cd1dc1d]
## 1.24.2
### Fixes
- Correctly handle assertion failure panics for eventually/consistnetly "g Gomega"s in a goroutine [78f1660]
- docs:Fix typo "you an" -> "you can" (#607) [3187c1f]
- fixes issue #600 (#606) [808d192]
### Maintenance
- Bump golang.org/x/net from 0.2.0 to 0.4.0 (#611) [6ebc0bf]
- Bump nokogiri from 1.13.9 to 1.13.10 in /docs (#612) [258cfc8]
- Bump github.com/onsi/ginkgo/v2 from 2.5.0 to 2.5.1 (#609) [e6c3eb9]
## 1.24.1
### Fixes
- maintain backward compatibility for Eventually and Consisntetly's signatures [4c7df5e]
- fix small typo (#601) [ea0ebe6]
### Maintenance
- Bump golang.org/x/net from 0.1.0 to 0.2.0 (#603) [1ba8372]
- Bump github.com/onsi/ginkgo/v2 from 2.4.0 to 2.5.0 (#602) [f9426cb]
- fix label-filter in test.yml [d795db6]
- stop running flakey tests and rely on external network dependencies in CI [7133290]
## 1.24.0
### Features
Introducting [gcustom](https://onsi.github.io/gomega/#gcustom-a-convenient-mechanism-for-buildling-custom-matchers) - a convenient mechanism for building custom matchers.
This is an RC release for `gcustom`. The external API may be tweaked in response to feedback however it is expected to remain mostly stable.
### Maintenance
- Update BeComparableTo documentation [756eaa0]
## 1.23.0
### Features
- Custom formatting on a per-type basis can be provided using `format.RegisterCustomFormatter()` -- see the docs [here](https://onsi.github.io/gomega/#adjusting-output)
- Substantial improvement have been made to `StopTrying()`:
- Users can now use `StopTrying().Wrap(err)` to wrap errors and `StopTrying().Attach(description, object)` to attach arbitrary objects to the `StopTrying()` error
- `StopTrying()` is now always interpreted as a failure. If you are an early adopter of `StopTrying()` you may need to change your code as the prior version would match against the returned value even if `StopTrying()` was returned. Going forward the `StopTrying()` api should remain stable.
- `StopTrying()` and `StopTrying().Now()` can both be used in matchers - not just polled functions.
- `TryAgainAfter(duration)` is used like `StopTrying()` but instructs `Eventually` and `Consistently` that the poll should be tried again after the specified duration. This allows you to dynamically adjust the polling duration.
- `ctx` can now be passed-in as the first argument to `Eventually` and `Consistently`.
## Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.3.0 to 2.3.1 (#597) [afed901]
- Bump nokogiri from 1.13.8 to 1.13.9 in /docs (#599) [7c691b3]
- Bump github.com/google/go-cmp from 0.5.8 to 0.5.9 (#587) [ff22665]
## 1.22.1
## Fixes
- When passed a context and no explicit timeout, Eventually will only timeout when the context is cancelled [e5105cf]
- Allow StopTrying() to be wrapped [bf3cba9]
## Maintenance
- bump to ginkgo v2.3.0 [c5d5c39]
## 1.22.0
### Features
Several improvements have been made to `Eventually` and `Consistently` in this and the most recent releases:
- Eventually and Consistently can take a context.Context [65c01bc]
This enables integration with Ginkgo 2.3.0's interruptible nodes and node timeouts.
- Eventually and Consistently that are passed a SpecContext can provide reports when an interrupt occurs [0d063c9]
- Eventually/Consistently will forward an attached context to functions that ask for one [e2091c5]
- Eventually/Consistently supports passing arguments to functions via WithArguments() [a2dc7c3]
- Eventually and Consistently can now be stopped early with StopTrying(message) and StopTrying(message).Now() [52976bb]
These improvements are all documented in [Gomega's docs](https://onsi.github.io/gomega/#making-asynchronous-assertions)
## Fixes
## Maintenance
## 1.21.1
### Features
- Eventually and Consistently that are passed a SpecContext can provide reports when an interrupt occurs [0d063c9]
## 1.21.0
### Features
- Eventually and Consistently can take a context.Context [65c01bc]
This enables integration with Ginkgo 2.3.0's interruptible nodes and node timeouts.
- Introduces Eventually.Within.ProbeEvery with tests and documentation (#591) [f633800]
- New BeKeyOf matcher with documentation and unit tests (#590) [fb586b3]
## Fixes
- Cover the entire gmeasure suite with leak detection [8c54344]
- Fix gmeasure leak [119d4ce]
- Ignore new Ginkgo ProgressSignal goroutine in gleak [ba548e2]
## Maintenance
- Fixes crashes on newer Ruby 3 installations by upgrading github-pages gem dependency (#596) [12469a0]
## 1.20.2
## Fixes
- label specs that rely on remote access; bump timeout on short-circuit test to make it less flaky [35eeadf]
- gexec: allow more headroom for SIGABRT-related unit tests (#581) [5b78f40]
- Enable reading from a closed gbytes.Buffer (#575) [061fd26]
## Maintenance
- Bump github.com/onsi/ginkgo/v2 from 2.1.5 to 2.1.6 (#583) [55d895b]
- Bump github.com/onsi/ginkgo/v2 from 2.1.4 to 2.1.5 (#582) [346de7c]
## 1.20.1
## Fixes
- fix false positive gleaks when using ginkgo -p (#577) [cb46517]
- Fix typos in gomega_dsl.go (#569) [5f71ed2]
- don't panic on Eventually(nil), fixing #555 (#567) [9d1186f]
- vet optional description args in assertions, fixing #560 (#566) [8e37808]
## Maintenance
- test: add new Go 1.19 to test matrix (#571) [40d7efe]
- Bump tzinfo from 1.2.9 to 1.2.10 in /docs (#564) [5f26371]
## 1.20.0
## Features
- New [`gleak`](https://onsi.github.io/gomega/#codegleakcode-finding-leaked-goroutines) experimental goroutine leak detection package! (#538) [85ba7bc]
- New `BeComparableTo` matcher(#546) that uses `gocmp` to make comparisons [e77ea75]
- New `HaveExistingField` matcher (#553) [fd130e1]
- Document how to wrap Gomega (#539) [56714a4]
## Fixes
- Support pointer receivers in HaveField; fixes #543 (#544) [8dab36e]
## Maintenance
- Bump various dependencies:
- Upgrade to yaml.v3 (#556) [f5a83b1]
- Bump github/codeql-action from 1 to 2 (#549) [52f5adf]
- Bump github.com/google/go-cmp from 0.5.7 to 0.5.8 (#551) [5f3942d]
- Bump nokogiri from 1.13.4 to 1.13.6 in /docs (#554) [eb4b4c2]
- Use latest ginkgo (#535) [1c29028]
- Bump nokogiri from 1.13.3 to 1.13.4 in /docs (#541) [1ce84d5]
- Bump actions/setup-go from 2 to 3 (#540) [755485e]
- Bump nokogiri from 1.12.5 to 1.13.3 in /docs (#522) [4fbb0dc]
- Bump actions/checkout from 2 to 3 (#526) [ac49202]
## 1.19.0
## Features
- New [`HaveEach`](https://onsi.github.io/gomega/#haveeachelement-interface) matcher to ensure that each and every element in an `array`, `slice`, or `map` satisfies the passed in matcher. (#523) [9fc2ae2] (#524) [c8ba582]
- Users can now wrap the `Gomega` interface to implement custom behavior on each assertion. (#521) [1f2e714]
- [`ContainElement`](https://onsi.github.io/gomega/#containelementelement-interface) now accepts an additional pointer argument. Elements that satisfy the matcher are stored in the pointer enabling developers to easily add subsequent, more detailed, assertions against the matching element. (#527) [1a4e27f]
## Fixes
- update RELEASING instructions to match ginkgo [0917cde]
- Bump github.com/onsi/ginkgo/v2 from 2.0.0 to 2.1.3 (#519) [49ab4b0]
- Fix CVE-2021-38561 (#534) [f1b4456]
- Fix max number of samples in experiments on non-64-bit systems. (#528) [1c84497]
- Remove dependency on ginkgo v1.16.4 (#530) [4dea8d5]
- Fix for Go 1.18 (#532) [56d2a29]
- Document precendence of timeouts (#533) [b607941]
## 1.18.1
## Fixes
- Add pointer support to HaveField matcher (#495) [79e41a3]
## 1.18.0
## Features
- Docs now live on the master branch in the docs folder which will make for easier PRs. The docs also use Ginkgo 2.0's new docs html/css/js. [2570272]
- New HaveValue matcher can handle actuals that are either values (in which case they are passed on unscathed) or pointers (in which case they are indirected). [Docs here.](https://onsi.github.io/gomega/#working-with-values) (#485) [bdc087c]
- Gmeasure has been declared GA [360db9d]
## Fixes
- Gomega now uses ioutil for Go 1.15 and lower (#492) - official support is only for the most recent two major versions of Go but this will unblock users who need to stay on older unsupported versions of Go. [c29c1c0]
## Maintenace
- Remove Travis workflow (#491) [72e6040]
- Upgrade to Ginkgo 2.0.0 GA [f383637]
- chore: fix description of HaveField matcher (#487) [2b4b2c0]
- use tools.go to ensure Ginkgo cli dependencies are included [f58a52b]
- remove dockerfile and simplify github actions to match ginkgo's actions [3f8160d]
## 1.17.0
### Features
- Add HaveField matcher [3a26311]
- add Error() assertions on the final error value of multi-return values (#480) [2f96943]
- separate out offsets and timeouts (#478) [18a4723]
- fix transformation error reporting (#479) [e001fab]
- allow transform functions to report errors (#472) [bf93408]
### Fixes
Stop using deprecated ioutil package (#467) [07f405d]
## 1.16.0
### Features
- feat: HaveHTTPStatus multiple expected values (#465) [aa69f1b]
- feat: HaveHTTPHeaderWithValue() matcher (#463) [dd83a96]
- feat: HaveHTTPBody matcher (#462) [504e1f2]
- feat: formatter for HTTP responses (#461) [e5b3157]
## 1.15.0
### Fixes
The previous version (1.14.0) introduced a change to allow `Eventually` and `Consistently` to support functions that make assertions. This was accomplished by overriding the global fail handler when running the callbacks passed to `Eventually/Consistently` in order to capture any resulting errors. Issue #457 uncovered a flaw with this approach: when multiple `Eventually`s are running concurrently they race when overriding the singleton global fail handler.
1.15.0 resolves this by requiring users who want to make assertions in `Eventually/Consistently` call backs to explicitly pass in a function that takes a `Gomega` as an argument. The passed-in `Gomega` instance can be used to make assertions. Any failures will cause `Eventually` to retry the callback. This cleaner interface avoids the issue of swapping out globals but comes at the cost of changing the contract introduced in v1.14.0. As such 1.15.0 introduces a breaking change with respect to 1.14.0 - however we expect that adoption of this feature in 1.14.0 remains limited.
In addition, 1.15.0 cleans up some of Gomega's internals. Most users shouldn't notice any differences stemming from the refactoring that was made.
## 1.14.0
### Features
- gmeasure.SamplingConfig now suppers a MinSamplingInterval [e94dbca]
- Eventually and Consistently support functions that make assertions [2f04e6e]
- Eventually and Consistently now allow their passed-in functions to make assertions.
These assertions must pass or the function is considered to have failed and is retried.
- Eventually and Consistently can now take functions with no return values. These implicitly return nil
if they contain no failed assertion. Otherwise they return an error wrapping the first assertion failure. This allows
these functions to be used with the Succeed() matcher.
- Introduce InterceptGomegaFailure - an analogue to InterceptGomegaFailures - that captures the first assertion failure
and halts execution in its passed-in callback.
### Fixes
- Call Verify GHTTPWithGomega receiver funcs (#454) [496e6fd]
- Build a binary with an expected name (#446) [7356360]
## 1.13.0
### Features
- gmeasure provides BETA support for benchmarking (#447) [8f2dfbf]
- Set consistently and eventually defaults on init (#443) [12eb778]
## 1.12.0
### Features
- Add Satisfy() matcher (#437) [c548f31]
- tweak truncation message [3360b8c]
- Add format.GomegaStringer (#427) [cc80b6f]
- Add Clear() method to gbytes.Buffer [c3c0920]
### Fixes
- Fix error message in BeNumericallyMatcher (#432) [09c074a]
- Bump github.com/onsi/ginkgo from 1.12.1 to 1.16.2 (#442) [e5f6ea0]
- Bump github.com/golang/protobuf from 1.4.3 to 1.5.2 (#431) [adae3bf]
- Bump golang.org/x/net (#441) [3275b35]
## 1.11.0
### Features
- feature: add index to gstruct element func (#419) [334e00d]
- feat(gexec) Add CompileTest functions. Close #410 (#411) [47c613f]
### Fixes
- Check more carefully for nils in WithTransform (#423) [3c60a15]
- fix: typo in Makefile [b82522a]
- Allow WithTransform function to accept a nil value (#422) [b75d2f2]
- fix: print value type for interface{} containers (#409) [f08e2dc]
- fix(BeElementOf): consistently flatten expected values [1fa9468]
## 1.10.5
### Fixes
- fix: collections matchers should display type of expectation (#408) [6b4eb5a]
- fix(ContainElements): consistently flatten expected values [073b880]
- fix(ConsistOf): consistently flatten expected values [7266efe]
## 1.10.4
### Fixes
- update golang net library to more recent version without vulnerability (#406) [817a8b9]
- Correct spelling: alloted -> allotted (#403) [0bae715]
- fix a panic in MessageWithDiff with long message (#402) [ea06b9b]
## 1.10.3
### Fixes
- updates golang/x/net to fix vulnerability detected by snyk (#394) [c479356]
## 1.10.2
### Fixes
- Add ExpectWithOffset, EventuallyWithOffset and ConsistentlyWithOffset to WithT (#391) [990941a]
## 1.10.1 ## 1.10.1
### Fixes ### Fixes

View File

@ -1,6 +0,0 @@
test:
[ -z "`gofmt -s -w -l -e .`" ]
go vet
ginkgo -p -r --randomizeAllSpecs --failOnPending --randomizeSuites --race
.PHONY: test

View File

@ -1,6 +1,6 @@
![Gomega: Ginkgo's Preferred Matcher Library](http://onsi.github.io/gomega/images/gomega.png) ![Gomega: Ginkgo's Preferred Matcher Library](http://onsi.github.io/gomega/images/gomega.png)
[![Build Status](https://travis-ci.org/onsi/gomega.svg?branch=master)](https://travis-ci.org/onsi/gomega) [![test](https://github.com/onsi/gomega/actions/workflows/test.yml/badge.svg)](https://github.com/onsi/gomega/actions/workflows/test.yml)
Jump straight to the [docs](http://onsi.github.io/gomega/) to learn about Gomega, including a list of [all available matchers](http://onsi.github.io/gomega/#provided-matchers). Jump straight to the [docs](http://onsi.github.io/gomega/) to learn about Gomega, including a list of [all available matchers](http://onsi.github.io/gomega/#provided-matchers).

View File

@ -1,12 +1,23 @@
A Gomega release is a tagged sha and a GitHub release. To cut a release: A Gomega release is a tagged sha and a GitHub release. To cut a release:
1. Ensure CHANGELOG.md is up to date. 1. Ensure CHANGELOG.md is up to date.
- Use `git log --pretty=format:'- %s [%h]' HEAD...vX.X.X` to list all the commits since the last release - Use
```bash
LAST_VERSION=$(git tag --sort=version:refname | tail -n1)
CHANGES=$(git log --pretty=format:'- %s [%h]' HEAD...$LAST_VERSION)
echo -e "## NEXT\n\n$CHANGES\n\n### Features\n\n### Fixes\n\n### Maintenance\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
```
to update the changelog
- Categorize the changes into - Categorize the changes into
- Breaking Changes (requires a major version) - Breaking Changes (requires a major version)
- New Features (minor version) - New Features (minor version)
- Fixes (fix version) - Fixes (fix version)
- Maintenance (which in general should not be mentioned in `CHANGELOG.md` as they have no user impact) - Maintenance (which in general should not be mentioned in `CHANGELOG.md` as they have no user impact)
2. Update GOMEGA_VERSION in `gomega_dsl.go` 1. Update GOMEGA_VERSION in `gomega_dsl.go`
3. Push a commit with the version number as the commit message (e.g. `v1.3.0`) 1. Commit, push, and release:
4. Create a new [GitHub release](https://help.github.com/articles/creating-releases/) with the version number as the tag (e.g. `v1.3.0`). List the key changes in the release notes. ```
git commit -m "vM.m.p"
git push
gh release create "vM.m.p"
git fetch --tags origin master
```

View File

@ -7,6 +7,7 @@ Gomega's format package pretty-prints objects. It explores input objects recurs
package format package format
import ( import (
"context"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
@ -17,6 +18,10 @@ import (
// Use MaxDepth to set the maximum recursion depth when printing deeply nested objects // Use MaxDepth to set the maximum recursion depth when printing deeply nested objects
var MaxDepth = uint(10) var MaxDepth = uint(10)
// MaxLength of the string representation of an object.
// If MaxLength is set to 0, the Object will not be truncated.
var MaxLength = 4000
/* /*
By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output. By default, all objects (even those that implement fmt.Stringer and fmt.GoStringer) are recursively inspected to generate output.
@ -44,23 +49,68 @@ var TruncateThreshold uint = 50
// after the first diff location in a truncated string assertion error message. // after the first diff location in a truncated string assertion error message.
var CharactersAroundMismatchToInclude uint = 5 var CharactersAroundMismatchToInclude uint = 5
// Ctx interface defined here to keep backwards compatibility with go < 1.7 var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
// It matches the context.Context interface
type Ctx interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
var contextType = reflect.TypeOf((*Ctx)(nil)).Elem()
var timeType = reflect.TypeOf(time.Time{}) var timeType = reflect.TypeOf(time.Time{})
//The default indentation string emitted by the format package // The default indentation string emitted by the format package
var Indent = " " var Indent = " "
var longFormThreshold = 20 var longFormThreshold = 20
// GomegaStringer allows for custom formating of objects for gomega.
type GomegaStringer interface {
// GomegaString will be used to custom format an object.
// It does not follow UseStringerRepresentation value and will always be called regardless.
// It also ignores the MaxLength value.
GomegaString() string
}
/*
CustomFormatters can be registered with Gomega via RegisterCustomFormatter()
Any value to be rendered by Gomega is passed to each registered CustomFormatters.
The CustomFormatter signals that it will handle formatting the value by returning (formatted-string, true)
If the CustomFormatter does not want to handle the object it should return ("", false)
Strings returned by CustomFormatters are not truncated
*/
type CustomFormatter func(value interface{}) (string, bool)
type CustomFormatterKey uint
var customFormatterKey CustomFormatterKey = 1
type customFormatterKeyPair struct {
CustomFormatter
CustomFormatterKey
}
/*
RegisterCustomFormatter registers a CustomFormatter and returns a CustomFormatterKey
You can call UnregisterCustomFormatter with the returned key to unregister the associated CustomFormatter
*/
func RegisterCustomFormatter(customFormatter CustomFormatter) CustomFormatterKey {
key := customFormatterKey
customFormatterKey += 1
customFormatters = append(customFormatters, customFormatterKeyPair{customFormatter, key})
return key
}
/*
UnregisterCustomFormatter unregisters a previously registered CustomFormatter. You should pass in the key returned by RegisterCustomFormatter
*/
func UnregisterCustomFormatter(key CustomFormatterKey) {
formatters := []customFormatterKeyPair{}
for _, f := range customFormatters {
if f.CustomFormatterKey == key {
continue
}
formatters = append(formatters, f)
}
customFormatters = formatters
}
var customFormatters = []customFormatterKeyPair{}
/* /*
Generates a formatted matcher success/failure message of the form: Generates a formatted matcher success/failure message of the form:
@ -105,7 +155,13 @@ func MessageWithDiff(actual, message, expected string) string {
tabLength := 4 tabLength := 4
spaceFromMessageToActual := tabLength + len("<string>: ") - len(message) spaceFromMessageToActual := tabLength + len("<string>: ") - len(message)
padding := strings.Repeat(" ", spaceFromMessageToActual+spacesBeforeFormattedMismatch) + "|"
paddingCount := spaceFromMessageToActual + spacesBeforeFormattedMismatch
if paddingCount < 0 {
return Message(formattedActual, message, formattedExpected)
}
padding := strings.Repeat(" ", paddingCount) + "|"
return Message(formattedActual, message+padding, formattedExpected) return Message(formattedActual, message+padding, formattedExpected)
} }
@ -161,6 +217,33 @@ func findFirstMismatch(a, b string) int {
return 0 return 0
} }
const truncateHelpText = `
Gomega truncated this representation as it exceeds 'format.MaxLength'.
Consider having the object provide a custom 'GomegaStringer' representation
or adjust the parameters in Gomega's 'format' package.
Learn more here: https://onsi.github.io/gomega/#adjusting-output
`
func truncateLongStrings(s string) string {
if MaxLength > 0 && len(s) > MaxLength {
var sb strings.Builder
for i, r := range s {
if i < MaxLength {
sb.WriteRune(r)
continue
}
break
}
sb.WriteString("...\n")
sb.WriteString(truncateHelpText)
return sb.String()
}
return s
}
/* /*
Pretty prints the passed in object at the passed in indentation level. Pretty prints the passed in object at the passed in indentation level.
@ -175,45 +258,51 @@ Set PrintContextObjects to true to print the content of objects implementing con
func Object(object interface{}, indentation uint) string { func Object(object interface{}, indentation uint) string {
indent := strings.Repeat(Indent, int(indentation)) indent := strings.Repeat(Indent, int(indentation))
value := reflect.ValueOf(object) value := reflect.ValueOf(object)
return fmt.Sprintf("%s<%s>: %s", indent, formatType(object), formatValue(value, indentation)) commonRepresentation := ""
if err, ok := object.(error); ok {
commonRepresentation += "\n" + IndentString(err.Error(), indentation) + "\n" + indent
}
return fmt.Sprintf("%s<%s>: %s%s", indent, formatType(value), commonRepresentation, formatValue(value, indentation))
} }
/* /*
IndentString takes a string and indents each line by the specified amount. IndentString takes a string and indents each line by the specified amount.
*/ */
func IndentString(s string, indentation uint) string { func IndentString(s string, indentation uint) string {
return indentString(s, indentation, true)
}
func indentString(s string, indentation uint, indentFirstLine bool) string {
result := &strings.Builder{}
components := strings.Split(s, "\n") components := strings.Split(s, "\n")
result := ""
indent := strings.Repeat(Indent, int(indentation)) indent := strings.Repeat(Indent, int(indentation))
for i, component := range components { for i, component := range components {
result += indent + component if i > 0 || indentFirstLine {
result.WriteString(indent)
}
result.WriteString(component)
if i < len(components)-1 { if i < len(components)-1 {
result += "\n" result.WriteString("\n")
} }
} }
return result return result.String()
} }
func formatType(object interface{}) string { func formatType(v reflect.Value) string {
t := reflect.TypeOf(object) switch v.Kind() {
if t == nil { case reflect.Invalid:
return "nil" return "nil"
}
switch t.Kind() {
case reflect.Chan: case reflect.Chan:
v := reflect.ValueOf(object) return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
case reflect.Ptr: case reflect.Ptr:
return fmt.Sprintf("%T | %p", object, object) return fmt.Sprintf("%s | 0x%x", v.Type(), v.Pointer())
case reflect.Slice: case reflect.Slice:
v := reflect.ValueOf(object) return fmt.Sprintf("%s | len:%d, cap:%d", v.Type(), v.Len(), v.Cap())
return fmt.Sprintf("%T | len:%d, cap:%d", object, v.Len(), v.Cap())
case reflect.Map: case reflect.Map:
v := reflect.ValueOf(object) return fmt.Sprintf("%s | len:%d", v.Type(), v.Len())
return fmt.Sprintf("%T | len:%d", object, v.Len())
default: default:
return fmt.Sprintf("%T", object) return fmt.Sprintf("%s", v.Type())
} }
} }
@ -226,14 +315,30 @@ func formatValue(value reflect.Value, indentation uint) string {
return "nil" return "nil"
} }
if UseStringerRepresentation { if value.CanInterface() {
if value.CanInterface() { obj := value.Interface()
obj := value.Interface()
// if a CustomFormatter handles this values, we'll go with that
for _, customFormatter := range customFormatters {
formatted, handled := customFormatter.CustomFormatter(obj)
// do not truncate a user-provided CustomFormatter()
if handled {
return indentString(formatted, indentation+1, false)
}
}
// GomegaStringer will take precedence to other representations and disregards UseStringerRepresentation
if x, ok := obj.(GomegaStringer); ok {
// do not truncate a user-defined GomegaString() value
return indentString(x.GomegaString(), indentation+1, false)
}
if UseStringerRepresentation {
switch x := obj.(type) { switch x := obj.(type) {
case fmt.GoStringer: case fmt.GoStringer:
return x.GoString() return indentString(truncateLongStrings(x.GoString()), indentation+1, false)
case fmt.Stringer: case fmt.Stringer:
return x.String() return indentString(truncateLongStrings(x.String()), indentation+1, false)
} }
} }
} }
@ -264,26 +369,26 @@ func formatValue(value reflect.Value, indentation uint) string {
case reflect.Ptr: case reflect.Ptr:
return formatValue(value.Elem(), indentation) return formatValue(value.Elem(), indentation)
case reflect.Slice: case reflect.Slice:
return formatSlice(value, indentation) return truncateLongStrings(formatSlice(value, indentation))
case reflect.String: case reflect.String:
return formatString(value.String(), indentation) return truncateLongStrings(formatString(value.String(), indentation))
case reflect.Array: case reflect.Array:
return formatSlice(value, indentation) return truncateLongStrings(formatSlice(value, indentation))
case reflect.Map: case reflect.Map:
return formatMap(value, indentation) return truncateLongStrings(formatMap(value, indentation))
case reflect.Struct: case reflect.Struct:
if value.Type() == timeType && value.CanInterface() { if value.Type() == timeType && value.CanInterface() {
t, _ := value.Interface().(time.Time) t, _ := value.Interface().(time.Time)
return t.Format(time.RFC3339Nano) return t.Format(time.RFC3339Nano)
} }
return formatStruct(value, indentation) return truncateLongStrings(formatStruct(value, indentation))
case reflect.Interface: case reflect.Interface:
return formatValue(value.Elem(), indentation) return formatInterface(value, indentation)
default: default:
if value.CanInterface() { if value.CanInterface() {
return fmt.Sprintf("%#v", value.Interface()) return truncateLongStrings(fmt.Sprintf("%#v", value.Interface()))
} }
return fmt.Sprintf("%#v", value) return truncateLongStrings(fmt.Sprintf("%#v", value))
} }
} }
@ -373,6 +478,10 @@ func formatStruct(v reflect.Value, indentation uint) string {
return fmt.Sprintf("{%s}", strings.Join(result, ", ")) return fmt.Sprintf("{%s}", strings.Join(result, ", "))
} }
func formatInterface(v reflect.Value, indentation uint) string {
return fmt.Sprintf("<%s>%s", formatType(v.Elem()), formatValue(v.Elem(), indentation))
}
func isNilValue(a reflect.Value) bool { func isNilValue(a reflect.Value) bool {
switch a.Kind() { switch a.Kind() {
case reflect.Invalid: case reflect.Invalid:

View File

@ -14,103 +14,165 @@ Gomega is MIT-Licensed
package gomega package gomega
import ( import (
"errors"
"fmt" "fmt"
"reflect"
"time" "time"
"github.com/onsi/gomega/internal/assertion" "github.com/onsi/gomega/internal"
"github.com/onsi/gomega/internal/asyncassertion"
"github.com/onsi/gomega/internal/testingtsupport"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
const GOMEGA_VERSION = "1.10.1" const GOMEGA_VERSION = "1.27.6"
const nilFailHandlerPanic = `You are trying to make an assertion, but Gomega's fail handler is nil. const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler.
If you're using Ginkgo then you probably forgot to put your assertion in an It(). If you're using Ginkgo then you probably forgot to put your assertion in an It().
Alternatively, you may have forgotten to register a fail handler with RegisterFailHandler() or RegisterTestingT(). Alternatively, you may have forgotten to register a fail handler with RegisterFailHandler() or RegisterTestingT().
Depending on your vendoring solution you may be inadvertently importing gomega and subpackages (e.g. ghhtp, gexec,...) from different locations. Depending on your vendoring solution you may be inadvertently importing gomega and subpackages (e.g. ghhtp, gexec,...) from different locations.
` `
var globalFailWrapper *types.GomegaFailWrapper // Gomega describes the essential Gomega DSL. This interface allows libraries
// to abstract between the standard package-level function implementations
// and alternatives like *WithT.
//
// The types in the top-level DSL have gotten a bit messy due to earlier deprecations that avoid stuttering
// and due to an accidental use of a concrete type (*WithT) in an earlier release.
//
// As of 1.15 both the WithT and Ginkgo variants of Gomega are implemented by the same underlying object
// however one (the Ginkgo variant) is exported as an interface (types.Gomega) whereas the other (the withT variant)
// is shared as a concrete type (*WithT, which is aliased to *internal.Gomega). 1.15 did not clean this mess up to ensure
// that declarations of *WithT in existing code are not broken by the upgrade to 1.15.
type Gomega = types.Gomega
var defaultEventuallyTimeout = time.Second // DefaultGomega supplies the standard package-level implementation
var defaultEventuallyPollingInterval = 10 * time.Millisecond var Default = Gomega(internal.NewGomega(internal.FetchDefaultDurationBundle()))
var defaultConsistentlyDuration = 100 * time.Millisecond
var defaultConsistentlyPollingInterval = 10 * time.Millisecond // NewGomega returns an instance of Gomega wired into the passed-in fail handler.
// You generally don't need to use this when using Ginkgo - RegisterFailHandler will wire up the global gomega
// However creating a NewGomega with a custom fail handler can be useful in contexts where you want to use Gomega's
// rich ecosystem of matchers without causing a test to fail. For example, to aggregate a series of potential failures
// or for use in a non-test setting.
func NewGomega(fail types.GomegaFailHandler) Gomega {
return internal.NewGomega(internalGomega(Default).DurationBundle).ConfigureWithFailHandler(fail)
}
// WithT wraps a *testing.T and provides `Expect`, `Eventually`, and `Consistently` methods. This allows you to leverage
// Gomega's rich ecosystem of matchers in standard `testing` test suites.
//
// Use `NewWithT` to instantiate a `WithT`
//
// As of 1.15 both the WithT and Ginkgo variants of Gomega are implemented by the same underlying object
// however one (the Ginkgo variant) is exported as an interface (types.Gomega) whereas the other (the withT variant)
// is shared as a concrete type (*WithT, which is aliased to *internal.Gomega). 1.15 did not clean this mess up to ensure
// that declarations of *WithT in existing code are not broken by the upgrade to 1.15.
type WithT = internal.Gomega
// GomegaWithT is deprecated in favor of gomega.WithT, which does not stutter.
type GomegaWithT = WithT
// inner is an interface that allows users to provide a wrapper around Default. The wrapper
// must implement the inner interface and return either the original Default or the result of
// a call to NewGomega().
type inner interface {
Inner() Gomega
}
func internalGomega(g Gomega) *internal.Gomega {
if v, ok := g.(inner); ok {
return v.Inner().(*internal.Gomega)
}
return g.(*internal.Gomega)
}
// NewWithT takes a *testing.T and returns a `gomega.WithT` allowing you to use `Expect`, `Eventually`, and `Consistently` along with
// Gomega's rich ecosystem of matchers in standard `testing` test suits.
//
// func TestFarmHasCow(t *testing.T) {
// g := gomega.NewWithT(t)
//
// f := farm.New([]string{"Cow", "Horse"})
// g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
// }
func NewWithT(t types.GomegaTestingT) *WithT {
return internal.NewGomega(internalGomega(Default).DurationBundle).ConfigureWithT(t)
}
// NewGomegaWithT is deprecated in favor of gomega.NewWithT, which does not stutter.
var NewGomegaWithT = NewWithT
// RegisterFailHandler connects Ginkgo to Gomega. When a matcher fails // RegisterFailHandler connects Ginkgo to Gomega. When a matcher fails
// the fail handler passed into RegisterFailHandler is called. // the fail handler passed into RegisterFailHandler is called.
func RegisterFailHandler(handler types.GomegaFailHandler) { func RegisterFailHandler(fail types.GomegaFailHandler) {
RegisterFailHandlerWithT(testingtsupport.EmptyTWithHelper{}, handler) internalGomega(Default).ConfigureWithFailHandler(fail)
} }
// RegisterFailHandlerWithT ensures that the given types.TWithHelper and fail handler // RegisterFailHandlerWithT is deprecated and will be removed in a future release.
// are used globally. // users should use RegisterFailHandler, or RegisterTestingT
func RegisterFailHandlerWithT(t types.TWithHelper, handler types.GomegaFailHandler) { func RegisterFailHandlerWithT(_ types.GomegaTestingT, fail types.GomegaFailHandler) {
if handler == nil { fmt.Println("RegisterFailHandlerWithT is deprecated. Please use RegisterFailHandler or RegisterTestingT instead.")
globalFailWrapper = nil internalGomega(Default).ConfigureWithFailHandler(fail)
return
}
globalFailWrapper = &types.GomegaFailWrapper{
Fail: handler,
TWithHelper: t,
}
} }
// RegisterTestingT connects Gomega to Golang's XUnit style // RegisterTestingT connects Gomega to Golang's XUnit style
// Testing.T tests. It is now deprecated and you should use NewWithT() instead. // Testing.T tests. It is now deprecated and you should use NewWithT() instead to get a fresh instance of Gomega for each test.
//
// Legacy Documentation:
//
// You'll need to call this at the top of each XUnit style test:
//
// func TestFarmHasCow(t *testing.T) {
// RegisterTestingT(t)
//
// f := farm.New([]string{"Cow", "Horse"})
// Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
// }
//
// Note that this *testing.T is registered *globally* by Gomega (this is why you don't have to
// pass `t` down to the matcher itself). This means that you cannot run the XUnit style tests
// in parallel as the global fail handler cannot point to more than one testing.T at a time.
//
// NewWithT() does not have this limitation
//
// (As an aside: Ginkgo gets around this limitation by running parallel tests in different *processes*).
func RegisterTestingT(t types.GomegaTestingT) { func RegisterTestingT(t types.GomegaTestingT) {
tWithHelper, hasHelper := t.(types.TWithHelper) internalGomega(Default).ConfigureWithT(t)
if !hasHelper {
RegisterFailHandler(testingtsupport.BuildTestingTGomegaFailWrapper(t).Fail)
return
}
RegisterFailHandlerWithT(tWithHelper, testingtsupport.BuildTestingTGomegaFailWrapper(t).Fail)
} }
// InterceptGomegaFailures runs a given callback and returns an array of // InterceptGomegaFailures runs a given callback and returns an array of
// failure messages generated by any Gomega assertions within the callback. // failure messages generated by any Gomega assertions within the callback.
// // Execution continues after the first failure allowing users to collect all failures
// This is accomplished by temporarily replacing the *global* fail handler // in the callback.
// with a fail handler that simply annotates failures. The original fail handler
// is reset when InterceptGomegaFailures returns.
// //
// This is most useful when testing custom matchers, but can also be used to check // This is most useful when testing custom matchers, but can also be used to check
// on a value using a Gomega assertion without causing a test failure. // on a value using a Gomega assertion without causing a test failure.
func InterceptGomegaFailures(f func()) []string { func InterceptGomegaFailures(f func()) []string {
originalHandler := globalFailWrapper.Fail originalHandler := internalGomega(Default).Fail
failures := []string{} failures := []string{}
RegisterFailHandler(func(message string, callerSkip ...int) { internalGomega(Default).Fail = func(message string, callerSkip ...int) {
failures = append(failures, message) failures = append(failures, message)
}) }
defer func() {
internalGomega(Default).Fail = originalHandler
}()
f() f()
RegisterFailHandler(originalHandler)
return failures return failures
} }
// InterceptGomegaFailure runs a given callback and returns the first
// failure message generated by any Gomega assertions within the callback, wrapped in an error.
//
// The callback ceases execution as soon as the first failed assertion occurs, however Gomega
// does not register a failure with the FailHandler registered via RegisterFailHandler - it is up
// to the user to decide what to do with the returned error
func InterceptGomegaFailure(f func()) (err error) {
originalHandler := internalGomega(Default).Fail
internalGomega(Default).Fail = func(message string, callerSkip ...int) {
err = errors.New(message)
panic("stop execution")
}
defer func() {
internalGomega(Default).Fail = originalHandler
if e := recover(); e != nil {
if err == nil {
panic(e)
}
}
}()
f()
return err
}
func ensureDefaultGomegaIsConfigured() {
if !internalGomega(Default).IsConfigured() {
panic(nilGomegaPanic)
}
}
// Ω wraps an actual value allowing assertions to be made on it: // Ω wraps an actual value allowing assertions to be made on it:
// Ω("foo").Should(Equal("foo")) //
// Ω("foo").Should(Equal("foo"))
// //
// If Ω is passed more than one argument it will pass the *first* argument to the matcher. // If Ω is passed more than one argument it will pass the *first* argument to the matcher.
// All subsequent arguments will be required to be nil/zero. // All subsequent arguments will be required to be nil/zero.
@ -119,175 +181,314 @@ func InterceptGomegaFailures(f func()) []string {
// a value and an error - a common patter in Go. // a value and an error - a common patter in Go.
// //
// For example, given a function with signature: // For example, given a function with signature:
// func MyAmazingThing() (int, error) //
// func MyAmazingThing() (int, error)
// //
// Then: // Then:
// Ω(MyAmazingThing()).Should(Equal(3)) //
// Ω(MyAmazingThing()).Should(Equal(3))
//
// Will succeed only if `MyAmazingThing()` returns `(3, nil)` // Will succeed only if `MyAmazingThing()` returns `(3, nil)`
// //
// Ω and Expect are identical // Ω and Expect are identical
func Ω(actual interface{}, extra ...interface{}) Assertion { func Ω(actual interface{}, extra ...interface{}) Assertion {
return ExpectWithOffset(0, actual, extra...) ensureDefaultGomegaIsConfigured()
return Default.Ω(actual, extra...)
} }
// Expect wraps an actual value allowing assertions to be made on it: // Expect wraps an actual value allowing assertions to be made on it:
// Expect("foo").To(Equal("foo")) //
// Expect("foo").To(Equal("foo"))
// //
// If Expect is passed more than one argument it will pass the *first* argument to the matcher. // If Expect is passed more than one argument it will pass the *first* argument to the matcher.
// All subsequent arguments will be required to be nil/zero. // All subsequent arguments will be required to be nil/zero.
// //
// This is convenient if you want to make an assertion on a method/function that returns // This is convenient if you want to make an assertion on a method/function that returns
// a value and an error - a common patter in Go. // a value and an error - a common pattern in Go.
// //
// For example, given a function with signature: // For example, given a function with signature:
// func MyAmazingThing() (int, error) //
// func MyAmazingThing() (int, error)
// //
// Then: // Then:
// Expect(MyAmazingThing()).Should(Equal(3)) //
// Expect(MyAmazingThing()).Should(Equal(3))
//
// Will succeed only if `MyAmazingThing()` returns `(3, nil)` // Will succeed only if `MyAmazingThing()` returns `(3, nil)`
// //
// Expect and Ω are identical // Expect and Ω are identical
func Expect(actual interface{}, extra ...interface{}) Assertion { func Expect(actual interface{}, extra ...interface{}) Assertion {
return ExpectWithOffset(0, actual, extra...) ensureDefaultGomegaIsConfigured()
return Default.Expect(actual, extra...)
} }
// ExpectWithOffset wraps an actual value allowing assertions to be made on it: // ExpectWithOffset wraps an actual value allowing assertions to be made on it:
// ExpectWithOffset(1, "foo").To(Equal("foo")) //
// ExpectWithOffset(1, "foo").To(Equal("foo"))
// //
// Unlike `Expect` and `Ω`, `ExpectWithOffset` takes an additional integer argument // Unlike `Expect` and `Ω`, `ExpectWithOffset` takes an additional integer argument
// that is used to modify the call-stack offset when computing line numbers. // that is used to modify the call-stack offset when computing line numbers. It is
// the same as `Expect(...).WithOffset`.
// //
// This is most useful in helper functions that make assertions. If you want Gomega's // This is most useful in helper functions that make assertions. If you want Gomega's
// error message to refer to the calling line in the test (as opposed to the line in the helper function) // error message to refer to the calling line in the test (as opposed to the line in the helper function)
// set the first argument of `ExpectWithOffset` appropriately. // set the first argument of `ExpectWithOffset` appropriately.
func ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion { func ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion {
if globalFailWrapper == nil { ensureDefaultGomegaIsConfigured()
panic(nilFailHandlerPanic) return Default.ExpectWithOffset(offset, actual, extra...)
}
return assertion.New(actual, globalFailWrapper, offset, extra...)
} }
// Eventually wraps an actual value allowing assertions to be made on it. /*
// The assertion is tried periodically until it passes or a timeout occurs. Eventually enables making assertions on asynchronous behavior.
//
// Both the timeout and polling interval are configurable as optional arguments: Eventually checks that an assertion *eventually* passes. Eventually blocks when called and attempts an assertion periodically until it passes or a timeout occurs. Both the timeout and polling interval are configurable as optional arguments.
// The first optional argument is the timeout The first optional argument is the timeout (which defaults to 1s), the second is the polling interval (which defaults to 10ms). Both intervals can be specified as time.Duration, parsable duration strings or floats/integers (in which case they are interpreted as seconds). In addition an optional context.Context can be passed in - Eventually will keep trying until either the timeout epxires or the context is cancelled, whichever comes first.
// The second optional argument is the polling interval
// Eventually works with any Gomega compatible matcher and supports making assertions against three categories of actual value:
// Both intervals can either be specified as time.Duration, parsable duration strings or as floats/integers. In the
// last case they are interpreted as seconds. **Category 1: Making Eventually assertions on values**
//
// If Eventually is passed an actual that is a function taking no arguments and returning at least one value, There are several examples of values that can change over time. These can be passed in to Eventually and will be passed to the matcher repeatedly until a match occurs. For example:
// then Eventually will call the function periodically and try the matcher against the function's first return value.
// c := make(chan bool)
// Example: go DoStuff(c)
// Eventually(c, "50ms").Should(BeClosed())
// Eventually(func() int {
// return thingImPolling.Count() will poll the channel repeatedly until it is closed. In this example `Eventually` will block until either the specified timeout of 50ms has elapsed or the channel is closed, whichever comes first.
// }).Should(BeNumerically(">=", 17))
// Several Gomega libraries allow you to use Eventually in this way. For example, the gomega/gexec package allows you to block until a *gexec.Session exits successfully via:
// Note that this example could be rewritten:
// Eventually(session).Should(gexec.Exit(0))
// Eventually(thingImPolling.Count).Should(BeNumerically(">=", 17))
// And the gomega/gbytes package allows you to monitor a streaming *gbytes.Buffer until a given string is seen:
// If the function returns more than one value, then Eventually will pass the first value to the matcher and
// assert that all other values are nil/zero. Eventually(buffer).Should(gbytes.Say("hello there"))
// This allows you to pass Eventually a function that returns a value and an error - a common pattern in Go.
// In these examples, both `session` and `buffer` are designed to be thread-safe when polled by the `Exit` and `Say` matchers. This is not true in general of most raw values, so while it is tempting to do something like:
// For example, consider a method that returns a value and an error:
// func FetchFromDB() (string, error) // THIS IS NOT THREAD-SAFE
// var s *string
// Then go mutateStringEventually(s)
// Eventually(FetchFromDB).Should(Equal("hasselhoff")) Eventually(s).Should(Equal("I've changed"))
//
// Will pass only if the the returned error is nil and the returned string passes the matcher. this will trigger Go's race detector as the goroutine polling via Eventually will race over the value of s with the goroutine mutating the string. For cases like this you can use channels or introduce your own locking around s by passing Eventually a function.
//
// Eventually's default timeout is 1 second, and its default polling interval is 10ms **Category 2: Make Eventually assertions on functions**
func Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion {
return EventuallyWithOffset(0, actual, intervals...) Eventually can be passed functions that **return at least one value**. When configured this way, Eventually will poll the function repeatedly and pass the first returned value to the matcher.
For example:
Eventually(func() int {
return client.FetchCount()
}).Should(BeNumerically(">=", 17))
will repeatedly poll client.FetchCount until the BeNumerically matcher is satisfied. (Note that this example could have been written as Eventually(client.FetchCount).Should(BeNumerically(">=", 17)))
If multiple values are returned by the function, Eventually will pass the first value to the matcher and require that all others are zero-valued. This allows you to pass Eventually a function that returns a value and an error - a common pattern in Go.
For example, consider a method that returns a value and an error:
func FetchFromDB() (string, error)
Then
Eventually(FetchFromDB).Should(Equal("got it"))
will pass only if and when the returned error is nil *and* the returned string satisfies the matcher.
Eventually can also accept functions that take arguments, however you must provide those arguments using .WithArguments(). For example, consider a function that takes a user-id and makes a network request to fetch a full name:
func FetchFullName(userId int) (string, error)
You can poll this function like so:
Eventually(FetchFullName).WithArguments(1138).Should(Equal("Wookie"))
It is important to note that the function passed into Eventually is invoked *synchronously* when polled. Eventually does not (in fact, it cannot) kill the function if it takes longer to return than Eventually's configured timeout. A common practice here is to use a context. Here's an example that combines Ginkgo's spec timeout support with Eventually:
It("fetches the correct count", func(ctx SpecContext) {
Eventually(ctx, func() int {
return client.FetchCount(ctx, "/users")
}).Should(BeNumerically(">=", 17))
}, SpecTimeout(time.Second))
you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in contexts play nicely with paseed-in arguments as long as the context appears first. You can rewrite the above example as:
It("fetches the correct count", func(ctx SpecContext) {
Eventually(client.FetchCount).WithContext(ctx).WithArguments("/users").Should(BeNumerically(">=", 17))
}, SpecTimeout(time.Second))
Either way the context passd to Eventually is also passed to the underlying funciton. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit.
**Category 3: Making assertions _in_ the function passed into Eventually**
When testing complex systems it can be valuable to assert that a _set_ of assertions passes Eventually. Eventually supports this by accepting functions that take a single Gomega argument and return zero or more values.
Here's an example that makes some assertions and returns a value and error:
Eventually(func(g Gomega) (Widget, error) {
ids, err := client.FetchIDs()
g.Expect(err).NotTo(HaveOccurred())
g.Expect(ids).To(ContainElement(1138))
return client.FetchWidget(1138)
}).Should(Equal(expectedWidget))
will pass only if all the assertions in the polled function pass and the return value satisfied the matcher.
Eventually also supports a special case polling function that takes a single Gomega argument and returns no values. Eventually assumes such a function is making assertions and is designed to work with the Succeed matcher to validate that all assertions have passed.
For example:
Eventually(func(g Gomega) {
model, err := client.Find(1138)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(model.Reticulate()).To(Succeed())
g.Expect(model.IsReticulated()).To(BeTrue())
g.Expect(model.Save()).To(Succeed())
}).Should(Succeed())
will rerun the function until all assertions pass.
You can also pass additional arugments to functions that take a Gomega. The only rule is that the Gomega argument must be first. If you also want to pass the context attached to Eventually you must ensure that is the second argument. For example:
Eventually(func(g Gomega, ctx context.Context, path string, expected ...string){
tok, err := client.GetToken(ctx)
g.Expect(err).NotTo(HaveOccurred())
elements, err := client.Fetch(ctx, tok, path)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(elements).To(ConsistOf(expected))
}).WithContext(ctx).WithArguments("/names", "Joe", "Jane", "Sam").Should(Succeed())
You can ensure that you get a number of consecutive successful tries before succeeding using `MustPassRepeatedly(int)`. For Example:
int count := 0
Eventually(func() bool {
count++
return count > 2
}).MustPassRepeatedly(2).Should(BeTrue())
// Because we had to wait for 2 calls that returned true
Expect(count).To(Equal(3))
Finally, in addition to passing timeouts and a context to Eventually you can be more explicit with Eventually's chaining configuration methods:
Eventually(..., "1s", "2s", ctx).Should(...)
is equivalent to
Eventually(...).WithTimeout(time.Second).WithPolling(2*time.Second).WithContext(ctx).Should(...)
*/
func Eventually(actualOrCtx interface{}, args ...interface{}) AsyncAssertion {
ensureDefaultGomegaIsConfigured()
return Default.Eventually(actualOrCtx, args...)
} }
// EventuallyWithOffset operates like Eventually but takes an additional // EventuallyWithOffset operates like Eventually but takes an additional
// initial argument to indicate an offset in the call stack. This is useful when building helper // initial argument to indicate an offset in the call stack. This is useful when building helper
// functions that contain matchers. To learn more, read about `ExpectWithOffset`. // functions that contain matchers. To learn more, read about `ExpectWithOffset`.
func EventuallyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion { //
if globalFailWrapper == nil { // `EventuallyWithOffset` is the same as `Eventually(...).WithOffset`.
panic(nilFailHandlerPanic) //
} // `EventuallyWithOffset` specifying a timeout interval (and an optional polling interval) are
timeoutInterval := defaultEventuallyTimeout // the same as `Eventually(...).WithOffset(...).WithTimeout` or
pollingInterval := defaultEventuallyPollingInterval // `Eventually(...).WithOffset(...).WithTimeout(...).WithPolling`.
if len(intervals) > 0 { func EventuallyWithOffset(offset int, actualOrCtx interface{}, args ...interface{}) AsyncAssertion {
timeoutInterval = toDuration(intervals[0]) ensureDefaultGomegaIsConfigured()
} return Default.EventuallyWithOffset(offset, actualOrCtx, args...)
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
} }
// Consistently wraps an actual value allowing assertions to be made on it. /*
// The assertion is tried periodically and is required to pass for a period of time. Consistently, like Eventually, enables making assertions on asynchronous behavior.
//
// Both the total time and polling interval are configurable as optional arguments: Consistently blocks when called for a specified duration. During that duration Consistently repeatedly polls its matcher and ensures that it is satisfied. If the matcher is consistently satisfied, then Consistently will pass. Otherwise Consistently will fail.
// The first optional argument is the duration that Consistently will run for
// The second optional argument is the polling interval Both the total waiting duration and the polling interval are configurable as optional arguments. The first optional argument is the duration that Consistently will run for (defaults to 100ms), and the second argument is the polling interval (defaults to 10ms). As with Eventually, these intervals can be passed in as time.Duration, parsable duration strings or an integer or float number of seconds. You can also pass in an optional context.Context - Consistently will exit early (with a failure) if the context is cancelled before the waiting duration expires.
//
// Both intervals can either be specified as time.Duration, parsable duration strings or as floats/integers. In the Consistently accepts the same three categories of actual as Eventually, check the Eventually docs to learn more.
// last case they are interpreted as seconds.
// Consistently is useful in cases where you want to assert that something *does not happen* for a period of time. For example, you may want to assert that a goroutine does *not* send data down a channel. In this case you could write:
// If Consistently is passed an actual that is a function taking no arguments and returning at least one value,
// then Consistently will call the function periodically and try the matcher against the function's first return value. Consistently(channel, "200ms").ShouldNot(Receive())
//
// If the function returns more than one value, then Consistently will pass the first value to the matcher and This will block for 200 milliseconds and repeatedly check the channel and ensure nothing has been received.
// assert that all other values are nil/zero. */
// This allows you to pass Consistently a function that returns a value and an error - a common pattern in Go. func Consistently(actualOrCtx interface{}, args ...interface{}) AsyncAssertion {
// ensureDefaultGomegaIsConfigured()
// Consistently is useful in cases where you want to assert that something *does not happen* over a period of time. return Default.Consistently(actualOrCtx, args...)
// For example, you want to assert that a goroutine does *not* send data down a channel. In this case, you could:
//
// Consistently(channel).ShouldNot(Receive())
//
// Consistently's default duration is 100ms, and its default polling interval is 10ms
func Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion {
return ConsistentlyWithOffset(0, actual, intervals...)
} }
// ConsistentlyWithOffset operates like Consistently but takes an additional // ConsistentlyWithOffset operates like Consistently but takes an additional
// initial argument to indicate an offset in the call stack. This is useful when building helper // initial argument to indicate an offset in the call stack. This is useful when building helper
// functions that contain matchers. To learn more, read about `ExpectWithOffset`. // functions that contain matchers. To learn more, read about `ExpectWithOffset`.
func ConsistentlyWithOffset(offset int, actual interface{}, intervals ...interface{}) AsyncAssertion { //
if globalFailWrapper == nil { // `ConsistentlyWithOffset` is the same as `Consistently(...).WithOffset` and
panic(nilFailHandlerPanic) // optional `WithTimeout` and `WithPolling`.
} func ConsistentlyWithOffset(offset int, actualOrCtx interface{}, args ...interface{}) AsyncAssertion {
timeoutInterval := defaultConsistentlyDuration ensureDefaultGomegaIsConfigured()
pollingInterval := defaultConsistentlyPollingInterval return Default.ConsistentlyWithOffset(offset, actualOrCtx, args...)
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, globalFailWrapper, timeoutInterval, pollingInterval, offset)
} }
/*
StopTrying can be used to signal to Eventually and Consistentlythat they should abort and stop trying. This always results in a failure of the assertion - and the failure message is the content of the StopTrying signal.
You can send the StopTrying signal by either returning StopTrying("message") as an error from your passed-in function _or_ by calling StopTrying("message").Now() to trigger a panic and end execution.
You can also wrap StopTrying around an error with `StopTrying("message").Wrap(err)` and can attach additional objects via `StopTrying("message").Attach("description", object). When rendered, the signal will include the wrapped error and any attached objects rendered using Gomega's default formatting.
Here are a couple of examples. This is how you might use StopTrying() as an error to signal that Eventually should stop:
playerIndex, numPlayers := 0, 11
Eventually(func() (string, error) {
if playerIndex == numPlayers {
return "", StopTrying("no more players left")
}
name := client.FetchPlayer(playerIndex)
playerIndex += 1
return name, nil
}).Should(Equal("Patrick Mahomes"))
And here's an example where `StopTrying().Now()` is called to halt execution immediately:
Eventually(func() []string {
names, err := client.FetchAllPlayers()
if err == client.IRRECOVERABLE_ERROR {
StopTrying("Irrecoverable error occurred").Wrap(err).Now()
}
return names
}).Should(ContainElement("Patrick Mahomes"))
*/
var StopTrying = internal.StopTrying
/*
TryAgainAfter(<duration>) allows you to adjust the polling interval for the _next_ iteration of `Eventually` or `Consistently`. Like `StopTrying` you can either return `TryAgainAfter` as an error or trigger it immedieately with `.Now()`
When `TryAgainAfter(<duration>` is triggered `Eventually` and `Consistently` will wait for that duration. If a timeout occurs before the next poll is triggered both `Eventually` and `Consistently` will always fail with the content of the TryAgainAfter message. As with StopTrying you can `.Wrap()` and error and `.Attach()` additional objects to `TryAgainAfter`.
*/
var TryAgainAfter = internal.TryAgainAfter
/*
PollingSignalError is the error returned by StopTrying() and TryAgainAfter()
*/
type PollingSignalError = internal.PollingSignalError
// SetDefaultEventuallyTimeout sets the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses. // SetDefaultEventuallyTimeout sets the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses.
func SetDefaultEventuallyTimeout(t time.Duration) { func SetDefaultEventuallyTimeout(t time.Duration) {
defaultEventuallyTimeout = t Default.SetDefaultEventuallyTimeout(t)
} }
// SetDefaultEventuallyPollingInterval sets the default polling interval for Eventually. // SetDefaultEventuallyPollingInterval sets the default polling interval for Eventually.
func SetDefaultEventuallyPollingInterval(t time.Duration) { func SetDefaultEventuallyPollingInterval(t time.Duration) {
defaultEventuallyPollingInterval = t Default.SetDefaultEventuallyPollingInterval(t)
} }
// SetDefaultConsistentlyDuration sets the default duration for Consistently. Consistently will verify that your condition is satisfied for this long. // SetDefaultConsistentlyDuration sets the default duration for Consistently. Consistently will verify that your condition is satisfied for this long.
func SetDefaultConsistentlyDuration(t time.Duration) { func SetDefaultConsistentlyDuration(t time.Duration) {
defaultConsistentlyDuration = t Default.SetDefaultConsistentlyDuration(t)
} }
// SetDefaultConsistentlyPollingInterval sets the default polling interval for Consistently. // SetDefaultConsistentlyPollingInterval sets the default polling interval for Consistently.
func SetDefaultConsistentlyPollingInterval(t time.Duration) { func SetDefaultConsistentlyPollingInterval(t time.Duration) {
defaultConsistentlyPollingInterval = t Default.SetDefaultConsistentlyPollingInterval(t)
} }
// AsyncAssertion is returned by Eventually and Consistently and polls the actual value passed into Eventually against // AsyncAssertion is returned by Eventually and Consistently and polls the actual value passed into Eventually against
@ -303,15 +504,12 @@ func SetDefaultConsistentlyPollingInterval(t time.Duration) {
// //
// Example: // Example:
// //
// Eventually(myChannel).Should(Receive(), "Something should have come down the pipe.") // Eventually(myChannel).Should(Receive(), "Something should have come down the pipe.")
// Consistently(myChannel).ShouldNot(Receive(), func() string { return "Nothing should have come down the pipe." }) // Consistently(myChannel).ShouldNot(Receive(), func() string { return "Nothing should have come down the pipe." })
type AsyncAssertion interface { type AsyncAssertion = types.AsyncAssertion
Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
}
// GomegaAsyncAssertion is deprecated in favor of AsyncAssertion, which does not stutter. // GomegaAsyncAssertion is deprecated in favor of AsyncAssertion, which does not stutter.
type GomegaAsyncAssertion = AsyncAssertion type GomegaAsyncAssertion = types.AsyncAssertion
// Assertion is returned by Ω and Expect and compares the actual value to the matcher // Assertion is returned by Ω and Expect and compares the actual value to the matcher
// passed to the Should/ShouldNot and To/ToNot/NotTo methods. // passed to the Should/ShouldNot and To/ToNot/NotTo methods.
@ -329,135 +527,11 @@ type GomegaAsyncAssertion = AsyncAssertion
// //
// Example: // Example:
// //
// Ω(farm.HasCow()).Should(BeTrue(), "Farm %v should have a cow", farm) // Ω(farm.HasCow()).Should(BeTrue(), "Farm %v should have a cow", farm)
type Assertion interface { type Assertion = types.Assertion
Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
}
// GomegaAssertion is deprecated in favor of Assertion, which does not stutter. // GomegaAssertion is deprecated in favor of Assertion, which does not stutter.
type GomegaAssertion = Assertion type GomegaAssertion = types.Assertion
// OmegaMatcher is deprecated in favor of the better-named and better-organized types.GomegaMatcher but sticks around to support existing code that uses it // OmegaMatcher is deprecated in favor of the better-named and better-organized types.GomegaMatcher but sticks around to support existing code that uses it
type OmegaMatcher types.GomegaMatcher type OmegaMatcher = types.GomegaMatcher
// WithT wraps a *testing.T and provides `Expect`, `Eventually`, and `Consistently` methods. This allows you to leverage
// Gomega's rich ecosystem of matchers in standard `testing` test suites.
//
// Use `NewWithT` to instantiate a `WithT`
type WithT struct {
t types.GomegaTestingT
}
// GomegaWithT is deprecated in favor of gomega.WithT, which does not stutter.
type GomegaWithT = WithT
// NewWithT takes a *testing.T and returngs a `gomega.WithT` allowing you to use `Expect`, `Eventually`, and `Consistently` along with
// Gomega's rich ecosystem of matchers in standard `testing` test suits.
//
// func TestFarmHasCow(t *testing.T) {
// g := gomega.NewWithT(t)
//
// f := farm.New([]string{"Cow", "Horse"})
// g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
// }
func NewWithT(t types.GomegaTestingT) *WithT {
return &WithT{
t: t,
}
}
// NewGomegaWithT is deprecated in favor of gomega.NewWithT, which does not stutter.
func NewGomegaWithT(t types.GomegaTestingT) *GomegaWithT {
return NewWithT(t)
}
// Expect is used to make assertions. See documentation for Expect.
func (g *WithT) Expect(actual interface{}, extra ...interface{}) Assertion {
return assertion.New(actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), 0, extra...)
}
// Eventually is used to make asynchronous assertions. See documentation for Eventually.
func (g *WithT) Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion {
timeoutInterval := defaultEventuallyTimeout
pollingInterval := defaultEventuallyPollingInterval
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeEventually, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, 0)
}
// Consistently is used to make asynchronous assertions. See documentation for Consistently.
func (g *WithT) Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion {
timeoutInterval := defaultConsistentlyDuration
pollingInterval := defaultConsistentlyPollingInterval
if len(intervals) > 0 {
timeoutInterval = toDuration(intervals[0])
}
if len(intervals) > 1 {
pollingInterval = toDuration(intervals[1])
}
return asyncassertion.New(asyncassertion.AsyncAssertionTypeConsistently, actual, testingtsupport.BuildTestingTGomegaFailWrapper(g.t), timeoutInterval, pollingInterval, 0)
}
func toDuration(input interface{}) time.Duration {
duration, ok := input.(time.Duration)
if ok {
return duration
}
value := reflect.ValueOf(input)
kind := reflect.TypeOf(input).Kind()
if reflect.Int <= kind && kind <= reflect.Int64 {
return time.Duration(value.Int()) * time.Second
} else if reflect.Uint <= kind && kind <= reflect.Uint64 {
return time.Duration(value.Uint()) * time.Second
} else if reflect.Float32 <= kind && kind <= reflect.Float64 {
return time.Duration(value.Float() * float64(time.Second))
} else if reflect.String == kind {
duration, err := time.ParseDuration(value.String())
if err != nil {
panic(fmt.Sprintf("%#v is not a valid parsable duration string.", input))
}
return duration
}
panic(fmt.Sprintf("%v is not a valid interval. Must be time.Duration, parsable duration string or a number.", input))
}
// Gomega describes the essential Gomega DSL. This interface allows libraries
// to abstract between the standard package-level function implementations
// and alternatives like *WithT.
type Gomega interface {
Expect(actual interface{}, extra ...interface{}) Assertion
Eventually(actual interface{}, intervals ...interface{}) AsyncAssertion
Consistently(actual interface{}, intervals ...interface{}) AsyncAssertion
}
type globalFailHandlerGomega struct{}
// DefaultGomega supplies the standard package-level implementation
var Default Gomega = globalFailHandlerGomega{}
// Expect is used to make assertions. See documentation for Expect.
func (globalFailHandlerGomega) Expect(actual interface{}, extra ...interface{}) Assertion {
return Expect(actual, extra...)
}
// Eventually is used to make asynchronous assertions. See documentation for Eventually.
func (globalFailHandlerGomega) Eventually(actual interface{}, extra ...interface{}) AsyncAssertion {
return Eventually(actual, extra...)
}
// Consistently is used to make asynchronous assertions. See documentation for Consistently.
func (globalFailHandlerGomega) Consistently(actual interface{}, extra ...interface{}) AsyncAssertion {
return Consistently(actual, extra...)
}

161
vendor/github.com/onsi/gomega/internal/assertion.go generated vendored Normal file
View File

@ -0,0 +1,161 @@
package internal
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)
type Assertion struct {
actuals []interface{} // actual value plus all extra values
actualIndex int // value to pass to the matcher
vet vetinari // the vet to call before calling Gomega matcher
offset int
g *Gomega
}
// ...obligatory discworld reference, as "vetineer" doesn't sound ... quite right.
type vetinari func(assertion *Assertion, optionalDescription ...interface{}) bool
func NewAssertion(actualInput interface{}, g *Gomega, offset int, extra ...interface{}) *Assertion {
return &Assertion{
actuals: append([]interface{}{actualInput}, extra...),
actualIndex: 0,
vet: (*Assertion).vetActuals,
offset: offset,
g: g,
}
}
func (assertion *Assertion) WithOffset(offset int) types.Assertion {
assertion.offset = offset
return assertion
}
func (assertion *Assertion) Error() types.Assertion {
return &Assertion{
actuals: assertion.actuals,
actualIndex: len(assertion.actuals) - 1,
vet: (*Assertion).vetError,
offset: assertion.offset,
g: assertion.g,
}
}
func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.g.THelper()
vetOptionalDescription("Assertion", optionalDescription...)
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}
func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.g.THelper()
vetOptionalDescription("Assertion", optionalDescription...)
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.g.THelper()
vetOptionalDescription("Assertion", optionalDescription...)
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}
func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.g.THelper()
vetOptionalDescription("Assertion", optionalDescription...)
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.g.THelper()
vetOptionalDescription("Assertion", optionalDescription...)
return assertion.vet(assertion, optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) buildDescription(optionalDescription ...interface{}) string {
switch len(optionalDescription) {
case 0:
return ""
case 1:
if describe, ok := optionalDescription[0].(func() string); ok {
return describe() + "\n"
}
}
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
}
func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
actualInput := assertion.actuals[assertion.actualIndex]
matches, err := matcher.Match(actualInput)
assertion.g.THelper()
if err != nil {
description := assertion.buildDescription(optionalDescription...)
assertion.g.Fail(description+err.Error(), 2+assertion.offset)
return false
}
if matches != desiredMatch {
var message string
if desiredMatch {
message = matcher.FailureMessage(actualInput)
} else {
message = matcher.NegatedFailureMessage(actualInput)
}
description := assertion.buildDescription(optionalDescription...)
assertion.g.Fail(description+message, 2+assertion.offset)
return false
}
return true
}
// vetActuals vets the actual values, with the (optional) exception of a
// specific value, such as the first value in case non-error assertions, or the
// last value in case of Error()-based assertions.
func (assertion *Assertion) vetActuals(optionalDescription ...interface{}) bool {
success, message := vetActuals(assertion.actuals, assertion.actualIndex)
if success {
return true
}
description := assertion.buildDescription(optionalDescription...)
assertion.g.THelper()
assertion.g.Fail(description+message, 2+assertion.offset)
return false
}
// vetError vets the actual values, except for the final error value, in case
// the final error value is non-zero. Otherwise, it doesn't vet the actual
// values, as these are allowed to take on any values unless there is a non-zero
// error value.
func (assertion *Assertion) vetError(optionalDescription ...interface{}) bool {
if err := assertion.actuals[assertion.actualIndex]; err != nil {
// Go error result idiom: all other actual values must be zero values.
return assertion.vetActuals(optionalDescription...)
}
return true
}
// vetActuals vets a slice of actual values, optionally skipping a particular
// value slice element, such as the first or last value slice element.
func vetActuals(actuals []interface{}, skipIndex int) (bool, string) {
for i, actual := range actuals {
if i == skipIndex {
continue
}
if actual != nil {
zeroValue := reflect.Zero(reflect.TypeOf(actual)).Interface()
if !reflect.DeepEqual(zeroValue, actual) {
var message string
if err, ok := actual.(error); ok {
message = fmt.Sprintf("Unexpected error: %s\n%s", err, format.Object(err, 1))
} else {
message = fmt.Sprintf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i, actual, actual)
}
return false, message
}
}
}
return true, ""
}

View File

@ -1,109 +0,0 @@
package assertion
import (
"fmt"
"reflect"
"github.com/onsi/gomega/types"
)
type Assertion struct {
actualInput interface{}
failWrapper *types.GomegaFailWrapper
offset int
extra []interface{}
}
func New(actualInput interface{}, failWrapper *types.GomegaFailWrapper, offset int, extra ...interface{}) *Assertion {
return &Assertion{
actualInput: actualInput,
failWrapper: failWrapper,
offset: offset,
extra: extra,
}
}
func (assertion *Assertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}
func (assertion *Assertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, true, optionalDescription...)
}
func (assertion *Assertion) ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.vetExtras(optionalDescription...) && assertion.match(matcher, false, optionalDescription...)
}
func (assertion *Assertion) buildDescription(optionalDescription ...interface{}) string {
switch len(optionalDescription) {
case 0:
return ""
case 1:
if describe, ok := optionalDescription[0].(func() string); ok {
return describe() + "\n"
}
}
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
}
func (assertion *Assertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
matches, err := matcher.Match(assertion.actualInput)
assertion.failWrapper.TWithHelper.Helper()
if err != nil {
description := assertion.buildDescription(optionalDescription...)
assertion.failWrapper.Fail(description+err.Error(), 2+assertion.offset)
return false
}
if matches != desiredMatch {
var message string
if desiredMatch {
message = matcher.FailureMessage(assertion.actualInput)
} else {
message = matcher.NegatedFailureMessage(assertion.actualInput)
}
description := assertion.buildDescription(optionalDescription...)
assertion.failWrapper.Fail(description+message, 2+assertion.offset)
return false
}
return true
}
func (assertion *Assertion) vetExtras(optionalDescription ...interface{}) bool {
success, message := vetExtras(assertion.extra)
if success {
return true
}
description := assertion.buildDescription(optionalDescription...)
assertion.failWrapper.TWithHelper.Helper()
assertion.failWrapper.Fail(description+message, 2+assertion.offset)
return false
}
func vetExtras(extras []interface{}) (bool, string) {
for i, extra := range extras {
if extra != nil {
zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
if !reflect.DeepEqual(zeroValue, extra) {
message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
return false, message
}
}
}
return true, ""
}

View File

@ -0,0 +1,571 @@
package internal
import (
"context"
"errors"
"fmt"
"reflect"
"runtime"
"sync"
"time"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)
var errInterface = reflect.TypeOf((*error)(nil)).Elem()
var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem()
var contextType = reflect.TypeOf(new(context.Context)).Elem()
type formattedGomegaError interface {
FormattedGomegaError() string
}
type asyncPolledActualError struct {
message string
}
func (err *asyncPolledActualError) Error() string {
return err.message
}
func (err *asyncPolledActualError) FormattedGomegaError() string {
return err.message
}
type contextWithAttachProgressReporter interface {
AttachProgressReporter(func() string) func()
}
type asyncGomegaHaltExecutionError struct{}
func (a asyncGomegaHaltExecutionError) GinkgoRecoverShouldIgnoreThisPanic() {}
func (a asyncGomegaHaltExecutionError) Error() string {
return `An assertion has failed in a goroutine. You should call
defer GinkgoRecover()
at the top of the goroutine that caused this panic. This will allow Ginkgo and Gomega to correctly capture and manage this panic.`
}
type AsyncAssertionType uint
const (
AsyncAssertionTypeEventually AsyncAssertionType = iota
AsyncAssertionTypeConsistently
)
func (at AsyncAssertionType) String() string {
switch at {
case AsyncAssertionTypeEventually:
return "Eventually"
case AsyncAssertionTypeConsistently:
return "Consistently"
}
return "INVALID ASYNC ASSERTION TYPE"
}
type AsyncAssertion struct {
asyncType AsyncAssertionType
actualIsFunc bool
actual interface{}
argsToForward []interface{}
timeoutInterval time.Duration
pollingInterval time.Duration
mustPassRepeatedly int
ctx context.Context
offset int
g *Gomega
}
func NewAsyncAssertion(asyncType AsyncAssertionType, actualInput interface{}, g *Gomega, timeoutInterval time.Duration, pollingInterval time.Duration, mustPassRepeatedly int, ctx context.Context, offset int) *AsyncAssertion {
out := &AsyncAssertion{
asyncType: asyncType,
timeoutInterval: timeoutInterval,
pollingInterval: pollingInterval,
mustPassRepeatedly: mustPassRepeatedly,
offset: offset,
ctx: ctx,
g: g,
}
out.actual = actualInput
if actualInput != nil && reflect.TypeOf(actualInput).Kind() == reflect.Func {
out.actualIsFunc = true
}
return out
}
func (assertion *AsyncAssertion) WithOffset(offset int) types.AsyncAssertion {
assertion.offset = offset
return assertion
}
func (assertion *AsyncAssertion) WithTimeout(interval time.Duration) types.AsyncAssertion {
assertion.timeoutInterval = interval
return assertion
}
func (assertion *AsyncAssertion) WithPolling(interval time.Duration) types.AsyncAssertion {
assertion.pollingInterval = interval
return assertion
}
func (assertion *AsyncAssertion) Within(timeout time.Duration) types.AsyncAssertion {
assertion.timeoutInterval = timeout
return assertion
}
func (assertion *AsyncAssertion) ProbeEvery(interval time.Duration) types.AsyncAssertion {
assertion.pollingInterval = interval
return assertion
}
func (assertion *AsyncAssertion) WithContext(ctx context.Context) types.AsyncAssertion {
assertion.ctx = ctx
return assertion
}
func (assertion *AsyncAssertion) WithArguments(argsToForward ...interface{}) types.AsyncAssertion {
assertion.argsToForward = argsToForward
return assertion
}
func (assertion *AsyncAssertion) MustPassRepeatedly(count int) types.AsyncAssertion {
assertion.mustPassRepeatedly = count
return assertion
}
func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.g.THelper()
vetOptionalDescription("Asynchronous assertion", optionalDescription...)
return assertion.match(matcher, true, optionalDescription...)
}
func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.g.THelper()
vetOptionalDescription("Asynchronous assertion", optionalDescription...)
return assertion.match(matcher, false, optionalDescription...)
}
func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
switch len(optionalDescription) {
case 0:
return ""
case 1:
if describe, ok := optionalDescription[0].(func() string); ok {
return describe() + "\n"
}
}
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
}
func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error) {
if len(values) == 0 {
return nil, &asyncPolledActualError{
message: fmt.Sprintf("The function passed to %s did not return any values", assertion.asyncType),
}
}
actual := values[0].Interface()
if _, ok := AsPollingSignalError(actual); ok {
return actual, actual.(error)
}
var err error
for i, extraValue := range values[1:] {
extra := extraValue.Interface()
if extra == nil {
continue
}
if _, ok := AsPollingSignalError(extra); ok {
return actual, extra.(error)
}
extraType := reflect.TypeOf(extra)
zero := reflect.Zero(extraType).Interface()
if reflect.DeepEqual(extra, zero) {
continue
}
if i == len(values)-2 && extraType.Implements(errInterface) {
err = extra.(error)
}
if err == nil {
err = &asyncPolledActualError{
message: fmt.Sprintf("The function passed to %s had an unexpected non-nil/non-zero return value at index %d:\n%s", assertion.asyncType, i+1, format.Object(extra, 1)),
}
}
}
return actual, err
}
func (assertion *AsyncAssertion) invalidFunctionError(t reflect.Type) error {
return fmt.Errorf(`The function passed to %s had an invalid signature of %s. Functions passed to %s must either:
(a) have return values or
(b) take a Gomega interface as their first argument and use that Gomega instance to make assertions.
You can learn more at https://onsi.github.io/gomega/#eventually
`, assertion.asyncType, t, assertion.asyncType)
}
func (assertion *AsyncAssertion) noConfiguredContextForFunctionError() error {
return fmt.Errorf(`The function passed to %s requested a context.Context, but no context has been provided. Please pass one in using %s().WithContext().
You can learn more at https://onsi.github.io/gomega/#eventually
`, assertion.asyncType, assertion.asyncType)
}
func (assertion *AsyncAssertion) argumentMismatchError(t reflect.Type, numProvided int) error {
have := "have"
if numProvided == 1 {
have = "has"
}
return fmt.Errorf(`The function passed to %s has signature %s takes %d arguments but %d %s been provided. Please use %s().WithArguments() to pass the corect set of arguments.
You can learn more at https://onsi.github.io/gomega/#eventually
`, assertion.asyncType, t, t.NumIn(), numProvided, have, assertion.asyncType)
}
func (assertion *AsyncAssertion) invalidMustPassRepeatedlyError(reason string) error {
return fmt.Errorf(`Invalid use of MustPassRepeatedly with %s %s
You can learn more at https://onsi.github.io/gomega/#eventually
`, assertion.asyncType, reason)
}
func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error), error) {
if !assertion.actualIsFunc {
return func() (interface{}, error) { return assertion.actual, nil }, nil
}
actualValue := reflect.ValueOf(assertion.actual)
actualType := reflect.TypeOf(assertion.actual)
numIn, numOut, isVariadic := actualType.NumIn(), actualType.NumOut(), actualType.IsVariadic()
if numIn == 0 && numOut == 0 {
return nil, assertion.invalidFunctionError(actualType)
}
takesGomega, takesContext := false, false
if numIn > 0 {
takesGomega, takesContext = actualType.In(0).Implements(gomegaType), actualType.In(0).Implements(contextType)
}
if takesGomega && numIn > 1 && actualType.In(1).Implements(contextType) {
takesContext = true
}
if takesContext && len(assertion.argsToForward) > 0 && reflect.TypeOf(assertion.argsToForward[0]).Implements(contextType) {
takesContext = false
}
if !takesGomega && numOut == 0 {
return nil, assertion.invalidFunctionError(actualType)
}
if takesContext && assertion.ctx == nil {
return nil, assertion.noConfiguredContextForFunctionError()
}
var assertionFailure error
inValues := []reflect.Value{}
if takesGomega {
inValues = append(inValues, reflect.ValueOf(NewGomega(assertion.g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) {
skip := 0
if len(callerSkip) > 0 {
skip = callerSkip[0]
}
_, file, line, _ := runtime.Caller(skip + 1)
assertionFailure = &asyncPolledActualError{
message: fmt.Sprintf("The function passed to %s failed at %s:%d with:\n%s", assertion.asyncType, file, line, message),
}
// we throw an asyncGomegaHaltExecutionError so that defer GinkgoRecover() can catch this error if the user makes an assertion in a goroutine
panic(asyncGomegaHaltExecutionError{})
})))
}
if takesContext {
inValues = append(inValues, reflect.ValueOf(assertion.ctx))
}
for _, arg := range assertion.argsToForward {
inValues = append(inValues, reflect.ValueOf(arg))
}
if !isVariadic && numIn != len(inValues) {
return nil, assertion.argumentMismatchError(actualType, len(inValues))
} else if isVariadic && len(inValues) < numIn-1 {
return nil, assertion.argumentMismatchError(actualType, len(inValues))
}
if assertion.mustPassRepeatedly != 1 && assertion.asyncType != AsyncAssertionTypeEventually {
return nil, assertion.invalidMustPassRepeatedlyError("it can only be used with Eventually")
}
if assertion.mustPassRepeatedly < 1 {
return nil, assertion.invalidMustPassRepeatedlyError("parameter can't be < 1")
}
return func() (actual interface{}, err error) {
var values []reflect.Value
assertionFailure = nil
defer func() {
if numOut == 0 && takesGomega {
actual = assertionFailure
} else {
actual, err = assertion.processReturnValues(values)
_, isAsyncError := AsPollingSignalError(err)
if assertionFailure != nil && !isAsyncError {
err = assertionFailure
}
}
if e := recover(); e != nil {
if _, isAsyncError := AsPollingSignalError(e); isAsyncError {
err = e.(error)
} else if assertionFailure == nil {
panic(e)
}
}
}()
values = actualValue.Call(inValues)
return
}, nil
}
func (assertion *AsyncAssertion) afterTimeout() <-chan time.Time {
if assertion.timeoutInterval >= 0 {
return time.After(assertion.timeoutInterval)
}
if assertion.asyncType == AsyncAssertionTypeConsistently {
return time.After(assertion.g.DurationBundle.ConsistentlyDuration)
} else {
if assertion.ctx == nil {
return time.After(assertion.g.DurationBundle.EventuallyTimeout)
} else {
return nil
}
}
}
func (assertion *AsyncAssertion) afterPolling() <-chan time.Time {
if assertion.pollingInterval >= 0 {
return time.After(assertion.pollingInterval)
}
if assertion.asyncType == AsyncAssertionTypeConsistently {
return time.After(assertion.g.DurationBundle.ConsistentlyPollingInterval)
} else {
return time.After(assertion.g.DurationBundle.EventuallyPollingInterval)
}
}
func (assertion *AsyncAssertion) matcherSaysStopTrying(matcher types.GomegaMatcher, value interface{}) bool {
if assertion.actualIsFunc || types.MatchMayChangeInTheFuture(matcher, value) {
return false
}
return true
}
func (assertion *AsyncAssertion) pollMatcher(matcher types.GomegaMatcher, value interface{}) (matches bool, err error) {
defer func() {
if e := recover(); e != nil {
if _, isAsyncError := AsPollingSignalError(e); isAsyncError {
err = e.(error)
} else {
panic(e)
}
}
}()
matches, err = matcher.Match(value)
return
}
func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
timer := time.Now()
timeout := assertion.afterTimeout()
lock := sync.Mutex{}
var matches, hasLastValidActual bool
var actual, lastValidActual interface{}
var actualErr, matcherErr error
var oracleMatcherSaysStop bool
assertion.g.THelper()
pollActual, buildActualPollerErr := assertion.buildActualPoller()
if buildActualPollerErr != nil {
assertion.g.Fail(buildActualPollerErr.Error(), 2+assertion.offset)
return false
}
actual, actualErr = pollActual()
if actualErr == nil {
lastValidActual = actual
hasLastValidActual = true
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, actual)
matches, matcherErr = assertion.pollMatcher(matcher, actual)
}
renderError := func(preamble string, err error) string {
message := ""
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
message = err.Error()
for _, attachment := range pollingSignalErr.Attachments {
message += fmt.Sprintf("\n%s:\n", attachment.Description)
message += format.Object(attachment.Object, 1)
}
} else {
message = preamble + "\n" + format.Object(err, 1)
}
return message
}
messageGenerator := func() string {
// can be called out of band by Ginkgo if the user requests a progress report
lock.Lock()
defer lock.Unlock()
message := ""
if actualErr == nil {
if matcherErr == nil {
if desiredMatch != matches {
if desiredMatch {
message += matcher.FailureMessage(actual)
} else {
message += matcher.NegatedFailureMessage(actual)
}
} else {
if assertion.asyncType == AsyncAssertionTypeConsistently {
message += "There is no failure as the matcher passed to Consistently has not yet failed"
} else {
message += "There is no failure as the matcher passed to Eventually succeeded on its most recent iteration"
}
}
} else {
var fgErr formattedGomegaError
if errors.As(actualErr, &fgErr) {
message += fgErr.FormattedGomegaError() + "\n"
} else {
message += renderError(fmt.Sprintf("The matcher passed to %s returned the following error:", assertion.asyncType), matcherErr)
}
}
} else {
var fgErr formattedGomegaError
if errors.As(actualErr, &fgErr) {
message += fgErr.FormattedGomegaError() + "\n"
} else {
message += renderError(fmt.Sprintf("The function passed to %s returned the following error:", assertion.asyncType), actualErr)
}
if hasLastValidActual {
message += fmt.Sprintf("\nAt one point, however, the function did return successfully.\nYet, %s failed because", assertion.asyncType)
_, e := matcher.Match(lastValidActual)
if e != nil {
message += renderError(" the matcher returned the following error:", e)
} else {
message += " the matcher was not satisfied:\n"
if desiredMatch {
message += matcher.FailureMessage(lastValidActual)
} else {
message += matcher.NegatedFailureMessage(lastValidActual)
}
}
}
}
description := assertion.buildDescription(optionalDescription...)
return fmt.Sprintf("%s%s", description, message)
}
fail := func(preamble string) {
assertion.g.THelper()
assertion.g.Fail(fmt.Sprintf("%s after %.3fs.\n%s", preamble, time.Since(timer).Seconds(), messageGenerator()), 3+assertion.offset)
}
var contextDone <-chan struct{}
if assertion.ctx != nil {
contextDone = assertion.ctx.Done()
if v, ok := assertion.ctx.Value("GINKGO_SPEC_CONTEXT").(contextWithAttachProgressReporter); ok {
detach := v.AttachProgressReporter(messageGenerator)
defer detach()
}
}
// Used to count the number of times in a row a step passed
passedRepeatedlyCount := 0
for {
var nextPoll <-chan time.Time = nil
var isTryAgainAfterError = false
for _, err := range []error{actualErr, matcherErr} {
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
if pollingSignalErr.IsStopTrying() {
fail("Told to stop trying")
return false
}
if pollingSignalErr.IsTryAgainAfter() {
nextPoll = time.After(pollingSignalErr.TryAgainDuration())
isTryAgainAfterError = true
}
}
}
if actualErr == nil && matcherErr == nil && matches == desiredMatch {
if assertion.asyncType == AsyncAssertionTypeEventually {
passedRepeatedlyCount += 1
if passedRepeatedlyCount == assertion.mustPassRepeatedly {
return true
}
}
} else if !isTryAgainAfterError {
if assertion.asyncType == AsyncAssertionTypeConsistently {
fail("Failed")
return false
}
// Reset the consecutive pass count
passedRepeatedlyCount = 0
}
if oracleMatcherSaysStop {
if assertion.asyncType == AsyncAssertionTypeEventually {
fail("No future change is possible. Bailing out early")
return false
} else {
return true
}
}
if nextPoll == nil {
nextPoll = assertion.afterPolling()
}
select {
case <-nextPoll:
a, e := pollActual()
lock.Lock()
actual, actualErr = a, e
lock.Unlock()
if actualErr == nil {
lock.Lock()
lastValidActual = actual
hasLastValidActual = true
lock.Unlock()
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, actual)
m, e := assertion.pollMatcher(matcher, actual)
lock.Lock()
matches, matcherErr = m, e
lock.Unlock()
}
case <-contextDone:
fail("Context was cancelled")
return false
case <-timeout:
if assertion.asyncType == AsyncAssertionTypeEventually {
fail("Timed out")
return false
} else {
if isTryAgainAfterError {
fail("Timed out while waiting on TryAgainAfter")
return false
}
return true
}
}
}
}

View File

@ -1,198 +0,0 @@
// untested sections: 2
package asyncassertion
import (
"errors"
"fmt"
"reflect"
"time"
"github.com/onsi/gomega/internal/oraclematcher"
"github.com/onsi/gomega/types"
)
type AsyncAssertionType uint
const (
AsyncAssertionTypeEventually AsyncAssertionType = iota
AsyncAssertionTypeConsistently
)
type AsyncAssertion struct {
asyncType AsyncAssertionType
actualInput interface{}
timeoutInterval time.Duration
pollingInterval time.Duration
failWrapper *types.GomegaFailWrapper
offset int
}
func New(asyncType AsyncAssertionType, actualInput interface{}, failWrapper *types.GomegaFailWrapper, timeoutInterval time.Duration, pollingInterval time.Duration, offset int) *AsyncAssertion {
actualType := reflect.TypeOf(actualInput)
if actualType.Kind() == reflect.Func {
if actualType.NumIn() != 0 || actualType.NumOut() == 0 {
panic("Expected a function with no arguments and one or more return values.")
}
}
return &AsyncAssertion{
asyncType: asyncType,
actualInput: actualInput,
failWrapper: failWrapper,
timeoutInterval: timeoutInterval,
pollingInterval: pollingInterval,
offset: offset,
}
}
func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.match(matcher, true, optionalDescription...)
}
func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
assertion.failWrapper.TWithHelper.Helper()
return assertion.match(matcher, false, optionalDescription...)
}
func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
switch len(optionalDescription) {
case 0:
return ""
case 1:
if describe, ok := optionalDescription[0].(func() string); ok {
return describe() + "\n"
}
}
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
}
func (assertion *AsyncAssertion) actualInputIsAFunction() bool {
actualType := reflect.TypeOf(assertion.actualInput)
return actualType.Kind() == reflect.Func && actualType.NumIn() == 0 && actualType.NumOut() > 0
}
func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
if assertion.actualInputIsAFunction() {
values := reflect.ValueOf(assertion.actualInput).Call([]reflect.Value{})
extras := []interface{}{}
for _, value := range values[1:] {
extras = append(extras, value.Interface())
}
success, message := vetExtras(extras)
if !success {
return nil, errors.New(message)
}
return values[0].Interface(), nil
}
return assertion.actualInput, nil
}
func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
if assertion.actualInputIsAFunction() {
return true
}
return oraclematcher.MatchMayChangeInTheFuture(matcher, value)
}
func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
timer := time.Now()
timeout := time.After(assertion.timeoutInterval)
var matches bool
var err error
mayChange := true
value, err := assertion.pollActual()
if err == nil {
mayChange = assertion.matcherMayChange(matcher, value)
matches, err = matcher.Match(value)
}
assertion.failWrapper.TWithHelper.Helper()
fail := func(preamble string) {
errMsg := ""
message := ""
if err != nil {
errMsg = "Error: " + err.Error()
} else {
if desiredMatch {
message = matcher.FailureMessage(value)
} else {
message = matcher.NegatedFailureMessage(value)
}
}
assertion.failWrapper.TWithHelper.Helper()
description := assertion.buildDescription(optionalDescription...)
assertion.failWrapper.Fail(fmt.Sprintf("%s after %.3fs.\n%s%s%s", preamble, time.Since(timer).Seconds(), description, message, errMsg), 3+assertion.offset)
}
if assertion.asyncType == AsyncAssertionTypeEventually {
for {
if err == nil && matches == desiredMatch {
return true
}
if !mayChange {
fail("No future change is possible. Bailing out early")
return false
}
select {
case <-time.After(assertion.pollingInterval):
value, err = assertion.pollActual()
if err == nil {
mayChange = assertion.matcherMayChange(matcher, value)
matches, err = matcher.Match(value)
}
case <-timeout:
fail("Timed out")
return false
}
}
} else if assertion.asyncType == AsyncAssertionTypeConsistently {
for {
if !(err == nil && matches == desiredMatch) {
fail("Failed")
return false
}
if !mayChange {
return true
}
select {
case <-time.After(assertion.pollingInterval):
value, err = assertion.pollActual()
if err == nil {
mayChange = assertion.matcherMayChange(matcher, value)
matches, err = matcher.Match(value)
}
case <-timeout:
return true
}
}
}
return false
}
func vetExtras(extras []interface{}) (bool, string) {
for i, extra := range extras {
if extra != nil {
zeroValue := reflect.Zero(reflect.TypeOf(extra)).Interface()
if !reflect.DeepEqual(zeroValue, extra) {
message := fmt.Sprintf("Unexpected non-nil/non-zero extra argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
return false, message
}
}
}
return true, ""
}

View File

@ -0,0 +1,71 @@
package internal
import (
"fmt"
"os"
"reflect"
"time"
)
type DurationBundle struct {
EventuallyTimeout time.Duration
EventuallyPollingInterval time.Duration
ConsistentlyDuration time.Duration
ConsistentlyPollingInterval time.Duration
}
const (
EventuallyTimeoutEnvVarName = "GOMEGA_DEFAULT_EVENTUALLY_TIMEOUT"
EventuallyPollingIntervalEnvVarName = "GOMEGA_DEFAULT_EVENTUALLY_POLLING_INTERVAL"
ConsistentlyDurationEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_DURATION"
ConsistentlyPollingIntervalEnvVarName = "GOMEGA_DEFAULT_CONSISTENTLY_POLLING_INTERVAL"
)
func FetchDefaultDurationBundle() DurationBundle {
return DurationBundle{
EventuallyTimeout: durationFromEnv(EventuallyTimeoutEnvVarName, time.Second),
EventuallyPollingInterval: durationFromEnv(EventuallyPollingIntervalEnvVarName, 10*time.Millisecond),
ConsistentlyDuration: durationFromEnv(ConsistentlyDurationEnvVarName, 100*time.Millisecond),
ConsistentlyPollingInterval: durationFromEnv(ConsistentlyPollingIntervalEnvVarName, 10*time.Millisecond),
}
}
func durationFromEnv(key string, defaultDuration time.Duration) time.Duration {
value := os.Getenv(key)
if value == "" {
return defaultDuration
}
duration, err := time.ParseDuration(value)
if err != nil {
panic(fmt.Sprintf("Expected a duration when using %s! Parse error %v", key, err))
}
return duration
}
func toDuration(input interface{}) (time.Duration, error) {
duration, ok := input.(time.Duration)
if ok {
return duration, nil
}
value := reflect.ValueOf(input)
kind := reflect.TypeOf(input).Kind()
if reflect.Int <= kind && kind <= reflect.Int64 {
return time.Duration(value.Int()) * time.Second, nil
} else if reflect.Uint <= kind && kind <= reflect.Uint64 {
return time.Duration(value.Uint()) * time.Second, nil
} else if reflect.Float32 <= kind && kind <= reflect.Float64 {
return time.Duration(value.Float() * float64(time.Second)), nil
} else if reflect.String == kind {
duration, err := time.ParseDuration(value.String())
if err != nil {
return 0, fmt.Errorf("%#v is not a valid parsable duration string: %w", input, err)
}
return duration, nil
}
return 0, fmt.Errorf("%#v is not a valid interval. Must be a time.Duration, a parsable duration string, or a number.", input)
}

129
vendor/github.com/onsi/gomega/internal/gomega.go generated vendored Normal file
View File

@ -0,0 +1,129 @@
package internal
import (
"context"
"time"
"github.com/onsi/gomega/types"
)
type Gomega struct {
Fail types.GomegaFailHandler
THelper func()
DurationBundle DurationBundle
}
func NewGomega(bundle DurationBundle) *Gomega {
return &Gomega{
Fail: nil,
THelper: nil,
DurationBundle: bundle,
}
}
func (g *Gomega) IsConfigured() bool {
return g.Fail != nil && g.THelper != nil
}
func (g *Gomega) ConfigureWithFailHandler(fail types.GomegaFailHandler) *Gomega {
g.Fail = fail
g.THelper = func() {}
return g
}
func (g *Gomega) ConfigureWithT(t types.GomegaTestingT) *Gomega {
g.Fail = func(message string, _ ...int) {
t.Helper()
t.Fatalf("\n%s", message)
}
g.THelper = t.Helper
return g
}
func (g *Gomega) Ω(actual interface{}, extra ...interface{}) types.Assertion {
return g.ExpectWithOffset(0, actual, extra...)
}
func (g *Gomega) Expect(actual interface{}, extra ...interface{}) types.Assertion {
return g.ExpectWithOffset(0, actual, extra...)
}
func (g *Gomega) ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) types.Assertion {
return NewAssertion(actual, g, offset, extra...)
}
func (g *Gomega) Eventually(actualOrCtx interface{}, args ...interface{}) types.AsyncAssertion {
return g.makeAsyncAssertion(AsyncAssertionTypeEventually, 0, actualOrCtx, args...)
}
func (g *Gomega) EventuallyWithOffset(offset int, actualOrCtx interface{}, args ...interface{}) types.AsyncAssertion {
return g.makeAsyncAssertion(AsyncAssertionTypeEventually, offset, actualOrCtx, args...)
}
func (g *Gomega) Consistently(actualOrCtx interface{}, args ...interface{}) types.AsyncAssertion {
return g.makeAsyncAssertion(AsyncAssertionTypeConsistently, 0, actualOrCtx, args...)
}
func (g *Gomega) ConsistentlyWithOffset(offset int, actualOrCtx interface{}, args ...interface{}) types.AsyncAssertion {
return g.makeAsyncAssertion(AsyncAssertionTypeConsistently, offset, actualOrCtx, args...)
}
func (g *Gomega) makeAsyncAssertion(asyncAssertionType AsyncAssertionType, offset int, actualOrCtx interface{}, args ...interface{}) types.AsyncAssertion {
baseOffset := 3
timeoutInterval := -time.Duration(1)
pollingInterval := -time.Duration(1)
intervals := []interface{}{}
var ctx context.Context
actual := actualOrCtx
startingIndex := 0
if _, isCtx := actualOrCtx.(context.Context); isCtx && len(args) > 0 {
// the first argument is a context, we should accept it as the context _only if_ it is **not** the only argumnent **and** the second argument is not a parseable duration
// this is due to an unfortunate ambiguity in early version of Gomega in which multi-type durations are allowed after the actual
if _, err := toDuration(args[0]); err != nil {
ctx = actualOrCtx.(context.Context)
actual = args[0]
startingIndex = 1
}
}
for _, arg := range args[startingIndex:] {
switch v := arg.(type) {
case context.Context:
ctx = v
default:
intervals = append(intervals, arg)
}
}
var err error
if len(intervals) > 0 {
timeoutInterval, err = toDuration(intervals[0])
if err != nil {
g.Fail(err.Error(), offset+baseOffset)
}
}
if len(intervals) > 1 {
pollingInterval, err = toDuration(intervals[1])
if err != nil {
g.Fail(err.Error(), offset+baseOffset)
}
}
return NewAsyncAssertion(asyncAssertionType, actual, g, timeoutInterval, pollingInterval, 1, ctx, offset)
}
func (g *Gomega) SetDefaultEventuallyTimeout(t time.Duration) {
g.DurationBundle.EventuallyTimeout = t
}
func (g *Gomega) SetDefaultEventuallyPollingInterval(t time.Duration) {
g.DurationBundle.EventuallyPollingInterval = t
}
func (g *Gomega) SetDefaultConsistentlyDuration(t time.Duration) {
g.DurationBundle.ConsistentlyDuration = t
}
func (g *Gomega) SetDefaultConsistentlyPollingInterval(t time.Duration) {
g.DurationBundle.ConsistentlyPollingInterval = t
}

View File

@ -0,0 +1,48 @@
//go:build go1.16
// +build go1.16
// Package gutil is a replacement for ioutil, which should not be used in new
// code as of Go 1.16. With Go 1.16 and higher, this implementation
// uses the ioutil replacement functions in "io" and "os" with some
// Gomega specifics. This means that we should not get deprecation warnings
// for ioutil when they are added.
package gutil
import (
"io"
"os"
)
func NopCloser(r io.Reader) io.ReadCloser {
return io.NopCloser(r)
}
func ReadAll(r io.Reader) ([]byte, error) {
return io.ReadAll(r)
}
func ReadDir(dirname string) ([]string, error) {
entries, err := os.ReadDir(dirname)
if err != nil {
return nil, err
}
var names []string
for _, entry := range entries {
names = append(names, entry.Name())
}
return names, nil
}
func ReadFile(filename string) ([]byte, error) {
return os.ReadFile(filename)
}
func MkdirTemp(dir, pattern string) (string, error) {
return os.MkdirTemp(dir, pattern)
}
func WriteFile(filename string, data []byte) error {
return os.WriteFile(filename, data, 0644)
}

View File

@ -0,0 +1,47 @@
//go:build !go1.16
// +build !go1.16
// Package gutil is a replacement for ioutil, which should not be used in new
// code as of Go 1.16. With Go 1.15 and lower, this implementation
// uses the ioutil functions, meaning that although Gomega is not officially
// supported on these versions, it is still likely to work.
package gutil
import (
"io"
"io/ioutil"
)
func NopCloser(r io.Reader) io.ReadCloser {
return ioutil.NopCloser(r)
}
func ReadAll(r io.Reader) ([]byte, error) {
return ioutil.ReadAll(r)
}
func ReadDir(dirname string) ([]string, error) {
files, err := ioutil.ReadDir(dirname)
if err != nil {
return nil, err
}
var names []string
for _, file := range files {
names = append(names, file.Name())
}
return names, nil
}
func ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
func MkdirTemp(dir, pattern string) (string, error) {
return ioutil.TempDir(dir, pattern)
}
func WriteFile(filename string, data []byte) error {
return ioutil.WriteFile(filename, data, 0644)
}

View File

@ -1,25 +0,0 @@
package oraclematcher
import "github.com/onsi/gomega/types"
/*
GomegaMatchers that also match the OracleMatcher interface can convey information about
whether or not their result will change upon future attempts.
This allows `Eventually` and `Consistently` to short circuit if success becomes impossible.
For example, a process' exit code can never change. So, gexec's Exit matcher returns `true`
for `MatchMayChangeInTheFuture` until the process exits, at which point it returns `false` forevermore.
*/
type OracleMatcher interface {
MatchMayChangeInTheFuture(actual interface{}) bool
}
func MatchMayChangeInTheFuture(matcher types.GomegaMatcher, value interface{}) bool {
oracleMatcher, ok := matcher.(OracleMatcher)
if !ok {
return true
}
return oracleMatcher.MatchMayChangeInTheFuture(value)
}

View File

@ -0,0 +1,106 @@
package internal
import (
"errors"
"fmt"
"time"
)
type PollingSignalErrorType int
const (
PollingSignalErrorTypeStopTrying PollingSignalErrorType = iota
PollingSignalErrorTypeTryAgainAfter
)
type PollingSignalError interface {
error
Wrap(err error) PollingSignalError
Attach(description string, obj any) PollingSignalError
Now()
}
var StopTrying = func(message string) PollingSignalError {
return &PollingSignalErrorImpl{
message: message,
pollingSignalErrorType: PollingSignalErrorTypeStopTrying,
}
}
var TryAgainAfter = func(duration time.Duration) PollingSignalError {
return &PollingSignalErrorImpl{
message: fmt.Sprintf("told to try again after %s", duration),
duration: duration,
pollingSignalErrorType: PollingSignalErrorTypeTryAgainAfter,
}
}
type PollingSignalErrorAttachment struct {
Description string
Object any
}
type PollingSignalErrorImpl struct {
message string
wrappedErr error
pollingSignalErrorType PollingSignalErrorType
duration time.Duration
Attachments []PollingSignalErrorAttachment
}
func (s *PollingSignalErrorImpl) Wrap(err error) PollingSignalError {
s.wrappedErr = err
return s
}
func (s *PollingSignalErrorImpl) Attach(description string, obj any) PollingSignalError {
s.Attachments = append(s.Attachments, PollingSignalErrorAttachment{description, obj})
return s
}
func (s *PollingSignalErrorImpl) Error() string {
if s.wrappedErr == nil {
return s.message
} else {
return s.message + ": " + s.wrappedErr.Error()
}
}
func (s *PollingSignalErrorImpl) Unwrap() error {
if s == nil {
return nil
}
return s.wrappedErr
}
func (s *PollingSignalErrorImpl) Now() {
panic(s)
}
func (s *PollingSignalErrorImpl) IsStopTrying() bool {
return s.pollingSignalErrorType == PollingSignalErrorTypeStopTrying
}
func (s *PollingSignalErrorImpl) IsTryAgainAfter() bool {
return s.pollingSignalErrorType == PollingSignalErrorTypeTryAgainAfter
}
func (s *PollingSignalErrorImpl) TryAgainDuration() time.Duration {
return s.duration
}
func AsPollingSignalError(actual interface{}) (*PollingSignalErrorImpl, bool) {
if actual == nil {
return nil, false
}
if actualErr, ok := actual.(error); ok {
var target *PollingSignalErrorImpl
if errors.As(actualErr, &target) {
return target, true
} else {
return nil, false
}
}
return nil, false
}

View File

@ -1,60 +0,0 @@
package testingtsupport
import (
"regexp"
"runtime/debug"
"strings"
"github.com/onsi/gomega/types"
)
var StackTracePruneRE = regexp.MustCompile(`\/gomega\/|\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`)
type EmptyTWithHelper struct{}
func (e EmptyTWithHelper) Helper() {}
type gomegaTestingT interface {
Fatalf(format string, args ...interface{})
}
func BuildTestingTGomegaFailWrapper(t gomegaTestingT) *types.GomegaFailWrapper {
tWithHelper, hasHelper := t.(types.TWithHelper)
if !hasHelper {
tWithHelper = EmptyTWithHelper{}
}
fail := func(message string, callerSkip ...int) {
if hasHelper {
tWithHelper.Helper()
t.Fatalf("\n%s", message)
} else {
skip := 2
if len(callerSkip) > 0 {
skip += callerSkip[0]
}
stackTrace := pruneStack(string(debug.Stack()), skip)
t.Fatalf("\n%s\n%s\n", stackTrace, message)
}
}
return &types.GomegaFailWrapper{
Fail: fail,
TWithHelper: tWithHelper,
}
}
func pruneStack(fullStackTrace string, skip int) string {
stack := strings.Split(fullStackTrace, "\n")[1:]
if len(stack) > 2*skip {
stack = stack[2*skip:]
}
prunedStack := []string{}
for i := 0; i < len(stack)/2; i++ {
if !StackTracePruneRE.Match([]byte(stack[i*2])) {
prunedStack = append(prunedStack, stack[i*2])
prunedStack = append(prunedStack, stack[i*2+1])
}
}
return strings.Join(prunedStack, "\n")
}

22
vendor/github.com/onsi/gomega/internal/vetoptdesc.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
package internal
import (
"fmt"
"github.com/onsi/gomega/types"
)
// vetOptionalDescription vets the optional description args: if it finds any
// Gomega matcher at the beginning it panics. This allows for rendering Gomega
// matchers as part of an optional Description, as long as they're not in the
// first slot.
func vetOptionalDescription(assertion string, optionalDescription ...interface{}) {
if len(optionalDescription) == 0 {
return
}
if _, isGomegaMatcher := optionalDescription[0].(types.GomegaMatcher); isGomegaMatcher {
panic(fmt.Sprintf("%s has a GomegaMatcher as the first element of optionalDescription.\n\t"+
"Do you mean to use And/Or/SatisfyAll/SatisfyAny to combine multiple matchers?",
assertion))
}
}

View File

@ -3,139 +3,161 @@ package gomega
import ( import (
"time" "time"
"github.com/google/go-cmp/cmp"
"github.com/onsi/gomega/matchers" "github.com/onsi/gomega/matchers"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
//Equal uses reflect.DeepEqual to compare actual with expected. Equal is strict about // Equal uses reflect.DeepEqual to compare actual with expected. Equal is strict about
//types when performing comparisons. // types when performing comparisons.
//It is an error for both actual and expected to be nil. Use BeNil() instead. // It is an error for both actual and expected to be nil. Use BeNil() instead.
func Equal(expected interface{}) types.GomegaMatcher { func Equal(expected interface{}) types.GomegaMatcher {
return &matchers.EqualMatcher{ return &matchers.EqualMatcher{
Expected: expected, Expected: expected,
} }
} }
//BeEquivalentTo is more lax than Equal, allowing equality between different types. // BeEquivalentTo is more lax than Equal, allowing equality between different types.
//This is done by converting actual to have the type of expected before // This is done by converting actual to have the type of expected before
//attempting equality with reflect.DeepEqual. // attempting equality with reflect.DeepEqual.
//It is an error for actual and expected to be nil. Use BeNil() instead. // It is an error for actual and expected to be nil. Use BeNil() instead.
func BeEquivalentTo(expected interface{}) types.GomegaMatcher { func BeEquivalentTo(expected interface{}) types.GomegaMatcher {
return &matchers.BeEquivalentToMatcher{ return &matchers.BeEquivalentToMatcher{
Expected: expected, Expected: expected,
} }
} }
//BeIdenticalTo uses the == operator to compare actual with expected. // BeComparableTo uses gocmp.Equal from github.com/google/go-cmp (instead of reflect.DeepEqual) to perform a deep comparison.
//BeIdenticalTo is strict about types when performing comparisons. // You can pass cmp.Option as options.
//It is an error for both actual and expected to be nil. Use BeNil() instead. // It is an error for actual and expected to be nil. Use BeNil() instead.
func BeComparableTo(expected interface{}, opts ...cmp.Option) types.GomegaMatcher {
return &matchers.BeComparableToMatcher{
Expected: expected,
Options: opts,
}
}
// BeIdenticalTo uses the == operator to compare actual with expected.
// BeIdenticalTo is strict about types when performing comparisons.
// It is an error for both actual and expected to be nil. Use BeNil() instead.
func BeIdenticalTo(expected interface{}) types.GomegaMatcher { func BeIdenticalTo(expected interface{}) types.GomegaMatcher {
return &matchers.BeIdenticalToMatcher{ return &matchers.BeIdenticalToMatcher{
Expected: expected, Expected: expected,
} }
} }
//BeNil succeeds if actual is nil // BeNil succeeds if actual is nil
func BeNil() types.GomegaMatcher { func BeNil() types.GomegaMatcher {
return &matchers.BeNilMatcher{} return &matchers.BeNilMatcher{}
} }
//BeTrue succeeds if actual is true // BeTrue succeeds if actual is true
func BeTrue() types.GomegaMatcher { func BeTrue() types.GomegaMatcher {
return &matchers.BeTrueMatcher{} return &matchers.BeTrueMatcher{}
} }
//BeFalse succeeds if actual is false // BeFalse succeeds if actual is false
func BeFalse() types.GomegaMatcher { func BeFalse() types.GomegaMatcher {
return &matchers.BeFalseMatcher{} return &matchers.BeFalseMatcher{}
} }
//HaveOccurred succeeds if actual is a non-nil error // HaveOccurred succeeds if actual is a non-nil error
//The typical Go error checking pattern looks like: // The typical Go error checking pattern looks like:
// err := SomethingThatMightFail() //
// Expect(err).ShouldNot(HaveOccurred()) // err := SomethingThatMightFail()
// Expect(err).ShouldNot(HaveOccurred())
func HaveOccurred() types.GomegaMatcher { func HaveOccurred() types.GomegaMatcher {
return &matchers.HaveOccurredMatcher{} return &matchers.HaveOccurredMatcher{}
} }
//Succeed passes if actual is a nil error // Succeed passes if actual is a nil error
//Succeed is intended to be used with functions that return a single error value. Instead of // Succeed is intended to be used with functions that return a single error value. Instead of
// err := SomethingThatMightFail()
// Expect(err).ShouldNot(HaveOccurred())
// //
//You can write: // err := SomethingThatMightFail()
// Expect(SomethingThatMightFail()).Should(Succeed()) // Expect(err).ShouldNot(HaveOccurred())
// //
//It is a mistake to use Succeed with a function that has multiple return values. Gomega's Ω and Expect // You can write:
//functions automatically trigger failure if any return values after the first return value are non-zero/non-nil. //
//This means that Ω(MultiReturnFunc()).ShouldNot(Succeed()) can never pass. // Expect(SomethingThatMightFail()).Should(Succeed())
//
// It is a mistake to use Succeed with a function that has multiple return values. Gomega's Ω and Expect
// functions automatically trigger failure if any return values after the first return value are non-zero/non-nil.
// This means that Ω(MultiReturnFunc()).ShouldNot(Succeed()) can never pass.
func Succeed() types.GomegaMatcher { func Succeed() types.GomegaMatcher {
return &matchers.SucceedMatcher{} return &matchers.SucceedMatcher{}
} }
//MatchError succeeds if actual is a non-nil error that matches the passed in string/error. // MatchError succeeds if actual is a non-nil error that matches the passed in
// string, error, or matcher.
//
// These are valid use-cases:
// //
//These are valid use-cases:
// Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error" // Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error"
// Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual) // Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual)
// Expect(err).Should(MatchError(ContainsSubstring("sprocket not found"))) // asserts that edrr.Error() contains substring "sprocket not found"
// //
//It is an error for err to be nil or an object that does not implement the Error interface // It is an error for err to be nil or an object that does not implement the
// Error interface
func MatchError(expected interface{}) types.GomegaMatcher { func MatchError(expected interface{}) types.GomegaMatcher {
return &matchers.MatchErrorMatcher{ return &matchers.MatchErrorMatcher{
Expected: expected, Expected: expected,
} }
} }
//BeClosed succeeds if actual is a closed channel. // BeClosed succeeds if actual is a closed channel.
//It is an error to pass a non-channel to BeClosed, it is also an error to pass nil // It is an error to pass a non-channel to BeClosed, it is also an error to pass nil
// //
//In order to check whether or not the channel is closed, Gomega must try to read from the channel // In order to check whether or not the channel is closed, Gomega must try to read from the channel
//(even in the `ShouldNot(BeClosed())` case). You should keep this in mind if you wish to make subsequent assertions about // (even in the `ShouldNot(BeClosed())` case). You should keep this in mind if you wish to make subsequent assertions about
//values coming down the channel. // values coming down the channel.
// //
//Also, if you are testing that a *buffered* channel is closed you must first read all values out of the channel before // Also, if you are testing that a *buffered* channel is closed you must first read all values out of the channel before
//asserting that it is closed (it is not possible to detect that a buffered-channel has been closed until all its buffered values are read). // asserting that it is closed (it is not possible to detect that a buffered-channel has been closed until all its buffered values are read).
// //
//Finally, as a corollary: it is an error to check whether or not a send-only channel is closed. // Finally, as a corollary: it is an error to check whether or not a send-only channel is closed.
func BeClosed() types.GomegaMatcher { func BeClosed() types.GomegaMatcher {
return &matchers.BeClosedMatcher{} return &matchers.BeClosedMatcher{}
} }
//Receive succeeds if there is a value to be received on actual. // Receive succeeds if there is a value to be received on actual.
//Actual must be a channel (and cannot be a send-only channel) -- anything else is an error. // Actual must be a channel (and cannot be a send-only channel) -- anything else is an error.
// //
//Receive returns immediately and never blocks: // Receive returns immediately and never blocks:
// //
//- If there is nothing on the channel `c` then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass. // - If there is nothing on the channel `c` then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass.
// //
//- If the channel `c` is closed then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass. // - If the channel `c` is closed then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass.
// //
//- If there is something on the channel `c` ready to be read, then Expect(c).Should(Receive()) will pass and Ω(c).ShouldNot(Receive()) will fail. // - If there is something on the channel `c` ready to be read, then Expect(c).Should(Receive()) will pass and Ω(c).ShouldNot(Receive()) will fail.
// //
//If you have a go-routine running in the background that will write to channel `c` you can: // If you have a go-routine running in the background that will write to channel `c` you can:
// Eventually(c).Should(Receive())
// //
//This will timeout if nothing gets sent to `c` (you can modify the timeout interval as you normally do with `Eventually`) // Eventually(c).Should(Receive())
// //
//A similar use-case is to assert that no go-routine writes to a channel (for a period of time). You can do this with `Consistently`: // This will timeout if nothing gets sent to `c` (you can modify the timeout interval as you normally do with `Eventually`)
// Consistently(c).ShouldNot(Receive())
// //
//You can pass `Receive` a matcher. If you do so, it will match the received object against the matcher. For example: // A similar use-case is to assert that no go-routine writes to a channel (for a period of time). You can do this with `Consistently`:
// Expect(c).Should(Receive(Equal("foo")))
// //
//When given a matcher, `Receive` will always fail if there is nothing to be received on the channel. // Consistently(c).ShouldNot(Receive())
// //
//Passing Receive a matcher is especially useful when paired with Eventually: // You can pass `Receive` a matcher. If you do so, it will match the received object against the matcher. For example:
// //
// Eventually(c).Should(Receive(ContainSubstring("bar"))) // Expect(c).Should(Receive(Equal("foo")))
// //
//will repeatedly attempt to pull values out of `c` until a value matching "bar" is received. // When given a matcher, `Receive` will always fail if there is nothing to be received on the channel.
// //
//Finally, if you want to have a reference to the value *sent* to the channel you can pass the `Receive` matcher a pointer to a variable of the appropriate type: // Passing Receive a matcher is especially useful when paired with Eventually:
// var myThing thing //
// Eventually(thingChan).Should(Receive(&myThing)) // Eventually(c).Should(Receive(ContainSubstring("bar")))
// Expect(myThing.Sprocket).Should(Equal("foo")) //
// Expect(myThing.IsValid()).Should(BeTrue()) // will repeatedly attempt to pull values out of `c` until a value matching "bar" is received.
//
// Finally, if you want to have a reference to the value *sent* to the channel you can pass the `Receive` matcher a pointer to a variable of the appropriate type:
//
// var myThing thing
// Eventually(thingChan).Should(Receive(&myThing))
// Expect(myThing.Sprocket).Should(Equal("foo"))
// Expect(myThing.IsValid()).Should(BeTrue())
func Receive(args ...interface{}) types.GomegaMatcher { func Receive(args ...interface{}) types.GomegaMatcher {
var arg interface{} var arg interface{}
if len(args) > 0 { if len(args) > 0 {
@ -147,27 +169,27 @@ func Receive(args ...interface{}) types.GomegaMatcher {
} }
} }
//BeSent succeeds if a value can be sent to actual. // BeSent succeeds if a value can be sent to actual.
//Actual must be a channel (and cannot be a receive-only channel) that can sent the type of the value passed into BeSent -- anything else is an error. // Actual must be a channel (and cannot be a receive-only channel) that can sent the type of the value passed into BeSent -- anything else is an error.
//In addition, actual must not be closed. // In addition, actual must not be closed.
// //
//BeSent never blocks: // BeSent never blocks:
// //
//- If the channel `c` is not ready to receive then Expect(c).Should(BeSent("foo")) will fail immediately // - If the channel `c` is not ready to receive then Expect(c).Should(BeSent("foo")) will fail immediately
//- If the channel `c` is eventually ready to receive then Eventually(c).Should(BeSent("foo")) will succeed.. presuming the channel becomes ready to receive before Eventually's timeout // - If the channel `c` is eventually ready to receive then Eventually(c).Should(BeSent("foo")) will succeed.. presuming the channel becomes ready to receive before Eventually's timeout
//- If the channel `c` is closed then Expect(c).Should(BeSent("foo")) and Ω(c).ShouldNot(BeSent("foo")) will both fail immediately // - If the channel `c` is closed then Expect(c).Should(BeSent("foo")) and Ω(c).ShouldNot(BeSent("foo")) will both fail immediately
// //
//Of course, the value is actually sent to the channel. The point of `BeSent` is less to make an assertion about the availability of the channel (which is typically an implementation detail that your test should not be concerned with). // Of course, the value is actually sent to the channel. The point of `BeSent` is less to make an assertion about the availability of the channel (which is typically an implementation detail that your test should not be concerned with).
//Rather, the point of `BeSent` is to make it possible to easily and expressively write tests that can timeout on blocked channel sends. // Rather, the point of `BeSent` is to make it possible to easily and expressively write tests that can timeout on blocked channel sends.
func BeSent(arg interface{}) types.GomegaMatcher { func BeSent(arg interface{}) types.GomegaMatcher {
return &matchers.BeSentMatcher{ return &matchers.BeSentMatcher{
Arg: arg, Arg: arg,
} }
} }
//MatchRegexp succeeds if actual is a string or stringer that matches the // MatchRegexp succeeds if actual is a string or stringer that matches the
//passed-in regexp. Optional arguments can be provided to construct a regexp // passed-in regexp. Optional arguments can be provided to construct a regexp
//via fmt.Sprintf(). // via fmt.Sprintf().
func MatchRegexp(regexp string, args ...interface{}) types.GomegaMatcher { func MatchRegexp(regexp string, args ...interface{}) types.GomegaMatcher {
return &matchers.MatchRegexpMatcher{ return &matchers.MatchRegexpMatcher{
Regexp: regexp, Regexp: regexp,
@ -175,9 +197,9 @@ func MatchRegexp(regexp string, args ...interface{}) types.GomegaMatcher {
} }
} }
//ContainSubstring succeeds if actual is a string or stringer that contains the // ContainSubstring succeeds if actual is a string or stringer that contains the
//passed-in substring. Optional arguments can be provided to construct the substring // passed-in substring. Optional arguments can be provided to construct the substring
//via fmt.Sprintf(). // via fmt.Sprintf().
func ContainSubstring(substr string, args ...interface{}) types.GomegaMatcher { func ContainSubstring(substr string, args ...interface{}) types.GomegaMatcher {
return &matchers.ContainSubstringMatcher{ return &matchers.ContainSubstringMatcher{
Substr: substr, Substr: substr,
@ -185,9 +207,9 @@ func ContainSubstring(substr string, args ...interface{}) types.GomegaMatcher {
} }
} }
//HavePrefix succeeds if actual is a string or stringer that contains the // HavePrefix succeeds if actual is a string or stringer that contains the
//passed-in string as a prefix. Optional arguments can be provided to construct // passed-in string as a prefix. Optional arguments can be provided to construct
//via fmt.Sprintf(). // via fmt.Sprintf().
func HavePrefix(prefix string, args ...interface{}) types.GomegaMatcher { func HavePrefix(prefix string, args ...interface{}) types.GomegaMatcher {
return &matchers.HavePrefixMatcher{ return &matchers.HavePrefixMatcher{
Prefix: prefix, Prefix: prefix,
@ -195,9 +217,9 @@ func HavePrefix(prefix string, args ...interface{}) types.GomegaMatcher {
} }
} }
//HaveSuffix succeeds if actual is a string or stringer that contains the // HaveSuffix succeeds if actual is a string or stringer that contains the
//passed-in string as a suffix. Optional arguments can be provided to construct // passed-in string as a suffix. Optional arguments can be provided to construct
//via fmt.Sprintf(). // via fmt.Sprintf().
func HaveSuffix(suffix string, args ...interface{}) types.GomegaMatcher { func HaveSuffix(suffix string, args ...interface{}) types.GomegaMatcher {
return &matchers.HaveSuffixMatcher{ return &matchers.HaveSuffixMatcher{
Suffix: suffix, Suffix: suffix,
@ -205,136 +227,191 @@ func HaveSuffix(suffix string, args ...interface{}) types.GomegaMatcher {
} }
} }
//MatchJSON succeeds if actual is a string or stringer of JSON that matches // MatchJSON succeeds if actual is a string or stringer of JSON that matches
//the expected JSON. The JSONs are decoded and the resulting objects are compared via // the expected JSON. The JSONs are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter. // reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
func MatchJSON(json interface{}) types.GomegaMatcher { func MatchJSON(json interface{}) types.GomegaMatcher {
return &matchers.MatchJSONMatcher{ return &matchers.MatchJSONMatcher{
JSONToMatch: json, JSONToMatch: json,
} }
} }
//MatchXML succeeds if actual is a string or stringer of XML that matches // MatchXML succeeds if actual is a string or stringer of XML that matches
//the expected XML. The XMLs are decoded and the resulting objects are compared via // the expected XML. The XMLs are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like whitespaces shouldn't matter. // reflect.DeepEqual so things like whitespaces shouldn't matter.
func MatchXML(xml interface{}) types.GomegaMatcher { func MatchXML(xml interface{}) types.GomegaMatcher {
return &matchers.MatchXMLMatcher{ return &matchers.MatchXMLMatcher{
XMLToMatch: xml, XMLToMatch: xml,
} }
} }
//MatchYAML succeeds if actual is a string or stringer of YAML that matches // MatchYAML succeeds if actual is a string or stringer of YAML that matches
//the expected YAML. The YAML's are decoded and the resulting objects are compared via // the expected YAML. The YAML's are decoded and the resulting objects are compared via
//reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter. // reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
func MatchYAML(yaml interface{}) types.GomegaMatcher { func MatchYAML(yaml interface{}) types.GomegaMatcher {
return &matchers.MatchYAMLMatcher{ return &matchers.MatchYAMLMatcher{
YAMLToMatch: yaml, YAMLToMatch: yaml,
} }
} }
//BeEmpty succeeds if actual is empty. Actual must be of type string, array, map, chan, or slice. // BeEmpty succeeds if actual is empty. Actual must be of type string, array, map, chan, or slice.
func BeEmpty() types.GomegaMatcher { func BeEmpty() types.GomegaMatcher {
return &matchers.BeEmptyMatcher{} return &matchers.BeEmptyMatcher{}
} }
//HaveLen succeeds if actual has the passed-in length. Actual must be of type string, array, map, chan, or slice. // HaveLen succeeds if actual has the passed-in length. Actual must be of type string, array, map, chan, or slice.
func HaveLen(count int) types.GomegaMatcher { func HaveLen(count int) types.GomegaMatcher {
return &matchers.HaveLenMatcher{ return &matchers.HaveLenMatcher{
Count: count, Count: count,
} }
} }
//HaveCap succeeds if actual has the passed-in capacity. Actual must be of type array, chan, or slice. // HaveCap succeeds if actual has the passed-in capacity. Actual must be of type array, chan, or slice.
func HaveCap(count int) types.GomegaMatcher { func HaveCap(count int) types.GomegaMatcher {
return &matchers.HaveCapMatcher{ return &matchers.HaveCapMatcher{
Count: count, Count: count,
} }
} }
//BeZero succeeds if actual is the zero value for its type or if actual is nil. // BeZero succeeds if actual is the zero value for its type or if actual is nil.
func BeZero() types.GomegaMatcher { func BeZero() types.GomegaMatcher {
return &matchers.BeZeroMatcher{} return &matchers.BeZeroMatcher{}
} }
//ContainElement succeeds if actual contains the passed in element. // ContainElement succeeds if actual contains the passed in element. By default
//By default ContainElement() uses Equal() to perform the match, however a // ContainElement() uses Equal() to perform the match, however a matcher can be
//matcher can be passed in instead: // passed in instead:
// Expect([]string{"Foo", "FooBar"}).Should(ContainElement(ContainSubstring("Bar")))
// //
//Actual must be an array, slice or map. // Expect([]string{"Foo", "FooBar"}).Should(ContainElement(ContainSubstring("Bar")))
//For maps, ContainElement searches through the map's values. //
func ContainElement(element interface{}) types.GomegaMatcher { // Actual must be an array, slice or map. For maps, ContainElement searches
// through the map's values.
//
// If you want to have a copy of the matching element(s) found you can pass a
// pointer to a variable of the appropriate type. If the variable isn't a slice
// or map, then exactly one match will be expected and returned. If the variable
// is a slice or map, then at least one match is expected and all matches will be
// stored in the variable.
//
// var findings []string
// Expect([]string{"Foo", "FooBar"}).Should(ContainElement(ContainSubString("Bar", &findings)))
func ContainElement(element interface{}, result ...interface{}) types.GomegaMatcher {
return &matchers.ContainElementMatcher{ return &matchers.ContainElementMatcher{
Element: element, Element: element,
Result: result,
} }
} }
//BeElementOf succeeds if actual is contained in the passed in elements. // BeElementOf succeeds if actual is contained in the passed in elements.
//BeElementOf() always uses Equal() to perform the match. // BeElementOf() always uses Equal() to perform the match.
//When the passed in elements are comprised of a single element that is either an Array or Slice, BeElementOf() behaves // When the passed in elements are comprised of a single element that is either an Array or Slice, BeElementOf() behaves
//as the reverse of ContainElement() that operates with Equal() to perform the match. // as the reverse of ContainElement() that operates with Equal() to perform the match.
// Expect(2).Should(BeElementOf([]int{1, 2}))
// Expect(2).Should(BeElementOf([2]int{1, 2}))
//Otherwise, BeElementOf() provides a syntactic sugar for Or(Equal(_), Equal(_), ...):
// Expect(2).Should(BeElementOf(1, 2))
// //
//Actual must be typed. // Expect(2).Should(BeElementOf([]int{1, 2}))
// Expect(2).Should(BeElementOf([2]int{1, 2}))
//
// Otherwise, BeElementOf() provides a syntactic sugar for Or(Equal(_), Equal(_), ...):
//
// Expect(2).Should(BeElementOf(1, 2))
//
// Actual must be typed.
func BeElementOf(elements ...interface{}) types.GomegaMatcher { func BeElementOf(elements ...interface{}) types.GomegaMatcher {
return &matchers.BeElementOfMatcher{ return &matchers.BeElementOfMatcher{
Elements: elements, Elements: elements,
} }
} }
//ConsistOf succeeds if actual contains precisely the elements passed into the matcher. The ordering of the elements does not matter. // BeKeyOf succeeds if actual is contained in the keys of the passed in map.
//By default ConsistOf() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples: // BeKeyOf() always uses Equal() to perform the match between actual and the map keys.
// //
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf("FooBar", "Foo")) // Expect("foo").Should(BeKeyOf(map[string]bool{"foo": true, "bar": false}))
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Bar"), "Foo")) func BeKeyOf(element interface{}) types.GomegaMatcher {
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Foo"), ContainSubstring("Foo"))) return &matchers.BeKeyOfMatcher{
Map: element,
}
}
// ConsistOf succeeds if actual contains precisely the elements passed into the matcher. The ordering of the elements does not matter.
// By default ConsistOf() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
// //
//Actual must be an array, slice or map. For maps, ConsistOf matches against the map's values. // Expect([]string{"Foo", "FooBar"}).Should(ConsistOf("FooBar", "Foo"))
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Bar"), "Foo"))
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Foo"), ContainSubstring("Foo")))
// //
//You typically pass variadic arguments to ConsistOf (as in the examples above). However, if you need to pass in a slice you can provided that it // Actual must be an array, slice or map. For maps, ConsistOf matches against the map's values.
//is the only element passed in to ConsistOf:
// //
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf([]string{"FooBar", "Foo"})) // You typically pass variadic arguments to ConsistOf (as in the examples above). However, if you need to pass in a slice you can provided that it
// is the only element passed in to ConsistOf:
// //
//Note that Go's type system does not allow you to write this as ConsistOf([]string{"FooBar", "Foo"}...) as []string and []interface{} are different types - hence the need for this special rule. // Expect([]string{"Foo", "FooBar"}).Should(ConsistOf([]string{"FooBar", "Foo"}))
//
// Note that Go's type system does not allow you to write this as ConsistOf([]string{"FooBar", "Foo"}...) as []string and []interface{} are different types - hence the need for this special rule.
func ConsistOf(elements ...interface{}) types.GomegaMatcher { func ConsistOf(elements ...interface{}) types.GomegaMatcher {
return &matchers.ConsistOfMatcher{ return &matchers.ConsistOfMatcher{
Elements: elements, Elements: elements,
} }
} }
//ContainElements succeeds if actual contains the passed in elements. The ordering of the elements does not matter. // HaveExactElemets succeeds if actual contains elements that precisely match the elemets passed into the matcher. The ordering of the elements does matter.
//By default ContainElements() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples: // By default HaveExactElements() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
// //
// Expect([]string{"Foo", "FooBar"}).Should(ContainElements("FooBar")) // Expect([]string{"Foo", "FooBar"}).Should(HaveExactElements("Foo", "FooBar"))
// Expect([]string{"Foo", "FooBar"}).Should(ContainElements(ContainSubstring("Bar"), "Foo")) // Expect([]string{"Foo", "FooBar"}).Should(HaveExactElements("Foo", ContainSubstring("Bar")))
// Expect([]string{"Foo", "FooBar"}).Should(HaveExactElements(ContainSubstring("Foo"), ContainSubstring("Foo")))
// //
//Actual must be an array, slice or map. // Actual must be an array or slice.
//For maps, ContainElements searches through the map's values. func HaveExactElements(elements ...interface{}) types.GomegaMatcher {
return &matchers.HaveExactElementsMatcher{
Elements: elements,
}
}
// ContainElements succeeds if actual contains the passed in elements. The ordering of the elements does not matter.
// By default ContainElements() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
//
// Expect([]string{"Foo", "FooBar"}).Should(ContainElements("FooBar"))
// Expect([]string{"Foo", "FooBar"}).Should(ContainElements(ContainSubstring("Bar"), "Foo"))
//
// Actual must be an array, slice or map.
// For maps, ContainElements searches through the map's values.
func ContainElements(elements ...interface{}) types.GomegaMatcher { func ContainElements(elements ...interface{}) types.GomegaMatcher {
return &matchers.ContainElementsMatcher{ return &matchers.ContainElementsMatcher{
Elements: elements, Elements: elements,
} }
} }
//HaveKey succeeds if actual is a map with the passed in key. // HaveEach succeeds if actual solely contains elements that match the passed in element.
//By default HaveKey uses Equal() to perform the match, however a // Please note that if actual is empty, HaveEach always will succeed.
//matcher can be passed in instead: // By default HaveEach() uses Equal() to perform the match, however a
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKey(MatchRegexp(`.+Foo$`))) // matcher can be passed in instead:
//
// Expect([]string{"Foo", "FooBar"}).Should(HaveEach(ContainSubstring("Foo")))
//
// Actual must be an array, slice or map.
// For maps, HaveEach searches through the map's values.
func HaveEach(element interface{}) types.GomegaMatcher {
return &matchers.HaveEachMatcher{
Element: element,
}
}
// HaveKey succeeds if actual is a map with the passed in key.
// By default HaveKey uses Equal() to perform the match, however a
// matcher can be passed in instead:
//
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKey(MatchRegexp(`.+Foo$`)))
func HaveKey(key interface{}) types.GomegaMatcher { func HaveKey(key interface{}) types.GomegaMatcher {
return &matchers.HaveKeyMatcher{ return &matchers.HaveKeyMatcher{
Key: key, Key: key,
} }
} }
//HaveKeyWithValue succeeds if actual is a map with the passed in key and value. // HaveKeyWithValue succeeds if actual is a map with the passed in key and value.
//By default HaveKeyWithValue uses Equal() to perform the match, however a // By default HaveKeyWithValue uses Equal() to perform the match, however a
//matcher can be passed in instead: // matcher can be passed in instead:
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue("Foo", "Bar")) //
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue(MatchRegexp(`.+Foo$`), "Bar")) // Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue("Foo", "Bar"))
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue(MatchRegexp(`.+Foo$`), "Bar"))
func HaveKeyWithValue(key interface{}, value interface{}) types.GomegaMatcher { func HaveKeyWithValue(key interface{}, value interface{}) types.GomegaMatcher {
return &matchers.HaveKeyWithValueMatcher{ return &matchers.HaveKeyWithValueMatcher{
Key: key, Key: key,
@ -342,17 +419,79 @@ func HaveKeyWithValue(key interface{}, value interface{}) types.GomegaMatcher {
} }
} }
//BeNumerically performs numerical assertions in a type-agnostic way. // HaveField succeeds if actual is a struct and the value at the passed in field
//Actual and expected should be numbers, though the specific type of // matches the passed in matcher. By default HaveField used Equal() to perform the match,
//number is irrelevant (float32, float64, uint8, etc...). // however a matcher can be passed in in stead.
// //
//There are six, self-explanatory, supported comparators: // The field must be a string that resolves to the name of a field in the struct. Structs can be traversed
// Expect(1.0).Should(BeNumerically("==", 1)) // using the '.' delimiter. If the field ends with '()' a method named field is assumed to exist on the struct and is invoked.
// Expect(1.0).Should(BeNumerically("~", 0.999, 0.01)) // Such methods must take no arguments and return a single value:
// Expect(1.0).Should(BeNumerically(">", 0.9)) //
// Expect(1.0).Should(BeNumerically(">=", 1.0)) // type Book struct {
// Expect(1.0).Should(BeNumerically("<", 3)) // Title string
// Expect(1.0).Should(BeNumerically("<=", 1.0)) // Author Person
// }
// type Person struct {
// FirstName string
// LastName string
// DOB time.Time
// }
// Expect(book).To(HaveField("Title", "Les Miserables"))
// Expect(book).To(HaveField("Title", ContainSubstring("Les"))
// Expect(book).To(HaveField("Author.FirstName", Equal("Victor"))
// Expect(book).To(HaveField("Author.DOB.Year()", BeNumerically("<", 1900))
func HaveField(field string, expected interface{}) types.GomegaMatcher {
return &matchers.HaveFieldMatcher{
Field: field,
Expected: expected,
}
}
// HaveExistingField succeeds if actual is a struct and the specified field
// exists.
//
// HaveExistingField can be combined with HaveField in order to cover use cases
// with optional fields. HaveField alone would trigger an error in such situations.
//
// Expect(MrHarmless).NotTo(And(HaveExistingField("Title"), HaveField("Title", "Supervillain")))
func HaveExistingField(field string) types.GomegaMatcher {
return &matchers.HaveExistingFieldMatcher{
Field: field,
}
}
// HaveValue applies the given matcher to the value of actual, optionally and
// repeatedly dereferencing pointers or taking the concrete value of interfaces.
// Thus, the matcher will always be applied to non-pointer and non-interface
// values only. HaveValue will fail with an error if a pointer or interface is
// nil. It will also fail for more than 31 pointer or interface dereferences to
// guard against mistakenly applying it to arbitrarily deep linked pointers.
//
// HaveValue differs from gstruct.PointTo in that it does not expect actual to
// be a pointer (as gstruct.PointTo does) but instead also accepts non-pointer
// and even interface values.
//
// actual := 42
// Expect(actual).To(HaveValue(42))
// Expect(&actual).To(HaveValue(42))
func HaveValue(matcher types.GomegaMatcher) types.GomegaMatcher {
return &matchers.HaveValueMatcher{
Matcher: matcher,
}
}
// BeNumerically performs numerical assertions in a type-agnostic way.
// Actual and expected should be numbers, though the specific type of
// number is irrelevant (float32, float64, uint8, etc...).
//
// There are six, self-explanatory, supported comparators:
//
// Expect(1.0).Should(BeNumerically("==", 1))
// Expect(1.0).Should(BeNumerically("~", 0.999, 0.01))
// Expect(1.0).Should(BeNumerically(">", 0.9))
// Expect(1.0).Should(BeNumerically(">=", 1.0))
// Expect(1.0).Should(BeNumerically("<", 3))
// Expect(1.0).Should(BeNumerically("<=", 1.0))
func BeNumerically(comparator string, compareTo ...interface{}) types.GomegaMatcher { func BeNumerically(comparator string, compareTo ...interface{}) types.GomegaMatcher {
return &matchers.BeNumericallyMatcher{ return &matchers.BeNumericallyMatcher{
Comparator: comparator, Comparator: comparator,
@ -360,10 +499,11 @@ func BeNumerically(comparator string, compareTo ...interface{}) types.GomegaMatc
} }
} }
//BeTemporally compares time.Time's like BeNumerically // BeTemporally compares time.Time's like BeNumerically
//Actual and expected must be time.Time. The comparators are the same as for BeNumerically // Actual and expected must be time.Time. The comparators are the same as for BeNumerically
// Expect(time.Now()).Should(BeTemporally(">", time.Time{})) //
// Expect(time.Now()).Should(BeTemporally("~", time.Now(), time.Second)) // Expect(time.Now()).Should(BeTemporally(">", time.Time{}))
// Expect(time.Now()).Should(BeTemporally("~", time.Now(), time.Second))
func BeTemporally(comparator string, compareTo time.Time, threshold ...time.Duration) types.GomegaMatcher { func BeTemporally(comparator string, compareTo time.Time, threshold ...time.Duration) types.GomegaMatcher {
return &matchers.BeTemporallyMatcher{ return &matchers.BeTemporallyMatcher{
Comparator: comparator, Comparator: comparator,
@ -372,105 +512,147 @@ func BeTemporally(comparator string, compareTo time.Time, threshold ...time.Dura
} }
} }
//BeAssignableToTypeOf succeeds if actual is assignable to the type of expected. // BeAssignableToTypeOf succeeds if actual is assignable to the type of expected.
//It will return an error when one of the values is nil. // It will return an error when one of the values is nil.
// Expect(0).Should(BeAssignableToTypeOf(0)) // Same values //
// Expect(5).Should(BeAssignableToTypeOf(-1)) // different values same type // Expect(0).Should(BeAssignableToTypeOf(0)) // Same values
// Expect("foo").Should(BeAssignableToTypeOf("bar")) // different values same type // Expect(5).Should(BeAssignableToTypeOf(-1)) // different values same type
// Expect(struct{ Foo string }{}).Should(BeAssignableToTypeOf(struct{ Foo string }{})) // Expect("foo").Should(BeAssignableToTypeOf("bar")) // different values same type
// Expect(struct{ Foo string }{}).Should(BeAssignableToTypeOf(struct{ Foo string }{}))
func BeAssignableToTypeOf(expected interface{}) types.GomegaMatcher { func BeAssignableToTypeOf(expected interface{}) types.GomegaMatcher {
return &matchers.AssignableToTypeOfMatcher{ return &matchers.AssignableToTypeOfMatcher{
Expected: expected, Expected: expected,
} }
} }
//Panic succeeds if actual is a function that, when invoked, panics. // Panic succeeds if actual is a function that, when invoked, panics.
//Actual must be a function that takes no arguments and returns no results. // Actual must be a function that takes no arguments and returns no results.
func Panic() types.GomegaMatcher { func Panic() types.GomegaMatcher {
return &matchers.PanicMatcher{} return &matchers.PanicMatcher{}
} }
//PanicWith succeeds if actual is a function that, when invoked, panics with a specific value. // PanicWith succeeds if actual is a function that, when invoked, panics with a specific value.
//Actual must be a function that takes no arguments and returns no results. // Actual must be a function that takes no arguments and returns no results.
// //
//By default PanicWith uses Equal() to perform the match, however a // By default PanicWith uses Equal() to perform the match, however a
//matcher can be passed in instead: // matcher can be passed in instead:
// Expect(fn).Should(PanicWith(MatchRegexp(`.+Foo$`))) //
// Expect(fn).Should(PanicWith(MatchRegexp(`.+Foo$`)))
func PanicWith(expected interface{}) types.GomegaMatcher { func PanicWith(expected interface{}) types.GomegaMatcher {
return &matchers.PanicMatcher{Expected: expected} return &matchers.PanicMatcher{Expected: expected}
} }
//BeAnExistingFile succeeds if a file exists. // BeAnExistingFile succeeds if a file exists.
//Actual must be a string representing the abs path to the file being checked. // Actual must be a string representing the abs path to the file being checked.
func BeAnExistingFile() types.GomegaMatcher { func BeAnExistingFile() types.GomegaMatcher {
return &matchers.BeAnExistingFileMatcher{} return &matchers.BeAnExistingFileMatcher{}
} }
//BeARegularFile succeeds if a file exists and is a regular file. // BeARegularFile succeeds if a file exists and is a regular file.
//Actual must be a string representing the abs path to the file being checked. // Actual must be a string representing the abs path to the file being checked.
func BeARegularFile() types.GomegaMatcher { func BeARegularFile() types.GomegaMatcher {
return &matchers.BeARegularFileMatcher{} return &matchers.BeARegularFileMatcher{}
} }
//BeADirectory succeeds if a file exists and is a directory. // BeADirectory succeeds if a file exists and is a directory.
//Actual must be a string representing the abs path to the file being checked. // Actual must be a string representing the abs path to the file being checked.
func BeADirectory() types.GomegaMatcher { func BeADirectory() types.GomegaMatcher {
return &matchers.BeADirectoryMatcher{} return &matchers.BeADirectoryMatcher{}
} }
//HaveHTTPStatus succeeds if the Status or StatusCode field of an HTTP response matches. // HaveHTTPStatus succeeds if the Status or StatusCode field of an HTTP response matches.
//Actual must be either a *http.Response or *httptest.ResponseRecorder. // Actual must be either a *http.Response or *httptest.ResponseRecorder.
//Expected must be either an int or a string. // Expected must be either an int or a string.
// Expect(resp).Should(HaveHTTPStatus(http.StatusOK)) // asserts that resp.StatusCode == 200 //
// Expect(resp).Should(HaveHTTPStatus("404 Not Found")) // asserts that resp.Status == "404 Not Found" // Expect(resp).Should(HaveHTTPStatus(http.StatusOK)) // asserts that resp.StatusCode == 200
func HaveHTTPStatus(expected interface{}) types.GomegaMatcher { // Expect(resp).Should(HaveHTTPStatus("404 Not Found")) // asserts that resp.Status == "404 Not Found"
// Expect(resp).Should(HaveHTTPStatus(http.StatusOK, http.StatusNoContent)) // asserts that resp.StatusCode == 200 || resp.StatusCode == 204
func HaveHTTPStatus(expected ...interface{}) types.GomegaMatcher {
return &matchers.HaveHTTPStatusMatcher{Expected: expected} return &matchers.HaveHTTPStatusMatcher{Expected: expected}
} }
//And succeeds only if all of the given matchers succeed. // HaveHTTPHeaderWithValue succeeds if the header is found and the value matches.
//The matchers are tried in order, and will fail-fast if one doesn't succeed. // Actual must be either a *http.Response or *httptest.ResponseRecorder.
// Expect("hi").To(And(HaveLen(2), Equal("hi")) // Expected must be a string header name, followed by a header value which
// can be a string, or another matcher.
func HaveHTTPHeaderWithValue(header string, value interface{}) types.GomegaMatcher {
return &matchers.HaveHTTPHeaderWithValueMatcher{
Header: header,
Value: value,
}
}
// HaveHTTPBody matches if the body matches.
// Actual must be either a *http.Response or *httptest.ResponseRecorder.
// Expected must be either a string, []byte, or other matcher
func HaveHTTPBody(expected interface{}) types.GomegaMatcher {
return &matchers.HaveHTTPBodyMatcher{Expected: expected}
}
// And succeeds only if all of the given matchers succeed.
// The matchers are tried in order, and will fail-fast if one doesn't succeed.
// //
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions. // Expect("hi").To(And(HaveLen(2), Equal("hi"))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func And(ms ...types.GomegaMatcher) types.GomegaMatcher { func And(ms ...types.GomegaMatcher) types.GomegaMatcher {
return &matchers.AndMatcher{Matchers: ms} return &matchers.AndMatcher{Matchers: ms}
} }
//SatisfyAll is an alias for And(). // SatisfyAll is an alias for And().
// Expect("hi").Should(SatisfyAll(HaveLen(2), Equal("hi"))) //
// Expect("hi").Should(SatisfyAll(HaveLen(2), Equal("hi")))
func SatisfyAll(matchers ...types.GomegaMatcher) types.GomegaMatcher { func SatisfyAll(matchers ...types.GomegaMatcher) types.GomegaMatcher {
return And(matchers...) return And(matchers...)
} }
//Or succeeds if any of the given matchers succeed. // Or succeeds if any of the given matchers succeed.
//The matchers are tried in order and will return immediately upon the first successful match. // The matchers are tried in order and will return immediately upon the first successful match.
// Expect("hi").To(Or(HaveLen(3), HaveLen(2))
// //
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions. // Expect("hi").To(Or(HaveLen(3), HaveLen(2))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func Or(ms ...types.GomegaMatcher) types.GomegaMatcher { func Or(ms ...types.GomegaMatcher) types.GomegaMatcher {
return &matchers.OrMatcher{Matchers: ms} return &matchers.OrMatcher{Matchers: ms}
} }
//SatisfyAny is an alias for Or(). // SatisfyAny is an alias for Or().
// Expect("hi").SatisfyAny(Or(HaveLen(3), HaveLen(2)) //
// Expect("hi").SatisfyAny(Or(HaveLen(3), HaveLen(2))
func SatisfyAny(matchers ...types.GomegaMatcher) types.GomegaMatcher { func SatisfyAny(matchers ...types.GomegaMatcher) types.GomegaMatcher {
return Or(matchers...) return Or(matchers...)
} }
//Not negates the given matcher; it succeeds if the given matcher fails. // Not negates the given matcher; it succeeds if the given matcher fails.
// Expect(1).To(Not(Equal(2))
// //
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions. // Expect(1).To(Not(Equal(2))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func Not(matcher types.GomegaMatcher) types.GomegaMatcher { func Not(matcher types.GomegaMatcher) types.GomegaMatcher {
return &matchers.NotMatcher{Matcher: matcher} return &matchers.NotMatcher{Matcher: matcher}
} }
//WithTransform applies the `transform` to the actual value and matches it against `matcher`. // WithTransform applies the `transform` to the actual value and matches it against `matcher`.
//The given transform must be a function of one parameter that returns one value. // The given transform must be either a function of one parameter that returns one value or a
// var plus1 = func(i int) int { return i + 1 } // function of one parameter that returns two values, where the second value must be of the
// Expect(1).To(WithTransform(plus1, Equal(2)) // error type.
// //
//And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions. // var plus1 = func(i int) int { return i + 1 }
// Expect(1).To(WithTransform(plus1, Equal(2))
//
// var failingplus1 = func(i int) (int, error) { return 42, "this does not compute" }
// Expect(1).To(WithTransform(failingplus1, Equal(2)))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func WithTransform(transform interface{}, matcher types.GomegaMatcher) types.GomegaMatcher { func WithTransform(transform interface{}, matcher types.GomegaMatcher) types.GomegaMatcher {
return matchers.NewWithTransformMatcher(transform, matcher) return matchers.NewWithTransformMatcher(transform, matcher)
} }
// Satisfy matches the actual value against the `predicate` function.
// The given predicate must be a function of one paramter that returns bool.
//
// var isEven = func(i int) bool { return i%2 == 0 }
// Expect(2).To(Satisfy(isEven))
func Satisfy(predicate interface{}) types.GomegaMatcher {
return matchers.NewSatisfyMatcher(predicate)
}

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/internal/oraclematcher"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
@ -52,12 +51,12 @@ func (m *AndMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
if m.firstFailedMatcher == nil { if m.firstFailedMatcher == nil {
// so all matchers succeeded.. Any one of them changing would change the result. // so all matchers succeeded.. Any one of them changing would change the result.
for _, matcher := range m.Matchers { for _, matcher := range m.Matchers {
if oraclematcher.MatchMayChangeInTheFuture(matcher, actual) { if types.MatchMayChangeInTheFuture(matcher, actual) {
return true return true
} }
} }
return false // none of were going to change return false // none of were going to change
} }
// one of the matchers failed.. it must be able to change in order to affect the result // one of the matchers failed.. it must be able to change in order to affect the result
return oraclematcher.MatchMayChangeInTheFuture(m.firstFailedMatcher, actual) return types.MatchMayChangeInTheFuture(m.firstFailedMatcher, actual)
} }

View File

@ -0,0 +1,49 @@
package matchers
import (
"bytes"
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/onsi/gomega/format"
)
type BeComparableToMatcher struct {
Expected interface{}
Options cmp.Options
}
func (matcher *BeComparableToMatcher) Match(actual interface{}) (success bool, matchErr error) {
if actual == nil && matcher.Expected == nil {
return false, fmt.Errorf("Refusing to compare <nil> to <nil>.\nBe explicit and use BeNil() instead. This is to avoid mistakes where both sides of an assertion are erroneously uninitialized.")
}
// Shortcut for byte slices.
// Comparing long byte slices with reflect.DeepEqual is very slow,
// so use bytes.Equal if actual and expected are both byte slices.
if actualByteSlice, ok := actual.([]byte); ok {
if expectedByteSlice, ok := matcher.Expected.([]byte); ok {
return bytes.Equal(actualByteSlice, expectedByteSlice), nil
}
}
defer func() {
if r := recover(); r != nil {
success = false
if err, ok := r.(error); ok {
matchErr = err
} else if errMsg, ok := r.(string); ok {
matchErr = fmt.Errorf(errMsg)
}
}
}()
return cmp.Equal(actual, matcher.Expected, matcher.Options...), nil
}
func (matcher *BeComparableToMatcher) FailureMessage(actual interface{}) (message string) {
return cmp.Diff(matcher.Expected, actual, matcher.Options)
}
func (matcher *BeComparableToMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to equal", matcher.Expected)
}

View File

@ -18,23 +18,9 @@ func (matcher *BeElementOfMatcher) Match(actual interface{}) (success bool, err
return false, fmt.Errorf("BeElement matcher expects actual to be typed") return false, fmt.Errorf("BeElement matcher expects actual to be typed")
} }
length := len(matcher.Elements)
valueAt := func(i int) interface{} {
return matcher.Elements[i]
}
// Special handling of a single element of type Array or Slice
if length == 1 && isArrayOrSlice(valueAt(0)) {
element := valueAt(0)
value := reflect.ValueOf(element)
length = value.Len()
valueAt = func(i int) interface{} {
return value.Index(i).Interface()
}
}
var lastError error var lastError error
for i := 0; i < length; i++ { for _, m := range flatten(matcher.Elements) {
matcher := &EqualMatcher{Expected: valueAt(i)} matcher := &EqualMatcher{Expected: m}
success, err := matcher.Match(actual) success, err := matcher.Match(actual)
if err != nil { if err != nil {
lastError = err lastError = err
@ -49,9 +35,9 @@ func (matcher *BeElementOfMatcher) Match(actual interface{}) (success bool, err
} }
func (matcher *BeElementOfMatcher) FailureMessage(actual interface{}) (message string) { func (matcher *BeElementOfMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be an element of", matcher.Elements) return format.Message(actual, "to be an element of", presentable(matcher.Elements))
} }
func (matcher *BeElementOfMatcher) NegatedFailureMessage(actual interface{}) (message string) { func (matcher *BeElementOfMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be an element of", matcher.Elements) return format.Message(actual, "not to be an element of", presentable(matcher.Elements))
} }

View File

@ -0,0 +1,45 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type BeKeyOfMatcher struct {
Map interface{}
}
func (matcher *BeKeyOfMatcher) Match(actual interface{}) (success bool, err error) {
if !isMap(matcher.Map) {
return false, fmt.Errorf("BeKeyOf matcher needs expected to be a map type")
}
if reflect.TypeOf(actual) == nil {
return false, fmt.Errorf("BeKeyOf matcher expects actual to be typed")
}
var lastError error
for _, key := range reflect.ValueOf(matcher.Map).MapKeys() {
matcher := &EqualMatcher{Expected: key.Interface()}
success, err := matcher.Match(actual)
if err != nil {
lastError = err
continue
}
if success {
return true, nil
}
}
return false, lastError
}
func (matcher *BeKeyOfMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to be a key of", presentable(valuesOf(matcher.Map)))
}
func (matcher *BeKeyOfMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to be a key of", presentable(valuesOf(matcher.Map)))
}

View File

@ -45,7 +45,7 @@ func (matcher *BeNumericallyMatcher) Match(actual interface{}) (success bool, er
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1)) return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
} }
if len(matcher.CompareTo) == 2 && !isNumber(matcher.CompareTo[1]) { if len(matcher.CompareTo) == 2 && !isNumber(matcher.CompareTo[1]) {
return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1)) return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[1], 1))
} }
switch matcher.Comparator { switch matcher.Comparator {

View File

@ -48,33 +48,69 @@ func neighbours(value, matcher interface{}) (bool, error) {
func equalMatchersToElements(matchers []interface{}) (elements []interface{}) { func equalMatchersToElements(matchers []interface{}) (elements []interface{}) {
for _, matcher := range matchers { for _, matcher := range matchers {
equalMatcher, ok := matcher.(*EqualMatcher) if equalMatcher, ok := matcher.(*EqualMatcher); ok {
if ok { elements = append(elements, equalMatcher.Expected)
matcher = equalMatcher.Expected } else if _, ok := matcher.(*BeNilMatcher); ok {
elements = append(elements, nil)
} else {
elements = append(elements, matcher)
} }
elements = append(elements, matcher)
} }
return return
} }
func flatten(elems []interface{}) []interface{} {
if len(elems) != 1 || !isArrayOrSlice(elems[0]) {
return elems
}
value := reflect.ValueOf(elems[0])
flattened := make([]interface{}, value.Len())
for i := 0; i < value.Len(); i++ {
flattened[i] = value.Index(i).Interface()
}
return flattened
}
func matchers(expectedElems []interface{}) (matchers []interface{}) { func matchers(expectedElems []interface{}) (matchers []interface{}) {
elems := expectedElems for _, e := range flatten(expectedElems) {
if len(expectedElems) == 1 && isArrayOrSlice(expectedElems[0]) { if e == nil {
elems = []interface{}{} matchers = append(matchers, &BeNilMatcher{})
value := reflect.ValueOf(expectedElems[0]) } else if matcher, isMatcher := e.(omegaMatcher); isMatcher {
for i := 0; i < value.Len(); i++ { matchers = append(matchers, matcher)
elems = append(elems, value.Index(i).Interface()) } else {
matchers = append(matchers, &EqualMatcher{Expected: e})
}
}
return
}
func presentable(elems []interface{}) interface{} {
elems = flatten(elems)
if len(elems) == 0 {
return []interface{}{}
}
sv := reflect.ValueOf(elems)
firstEl := sv.Index(0)
if firstEl.IsNil() {
return elems
}
tt := firstEl.Elem().Type()
for i := 1; i < sv.Len(); i++ {
el := sv.Index(i)
if el.IsNil() || (sv.Index(i).Elem().Type() != tt) {
return elems
} }
} }
for _, e := range elems { ss := reflect.MakeSlice(reflect.SliceOf(tt), sv.Len(), sv.Len())
matcher, isMatcher := e.(omegaMatcher) for i := 0; i < sv.Len(); i++ {
if !isMatcher { ss.Index(i).Set(sv.Index(i).Elem())
matcher = &EqualMatcher{Expected: e}
}
matchers = append(matchers, matcher)
} }
return
return ss.Interface()
} }
func valuesOf(actual interface{}) []interface{} { func valuesOf(actual interface{}) []interface{} {
@ -95,11 +131,11 @@ func valuesOf(actual interface{}) []interface{} {
} }
func (matcher *ConsistOfMatcher) FailureMessage(actual interface{}) (message string) { func (matcher *ConsistOfMatcher) FailureMessage(actual interface{}) (message string) {
message = format.Message(actual, "to consist of", matcher.Elements) message = format.Message(actual, "to consist of", presentable(matcher.Elements))
message = appendMissingElements(message, matcher.missingElements) message = appendMissingElements(message, matcher.missingElements)
if len(matcher.extraElements) > 0 { if len(matcher.extraElements) > 0 {
message = fmt.Sprintf("%s\nthe extra elements were\n%s", message, message = fmt.Sprintf("%s\nthe extra elements were\n%s", message,
format.Object(matcher.extraElements, 1)) format.Object(presentable(matcher.extraElements), 1))
} }
return return
} }
@ -109,9 +145,9 @@ func appendMissingElements(message string, missingElements []interface{}) string
return message return message
} }
return fmt.Sprintf("%s\nthe missing elements were\n%s", message, return fmt.Sprintf("%s\nthe missing elements were\n%s", message,
format.Object(missingElements, 1)) format.Object(presentable(missingElements), 1))
} }
func (matcher *ConsistOfMatcher) NegatedFailureMessage(actual interface{}) (message string) { func (matcher *ConsistOfMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to consist of", matcher.Elements) return format.Message(actual, "not to consist of", presentable(matcher.Elements))
} }

View File

@ -3,6 +3,7 @@
package matchers package matchers
import ( import (
"errors"
"fmt" "fmt"
"reflect" "reflect"
@ -11,6 +12,7 @@ import (
type ContainElementMatcher struct { type ContainElementMatcher struct {
Element interface{} Element interface{}
Result []interface{}
} }
func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, err error) { func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, err error) {
@ -18,6 +20,49 @@ func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, e
return false, fmt.Errorf("ContainElement matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1)) return false, fmt.Errorf("ContainElement matcher expects an array/slice/map. Got:\n%s", format.Object(actual, 1))
} }
var actualT reflect.Type
var result reflect.Value
switch l := len(matcher.Result); {
case l > 1:
return false, errors.New("ContainElement matcher expects at most a single optional pointer to store its findings at")
case l == 1:
if reflect.ValueOf(matcher.Result[0]).Kind() != reflect.Ptr {
return false, fmt.Errorf("ContainElement matcher expects a non-nil pointer to store its findings at. Got\n%s",
format.Object(matcher.Result[0], 1))
}
actualT = reflect.TypeOf(actual)
resultReference := matcher.Result[0]
result = reflect.ValueOf(resultReference).Elem() // what ResultReference points to, to stash away our findings
switch result.Kind() {
case reflect.Array:
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.SliceOf(actualT.Elem()).String(), result.Type().String())
case reflect.Slice:
if !isArrayOrSlice(actual) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
reflect.MapOf(actualT.Key(), actualT.Elem()).String(), result.Type().String())
}
if !actualT.Elem().AssignableTo(result.Type().Elem()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
case reflect.Map:
if !isMap(actual) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
if !actualT.AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.String(), result.Type().String())
}
default:
if !actualT.Elem().AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return findings. Need *%s, got *%s",
actualT.Elem().String(), result.Type().String())
}
}
}
elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher) elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
if !elementIsMatcher { if !elementIsMatcher {
elemMatcher = &EqualMatcher{Expected: matcher.Element} elemMatcher = &EqualMatcher{Expected: matcher.Element}
@ -25,30 +70,99 @@ func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, e
value := reflect.ValueOf(actual) value := reflect.ValueOf(actual)
var valueAt func(int) interface{} var valueAt func(int) interface{}
var getFindings func() reflect.Value
var foundAt func(int)
if isMap(actual) { if isMap(actual) {
keys := value.MapKeys() keys := value.MapKeys()
valueAt = func(i int) interface{} { valueAt = func(i int) interface{} {
return value.MapIndex(keys[i]).Interface() return value.MapIndex(keys[i]).Interface()
} }
if result.Kind() != reflect.Invalid {
fm := reflect.MakeMap(actualT)
getFindings = func() reflect.Value {
return fm
}
foundAt = func(i int) {
fm.SetMapIndex(keys[i], value.MapIndex(keys[i]))
}
}
} else { } else {
valueAt = func(i int) interface{} { valueAt = func(i int) interface{} {
return value.Index(i).Interface() return value.Index(i).Interface()
} }
if result.Kind() != reflect.Invalid {
var f reflect.Value
if result.Kind() == reflect.Slice {
f = reflect.MakeSlice(result.Type(), 0, 0)
} else {
f = reflect.MakeSlice(reflect.SliceOf(result.Type()), 0, 0)
}
getFindings = func() reflect.Value {
return f
}
foundAt = func(i int) {
f = reflect.Append(f, value.Index(i))
}
}
} }
var lastError error var lastError error
for i := 0; i < value.Len(); i++ { for i := 0; i < value.Len(); i++ {
success, err := elemMatcher.Match(valueAt(i)) elem := valueAt(i)
success, err := elemMatcher.Match(elem)
if err != nil { if err != nil {
lastError = err lastError = err
continue continue
} }
if success { if success {
return true, nil if result.Kind() == reflect.Invalid {
return true, nil
}
foundAt(i)
} }
} }
return false, lastError // when the expectation isn't interested in the findings except for success
// or non-success, then we're done here and return the last matcher error
// seen, if any, as well as non-success.
if result.Kind() == reflect.Invalid {
return false, lastError
}
// pick up any findings the test is interested in as it specified a non-nil
// result reference. However, the expection always is that there are at
// least one or multiple findings. So, if a result is expected, but we had
// no findings, then this is an error.
findings := getFindings()
if findings.Len() == 0 {
return false, lastError
}
// there's just a single finding and the result is neither a slice nor a map
// (so it's a scalar): pick the one and only finding and return it in the
// place the reference points to.
if findings.Len() == 1 && !isArrayOrSlice(result.Interface()) && !isMap(result.Interface()) {
if isMap(actual) {
miter := findings.MapRange()
miter.Next()
result.Set(miter.Value())
} else {
result.Set(findings.Index(0))
}
return true, nil
}
// at least one or even multiple findings and a the result references a
// slice or a map, so all we need to do is to store our findings where the
// reference points to.
if !findings.Type().AssignableTo(result.Type()) {
return false, fmt.Errorf("ContainElement cannot return multiple findings. Need *%s, got *%s",
findings.Type().String(), result.Type().String())
}
result.Set(findings)
return true, nil
} }
func (matcher *ContainElementMatcher) FailureMessage(actual interface{}) (message string) { func (matcher *ContainElementMatcher) FailureMessage(actual interface{}) (message string) {

View File

@ -35,10 +35,10 @@ func (matcher *ContainElementsMatcher) Match(actual interface{}) (success bool,
} }
func (matcher *ContainElementsMatcher) FailureMessage(actual interface{}) (message string) { func (matcher *ContainElementsMatcher) FailureMessage(actual interface{}) (message string) {
message = format.Message(actual, "to contain elements", matcher.Elements) message = format.Message(actual, "to contain elements", presentable(matcher.Elements))
return appendMissingElements(message, matcher.missingElements) return appendMissingElements(message, matcher.missingElements)
} }
func (matcher *ContainElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) { func (matcher *ContainElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to contain elements", matcher.Elements) return format.Message(actual, "not to contain elements", presentable(matcher.Elements))
} }

View File

@ -0,0 +1,65 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type HaveEachMatcher struct {
Element interface{}
}
func (matcher *HaveEachMatcher) Match(actual interface{}) (success bool, err error) {
if !isArrayOrSlice(actual) && !isMap(actual) {
return false, fmt.Errorf("HaveEach matcher expects an array/slice/map. Got:\n%s",
format.Object(actual, 1))
}
elemMatcher, elementIsMatcher := matcher.Element.(omegaMatcher)
if !elementIsMatcher {
elemMatcher = &EqualMatcher{Expected: matcher.Element}
}
value := reflect.ValueOf(actual)
if value.Len() == 0 {
return false, fmt.Errorf("HaveEach matcher expects a non-empty array/slice/map. Got:\n%s",
format.Object(actual, 1))
}
var valueAt func(int) interface{}
if isMap(actual) {
keys := value.MapKeys()
valueAt = func(i int) interface{} {
return value.MapIndex(keys[i]).Interface()
}
} else {
valueAt = func(i int) interface{} {
return value.Index(i).Interface()
}
}
// if there are no elements, then HaveEach will match.
for i := 0; i < value.Len(); i++ {
success, err := elemMatcher.Match(valueAt(i))
if err != nil {
return false, err
}
if !success {
return false, nil
}
}
return true, nil
}
// FailureMessage returns a suitable failure message.
func (matcher *HaveEachMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to contain element matching", matcher.Element)
}
// NegatedFailureMessage returns a suitable negated failure message.
func (matcher *HaveEachMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to contain element matching", matcher.Element)
}

View File

@ -0,0 +1,83 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
)
type mismatchFailure struct {
failure string
index int
}
type HaveExactElementsMatcher struct {
Elements []interface{}
mismatchFailures []mismatchFailure
missingIndex int
extraIndex int
}
func (matcher *HaveExactElementsMatcher) Match(actual interface{}) (success bool, err error) {
matcher.resetState()
if isMap(actual) {
return false, fmt.Errorf("error")
}
matchers := matchers(matcher.Elements)
values := valuesOf(actual)
lenMatchers := len(matchers)
lenValues := len(values)
for i := 0; i < lenMatchers || i < lenValues; i++ {
if i >= lenMatchers {
matcher.extraIndex = i
continue
}
if i >= lenValues {
matcher.missingIndex = i
return
}
elemMatcher := matchers[i].(omegaMatcher)
match, err := elemMatcher.Match(values[i])
if err != nil || !match {
matcher.mismatchFailures = append(matcher.mismatchFailures, mismatchFailure{
index: i,
failure: elemMatcher.FailureMessage(values[i]),
})
}
}
return matcher.missingIndex+matcher.extraIndex+len(matcher.mismatchFailures) == 0, nil
}
func (matcher *HaveExactElementsMatcher) FailureMessage(actual interface{}) (message string) {
message = format.Message(actual, "to have exact elements with", presentable(matcher.Elements))
if matcher.missingIndex > 0 {
message = fmt.Sprintf("%s\nthe missing elements start from index %d", message, matcher.missingIndex)
}
if matcher.extraIndex > 0 {
message = fmt.Sprintf("%s\nthe extra elements start from index %d", message, matcher.extraIndex)
}
if len(matcher.mismatchFailures) != 0 {
message = fmt.Sprintf("%s\nthe mismatch indexes were:", message)
}
for _, mismatch := range matcher.mismatchFailures {
message = fmt.Sprintf("%s\n%d: %s", message, mismatch.index, mismatch.failure)
}
return
}
func (matcher *HaveExactElementsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to contain elements", presentable(matcher.Elements))
}
func (matcher *HaveExactElementsMatcher) resetState() {
matcher.mismatchFailures = nil
matcher.missingIndex = 0
matcher.extraIndex = 0
}

View File

@ -0,0 +1,36 @@
package matchers
import (
"errors"
"fmt"
"github.com/onsi/gomega/format"
)
type HaveExistingFieldMatcher struct {
Field string
}
func (matcher *HaveExistingFieldMatcher) Match(actual interface{}) (success bool, err error) {
// we don't care about the field's actual value, just about any error in
// trying to find the field (or method).
_, err = extractField(actual, matcher.Field, "HaveExistingField")
if err == nil {
return true, nil
}
var mferr missingFieldError
if errors.As(err, &mferr) {
// missing field errors aren't errors in this context, but instead
// unsuccessful matches.
return false, nil
}
return false, err
}
func (matcher *HaveExistingFieldMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected\n%s\nto have field '%s'", format.Object(actual, 1), matcher.Field)
}
func (matcher *HaveExistingFieldMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected\n%s\nnot to have field '%s'", format.Object(actual, 1), matcher.Field)
}

99
vendor/github.com/onsi/gomega/matchers/have_field.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
package matchers
import (
"fmt"
"reflect"
"strings"
"github.com/onsi/gomega/format"
)
// missingFieldError represents a missing field extraction error that
// HaveExistingFieldMatcher can ignore, as opposed to other, sever field
// extraction errors, such as nil pointers, et cetera.
type missingFieldError string
func (e missingFieldError) Error() string {
return string(e)
}
func extractField(actual interface{}, field string, matchername string) (interface{}, error) {
fields := strings.SplitN(field, ".", 2)
actualValue := reflect.ValueOf(actual)
if actualValue.Kind() == reflect.Ptr {
actualValue = actualValue.Elem()
}
if actualValue == (reflect.Value{}) {
return nil, fmt.Errorf("%s encountered nil while dereferencing a pointer of type %T.", matchername, actual)
}
if actualValue.Kind() != reflect.Struct {
return nil, fmt.Errorf("%s encountered:\n%s\nWhich is not a struct.", matchername, format.Object(actual, 1))
}
var extractedValue reflect.Value
if strings.HasSuffix(fields[0], "()") {
extractedValue = actualValue.MethodByName(strings.TrimSuffix(fields[0], "()"))
if extractedValue == (reflect.Value{}) && actualValue.CanAddr() {
extractedValue = actualValue.Addr().MethodByName(strings.TrimSuffix(fields[0], "()"))
}
if extractedValue == (reflect.Value{}) {
return nil, missingFieldError(fmt.Sprintf("%s could not find method named '%s' in struct of type %T.", matchername, fields[0], actual))
}
t := extractedValue.Type()
if t.NumIn() != 0 || t.NumOut() != 1 {
return nil, fmt.Errorf("%s found an invalid method named '%s' in struct of type %T.\nMethods must take no arguments and return exactly one value.", matchername, fields[0], actual)
}
extractedValue = extractedValue.Call([]reflect.Value{})[0]
} else {
extractedValue = actualValue.FieldByName(fields[0])
if extractedValue == (reflect.Value{}) {
return nil, missingFieldError(fmt.Sprintf("%s could not find field named '%s' in struct:\n%s", matchername, fields[0], format.Object(actual, 1)))
}
}
if len(fields) == 1 {
return extractedValue.Interface(), nil
} else {
return extractField(extractedValue.Interface(), fields[1], matchername)
}
}
type HaveFieldMatcher struct {
Field string
Expected interface{}
extractedField interface{}
expectedMatcher omegaMatcher
}
func (matcher *HaveFieldMatcher) Match(actual interface{}) (success bool, err error) {
matcher.extractedField, err = extractField(actual, matcher.Field, "HaveField")
if err != nil {
return false, err
}
var isMatcher bool
matcher.expectedMatcher, isMatcher = matcher.Expected.(omegaMatcher)
if !isMatcher {
matcher.expectedMatcher = &EqualMatcher{Expected: matcher.Expected}
}
return matcher.expectedMatcher.Match(matcher.extractedField)
}
func (matcher *HaveFieldMatcher) FailureMessage(actual interface{}) (message string) {
message = fmt.Sprintf("Value for field '%s' failed to satisfy matcher.\n", matcher.Field)
message += matcher.expectedMatcher.FailureMessage(matcher.extractedField)
return message
}
func (matcher *HaveFieldMatcher) NegatedFailureMessage(actual interface{}) (message string) {
message = fmt.Sprintf("Value for field '%s' satisfied matcher, but should not have.\n", matcher.Field)
message += matcher.expectedMatcher.NegatedFailureMessage(matcher.extractedField)
return message
}

View File

@ -0,0 +1,101 @@
package matchers
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/internal/gutil"
"github.com/onsi/gomega/types"
)
type HaveHTTPBodyMatcher struct {
Expected interface{}
cachedBody []byte
}
func (matcher *HaveHTTPBodyMatcher) Match(actual interface{}) (bool, error) {
body, err := matcher.body(actual)
if err != nil {
return false, err
}
switch e := matcher.Expected.(type) {
case string:
return (&EqualMatcher{Expected: e}).Match(string(body))
case []byte:
return (&EqualMatcher{Expected: e}).Match(body)
case types.GomegaMatcher:
return e.Match(body)
default:
return false, fmt.Errorf("HaveHTTPBody matcher expects string, []byte, or GomegaMatcher. Got:\n%s", format.Object(matcher.Expected, 1))
}
}
func (matcher *HaveHTTPBodyMatcher) FailureMessage(actual interface{}) (message string) {
body, err := matcher.body(actual)
if err != nil {
return fmt.Sprintf("failed to read body: %s", err)
}
switch e := matcher.Expected.(type) {
case string:
return (&EqualMatcher{Expected: e}).FailureMessage(string(body))
case []byte:
return (&EqualMatcher{Expected: e}).FailureMessage(body)
case types.GomegaMatcher:
return e.FailureMessage(body)
default:
return fmt.Sprintf("HaveHTTPBody matcher expects string, []byte, or GomegaMatcher. Got:\n%s", format.Object(matcher.Expected, 1))
}
}
func (matcher *HaveHTTPBodyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
body, err := matcher.body(actual)
if err != nil {
return fmt.Sprintf("failed to read body: %s", err)
}
switch e := matcher.Expected.(type) {
case string:
return (&EqualMatcher{Expected: e}).NegatedFailureMessage(string(body))
case []byte:
return (&EqualMatcher{Expected: e}).NegatedFailureMessage(body)
case types.GomegaMatcher:
return e.NegatedFailureMessage(body)
default:
return fmt.Sprintf("HaveHTTPBody matcher expects string, []byte, or GomegaMatcher. Got:\n%s", format.Object(matcher.Expected, 1))
}
}
// body returns the body. It is cached because once we read it in Match()
// the Reader is closed and it is not readable again in FailureMessage()
// or NegatedFailureMessage()
func (matcher *HaveHTTPBodyMatcher) body(actual interface{}) ([]byte, error) {
if matcher.cachedBody != nil {
return matcher.cachedBody, nil
}
body := func(a *http.Response) ([]byte, error) {
if a.Body != nil {
defer a.Body.Close()
var err error
matcher.cachedBody, err = gutil.ReadAll(a.Body)
if err != nil {
return nil, fmt.Errorf("error reading response body: %w", err)
}
}
return matcher.cachedBody, nil
}
switch a := actual.(type) {
case *http.Response:
return body(a)
case *httptest.ResponseRecorder:
return body(a.Result())
default:
return nil, fmt.Errorf("HaveHTTPBody matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))
}
}

View File

@ -0,0 +1,81 @@
package matchers
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)
type HaveHTTPHeaderWithValueMatcher struct {
Header string
Value interface{}
}
func (matcher *HaveHTTPHeaderWithValueMatcher) Match(actual interface{}) (success bool, err error) {
headerValue, err := matcher.extractHeader(actual)
if err != nil {
return false, err
}
headerMatcher, err := matcher.getSubMatcher()
if err != nil {
return false, err
}
return headerMatcher.Match(headerValue)
}
func (matcher *HaveHTTPHeaderWithValueMatcher) FailureMessage(actual interface{}) string {
headerValue, err := matcher.extractHeader(actual)
if err != nil {
panic(err) // protected by Match()
}
headerMatcher, err := matcher.getSubMatcher()
if err != nil {
panic(err) // protected by Match()
}
diff := format.IndentString(headerMatcher.FailureMessage(headerValue), 1)
return fmt.Sprintf("HTTP header %q:\n%s", matcher.Header, diff)
}
func (matcher *HaveHTTPHeaderWithValueMatcher) NegatedFailureMessage(actual interface{}) (message string) {
headerValue, err := matcher.extractHeader(actual)
if err != nil {
panic(err) // protected by Match()
}
headerMatcher, err := matcher.getSubMatcher()
if err != nil {
panic(err) // protected by Match()
}
diff := format.IndentString(headerMatcher.NegatedFailureMessage(headerValue), 1)
return fmt.Sprintf("HTTP header %q:\n%s", matcher.Header, diff)
}
func (matcher *HaveHTTPHeaderWithValueMatcher) getSubMatcher() (types.GomegaMatcher, error) {
switch m := matcher.Value.(type) {
case string:
return &EqualMatcher{Expected: matcher.Value}, nil
case types.GomegaMatcher:
return m, nil
default:
return nil, fmt.Errorf("HaveHTTPHeaderWithValue matcher must be passed a string or a GomegaMatcher. Got:\n%s", format.Object(matcher.Value, 1))
}
}
func (matcher *HaveHTTPHeaderWithValueMatcher) extractHeader(actual interface{}) (string, error) {
switch r := actual.(type) {
case *http.Response:
return r.Header.Get(matcher.Header), nil
case *httptest.ResponseRecorder:
return r.Result().Header.Get(matcher.Header), nil
default:
return "", fmt.Errorf("HaveHTTPHeaderWithValue matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))
}
}

View File

@ -4,12 +4,15 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect"
"strings"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/internal/gutil"
) )
type HaveHTTPStatusMatcher struct { type HaveHTTPStatusMatcher struct {
Expected interface{} Expected []interface{}
} }
func (matcher *HaveHTTPStatusMatcher) Match(actual interface{}) (success bool, err error) { func (matcher *HaveHTTPStatusMatcher) Match(actual interface{}) (success bool, err error) {
@ -23,20 +26,71 @@ func (matcher *HaveHTTPStatusMatcher) Match(actual interface{}) (success bool, e
return false, fmt.Errorf("HaveHTTPStatus matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1)) return false, fmt.Errorf("HaveHTTPStatus matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))
} }
switch e := matcher.Expected.(type) { if len(matcher.Expected) == 0 {
case int: return false, fmt.Errorf("HaveHTTPStatus matcher must be passed an int or a string. Got nothing")
return resp.StatusCode == e, nil
case string:
return resp.Status == e, nil
} }
return false, fmt.Errorf("HaveHTTPStatus matcher must be passed an int or a string. Got:\n%s", format.Object(matcher.Expected, 1)) for _, expected := range matcher.Expected {
switch e := expected.(type) {
case int:
if resp.StatusCode == e {
return true, nil
}
case string:
if resp.Status == e {
return true, nil
}
default:
return false, fmt.Errorf("HaveHTTPStatus matcher must be passed int or string types. Got:\n%s", format.Object(expected, 1))
}
}
return false, nil
} }
func (matcher *HaveHTTPStatusMatcher) FailureMessage(actual interface{}) (message string) { func (matcher *HaveHTTPStatusMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to have HTTP status", matcher.Expected) return fmt.Sprintf("Expected\n%s\n%s\n%s", formatHttpResponse(actual), "to have HTTP status", matcher.expectedString())
} }
func (matcher *HaveHTTPStatusMatcher) NegatedFailureMessage(actual interface{}) (message string) { func (matcher *HaveHTTPStatusMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to have HTTP status", matcher.Expected) return fmt.Sprintf("Expected\n%s\n%s\n%s", formatHttpResponse(actual), "not to have HTTP status", matcher.expectedString())
}
func (matcher *HaveHTTPStatusMatcher) expectedString() string {
var lines []string
for _, expected := range matcher.Expected {
lines = append(lines, format.Object(expected, 1))
}
return strings.Join(lines, "\n")
}
func formatHttpResponse(input interface{}) string {
var resp *http.Response
switch r := input.(type) {
case *http.Response:
resp = r
case *httptest.ResponseRecorder:
resp = r.Result()
default:
return "cannot format invalid HTTP response"
}
body := "<nil>"
if resp.Body != nil {
defer resp.Body.Close()
data, err := gutil.ReadAll(resp.Body)
if err != nil {
data = []byte("<error reading body>")
}
body = format.Object(string(data), 0)
}
var s strings.Builder
s.WriteString(fmt.Sprintf("%s<%s>: {\n", format.Indent, reflect.TypeOf(input)))
s.WriteString(fmt.Sprintf("%s%sStatus: %s\n", format.Indent, format.Indent, format.Object(resp.Status, 0)))
s.WriteString(fmt.Sprintf("%s%sStatusCode: %s\n", format.Indent, format.Indent, format.Object(resp.StatusCode, 0)))
s.WriteString(fmt.Sprintf("%s%sBody: %s\n", format.Indent, format.Indent, body))
s.WriteString(fmt.Sprintf("%s}", format.Indent))
return s.String()
} }

View File

@ -31,5 +31,5 @@ func (matcher *HaveOccurredMatcher) FailureMessage(actual interface{}) (message
} }
func (matcher *HaveOccurredMatcher) NegatedFailureMessage(actual interface{}) (message string) { func (matcher *HaveOccurredMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Unexpected error:\n%s\n%s\n%s", format.Object(actual, 1), format.IndentString(actual.(error).Error(), 1), "occurred") return fmt.Sprintf("Unexpected error:\n%s\n%s", format.Object(actual, 1), "occurred")
} }

54
vendor/github.com/onsi/gomega/matchers/have_value.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
package matchers
import (
"errors"
"reflect"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)
const maxIndirections = 31
type HaveValueMatcher struct {
Matcher types.GomegaMatcher // the matcher to apply to the "resolved" actual value.
resolvedActual interface{} // the ("resolved") value.
}
func (m *HaveValueMatcher) Match(actual interface{}) (bool, error) {
val := reflect.ValueOf(actual)
for allowedIndirs := maxIndirections; allowedIndirs > 0; allowedIndirs-- {
// return an error if value isn't valid. Please note that we cannot
// check for nil here, as we might not deal with a pointer or interface
// at this point.
if !val.IsValid() {
return false, errors.New(format.Message(
actual, "not to be <nil>"))
}
switch val.Kind() {
case reflect.Ptr, reflect.Interface:
// resolve pointers and interfaces to their values, then rinse and
// repeat.
if val.IsNil() {
return false, errors.New(format.Message(
actual, "not to be <nil>"))
}
val = val.Elem()
continue
default:
// forward the final value to the specified matcher.
m.resolvedActual = val.Interface()
return m.Matcher.Match(m.resolvedActual)
}
}
// too many indirections: extreme star gazing, indeed...?
return false, errors.New(format.Message(actual, "too many indirections"))
}
func (m *HaveValueMatcher) FailureMessage(_ interface{}) (message string) {
return m.Matcher.FailureMessage(m.resolvedActual)
}
func (m *HaveValueMatcher) NegatedFailureMessage(_ interface{}) (message string) {
return m.Matcher.NegatedFailureMessage(m.resolvedActual)
}

View File

@ -1,11 +1,11 @@
package matchers package matchers
import ( import (
"errors"
"fmt" "fmt"
"reflect" "reflect"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"golang.org/x/xerrors"
) )
type MatchErrorMatcher struct { type MatchErrorMatcher struct {
@ -25,7 +25,17 @@ func (matcher *MatchErrorMatcher) Match(actual interface{}) (success bool, err e
expected := matcher.Expected expected := matcher.Expected
if isError(expected) { if isError(expected) {
return reflect.DeepEqual(actualErr, expected) || xerrors.Is(actualErr, expected.(error)), nil // first try the built-in errors.Is
if errors.Is(actualErr, expected.(error)) {
return true, nil
}
// if not, try DeepEqual along the error chain
for unwrapped := actualErr; unwrapped != nil; unwrapped = errors.Unwrap(unwrapped) {
if reflect.DeepEqual(unwrapped, expected) {
return true, nil
}
}
return false, nil
} }
if isString(expected) { if isString(expected) {

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
type MatchYAMLMatcher struct { type MatchYAMLMatcher struct {

View File

@ -1,7 +1,6 @@
package matchers package matchers
import ( import (
"github.com/onsi/gomega/internal/oraclematcher"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
@ -26,5 +25,5 @@ func (m *NotMatcher) NegatedFailureMessage(actual interface{}) (message string)
} }
func (m *NotMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { func (m *NotMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
return oraclematcher.MatchMayChangeInTheFuture(m.Matcher, actual) // just return m.Matcher's value return types.MatchMayChangeInTheFuture(m.Matcher, actual) // just return m.Matcher's value
} }

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
"github.com/onsi/gomega/internal/oraclematcher"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
@ -54,11 +53,11 @@ func (m *OrMatcher) MatchMayChangeInTheFuture(actual interface{}) bool {
if m.firstSuccessfulMatcher != nil { if m.firstSuccessfulMatcher != nil {
// one of the matchers succeeded.. it must be able to change in order to affect the result // one of the matchers succeeded.. it must be able to change in order to affect the result
return oraclematcher.MatchMayChangeInTheFuture(m.firstSuccessfulMatcher, actual) return types.MatchMayChangeInTheFuture(m.firstSuccessfulMatcher, actual)
} else { } else {
// so all matchers failed.. Any one of them changing would change the result. // so all matchers failed.. Any one of them changing would change the result.
for _, matcher := range m.Matchers { for _, matcher := range m.Matchers {
if oraclematcher.MatchMayChangeInTheFuture(matcher, actual) { if types.MatchMayChangeInTheFuture(matcher, actual) {
return true return true
} }
} }

View File

@ -0,0 +1,66 @@
package matchers
import (
"fmt"
"reflect"
"github.com/onsi/gomega/format"
)
type SatisfyMatcher struct {
Predicate interface{}
// cached type
predicateArgType reflect.Type
}
func NewSatisfyMatcher(predicate interface{}) *SatisfyMatcher {
if predicate == nil {
panic("predicate cannot be nil")
}
predicateType := reflect.TypeOf(predicate)
if predicateType.Kind() != reflect.Func {
panic("predicate must be a function")
}
if predicateType.NumIn() != 1 {
panic("predicate must have 1 argument")
}
if predicateType.NumOut() != 1 || predicateType.Out(0).Kind() != reflect.Bool {
panic("predicate must return bool")
}
return &SatisfyMatcher{
Predicate: predicate,
predicateArgType: predicateType.In(0),
}
}
func (m *SatisfyMatcher) Match(actual interface{}) (success bool, err error) {
// prepare a parameter to pass to the predicate
var param reflect.Value
if actual != nil && reflect.TypeOf(actual).AssignableTo(m.predicateArgType) {
// The dynamic type of actual is compatible with the predicate argument.
param = reflect.ValueOf(actual)
} else if actual == nil && m.predicateArgType.Kind() == reflect.Interface {
// The dynamic type of actual is unknown, so there's no way to make its
// reflect.Value. Create a nil of the predicate argument, which is known.
param = reflect.Zero(m.predicateArgType)
} else {
return false, fmt.Errorf("predicate expects '%s' but we have '%T'", m.predicateArgType, actual)
}
// call the predicate with `actual`
fn := reflect.ValueOf(m.Predicate)
result := fn.Call([]reflect.Value{param})
return result[0].Bool(), nil
}
func (m *SatisfyMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to satisfy predicate", m.Predicate)
}
func (m *SatisfyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to not satisfy predicate", m.Predicate)
}

View File

@ -1,11 +1,16 @@
package matchers package matchers
import ( import (
"errors"
"fmt" "fmt"
"github.com/onsi/gomega/format" "github.com/onsi/gomega/format"
) )
type formattedGomegaError interface {
FormattedGomegaError() string
}
type SucceedMatcher struct { type SucceedMatcher struct {
} }
@ -25,7 +30,11 @@ func (matcher *SucceedMatcher) Match(actual interface{}) (success bool, err erro
} }
func (matcher *SucceedMatcher) FailureMessage(actual interface{}) (message string) { func (matcher *SucceedMatcher) FailureMessage(actual interface{}) (message string) {
return fmt.Sprintf("Expected success, but got an error:\n%s\n%s", format.Object(actual, 1), format.IndentString(actual.(error).Error(), 1)) var fgErr formattedGomegaError
if errors.As(actual.(error), &fgErr) {
return fgErr.FormattedGomegaError()
}
return fmt.Sprintf("Expected success, but got an error:\n%s", format.Object(actual, 1))
} }
func (matcher *SucceedMatcher) NegatedFailureMessage(actual interface{}) (message string) { func (matcher *SucceedMatcher) NegatedFailureMessage(actual interface{}) (message string) {

View File

@ -4,13 +4,12 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/onsi/gomega/internal/oraclematcher"
"github.com/onsi/gomega/types" "github.com/onsi/gomega/types"
) )
type WithTransformMatcher struct { type WithTransformMatcher struct {
// input // input
Transform interface{} // must be a function of one parameter that returns one value Transform interface{} // must be a function of one parameter that returns one value and an optional error
Matcher types.GomegaMatcher Matcher types.GomegaMatcher
// cached value // cached value
@ -20,6 +19,9 @@ type WithTransformMatcher struct {
transformedValue interface{} transformedValue interface{}
} }
// reflect.Type for error
var errorT = reflect.TypeOf((*error)(nil)).Elem()
func NewWithTransformMatcher(transform interface{}, matcher types.GomegaMatcher) *WithTransformMatcher { func NewWithTransformMatcher(transform interface{}, matcher types.GomegaMatcher) *WithTransformMatcher {
if transform == nil { if transform == nil {
panic("transform function cannot be nil") panic("transform function cannot be nil")
@ -28,8 +30,10 @@ func NewWithTransformMatcher(transform interface{}, matcher types.GomegaMatcher)
if txType.NumIn() != 1 { if txType.NumIn() != 1 {
panic("transform function must have 1 argument") panic("transform function must have 1 argument")
} }
if txType.NumOut() != 1 { if numout := txType.NumOut(); numout != 1 {
panic("transform function must have 1 return value") if numout != 2 || !txType.Out(1).AssignableTo(errorT) {
panic("transform function must either have 1 return value, or 1 return value plus 1 error value")
}
} }
return &WithTransformMatcher{ return &WithTransformMatcher{
@ -40,15 +44,29 @@ func NewWithTransformMatcher(transform interface{}, matcher types.GomegaMatcher)
} }
func (m *WithTransformMatcher) Match(actual interface{}) (bool, error) { func (m *WithTransformMatcher) Match(actual interface{}) (bool, error) {
// return error if actual's type is incompatible with Transform function's argument type // prepare a parameter to pass to the Transform function
actualType := reflect.TypeOf(actual) var param reflect.Value
if !actualType.AssignableTo(m.transformArgType) { if actual != nil && reflect.TypeOf(actual).AssignableTo(m.transformArgType) {
return false, fmt.Errorf("Transform function expects '%s' but we have '%s'", m.transformArgType, actualType) // The dynamic type of actual is compatible with the transform argument.
param = reflect.ValueOf(actual)
} else if actual == nil && m.transformArgType.Kind() == reflect.Interface {
// The dynamic type of actual is unknown, so there's no way to make its
// reflect.Value. Create a nil of the transform argument, which is known.
param = reflect.Zero(m.transformArgType)
} else {
return false, fmt.Errorf("Transform function expects '%s' but we have '%T'", m.transformArgType, actual)
} }
// call the Transform function with `actual` // call the Transform function with `actual`
fn := reflect.ValueOf(m.Transform) fn := reflect.ValueOf(m.Transform)
result := fn.Call([]reflect.Value{reflect.ValueOf(actual)}) result := fn.Call([]reflect.Value{param})
if len(result) == 2 {
if !result[1].IsNil() {
return false, fmt.Errorf("Transform function failed: %s", result[1].Interface().(error).Error())
}
}
m.transformedValue = result[0].Interface() // expect exactly one value m.transformedValue = result[0].Interface() // expect exactly one value
return m.Matcher.Match(m.transformedValue) return m.Matcher.Match(m.transformedValue)
@ -68,5 +86,5 @@ func (m *WithTransformMatcher) MatchMayChangeInTheFuture(_ interface{}) bool {
// Querying the next matcher is fine if the transformer always will return the same value. // Querying the next matcher is fine if the transformer always will return the same value.
// But if the transformer is non-deterministic and returns a different value each time, then there // But if the transformer is non-deterministic and returns a different value each time, then there
// is no point in querying the next matcher, since it can only comment on the last transformed value. // is no point in querying the next matcher, since it can only comment on the last transformed value.
return oraclematcher.MatchMayChangeInTheFuture(m.Matcher, m.transformedValue) return types.MatchMayChangeInTheFuture(m.Matcher, m.transformedValue)
} }

View File

@ -1,26 +1,93 @@
package types package types
type TWithHelper interface { import (
Helper() "context"
} "time"
)
type GomegaFailHandler func(message string, callerSkip ...int) type GomegaFailHandler func(message string, callerSkip ...int)
type GomegaFailWrapper struct { // A simple *testing.T interface wrapper
Fail GomegaFailHandler
TWithHelper TWithHelper
}
//A simple *testing.T interface wrapper
type GomegaTestingT interface { type GomegaTestingT interface {
Helper()
Fatalf(format string, args ...interface{}) Fatalf(format string, args ...interface{})
} }
//All Gomega matchers must implement the GomegaMatcher interface // Gomega represents an object that can perform synchronous and assynchronous assertions with Gomega matchers
type Gomega interface {
Ω(actual interface{}, extra ...interface{}) Assertion
Expect(actual interface{}, extra ...interface{}) Assertion
ExpectWithOffset(offset int, actual interface{}, extra ...interface{}) Assertion
Eventually(actualOrCtx interface{}, args ...interface{}) AsyncAssertion
EventuallyWithOffset(offset int, actualOrCtx interface{}, args ...interface{}) AsyncAssertion
Consistently(actualOrCtx interface{}, args ...interface{}) AsyncAssertion
ConsistentlyWithOffset(offset int, actualOrCtx interface{}, args ...interface{}) AsyncAssertion
SetDefaultEventuallyTimeout(time.Duration)
SetDefaultEventuallyPollingInterval(time.Duration)
SetDefaultConsistentlyDuration(time.Duration)
SetDefaultConsistentlyPollingInterval(time.Duration)
}
// All Gomega matchers must implement the GomegaMatcher interface
// //
//For details on writing custom matchers, check out: http://onsi.github.io/gomega/#adding-your-own-matchers // For details on writing custom matchers, check out: http://onsi.github.io/gomega/#adding-your-own-matchers
type GomegaMatcher interface { type GomegaMatcher interface {
Match(actual interface{}) (success bool, err error) Match(actual interface{}) (success bool, err error)
FailureMessage(actual interface{}) (message string) FailureMessage(actual interface{}) (message string)
NegatedFailureMessage(actual interface{}) (message string) NegatedFailureMessage(actual interface{}) (message string)
} }
/*
GomegaMatchers that also match the OracleMatcher interface can convey information about
whether or not their result will change upon future attempts.
This allows `Eventually` and `Consistently` to short circuit if success becomes impossible.
For example, a process' exit code can never change. So, gexec's Exit matcher returns `true`
for `MatchMayChangeInTheFuture` until the process exits, at which point it returns `false` forevermore.
*/
type OracleMatcher interface {
MatchMayChangeInTheFuture(actual interface{}) bool
}
func MatchMayChangeInTheFuture(matcher GomegaMatcher, value interface{}) bool {
oracleMatcher, ok := matcher.(OracleMatcher)
if !ok {
return true
}
return oracleMatcher.MatchMayChangeInTheFuture(value)
}
// AsyncAssertions are returned by Eventually and Consistently and enable matchers to be polled repeatedly to ensure
// they are eventually satisfied
type AsyncAssertion interface {
Should(matcher GomegaMatcher, optionalDescription ...interface{}) bool
ShouldNot(matcher GomegaMatcher, optionalDescription ...interface{}) bool
WithOffset(offset int) AsyncAssertion
WithTimeout(interval time.Duration) AsyncAssertion
WithPolling(interval time.Duration) AsyncAssertion
Within(timeout time.Duration) AsyncAssertion
ProbeEvery(interval time.Duration) AsyncAssertion
WithContext(ctx context.Context) AsyncAssertion
WithArguments(argsToForward ...interface{}) AsyncAssertion
MustPassRepeatedly(count int) AsyncAssertion
}
// Assertions are returned by Ω and Expect and enable assertions against Gomega matchers
type Assertion interface {
Should(matcher GomegaMatcher, optionalDescription ...interface{}) bool
ShouldNot(matcher GomegaMatcher, optionalDescription ...interface{}) bool
To(matcher GomegaMatcher, optionalDescription ...interface{}) bool
ToNot(matcher GomegaMatcher, optionalDescription ...interface{}) bool
NotTo(matcher GomegaMatcher, optionalDescription ...interface{}) bool
WithOffset(offset int) Assertion
Error() Assertion
}

27
vendor/golang.org/x/xerrors/LICENSE generated vendored
View File

@ -1,27 +0,0 @@
Copyright (c) 2019 The Go Authors. 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.
* Neither the name of Google Inc. nor the names of its
contributors may 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
OWNER 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.

22
vendor/golang.org/x/xerrors/PATENTS generated vendored
View File

@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

2
vendor/golang.org/x/xerrors/README generated vendored
View File

@ -1,2 +0,0 @@
This repository holds the transition packages for the new Go 1.13 error values.
See golang.org/design/29934-error-values.

View File

@ -1,193 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"bytes"
"fmt"
"io"
"reflect"
"strconv"
)
// FormatError calls the FormatError method of f with an errors.Printer
// configured according to s and verb, and writes the result to s.
func FormatError(f Formatter, s fmt.State, verb rune) {
// Assuming this function is only called from the Format method, and given
// that FormatError takes precedence over Format, it cannot be called from
// any package that supports errors.Formatter. It is therefore safe to
// disregard that State may be a specific printer implementation and use one
// of our choice instead.
// limitations: does not support printing error as Go struct.
var (
sep = " " // separator before next error
p = &state{State: s}
direct = true
)
var err error = f
switch verb {
// Note that this switch must match the preference order
// for ordinary string printing (%#v before %+v, and so on).
case 'v':
if s.Flag('#') {
if stringer, ok := err.(fmt.GoStringer); ok {
io.WriteString(&p.buf, stringer.GoString())
goto exit
}
// proceed as if it were %v
} else if s.Flag('+') {
p.printDetail = true
sep = "\n - "
}
case 's':
case 'q', 'x', 'X':
// Use an intermediate buffer in the rare cases that precision,
// truncation, or one of the alternative verbs (q, x, and X) are
// specified.
direct = false
default:
p.buf.WriteString("%!")
p.buf.WriteRune(verb)
p.buf.WriteByte('(')
switch {
case err != nil:
p.buf.WriteString(reflect.TypeOf(f).String())
default:
p.buf.WriteString("<nil>")
}
p.buf.WriteByte(')')
io.Copy(s, &p.buf)
return
}
loop:
for {
switch v := err.(type) {
case Formatter:
err = v.FormatError((*printer)(p))
case fmt.Formatter:
v.Format(p, 'v')
break loop
default:
io.WriteString(&p.buf, v.Error())
break loop
}
if err == nil {
break
}
if p.needColon || !p.printDetail {
p.buf.WriteByte(':')
p.needColon = false
}
p.buf.WriteString(sep)
p.inDetail = false
p.needNewline = false
}
exit:
width, okW := s.Width()
prec, okP := s.Precision()
if !direct || (okW && width > 0) || okP {
// Construct format string from State s.
format := []byte{'%'}
if s.Flag('-') {
format = append(format, '-')
}
if s.Flag('+') {
format = append(format, '+')
}
if s.Flag(' ') {
format = append(format, ' ')
}
if okW {
format = strconv.AppendInt(format, int64(width), 10)
}
if okP {
format = append(format, '.')
format = strconv.AppendInt(format, int64(prec), 10)
}
format = append(format, string(verb)...)
fmt.Fprintf(s, string(format), p.buf.String())
} else {
io.Copy(s, &p.buf)
}
}
var detailSep = []byte("\n ")
// state tracks error printing state. It implements fmt.State.
type state struct {
fmt.State
buf bytes.Buffer
printDetail bool
inDetail bool
needColon bool
needNewline bool
}
func (s *state) Write(b []byte) (n int, err error) {
if s.printDetail {
if len(b) == 0 {
return 0, nil
}
if s.inDetail && s.needColon {
s.needNewline = true
if b[0] == '\n' {
b = b[1:]
}
}
k := 0
for i, c := range b {
if s.needNewline {
if s.inDetail && s.needColon {
s.buf.WriteByte(':')
s.needColon = false
}
s.buf.Write(detailSep)
s.needNewline = false
}
if c == '\n' {
s.buf.Write(b[k:i])
k = i + 1
s.needNewline = true
}
}
s.buf.Write(b[k:])
if !s.inDetail {
s.needColon = true
}
} else if !s.inDetail {
s.buf.Write(b)
}
return len(b), nil
}
// printer wraps a state to implement an xerrors.Printer.
type printer state
func (s *printer) Print(args ...interface{}) {
if !s.inDetail || s.printDetail {
fmt.Fprint((*state)(s), args...)
}
}
func (s *printer) Printf(format string, args ...interface{}) {
if !s.inDetail || s.printDetail {
fmt.Fprintf((*state)(s), format, args...)
}
}
func (s *printer) Detail() bool {
s.inDetail = true
return s.printDetail
}

View File

@ -1 +0,0 @@
issuerepo: golang/go

22
vendor/golang.org/x/xerrors/doc.go generated vendored
View File

@ -1,22 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package xerrors implements functions to manipulate errors.
//
// This package is based on the Go 2 proposal for error values:
// https://golang.org/design/29934-error-values
//
// These functions were incorporated into the standard library's errors package
// in Go 1.13:
// - Is
// - As
// - Unwrap
//
// Also, Errorf's %w verb was incorporated into fmt.Errorf.
//
// Use this package to get equivalent behavior in all supported Go versions.
//
// No other features of this package were included in Go 1.13, and at present
// there are no plans to include any of them.
package xerrors // import "golang.org/x/xerrors"

View File

@ -1,33 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import "fmt"
// errorString is a trivial implementation of error.
type errorString struct {
s string
frame Frame
}
// New returns an error that formats as the given text.
//
// The returned error contains a Frame set to the caller's location and
// implements Formatter to show this information when printed with details.
func New(text string) error {
return &errorString{text, Caller(1)}
}
func (e *errorString) Error() string {
return e.s
}
func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *errorString) FormatError(p Printer) (next error) {
p.Print(e.s)
e.frame.Format(p)
return nil
}

187
vendor/golang.org/x/xerrors/fmt.go generated vendored
View File

@ -1,187 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/xerrors/internal"
)
const percentBangString = "%!"
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// The returned error includes the file and line number of the caller when
// formatted with additional detail enabled. If the last argument is an error
// the returned error's Format method will return it if the format string ends
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
// format string ends with ": %w", the returned error implements an Unwrap
// method returning it.
//
// If the format specifier includes a %w verb with an error operand in a
// position other than at the end, the returned error will still implement an
// Unwrap method returning the operand, but the error's Format method will not
// return the wrapped error.
//
// It is invalid to include more than one %w verb or to supply it with an
// operand that does not implement the error interface. The %w verb is otherwise
// a synonym for %v.
func Errorf(format string, a ...interface{}) error {
format = formatPlusW(format)
// Support a ": %[wsv]" suffix, which works well with xerrors.Formatter.
wrap := strings.HasSuffix(format, ": %w")
idx, format2, ok := parsePercentW(format)
percentWElsewhere := !wrap && idx >= 0
if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) {
err := errorAt(a, len(a)-1)
if err == nil {
return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
}
// TODO: this is not entirely correct. The error value could be
// printed elsewhere in format if it mixes numbered with unnumbered
// substitutions. With relatively small changes to doPrintf we can
// have it optionally ignore extra arguments and pass the argument
// list in its entirety.
msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
frame := Frame{}
if internal.EnableTrace {
frame = Caller(1)
}
if wrap {
return &wrapError{msg, err, frame}
}
return &noWrapError{msg, err, frame}
}
// Support %w anywhere.
// TODO: don't repeat the wrapped error's message when %w occurs in the middle.
msg := fmt.Sprintf(format2, a...)
if idx < 0 {
return &noWrapError{msg, nil, Caller(1)}
}
err := errorAt(a, idx)
if !ok || err == nil {
// Too many %ws or argument of %w is not an error. Approximate the Go
// 1.13 fmt.Errorf message.
return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)}
}
frame := Frame{}
if internal.EnableTrace {
frame = Caller(1)
}
return &wrapError{msg, err, frame}
}
func errorAt(args []interface{}, i int) error {
if i < 0 || i >= len(args) {
return nil
}
err, ok := args[i].(error)
if !ok {
return nil
}
return err
}
// formatPlusW is used to avoid the vet check that will barf at %w.
func formatPlusW(s string) string {
return s
}
// Return the index of the only %w in format, or -1 if none.
// Also return a rewritten format string with %w replaced by %v, and
// false if there is more than one %w.
// TODO: handle "%[N]w".
func parsePercentW(format string) (idx int, newFormat string, ok bool) {
// Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go.
idx = -1
ok = true
n := 0
sz := 0
var isW bool
for i := 0; i < len(format); i += sz {
if format[i] != '%' {
sz = 1
continue
}
// "%%" is not a format directive.
if i+1 < len(format) && format[i+1] == '%' {
sz = 2
continue
}
sz, isW = parsePrintfVerb(format[i:])
if isW {
if idx >= 0 {
ok = false
} else {
idx = n
}
// "Replace" the last character, the 'w', with a 'v'.
p := i + sz - 1
format = format[:p] + "v" + format[p+1:]
}
n++
}
return idx, format, ok
}
// Parse the printf verb starting with a % at s[0].
// Return how many bytes it occupies and whether the verb is 'w'.
func parsePrintfVerb(s string) (int, bool) {
// Assume only that the directive is a sequence of non-letters followed by a single letter.
sz := 0
var r rune
for i := 1; i < len(s); i += sz {
r, sz = utf8.DecodeRuneInString(s[i:])
if unicode.IsLetter(r) {
return i + sz, r == 'w'
}
}
return len(s), false
}
type noWrapError struct {
msg string
err error
frame Frame
}
func (e *noWrapError) Error() string {
return fmt.Sprint(e)
}
func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *noWrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
type wrapError struct {
msg string
err error
frame Frame
}
func (e *wrapError) Error() string {
return fmt.Sprint(e)
}
func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *wrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
func (e *wrapError) Unwrap() error {
return e.err
}

View File

@ -1,34 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
// A Formatter formats error messages.
type Formatter interface {
error
// FormatError prints the receiver's first error and returns the next error in
// the error chain, if any.
FormatError(p Printer) (next error)
}
// A Printer formats error messages.
//
// The most common implementation of Printer is the one provided by package fmt
// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message
// typically provide their own implementations.
type Printer interface {
// Print appends args to the message output.
Print(args ...interface{})
// Printf writes a formatted string.
Printf(format string, args ...interface{})
// Detail reports whether error detail is requested.
// After the first call to Detail, all text written to the Printer
// is formatted as additional detail, or ignored when
// detail has not been requested.
// If Detail returns false, the caller can avoid printing the detail at all.
Detail() bool
}

56
vendor/golang.org/x/xerrors/frame.go generated vendored
View File

@ -1,56 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"runtime"
)
// A Frame contains part of a call stack.
type Frame struct {
// Make room for three PCs: the one we were asked for, what it called,
// and possibly a PC for skipPleaseUseCallersFrames. See:
// https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
frames [3]uintptr
}
// Caller returns a Frame that describes a frame on the caller's stack.
// The argument skip is the number of frames to skip over.
// Caller(0) returns the frame for the caller of Caller.
func Caller(skip int) Frame {
var s Frame
runtime.Callers(skip+1, s.frames[:])
return s
}
// location reports the file, line, and function of a frame.
//
// The returned function may be "" even if file and line are not.
func (f Frame) location() (function, file string, line int) {
frames := runtime.CallersFrames(f.frames[:])
if _, ok := frames.Next(); !ok {
return "", "", 0
}
fr, ok := frames.Next()
if !ok {
return "", "", 0
}
return fr.Function, fr.File, fr.Line
}
// Format prints the stack as error detail.
// It should be called from an error's Format implementation
// after printing any other error detail.
func (f Frame) Format(p Printer) {
if p.Detail() {
function, file, line := f.location()
if function != "" {
p.Printf("%s\n ", function)
}
if file != "" {
p.Printf("%s:%d\n", file, line)
}
}
}

View File

@ -1,8 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
// EnableTrace indicates whether stack information should be recorded in errors.
var EnableTrace = true

106
vendor/golang.org/x/xerrors/wrap.go generated vendored
View File

@ -1,106 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"reflect"
)
// A Wrapper provides context around another error.
type Wrapper interface {
// Unwrap returns the next error in the error chain.
// If there is no next error, Unwrap returns nil.
Unwrap() error
}
// Opaque returns an error with the same error formatting as err
// but that does not match err and cannot be unwrapped.
func Opaque(err error) error {
return noWrapper{err}
}
type noWrapper struct {
error
}
func (e noWrapper) FormatError(p Printer) (next error) {
if f, ok := e.error.(Formatter); ok {
return f.FormatError(p)
}
p.Print(e.error)
return nil
}
// Unwrap returns the result of calling the Unwrap method on err, if err implements
// Unwrap. Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
u, ok := err.(Wrapper)
if !ok {
return nil
}
return u.Unwrap()
}
// Is reports whether any error in err's chain matches target.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflect.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporing target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {
return false
}
}
}
// As finds the first error in err's chain that matches the type to which target
// points, and if so, sets the target to its value and returns true. An error
// matches a type if it is assignable to the target type, or if it has a method
// As(interface{}) bool such that As(target) returns true. As will panic if target
// is not a non-nil pointer to a type which implements error or is of interface type.
//
// The As method should set the target to its value and return true if err
// matches the type to which target points.
func As(err error, target interface{}) bool {
if target == nil {
panic("errors: target cannot be nil")
}
val := reflect.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflect.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
targetType := typ.Elem()
for err != nil {
if reflect.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflect.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
err = Unwrap(err)
}
return false
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()

20
vendor/modules.txt vendored
View File

@ -67,7 +67,7 @@ github.com/gogo/protobuf/sortkeys
# github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da # github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
## explicit ## explicit
github.com/golang/groupcache/lru github.com/golang/groupcache/lru
# github.com/golang/protobuf v1.5.2 # github.com/golang/protobuf v1.5.3
## explicit; go 1.9 ## explicit; go 1.9
github.com/golang/protobuf/descriptor github.com/golang/protobuf/descriptor
github.com/golang/protobuf/jsonpb github.com/golang/protobuf/jsonpb
@ -78,8 +78,8 @@ github.com/golang/protobuf/ptypes/any
github.com/golang/protobuf/ptypes/duration github.com/golang/protobuf/ptypes/duration
github.com/golang/protobuf/ptypes/timestamp github.com/golang/protobuf/ptypes/timestamp
github.com/golang/protobuf/ptypes/wrappers github.com/golang/protobuf/ptypes/wrappers
# github.com/google/go-cmp v0.5.5 # github.com/google/go-cmp v0.5.9
## explicit; go 1.8 ## explicit; go 1.13
github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp
github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/diff
github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/flags
@ -161,14 +161,12 @@ github.com/onsi/ginkgo/reporters/stenographer
github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable
github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty
github.com/onsi/ginkgo/types github.com/onsi/ginkgo/types
# github.com/onsi/gomega v1.10.1 # github.com/onsi/gomega v1.27.6
## explicit ## explicit; go 1.18
github.com/onsi/gomega github.com/onsi/gomega
github.com/onsi/gomega/format github.com/onsi/gomega/format
github.com/onsi/gomega/internal/assertion github.com/onsi/gomega/internal
github.com/onsi/gomega/internal/asyncassertion github.com/onsi/gomega/internal/gutil
github.com/onsi/gomega/internal/oraclematcher
github.com/onsi/gomega/internal/testingtsupport
github.com/onsi/gomega/matchers github.com/onsi/gomega/matchers
github.com/onsi/gomega/matchers/support/goraph/bipartitegraph github.com/onsi/gomega/matchers/support/goraph/bipartitegraph
github.com/onsi/gomega/matchers/support/goraph/edge github.com/onsi/gomega/matchers/support/goraph/edge
@ -353,10 +351,6 @@ golang.org/x/text/unicode/norm
# golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac # golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
## explicit ## explicit
golang.org/x/time/rate golang.org/x/time/rate
# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
## explicit; go 1.11
golang.org/x/xerrors
golang.org/x/xerrors/internal
# google.golang.org/appengine v1.6.7 # google.golang.org/appengine v1.6.7
## explicit; go 1.11 ## explicit; go 1.11
google.golang.org/appengine/internal google.golang.org/appengine/internal