init
This commit is contained in:
commit
4e08e8410a
587 changed files with 239538 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.idea/
|
36
cmd/sample-server/main.go
Normal file
36
cmd/sample-server/main.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"keksvpn/pkg/wgfunc"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
serverConfig := wgfunc.ServerConfig{
|
||||||
|
Name: "sample-server",
|
||||||
|
IPRanges: []string{"10.0.1.0/24"},
|
||||||
|
Port: 0,
|
||||||
|
PrivateKey: "kDUIomFdDOW0VaOMS6Vu8+1rw/8SemeobSa7ZN66J38=\n",
|
||||||
|
}
|
||||||
|
server, err := wgfunc.NewServer(serverConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Start Server")
|
||||||
|
err = server.Up()
|
||||||
|
if err != nil {
|
||||||
|
server.Down()
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Server online")
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
fmt.Println("Shoutdown server")
|
||||||
|
err = server.Down()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}1
|
||||||
|
fmt.Println("bye")
|
||||||
|
}
|
26
go.mod
Normal file
26
go.mod
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
module keksvpn
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
|
github.com/vishvananda/netlink v1.1.0
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.7 // indirect
|
||||||
|
github.com/josharian/native v1.0.0 // indirect
|
||||||
|
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.6.0 // indirect
|
||||||
|
github.com/mdlayher/socket v0.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220207234003-57398862261d // indirect
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
63
go.sum
Normal file
63
go.sum
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||||
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
|
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||||
|
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
|
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
||||||
|
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||||
|
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
|
||||||
|
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||||
|
github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI=
|
||||||
|
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||||
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||||
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||||
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab h1:lnZ4LoV0UMdibeCUfIB2a4uFwRu491WX/VB2reB8xNc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444 h1:/d2cWp6PSamH4jDPFLyO150psQdqvtoNX8Zjg3AQ31g=
|
||||||
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220207234003-57398862261d h1:Bm7BNOQt2Qv7ZqysjeLjgCBanX+88Z/OtdvsrEv1Djc=
|
||||||
|
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178 h1:Nrf94TOjrvW8nm6N3u2xtbnMZaZudNI9b8nIJH8p8qY=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68 h1:9c4/JVIQUc2qCJEEIiGIs3HmmnFjhPj4qHW4+Uj+u3U=
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220208144051-fde48d68ee68/go.mod h1:8P32Ilp1kCpwB4ItaHyvSk4xAtnpQ+8gQVfg5WaO1TU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
1
pkg/wgfunc/Readme.md
Normal file
1
pkg/wgfunc/Readme.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Easy way to manage wireaguard "server" and "client"
|
20
pkg/wgfunc/client.go
Normal file
20
pkg/wgfunc/client.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package wgfunc
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
ServerURL string
|
||||||
|
ServerPublicKey string
|
||||||
|
PrivateKey string
|
||||||
|
PresharedKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(config ClientConfig) Client {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client)Connect() error {
|
||||||
|
|
||||||
|
}
|
293
pkg/wgfunc/server.go
Normal file
293
pkg/wgfunc/server.go
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
package wgfunc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
name string
|
||||||
|
iPRanges []string
|
||||||
|
|
||||||
|
wgDev *netlink.GenericLink
|
||||||
|
wgConfig wgtypes.Config
|
||||||
|
wgClient *wgctrl.Client
|
||||||
|
wgUpdate sync.Mutex
|
||||||
|
|
||||||
|
port int //Wireguard Server Port
|
||||||
|
privateKey string //Wireguard Server Private Key
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct{
|
||||||
|
Name string
|
||||||
|
IPRanges []string
|
||||||
|
Port int //Wireguard Server Port
|
||||||
|
PrivateKey string //Wireguard Server Private Key
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(config ServerConfig) (Server, error){
|
||||||
|
server := Server{
|
||||||
|
name: config.Name,
|
||||||
|
iPRanges: config.IPRanges,
|
||||||
|
port: config.Port,
|
||||||
|
privateKey: config.PrivateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeerConfig struct {
|
||||||
|
PublicKey string
|
||||||
|
PreSharedKey string
|
||||||
|
AllowedIPs []string
|
||||||
|
Endpoint string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Down() (error) {
|
||||||
|
return s.removeNetworkDevice()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Up() (error) {
|
||||||
|
err := s.createNetworkDevice()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.addIPToNetwork()
|
||||||
|
if err != nil{
|
||||||
|
err2 := s.removeNetworkDevice()
|
||||||
|
if err2 != nil {
|
||||||
|
return fmt.Errorf("Error deleting Network Device %w because of IP range error %s.", err2, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.wgSetup()
|
||||||
|
if err != nil{
|
||||||
|
err2 := s.removeNetworkDevice()
|
||||||
|
if err2 != nil {
|
||||||
|
return fmt.Errorf("Error deleting Network Device %w because of Wiregaurd error %s.", err2, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.upNetworkDevice()
|
||||||
|
if err != nil{ //todo: test
|
||||||
|
err2 := s.removeNetworkDevice()
|
||||||
|
if err2 != nil {
|
||||||
|
return fmt.Errorf("Error deleting Network Device %w because of Wiregaurd error %s.", err2, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AddPeer(config PeerConfig) (error) {
|
||||||
|
s.wgUpdate.Lock()
|
||||||
|
defer s.wgUpdate.Unlock()
|
||||||
|
if s.wgConfig.PrivateKey == nil {
|
||||||
|
return errors.New("server not up")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := wgctrl.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := wgtypes.ParseKey(config.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipList []net.IPNet
|
||||||
|
|
||||||
|
for _, ip := range config.AllowedIPs {
|
||||||
|
_, net, err := net.ParseCIDR(ip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ipList = append(ipList, *net)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := 30 * time.Second
|
||||||
|
|
||||||
|
var endpoint *net.UDPAddr
|
||||||
|
if config.Endpoint != "" {
|
||||||
|
//todo: allow hostnames
|
||||||
|
details := strings.Split(config.Endpoint, ":")
|
||||||
|
ip := net.ParseIP(details[0])
|
||||||
|
if ip == nil{
|
||||||
|
return errors.New("Invalide endpoint ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(details[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cant convert port to int: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
udpaddr := net.UDPAddr{
|
||||||
|
IP: ip,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
endpoint = &udpaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var preKey *wgtypes.Key
|
||||||
|
if config.PreSharedKey != "" {
|
||||||
|
presharedKey, err := wgtypes.ParseKey(config.PreSharedKey )
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
preKey = &presharedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
peerConfig := wgtypes.PeerConfig{
|
||||||
|
PublicKey: pubKey,
|
||||||
|
Remove: false,
|
||||||
|
UpdateOnly: false,
|
||||||
|
PresharedKey: preKey,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
PersistentKeepaliveInterval: &d,
|
||||||
|
ReplaceAllowedIPs: true,
|
||||||
|
AllowedIPs: ipList,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wgConfig.Peers = append(s.wgConfig.Peers, peerConfig)
|
||||||
|
|
||||||
|
err = client.ConfigureDevice(s.name, s.wgConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemovePeer(publicKey string) (error) {
|
||||||
|
s.wgUpdate.Lock()
|
||||||
|
defer s.wgUpdate.Unlock()
|
||||||
|
|
||||||
|
if s.wgConfig.PrivateKey == nil {
|
||||||
|
return errors.New("server not up")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := wgctrl.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var newlist []wgtypes.PeerConfig
|
||||||
|
|
||||||
|
for _ , peer := range s.wgConfig.Peers {
|
||||||
|
if peer.PublicKey.String() != publicKey {
|
||||||
|
newlist = append(newlist, peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wgConfig.Peers = newlist
|
||||||
|
err = client.ConfigureDevice(s.name, s.wgConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) createNetworkDevice() (error) {
|
||||||
|
la := netlink.NewLinkAttrs()
|
||||||
|
la.Name = s.name
|
||||||
|
|
||||||
|
wgDev := &netlink.GenericLink{
|
||||||
|
LinkAttrs: la,
|
||||||
|
LinkType: "wireguard",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := netlink.LinkAdd(wgDev)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wgDev = wgDev
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) upNetworkDevice() (error) {
|
||||||
|
err := netlink.LinkSetUp(s.wgDev) //todo: test
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) removeNetworkDevice() (error) {
|
||||||
|
la := netlink.NewLinkAttrs()
|
||||||
|
la.Name = s.name
|
||||||
|
|
||||||
|
wgDev := &netlink.GenericLink{
|
||||||
|
LinkAttrs: la,
|
||||||
|
LinkType: "wireguard",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := netlink.LinkDel(wgDev)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) addIPToNetwork() (error) {
|
||||||
|
var ips []*netlink.Addr
|
||||||
|
for _, iprange := range s.iPRanges {
|
||||||
|
ip, err := netlink.ParseAddr(iprange)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ips = append(ips, ip)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range ips {
|
||||||
|
err := netlink.AddrAdd(s.wgDev, ip)
|
||||||
|
if err != nil { //ToDo: Check how to test this case
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) wgSetup() (error) {
|
||||||
|
if s.wgConfig.PrivateKey != nil {
|
||||||
|
return errors.New("Wireguard already setedup")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := wgctrl.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.wgClient = client
|
||||||
|
|
||||||
|
privkey, err := wgtypes.ParseKey(s.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
s.wgConfig = wgtypes.Config{
|
||||||
|
PrivateKey: &privkey,
|
||||||
|
ListenPort: &s.port,
|
||||||
|
FirewallMark: nil,
|
||||||
|
ReplacePeers: true,
|
||||||
|
Peers: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.wgClient.ConfigureDevice(s.name, s.wgConfig)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
336
pkg/wgfunc/server_test.go
Normal file
336
pkg/wgfunc/server_test.go
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
package wgfunc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
import "github.com/vishvananda/netlink"
|
||||||
|
import "github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
func TestCreateAndDeleteDevice(t *testing.T) {
|
||||||
|
serverConfig := ServerConfig{
|
||||||
|
Name: "wgUnit",
|
||||||
|
}
|
||||||
|
server, err := NewServer(serverConfig)
|
||||||
|
|
||||||
|
err = server.createNetworkDevice()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
assert.Nil(t, err, "shouldn't return error creating the network Device: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
foundDevice := checkForNetworkDevice("wgUnit", t)
|
||||||
|
assert.Truef(t, foundDevice, "should fount device with name wgUnit")
|
||||||
|
|
||||||
|
err = server.removeNetworkDevice()
|
||||||
|
if err != nil {
|
||||||
|
assert.Nil(t, err, "shouldn't return an error doing remove the network device again")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundDevice = checkForNetworkDevice("wgUnit", t)
|
||||||
|
|
||||||
|
assert.Falsef(t, foundDevice, "should not fount device with name wgUnit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveDeviceIfIPRangeInvalide(t *testing.T) {
|
||||||
|
serverConfig := ServerConfig{
|
||||||
|
Name: "wgUnit",
|
||||||
|
IPRanges: []string{"10.0.0.1/24", "abc/24"},
|
||||||
|
}
|
||||||
|
server, err := NewServer(serverConfig)
|
||||||
|
assert.Nil(t, err, "shouldn't return error while creating server")
|
||||||
|
|
||||||
|
err = server.Up()
|
||||||
|
if err == nil || err.Error() != "invalid CIDR address: abc/24" {
|
||||||
|
t.Errorf("Should return invalide CIDR error")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundDevice := checkForNetworkDevice("wgUnit", t)
|
||||||
|
|
||||||
|
assert.Falsef(t, foundDevice, "should not fount device with name wgUnit")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestRemoveDeviceIfPrivateKeyIsInvalide(t *testing.T) {
|
||||||
|
serverConfig := ServerConfig{
|
||||||
|
Name: "wgUnit",
|
||||||
|
IPRanges: []string{"10.0.0.1/24"},
|
||||||
|
}
|
||||||
|
server, err := NewServer(serverConfig)
|
||||||
|
assert.Nil(t, err, "shouldn't return error while creating server")
|
||||||
|
|
||||||
|
err = server.Up()
|
||||||
|
if err == nil || err.Error() != "wgtypes: incorrect key size: 0" {
|
||||||
|
t.Errorf("Should return invalide key size error")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundDevice := checkForNetworkDevice("wgUnit", t)
|
||||||
|
|
||||||
|
assert.Falsef(t, foundDevice, "should not fount device with name wgUnit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCantRunSetupWireguardTwice(t *testing.T) {
|
||||||
|
server := Server{}
|
||||||
|
server.name = "wgUnit"
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
assert.Nil(t, err, "cant create private key")
|
||||||
|
server.wgConfig.PrivateKey = &key
|
||||||
|
|
||||||
|
err = server.Up()
|
||||||
|
if err == nil || err.Error() != "Wireguard already setedup" {
|
||||||
|
t.Errorf("Should fail setup wireguard if privatkey is already set")
|
||||||
|
}
|
||||||
|
|
||||||
|
foundDevice := checkForNetworkDevice("wgUnit", t)
|
||||||
|
|
||||||
|
assert.Falsef(t, foundDevice, "should not fount device with name wgUnit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoWireguardDevicesExists(t *testing.T) {
|
||||||
|
client, err := wgctrl.New()
|
||||||
|
assert.Nil(t, err, "cant create wgctrl client")
|
||||||
|
|
||||||
|
devices, err := client.Devices()
|
||||||
|
assert.Nil(t, err, "cant get devices from wgctrl client")
|
||||||
|
|
||||||
|
assert.Equalf(t, 0, len(devices), "there are wireguard devices")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateWireguarderver(t *testing.T) {
|
||||||
|
client, err := wgctrl.New()
|
||||||
|
assert.Nil(t, err, "cant create wgctrl client")
|
||||||
|
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
assert.Nil(t, err, "cant create private key")
|
||||||
|
|
||||||
|
serverConfig := ServerConfig{
|
||||||
|
Name: "wgUnit",
|
||||||
|
IPRanges: []string{"10.0.0.1/24"},
|
||||||
|
Port: 54123,
|
||||||
|
PrivateKey: key.String(),
|
||||||
|
}
|
||||||
|
server, err := NewServer(serverConfig)
|
||||||
|
assert.Nil(t, err, "cant create server")
|
||||||
|
|
||||||
|
err = server.Up()
|
||||||
|
|
||||||
|
foundDevice := checkForNetworkDevice("wgUnit", t)
|
||||||
|
|
||||||
|
assert.True(t, foundDevice, "should fount device with name wgUnit")
|
||||||
|
|
||||||
|
devices, err := client.Devices()
|
||||||
|
assert.Nil(t, err, "cant get devices from wgctrl client")
|
||||||
|
|
||||||
|
assert.Equalf(t, 1, len(devices), "there are wireguard devices")
|
||||||
|
|
||||||
|
helpTearDown(server, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCantAddPeerIfServerNotUp(t *testing.T) {
|
||||||
|
serverConfig := ServerConfig{
|
||||||
|
Name: "wgUnit",
|
||||||
|
}
|
||||||
|
server, err := NewServer(serverConfig)
|
||||||
|
assert.Nil(t, err, "cant create server")
|
||||||
|
|
||||||
|
peer := PeerConfig{}
|
||||||
|
err = server.AddPeer(peer)
|
||||||
|
if err == nil || err.Error() != "server not up" {
|
||||||
|
t.Errorf("should not be able to add peer if server is offline")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCantAddPeerIfPublicKeyIsInvalide(t *testing.T) {
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: "abc",
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/32"},
|
||||||
|
Endpoint: "",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
if err == nil || err.Error() != "wgtypes: failed to parse base64-encoded key: illegal base64 data at input byte 0" {
|
||||||
|
t.Errorf("should not be able to add peer with wrong pub key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCantAddPeerIfPublicKeyIsEmpty(t *testing.T) {
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: "",
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/32"},
|
||||||
|
Endpoint: "",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
if err == nil || err.Error() != "wgtypes: incorrect key size: 0" {
|
||||||
|
t.Errorf("should not be able to add peer with wrong pub key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddPeerInvalideEndpointIP(t *testing.T) {
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: key.PublicKey().String(),
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/32"},
|
||||||
|
Endpoint: "abc:123",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
|
||||||
|
assert.Equalf(t, "Invalide endpoint ip", err.Error(), "shouldnt be able to add peer with invalide endpoint ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddPeerInvalideEndpointPort(t *testing.T) {
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: key.PublicKey().String(),
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/32"},
|
||||||
|
Endpoint: "127.0.0.1:abv",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
|
||||||
|
assert.Equalf(t, "Cant convert port to int: strconv.Atoi: parsing \"abv\": invalid syntax", err.Error(), "shouldnt be able to add peer with invalide endpoint port")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddPeerWithEndpoitnDomain(t *testing.T) {
|
||||||
|
t.Skipf("Not implemented now")
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: key.PublicKey().String(),
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/32"},
|
||||||
|
Endpoint: "home.kekskurse.de:123",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
|
||||||
|
assert.Nil(t, err, "should be able to add peer with domain as endpoint")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddPeerWithInvalideAllowedIP(t *testing.T) {
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: key.PublicKey().String(),
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.g.3/32"},
|
||||||
|
Endpoint: "home.kekskurse.de:123",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
|
||||||
|
assert.Equalf(t, "invalid CIDR address: 10.0.g.3/32", err.Error(), "should not allow peer with invalide allowed ip")
|
||||||
|
|
||||||
|
config = PeerConfig{
|
||||||
|
PublicKey: key.PublicKey().String(),
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/54"},
|
||||||
|
Endpoint: "home.kekskurse.de:123",
|
||||||
|
}
|
||||||
|
err = server.AddPeer(config)
|
||||||
|
assert.Equalf(t, "invalid CIDR address: 10.0.0.3/54", err.Error(), "should not allow peer with invalide allowed ip")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddPeer(t *testing.T) {
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
|
||||||
|
key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: key.PublicKey().String(),
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/32"},
|
||||||
|
Endpoint: "",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
assert.Nil(t, err, "should be able to add peer")
|
||||||
|
|
||||||
|
assert.Equalf(t, key.PublicKey().String(), server.wgConfig.Peers[0].PublicKey.String(), "public key should be in the wgConfig Object")
|
||||||
|
assert.Equal(t, "10.0.0.3/32", server.wgConfig.Peers[0].AllowedIPs[0].String(), "allowd ip should be set currect")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemovePeer(t *testing.T) {
|
||||||
|
server := helperCreateServer(t)
|
||||||
|
defer helpTearDown(server, t)
|
||||||
|
|
||||||
|
key, _ := wgtypes.GeneratePrivateKey()
|
||||||
|
|
||||||
|
config := PeerConfig{
|
||||||
|
PublicKey: key.PublicKey().String(),
|
||||||
|
PreSharedKey: "",
|
||||||
|
AllowedIPs: []string{"10.0.0.3/32"},
|
||||||
|
Endpoint: "",
|
||||||
|
}
|
||||||
|
err := server.AddPeer(config)
|
||||||
|
assert.Nil(t, err, "should be able to add peer")
|
||||||
|
|
||||||
|
err = server.RemovePeer(config.PublicKey)
|
||||||
|
assert.Nil(t, err, "should be able to remove peer")
|
||||||
|
|
||||||
|
assert.Equalf(t, 0, len(server.wgConfig.Peers), "peer list should be empty again")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func helperCreateServer(t *testing.T) Server {
|
||||||
|
key, err := wgtypes.GeneratePrivateKey()
|
||||||
|
assert.Nil(t, err, "cant create private key")
|
||||||
|
|
||||||
|
serverConfig := ServerConfig{
|
||||||
|
Name: "wgUnit",
|
||||||
|
IPRanges: []string{"10.0.0.1/24"},
|
||||||
|
Port: 54123,
|
||||||
|
PrivateKey: key.String(),
|
||||||
|
}
|
||||||
|
server, err := NewServer(serverConfig)
|
||||||
|
assert.Nil(t, err, "cant create server")
|
||||||
|
|
||||||
|
err = server.Up()
|
||||||
|
|
||||||
|
foundDevice := checkForNetworkDevice("wgUnit", t)
|
||||||
|
|
||||||
|
assert.True(t, foundDevice, "should fount device with name wgUnit")
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpTearDown(server Server, t *testing.T) {
|
||||||
|
err := server.Down()
|
||||||
|
assert.Nil(t, err, "should not return error while shutting down server")
|
||||||
|
|
||||||
|
foundDevice := checkForNetworkDevice(server.name, t)
|
||||||
|
assert.Falsef(t, foundDevice, "device should not exist anymore")
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkForNetworkDevice(name string, t *testing.T) bool {
|
||||||
|
res, err := netlink.LinkList()
|
||||||
|
|
||||||
|
assert.Nil(t, err, "shouldn't return an error return devices")
|
||||||
|
|
||||||
|
foundDevice := false
|
||||||
|
for _, device := range res {
|
||||||
|
if device.Attrs().Name == name {
|
||||||
|
foundDevice = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundDevice
|
||||||
|
}
|
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
152
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = false
|
||||||
|
|
||||||
|
// ptrSize is the size of a pointer on the current arch.
|
||||||
|
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
||||||
|
// internal reflect.Value fields. These values are valid before golang
|
||||||
|
// commit ecccf07e7f9d which changed the format. The are also valid
|
||||||
|
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||||
|
// the original format. Code in the init function updates these offsets
|
||||||
|
// as necessary.
|
||||||
|
offsetPtr = uintptr(ptrSize)
|
||||||
|
offsetScalar = uintptr(0)
|
||||||
|
offsetFlag = uintptr(ptrSize * 2)
|
||||||
|
|
||||||
|
// flagKindWidth and flagKindShift indicate various bits that the
|
||||||
|
// reflect package uses internally to track kind information.
|
||||||
|
//
|
||||||
|
// flagRO indicates whether or not the value field of a reflect.Value is
|
||||||
|
// read-only.
|
||||||
|
//
|
||||||
|
// flagIndir indicates whether the value field of a reflect.Value is
|
||||||
|
// the actual data or a pointer to the data.
|
||||||
|
//
|
||||||
|
// These values are valid before golang commit 90a7c3c86944 which
|
||||||
|
// changed their positions. Code in the init function updates these
|
||||||
|
// flags as necessary.
|
||||||
|
flagKindWidth = uintptr(5)
|
||||||
|
flagKindShift = uintptr(flagKindWidth - 1)
|
||||||
|
flagRO = uintptr(1 << 0)
|
||||||
|
flagIndir = uintptr(1 << 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Older versions of reflect.Value stored small integers directly in the
|
||||||
|
// ptr field (which is named val in the older versions). Versions
|
||||||
|
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
||||||
|
// scalar for this purpose which unfortunately came before the flag
|
||||||
|
// field, so the offset of the flag field is different for those
|
||||||
|
// versions.
|
||||||
|
//
|
||||||
|
// This code constructs a new reflect.Value from a known small integer
|
||||||
|
// and checks if the size of the reflect.Value struct indicates it has
|
||||||
|
// the scalar field. When it does, the offsets are updated accordingly.
|
||||||
|
vv := reflect.ValueOf(0xf00)
|
||||||
|
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
||||||
|
offsetScalar = ptrSize * 2
|
||||||
|
offsetFlag = ptrSize * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit 90a7c3c86944 changed the flag positions such that the low
|
||||||
|
// order bits are the kind. This code extracts the kind from the flags
|
||||||
|
// field and ensures it's the correct type. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are updated
|
||||||
|
// accordingly.
|
||||||
|
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
||||||
|
upfv := *(*uintptr)(upf)
|
||||||
|
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
||||||
|
flagKindShift = 0
|
||||||
|
flagRO = 1 << 5
|
||||||
|
flagIndir = 1 << 6
|
||||||
|
|
||||||
|
// Commit adf9b30e5594 modified the flags to separate the
|
||||||
|
// flagRO flag into two bits which specifies whether or not the
|
||||||
|
// field is embedded. This causes flagIndir to move over a bit
|
||||||
|
// and means that flagRO is the combination of either of the
|
||||||
|
// original flagRO bit and the new bit.
|
||||||
|
//
|
||||||
|
// This code detects the change by extracting what used to be
|
||||||
|
// the indirect bit to ensure it's set. When it's not, the flag
|
||||||
|
// order has been changed to the newer format, so the flags are
|
||||||
|
// updated accordingly.
|
||||||
|
if upfv&flagIndir == 0 {
|
||||||
|
flagRO = 3 << 5
|
||||||
|
flagIndir = 1 << 7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||||
|
// the typical safety restrictions preventing access to unaddressable and
|
||||||
|
// unexported data. It works by digging the raw pointer to the underlying
|
||||||
|
// value out of the protected value and generating a new unprotected (unsafe)
|
||||||
|
// reflect.Value to it.
|
||||||
|
//
|
||||||
|
// This allows us to check for implementations of the Stringer and error
|
||||||
|
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||||
|
// inaccessible values such as unexported struct fields.
|
||||||
|
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
||||||
|
indirects := 1
|
||||||
|
vt := v.Type()
|
||||||
|
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
||||||
|
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
||||||
|
if rvf&flagIndir != 0 {
|
||||||
|
vt = reflect.PtrTo(v.Type())
|
||||||
|
indirects++
|
||||||
|
} else if offsetScalar != 0 {
|
||||||
|
// The value is in the scalar field when it's not one of the
|
||||||
|
// reference types.
|
||||||
|
switch vt.Kind() {
|
||||||
|
case reflect.Uintptr:
|
||||||
|
case reflect.Chan:
|
||||||
|
case reflect.Func:
|
||||||
|
case reflect.Map:
|
||||||
|
case reflect.Ptr:
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
default:
|
||||||
|
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
||||||
|
offsetScalar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pv := reflect.NewAt(vt, upv)
|
||||||
|
rv = pv
|
||||||
|
for i := 0; i < indirects; i++ {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||||
|
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build js appengine safe disableunsafe
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||||
|
// that bypasses the typical safety restrictions preventing access to
|
||||||
|
// unaddressable and unexported data. However, doing this relies on access to
|
||||||
|
// the unsafe package. This is a stub version which simply returns the passed
|
||||||
|
// reflect.Value when the unsafe package is not available.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
return v
|
||||||
|
}
|
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||||
|
// the technique used in the fmt package.
|
||||||
|
var (
|
||||||
|
panicBytes = []byte("(PANIC=")
|
||||||
|
plusBytes = []byte("+")
|
||||||
|
iBytes = []byte("i")
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
interfaceBytes = []byte("(interface {})")
|
||||||
|
commaNewlineBytes = []byte(",\n")
|
||||||
|
newlineBytes = []byte("\n")
|
||||||
|
openBraceBytes = []byte("{")
|
||||||
|
openBraceNewlineBytes = []byte("{\n")
|
||||||
|
closeBraceBytes = []byte("}")
|
||||||
|
asteriskBytes = []byte("*")
|
||||||
|
colonBytes = []byte(":")
|
||||||
|
colonSpaceBytes = []byte(": ")
|
||||||
|
openParenBytes = []byte("(")
|
||||||
|
closeParenBytes = []byte(")")
|
||||||
|
spaceBytes = []byte(" ")
|
||||||
|
pointerChainBytes = []byte("->")
|
||||||
|
nilAngleBytes = []byte("<nil>")
|
||||||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||||
|
maxShortBytes = []byte("<max>")
|
||||||
|
circularBytes = []byte("<already shown>")
|
||||||
|
circularShortBytes = []byte("<shown>")
|
||||||
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
|
openBracketBytes = []byte("[")
|
||||||
|
closeBracketBytes = []byte("]")
|
||||||
|
percentBytes = []byte("%")
|
||||||
|
precisionBytes = []byte(".")
|
||||||
|
openAngleBytes = []byte("<")
|
||||||
|
closeAngleBytes = []byte(">")
|
||||||
|
openMapBytes = []byte("map[")
|
||||||
|
closeMapBytes = []byte("]")
|
||||||
|
lenEqualsBytes = []byte("len=")
|
||||||
|
capEqualsBytes = []byte("cap=")
|
||||||
|
)
|
||||||
|
|
||||||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||||||
|
var hexDigits = "0123456789abcdef"
|
||||||
|
|
||||||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||||||
|
// calls.
|
||||||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.Write(panicBytes)
|
||||||
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||||||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||||
|
//
|
||||||
|
// It handles panics in any called methods by catching and displaying the error
|
||||||
|
// as the formatted value.
|
||||||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||||
|
// We need an interface to check if the type implements the error or
|
||||||
|
// Stringer interface. However, the reflect package won't give us an
|
||||||
|
// interface on certain things like unexported struct fields in order
|
||||||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||||||
|
// to bypass these restrictions since this package does not mutate the
|
||||||
|
// values.
|
||||||
|
if !v.CanInterface() {
|
||||||
|
if UnsafeDisabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||||||
|
// the base type or a pointer to the base type depending on settings.
|
||||||
|
// Technically calling one of these methods with a pointer receiver can
|
||||||
|
// mutate the value, however, types which choose to satisify an error or
|
||||||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||||||
|
// state inside these interface methods.
|
||||||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it an error or Stringer?
|
||||||
|
switch iface := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
return true
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||||||
|
func printBool(w io.Writer, val bool) {
|
||||||
|
if val {
|
||||||
|
w.Write(trueBytes)
|
||||||
|
} else {
|
||||||
|
w.Write(falseBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInt outputs a signed integer value to Writer w.
|
||||||
|
func printInt(w io.Writer, val int64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUint outputs an unsigned integer value to Writer w.
|
||||||
|
func printUint(w io.Writer, val uint64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFloat outputs a floating point value using the specified precision,
|
||||||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||||||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||||||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printComplex outputs a complex value using the specified float precision
|
||||||
|
// for the real and imaginary parts to Writer w.
|
||||||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
|
r := real(c)
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||||
|
i := imag(c)
|
||||||
|
if i >= 0 {
|
||||||
|
w.Write(plusBytes)
|
||||||
|
}
|
||||||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||||
|
w.Write(iBytes)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||||
|
// prefix to Writer w.
|
||||||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
|
// Null pointer.
|
||||||
|
num := uint64(p)
|
||||||
|
if num == 0 {
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
|
||||||
|
// It's simpler to construct the hex string right to left.
|
||||||
|
base := uint64(16)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for num >= base {
|
||||||
|
buf[i] = hexDigits[num%base]
|
||||||
|
num /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[num]
|
||||||
|
|
||||||
|
// Add '0x' prefix.
|
||||||
|
i--
|
||||||
|
buf[i] = 'x'
|
||||||
|
i--
|
||||||
|
buf[i] = '0'
|
||||||
|
|
||||||
|
// Strip unused leading bytes.
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
|
// elements to be sorted.
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
strings []string // either nil or same len and values
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||||||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||||||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||||
|
vs := &valuesSorter{values: values, cs: cs}
|
||||||
|
if canSortSimply(vs.values[0].Kind()) {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
if !cs.DisableMethods {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||||||
|
vs.strings = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vs.strings[i] = b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vs.strings == nil && cs.SpewKeys {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||||||
|
// (if the ConfigState allows it).
|
||||||
|
func canSortSimply(kind reflect.Kind) bool {
|
||||||
|
// This switch parallels valueSortLess, except for the default case.
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return true
|
||||||
|
case reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of values in the slice. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the values at the passed indices. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
if s.strings != nil {
|
||||||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueSortLess returns whether the first value should sort before the second
|
||||||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||||
|
// implementation.
|
||||||
|
func valueSortLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Array:
|
||||||
|
// Compare the contents of both arrays.
|
||||||
|
l := a.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
av := a.Index(i)
|
||||||
|
bv := b.Index(i)
|
||||||
|
if av.Interface() == bv.Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueSortLess(av, bv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.String() < b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns whether the value at index i should sort before the
|
||||||
|
// value at index j. It is part of the sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
if s.strings == nil {
|
||||||
|
return valueSortLess(s.values[i], s.values[j])
|
||||||
|
}
|
||||||
|
return s.strings[i] < s.strings[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortValues is a sort function that handles both native types and any type that
|
||||||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||||
|
// their Value.String() value to ensure display stability.
|
||||||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(newValuesSorter(values, cs))
|
||||||
|
}
|
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigState houses the configuration options used by spew to format and
|
||||||
|
// display values. There is a global instance, Config, that is used to control
|
||||||
|
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||||
|
// provides methods equivalent to the top-level functions.
|
||||||
|
//
|
||||||
|
// The zero value for ConfigState provides no indentation. You would typically
|
||||||
|
// want to set it to a space or a tab.
|
||||||
|
//
|
||||||
|
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||||
|
// with default settings. See the documentation of NewDefaultConfig for default
|
||||||
|
// values.
|
||||||
|
type ConfigState struct {
|
||||||
|
// Indent specifies the string to use for each indentation level. The
|
||||||
|
// global config instance that all top-level functions use set this to a
|
||||||
|
// single space by default. If you would like more indentation, you might
|
||||||
|
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// MaxDepth controls the maximum number of levels to descend into nested
|
||||||
|
// data structures. The default, 0, means there is no limit.
|
||||||
|
//
|
||||||
|
// NOTE: Circular data structures are properly detected, so it is not
|
||||||
|
// necessary to set this value unless you specifically want to limit deeply
|
||||||
|
// nested data structures.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||||
|
// invoked for types that implement them.
|
||||||
|
DisableMethods bool
|
||||||
|
|
||||||
|
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||||
|
// error and Stringer interfaces on types which only accept a pointer
|
||||||
|
// receiver when the current type is not a pointer.
|
||||||
|
//
|
||||||
|
// NOTE: This might be an unsafe action since calling one of these methods
|
||||||
|
// with a pointer receiver could technically mutate the value, however,
|
||||||
|
// in practice, types which choose to satisify an error or Stringer
|
||||||
|
// interface with a pointer receiver should not be mutating their state
|
||||||
|
// inside these interface methods. As a result, this option relies on
|
||||||
|
// access to the unsafe package, so it will not have any effect when
|
||||||
|
// running in environments without access to the unsafe package such as
|
||||||
|
// Google App Engine or with the "safe" build tag specified.
|
||||||
|
DisablePointerMethods bool
|
||||||
|
|
||||||
|
// DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
// pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
DisablePointerAddresses bool
|
||||||
|
|
||||||
|
// DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
// for arrays, slices, maps and channels. This is useful when diffing
|
||||||
|
// data structures in tests.
|
||||||
|
DisableCapacities bool
|
||||||
|
|
||||||
|
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||||
|
// a custom error or Stringer interface is invoked. The default, false,
|
||||||
|
// means it will print the results of invoking the custom error or Stringer
|
||||||
|
// interface and return immediately instead of continuing to recurse into
|
||||||
|
// the internals of the data type.
|
||||||
|
//
|
||||||
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||||
|
// that support the error or Stringer interfaces (if methods are
|
||||||
|
// enabled) are supported, with other types sorted according to the
|
||||||
|
// reflect.Value.String() output which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
|
|
||||||
|
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||||
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
|
// considered if SortKeys is true.
|
||||||
|
SpewKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the active configuration of the top-level functions.
|
||||||
|
// The configuration can be changed by modifying the contents of spew.Config.
|
||||||
|
var Config = ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the formatted string as a value that satisfies error. See NewFormatter
|
||||||
|
// for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
c.Printf, c.Println, or c.Printf.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(c, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(c, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by modifying the public members
|
||||||
|
of c. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) Dump(a ...interface{}) {
|
||||||
|
fdump(c, os.Stdout, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(c, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a spew Formatter interface using
|
||||||
|
// the ConfigState associated with s.
|
||||||
|
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = newFormatter(c, arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||||
|
//
|
||||||
|
// Indent: " "
|
||||||
|
// MaxDepth: 0
|
||||||
|
// DisableMethods: false
|
||||||
|
// DisablePointerMethods: false
|
||||||
|
// ContinueOnMethod: false
|
||||||
|
// SortKeys: false
|
||||||
|
func NewDefaultConfig() *ConfigState {
|
||||||
|
return &ConfigState{Indent: " "}
|
||||||
|
}
|
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
A quick overview of the additional features spew provides over the built-in
|
||||||
|
printing facilities for Go data types are as follows:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output (only when using
|
||||||
|
Dump style)
|
||||||
|
|
||||||
|
There are two different approaches spew allows for dumping Go data structures:
|
||||||
|
|
||||||
|
* Dump style which prints with newlines, customizable indentation,
|
||||||
|
and additional debug information such as types and all pointer addresses
|
||||||
|
used to indirect to the final value
|
||||||
|
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||||
|
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||||
|
similar to the default %v while providing the additional functionality
|
||||||
|
outlined above and passing unsupported format verbs such as %x and %q
|
||||||
|
along to fmt
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
|
||||||
|
This section demonstrates how to quickly get started with spew. See the
|
||||||
|
sections below for further details on formatting and configuration options.
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||||
|
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||||
|
%#+v (adds types and pointer addresses):
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available
|
||||||
|
via the spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
The following configuration options are available:
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of
|
||||||
|
capacities for arrays, slices, maps and channels. This is useful when
|
||||||
|
diffing data structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are
|
||||||
|
supported with other types sorted according to the
|
||||||
|
reflect.Value.String() output which guarantees display
|
||||||
|
stability. Natural map order is used by default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
Specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only
|
||||||
|
considered if SortKeys is true.
|
||||||
|
|
||||||
|
Dump Usage
|
||||||
|
|
||||||
|
Simply call spew.Dump with a list of variables you want to dump:
|
||||||
|
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||||
|
io.Writer. For example, to dump to standard error:
|
||||||
|
|
||||||
|
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||||
|
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Sample Dump Output
|
||||||
|
|
||||||
|
See the Dump example for details on the setup of the types and variables being
|
||||||
|
shown here.
|
||||||
|
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
(string) (len=3) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||||
|
command as shown.
|
||||||
|
([]uint8) (len=32 cap=32) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
|
||||||
|
Custom Formatter
|
||||||
|
|
||||||
|
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||||
|
so that it integrates cleanly with standard fmt package printing functions. The
|
||||||
|
formatter is useful for inline printing of smaller data types similar to the
|
||||||
|
standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Custom Formatter Usage
|
||||||
|
|
||||||
|
The simplest way to make use of the spew custom formatter is to call one of the
|
||||||
|
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||||
|
functions have syntax you are most likely already familiar with:
|
||||||
|
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Println(myVar, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
See the Index for the full list convenience functions.
|
||||||
|
|
||||||
|
Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
|
||||||
|
See the Printf example for details on the setup of variables being shown
|
||||||
|
here.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||||
|
detects them and handles them internally by printing the panic information
|
||||||
|
inline with the output. Since spew is intended to provide deep pretty printing
|
||||||
|
capabilities on structures, it intentionally does not return any errors.
|
||||||
|
*/
|
||||||
|
package spew
|
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
|
@ -0,0 +1,509 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||||
|
// convert cgo types to uint8 slices for hexdumping.
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
|
||||||
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
|
// It is used to detect character arrays to hexdump them.
|
||||||
|
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||||
|
|
||||||
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
|
// them.
|
||||||
|
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||||
|
|
||||||
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
|
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
type dumpState struct {
|
||||||
|
w io.Writer
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
ignoreNextIndent bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
// option.
|
||||||
|
func (d *dumpState) indent() {
|
||||||
|
if d.ignoreNextIndent {
|
||||||
|
d.ignoreNextIndent = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range d.pointers {
|
||||||
|
if depth >= d.depth {
|
||||||
|
delete(d.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by dereferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type information.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
d.w.Write([]byte(ve.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
|
||||||
|
// Display pointer information.
|
||||||
|
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
d.w.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.ignoreNextType = true
|
||||||
|
d.dump(ve)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||||
|
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||||
|
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
|
// Determine whether this type should be hex dumped or not. Also,
|
||||||
|
// for types which should be hexdumped, try to use the underlying data
|
||||||
|
// first, then fall back to trying to convert them to a uint8 slice.
|
||||||
|
var buf []uint8
|
||||||
|
doConvert := false
|
||||||
|
doHexDump := false
|
||||||
|
numEntries := v.Len()
|
||||||
|
if numEntries > 0 {
|
||||||
|
vt := v.Index(0).Type()
|
||||||
|
vts := vt.String()
|
||||||
|
switch {
|
||||||
|
// C types that need to be converted.
|
||||||
|
case cCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUnsignedCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUint8tCharRE.MatchString(vts):
|
||||||
|
doConvert = true
|
||||||
|
|
||||||
|
// Try to use existing uint8 slices and fall back to converting
|
||||||
|
// and copying if that fails.
|
||||||
|
case vt.Kind() == reflect.Uint8:
|
||||||
|
// We need an addressable interface to convert the type
|
||||||
|
// to a byte slice. However, the reflect package won't
|
||||||
|
// give us an interface on certain things like
|
||||||
|
// unexported struct fields in order to enforce
|
||||||
|
// visibility rules. We use unsafe, when available, to
|
||||||
|
// bypass these restrictions since this package does not
|
||||||
|
// mutate the values.
|
||||||
|
vs := v
|
||||||
|
if !vs.CanInterface() || !vs.CanAddr() {
|
||||||
|
vs = unsafeReflectValue(vs)
|
||||||
|
}
|
||||||
|
if !UnsafeDisabled {
|
||||||
|
vs = vs.Slice(0, numEntries)
|
||||||
|
|
||||||
|
// Use the existing uint8 slice if it can be
|
||||||
|
// type asserted.
|
||||||
|
iface := vs.Interface()
|
||||||
|
if slice, ok := iface.([]uint8); ok {
|
||||||
|
buf = slice
|
||||||
|
doHexDump = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The underlying data needs to be converted if it can't
|
||||||
|
// be type asserted to a uint8 slice.
|
||||||
|
doConvert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and convert the underlying type if needed.
|
||||||
|
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||||
|
// Convert and copy each element into a uint8 byte
|
||||||
|
// slice.
|
||||||
|
buf = make([]uint8, numEntries)
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
vv := v.Index(i)
|
||||||
|
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||||
|
}
|
||||||
|
doHexDump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexdump the entire slice as needed.
|
||||||
|
if doHexDump {
|
||||||
|
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||||
|
str := indent + hex.Dump(buf)
|
||||||
|
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||||
|
str = strings.TrimRight(str, d.cs.Indent)
|
||||||
|
d.w.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively call dump for each item.
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
d.dump(d.unpackValue(v.Index(i)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
// are detected and handled properly.
|
||||||
|
func (d *dumpState) dump(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
d.w.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
d.indent()
|
||||||
|
d.dumpPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !d.ignoreNextType {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write([]byte(v.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.ignoreNextType = false
|
||||||
|
|
||||||
|
// Display length and capacity if the built-in len and cap functions
|
||||||
|
// work with the value's kind and the len/cap itself is non-zero.
|
||||||
|
valueLen, valueCap := 0, 0
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||||
|
valueLen, valueCap = v.Len(), v.Cap()
|
||||||
|
case reflect.Map, reflect.String:
|
||||||
|
valueLen = v.Len()
|
||||||
|
}
|
||||||
|
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(lenEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueLen), 10)
|
||||||
|
}
|
||||||
|
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.w.Write(capEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueCap), 10)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||||
|
// is enabled
|
||||||
|
if !d.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(d.w, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(d.w, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(d.w, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(d.w, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(d.w, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(d.w, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(d.w, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.dumpSlice(v)
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
sortValues(keys, d.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
d.dump(d.unpackValue(key))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
numFields := v.NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
d.indent()
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
d.w.Write([]byte(vtf.Name))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.Field(i)))
|
||||||
|
if i < (numFields - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(d.w, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(d.w, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
// types are added.
|
||||||
|
default:
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdump is a helper function to consolidate the logic from the various public
|
||||||
|
// methods which take varying writers and config states.
|
||||||
|
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
for _, arg := range a {
|
||||||
|
if arg == nil {
|
||||||
|
w.Write(interfaceBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
w.Write(newlineBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dumpState{w: w, cs: cs}
|
||||||
|
d.pointers = make(map[uintptr]int)
|
||||||
|
d.dump(reflect.ValueOf(arg))
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(&Config, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(&Config, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by an exported package global,
|
||||||
|
spew.Config. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func Dump(a ...interface{}) {
|
||||||
|
fdump(&Config, os.Stdout, a...)
|
||||||
|
}
|
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||||
|
const supportedFlags = "0-+# "
|
||||||
|
|
||||||
|
// formatState implements the fmt.Formatter interface and contains information
|
||||||
|
// about the state of a formatting operation. The NewFormatter function can
|
||||||
|
// be used to get a new Formatter which can be used directly as arguments
|
||||||
|
// in standard fmt package printing calls.
|
||||||
|
type formatState struct {
|
||||||
|
value interface{}
|
||||||
|
fs fmt.State
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDefaultFormat recreates the original format string without precision
|
||||||
|
// and width information to pass in to fmt.Sprintf in the case of an
|
||||||
|
// unrecognized type. Unless new types are added to the language, this
|
||||||
|
// function won't ever be called.
|
||||||
|
func (f *formatState) buildDefaultFormat() (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune('v')
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructOrigFormat recreates the original format string including precision
|
||||||
|
// and width information to pass along to the standard fmt package. This allows
|
||||||
|
// automatic deferral of all format strings this package doesn't support.
|
||||||
|
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width, ok := f.fs.Width(); ok {
|
||||||
|
buf.WriteString(strconv.Itoa(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
if precision, ok := f.fs.Precision(); ok {
|
||||||
|
buf.Write(precisionBytes)
|
||||||
|
buf.WriteString(strconv.Itoa(precision))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune(verb)
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||||
|
// ensures that types for values which have been unpacked from an interface
|
||||||
|
// are displayed when the show types flag is also set.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
f.ignoreNextType = false
|
||||||
|
if !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
// Display nil if top level pointer is nil.
|
||||||
|
showTypes := f.fs.Flag('#')
|
||||||
|
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range f.pointers {
|
||||||
|
if depth >= f.depth {
|
||||||
|
delete(f.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to possibly show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by derferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.pointers[addr] = f.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type or indirection level depending on flags.
|
||||||
|
if showTypes && !f.ignoreNextType {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
f.fs.Write([]byte(ve.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
} else {
|
||||||
|
if nilFound || cycleFound {
|
||||||
|
indirects += strings.Count(ve.Type().String(), "*")
|
||||||
|
}
|
||||||
|
f.fs.Write(openAngleBytes)
|
||||||
|
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||||
|
f.fs.Write(closeAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display pointer information depending on flags.
|
||||||
|
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(f.fs, addr)
|
||||||
|
}
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
switch {
|
||||||
|
case nilFound == true:
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound == true:
|
||||||
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is the main workhorse for providing the Formatter interface. It
|
||||||
|
// uses the passed reflect value to figure out what kind of object we are
|
||||||
|
// dealing with and formats it appropriately. It is a recursive function,
|
||||||
|
// however circular data structures are detected and handled properly.
|
||||||
|
func (f *formatState) format(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
f.fs.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
f.formatPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write([]byte(v.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = false
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods
|
||||||
|
// flag is enabled.
|
||||||
|
if !f.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(f.fs, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(f.fs, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(f.fs, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(f.fs, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(f.fs, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(f.fs, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(f.fs, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
f.fs.Write(openBracketBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBracketBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
f.fs.Write([]byte(v.String()))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fs.Write(openMapBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
sortValues(keys, f.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(key))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.MapIndex(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeMapBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
numFields := v.NumField()
|
||||||
|
f.fs.Write(openBraceBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||||
|
f.fs.Write([]byte(vtf.Name))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
}
|
||||||
|
f.format(f.unpackValue(v.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(f.fs, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it if any get added.
|
||||||
|
default:
|
||||||
|
format := f.buildDefaultFormat()
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(f.fs, format, v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f.fs, format, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||||
|
// details.
|
||||||
|
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||||
|
f.fs = fs
|
||||||
|
|
||||||
|
// Use standard formatting for verbs that are not v.
|
||||||
|
if verb != 'v' {
|
||||||
|
format := f.constructOrigFormat(verb)
|
||||||
|
fmt.Fprintf(fs, format, f.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.value == nil {
|
||||||
|
if fs.Flag('#') {
|
||||||
|
fs.Write(interfaceBytes)
|
||||||
|
}
|
||||||
|
fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.format(reflect.ValueOf(f.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormatter is a helper function to consolidate the logic from the various
|
||||||
|
// public methods which take varying config states.
|
||||||
|
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||||
|
fs := &formatState{value: v, cs: cs}
|
||||||
|
fs.pointers = make(map[uintptr]int)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
Printf, Println, or Fprintf.
|
||||||
|
*/
|
||||||
|
func NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(&Config, v)
|
||||||
|
}
|
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a default spew Formatter interface.
|
||||||
|
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = NewFormatter(arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/go-cmp/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2017 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.
|
665
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
665
vendor/github.com/google/go-cmp/cmp/compare.go
generated
vendored
Normal file
|
@ -0,0 +1,665 @@
|
||||||
|
// 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 cmp determines equality of values.
|
||||||
|
//
|
||||||
|
// This package is intended to be a more powerful and safer alternative to
|
||||||
|
// reflect.DeepEqual for comparing whether two values are semantically equal.
|
||||||
|
// It is intended to only be used in tests, as performance is not a goal and
|
||||||
|
// it may panic if it cannot compare the values. Its propensity towards
|
||||||
|
// panicking means that its unsuitable for production environments where a
|
||||||
|
// spurious panic may be fatal.
|
||||||
|
//
|
||||||
|
// The primary features of cmp are:
|
||||||
|
//
|
||||||
|
// • When the default behavior of equality does not suit the needs of the test,
|
||||||
|
// custom equality functions can override the equality operation.
|
||||||
|
// For example, an equality function may report floats as equal so long as they
|
||||||
|
// are within some tolerance of each other.
|
||||||
|
//
|
||||||
|
// • Types that have an Equal method may use that method to determine equality.
|
||||||
|
// This allows package authors to determine the equality operation for the types
|
||||||
|
// that they define.
|
||||||
|
//
|
||||||
|
// • If no custom equality functions are used and no Equal method is defined,
|
||||||
|
// equality is determined by recursively comparing the primitive kinds on both
|
||||||
|
// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
|
||||||
|
// fields are not compared by default; they result in panics unless suppressed
|
||||||
|
// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly
|
||||||
|
// compared using the Exporter option.
|
||||||
|
package cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/diff"
|
||||||
|
"github.com/google/go-cmp/cmp/internal/function"
|
||||||
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
//
|
||||||
|
// • Let S be the set of all Ignore, Transformer, and Comparer options that
|
||||||
|
// 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 the number of Transformer and Comparer options in S is greater than one,
|
||||||
|
// then Equal panics because it is ambiguous which option to use.
|
||||||
|
// If S contains a single Transformer, then use that to transform 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.
|
||||||
|
// Otherwise, evaluation proceeds to the next rule.
|
||||||
|
//
|
||||||
|
// • 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
|
||||||
|
// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
|
||||||
|
// evaluation proceeds to the next rule.
|
||||||
|
//
|
||||||
|
// • Lastly, try to compare x and y based on their basic kinds.
|
||||||
|
// Simple kinds like booleans, integers, floats, complex numbers, strings, 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.
|
||||||
|
//
|
||||||
|
// Structs are equal if recursively calling Equal on all fields report equal.
|
||||||
|
// If a struct contains unexported fields, Equal panics unless an Ignore option
|
||||||
|
// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option
|
||||||
|
// explicitly permits comparing the unexported field.
|
||||||
|
//
|
||||||
|
// Slices are equal if they are both nil or both non-nil, where recursively
|
||||||
|
// calling Equal on all non-ignored slice or array elements report equal.
|
||||||
|
// Empty non-nil slices and nil slices are not equal; to equate empty slices,
|
||||||
|
// consider using cmpopts.EquateEmpty.
|
||||||
|
//
|
||||||
|
// Maps are equal if they are both nil or both non-nil, where recursively
|
||||||
|
// calling Equal on all non-ignored map entries report equal.
|
||||||
|
// Map keys are equal according to the == operator.
|
||||||
|
// To use custom comparisons for map keys, consider using cmpopts.SortMaps.
|
||||||
|
// Empty non-nil maps and nil maps are not equal; to equate empty maps,
|
||||||
|
// consider using cmpopts.EquateEmpty.
|
||||||
|
//
|
||||||
|
// Pointers and interfaces are equal if they are both nil or both non-nil,
|
||||||
|
// where they have the same underlying concrete type and recursively
|
||||||
|
// calling Equal on the underlying values reports equal.
|
||||||
|
//
|
||||||
|
// Before recursing into a pointer, slice element, or map, the current path
|
||||||
|
// is checked to detect whether the address has already been visited.
|
||||||
|
// If there is a cycle, then the pointed at values are considered equal
|
||||||
|
// only if both addresses were previously visited in the same path step.
|
||||||
|
func Equal(x, y interface{}, opts ...Option) bool {
|
||||||
|
s := newState(opts)
|
||||||
|
s.compareAny(rootStep(x, y))
|
||||||
|
return s.result.Equal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff returns a human-readable report of the differences between two values:
|
||||||
|
// y - x. It returns an empty string if and only if Equal returns true for the
|
||||||
|
// same input values and options.
|
||||||
|
//
|
||||||
|
// The output is displayed as a literal in pseudo-Go syntax.
|
||||||
|
// At the start of each line, a "-" prefix indicates an element removed from x,
|
||||||
|
// a "+" prefix to indicates an element added from y, and the lack of a prefix
|
||||||
|
// indicates an element common to both x and y. If possible, the output
|
||||||
|
// uses fmt.Stringer.String or error.Error methods to produce more humanly
|
||||||
|
// readable outputs. In such cases, the string is prefixed with either an
|
||||||
|
// 's' or 'e' character, respectively, to indicate that the method was called.
|
||||||
|
//
|
||||||
|
// Do not depend on this output being stable. If you need the ability to
|
||||||
|
// programmatically interpret the difference, consider using a custom Reporter.
|
||||||
|
func Diff(x, y interface{}, opts ...Option) string {
|
||||||
|
s := newState(opts)
|
||||||
|
|
||||||
|
// Optimization: If there are no other reporters, we can optimize for the
|
||||||
|
// common case where the result is equal (and thus no reported difference).
|
||||||
|
// This avoids the expensive construction of a difference tree.
|
||||||
|
if len(s.reporters) == 0 {
|
||||||
|
s.compareAny(rootStep(x, y))
|
||||||
|
if s.result.Equal() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s.result = diff.Result{} // Reset results
|
||||||
|
}
|
||||||
|
|
||||||
|
r := new(defaultReporter)
|
||||||
|
s.reporters = append(s.reporters, reporter{r})
|
||||||
|
s.compareAny(rootStep(x, y))
|
||||||
|
d := r.String()
|
||||||
|
if (d == "") != s.result.Equal() {
|
||||||
|
panic("inconsistent difference and equality results")
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// rootStep constructs the first path step. If x and y have differing types,
|
||||||
|
// then they are stored within an empty interface type.
|
||||||
|
func rootStep(x, y interface{}) PathStep {
|
||||||
|
vx := reflect.ValueOf(x)
|
||||||
|
vy := reflect.ValueOf(y)
|
||||||
|
|
||||||
|
// If the inputs are different types, auto-wrap them in an empty interface
|
||||||
|
// so that they have the same parent type.
|
||||||
|
var t reflect.Type
|
||||||
|
if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() {
|
||||||
|
t = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||||
|
if vx.IsValid() {
|
||||||
|
vvx := reflect.New(t).Elem()
|
||||||
|
vvx.Set(vx)
|
||||||
|
vx = vvx
|
||||||
|
}
|
||||||
|
if vy.IsValid() {
|
||||||
|
vvy := reflect.New(t).Elem()
|
||||||
|
vvy.Set(vy)
|
||||||
|
vy = vvy
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t = vx.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pathStep{t, vx, vy}
|
||||||
|
}
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
// These fields represent the "comparison state".
|
||||||
|
// Calling statelessCompare must not result in observable changes to these.
|
||||||
|
result diff.Result // The current result of comparison
|
||||||
|
curPath Path // The current path in the value tree
|
||||||
|
curPtrs pointerPath // The current set of visited pointers
|
||||||
|
reporters []reporter // Optional reporters
|
||||||
|
|
||||||
|
// recChecker checks for infinite cycles applying the same set of
|
||||||
|
// transformers upon the output of itself.
|
||||||
|
recChecker recChecker
|
||||||
|
|
||||||
|
// dynChecker triggers pseudo-random checks for option correctness.
|
||||||
|
// It is safe for statelessCompare to mutate this value.
|
||||||
|
dynChecker dynChecker
|
||||||
|
|
||||||
|
// These fields, once set by processOption, will not change.
|
||||||
|
exporters []exporter // List of exporters for structs with unexported fields
|
||||||
|
opts Options // List of all fundamental and filter options
|
||||||
|
}
|
||||||
|
|
||||||
|
func newState(opts []Option) *state {
|
||||||
|
// Always ensure a validator option exists to validate the inputs.
|
||||||
|
s := &state{opts: Options{validator{}}}
|
||||||
|
s.curPtrs.Init()
|
||||||
|
s.processOption(Options(opts))
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) processOption(opt Option) {
|
||||||
|
switch opt := opt.(type) {
|
||||||
|
case nil:
|
||||||
|
case Options:
|
||||||
|
for _, o := range opt {
|
||||||
|
s.processOption(o)
|
||||||
|
}
|
||||||
|
case coreOption:
|
||||||
|
type filtered interface {
|
||||||
|
isFiltered() bool
|
||||||
|
}
|
||||||
|
if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() {
|
||||||
|
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
||||||
|
}
|
||||||
|
s.opts = append(s.opts, opt)
|
||||||
|
case exporter:
|
||||||
|
s.exporters = append(s.exporters, opt)
|
||||||
|
case reporter:
|
||||||
|
s.reporters = append(s.reporters, opt)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown option %T", opt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// statelessCompare compares two values and returns the result.
|
||||||
|
// This function is stateless in that it does not alter the current result,
|
||||||
|
// or output to any registered reporters.
|
||||||
|
func (s *state) statelessCompare(step PathStep) diff.Result {
|
||||||
|
// We do not save and restore curPath and curPtrs because all of the
|
||||||
|
// compareX methods should properly push and pop from them.
|
||||||
|
// It is an implementation bug if the contents of the paths differ from
|
||||||
|
// when calling this function to when returning from it.
|
||||||
|
|
||||||
|
oldResult, oldReporters := s.result, s.reporters
|
||||||
|
s.result = diff.Result{} // Reset result
|
||||||
|
s.reporters = nil // Remove reporters to avoid spurious printouts
|
||||||
|
s.compareAny(step)
|
||||||
|
res := s.result
|
||||||
|
s.result, s.reporters = oldResult, oldReporters
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) compareAny(step PathStep) {
|
||||||
|
// Update the path stack.
|
||||||
|
s.curPath.push(step)
|
||||||
|
defer s.curPath.pop()
|
||||||
|
for _, r := range s.reporters {
|
||||||
|
r.PushStep(step)
|
||||||
|
defer r.PopStep()
|
||||||
|
}
|
||||||
|
s.recChecker.Check(s.curPath)
|
||||||
|
|
||||||
|
// Cycle-detection for slice elements (see NOTE in compareSlice).
|
||||||
|
t := step.Type()
|
||||||
|
vx, vy := step.Values()
|
||||||
|
if si, ok := step.(SliceIndex); ok && si.isSlice && vx.IsValid() && vy.IsValid() {
|
||||||
|
px, py := vx.Addr(), vy.Addr()
|
||||||
|
if eq, visited := s.curPtrs.Push(px, py); visited {
|
||||||
|
s.report(eq, reportByCycle)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.curPtrs.Pop(px, py)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 1: Check whether an option applies on this node in the value tree.
|
||||||
|
if s.tryOptions(t, vx, vy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 2: Check whether the type has a valid Equal method.
|
||||||
|
if s.tryMethod(t, vx, vy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 3: Compare based on the underlying kind.
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
s.report(vx.Bool() == vy.Bool(), 0)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
s.report(vx.Int() == vy.Int(), 0)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
s.report(vx.Uint() == vy.Uint(), 0)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
s.report(vx.Float() == vy.Float(), 0)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
s.report(vx.Complex() == vy.Complex(), 0)
|
||||||
|
case reflect.String:
|
||||||
|
s.report(vx.String() == vy.String(), 0)
|
||||||
|
case reflect.Chan, reflect.UnsafePointer:
|
||||||
|
s.report(vx.Pointer() == vy.Pointer(), 0)
|
||||||
|
case reflect.Func:
|
||||||
|
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||||
|
case reflect.Struct:
|
||||||
|
s.compareStruct(t, vx, vy)
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
s.compareSlice(t, vx, vy)
|
||||||
|
case reflect.Map:
|
||||||
|
s.compareMap(t, vx, vy)
|
||||||
|
case reflect.Ptr:
|
||||||
|
s.comparePtr(t, vx, vy)
|
||||||
|
case reflect.Interface:
|
||||||
|
s.compareInterface(t, vx, vy)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("%v kind not handled", t.Kind()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool {
|
||||||
|
// Evaluate all filters and apply the remaining options.
|
||||||
|
if opt := s.opts.filter(s, t, vx, vy); opt != nil {
|
||||||
|
opt.apply(s, vx, vy)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool {
|
||||||
|
// Check if this type even has an Equal method.
|
||||||
|
m, ok := t.MethodByName("Equal")
|
||||||
|
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
eq := s.callTTBFunc(m.Func, vx, vy)
|
||||||
|
s.report(eq, reportByMethod)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value {
|
||||||
|
if !s.dynChecker.Next() {
|
||||||
|
return f.Call([]reflect.Value{v})[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the function twice and ensure that we get the same results back.
|
||||||
|
// We run in goroutines so that the race detector (if enabled) can detect
|
||||||
|
// unsafe mutations to the input.
|
||||||
|
c := make(chan reflect.Value)
|
||||||
|
go detectRaces(c, f, v)
|
||||||
|
got := <-c
|
||||||
|
want := f.Call([]reflect.Value{v})[0]
|
||||||
|
if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() {
|
||||||
|
// To avoid false-positives with non-reflexive equality operations,
|
||||||
|
// we sanity check whether a value is equal to itself.
|
||||||
|
if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() {
|
||||||
|
return want
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f)))
|
||||||
|
}
|
||||||
|
return want
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) callTTBFunc(f, x, y reflect.Value) bool {
|
||||||
|
if !s.dynChecker.Next() {
|
||||||
|
return f.Call([]reflect.Value{x, y})[0].Bool()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swapping the input arguments is sufficient to check that
|
||||||
|
// f is symmetric and deterministic.
|
||||||
|
// We run in goroutines so that the race detector (if enabled) can detect
|
||||||
|
// unsafe mutations to the input.
|
||||||
|
c := make(chan reflect.Value)
|
||||||
|
go detectRaces(c, f, y, x)
|
||||||
|
got := <-c
|
||||||
|
want := f.Call([]reflect.Value{x, y})[0].Bool()
|
||||||
|
if !got.IsValid() || got.Bool() != want {
|
||||||
|
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f)))
|
||||||
|
}
|
||||||
|
return want
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) {
|
||||||
|
var ret reflect.Value
|
||||||
|
defer func() {
|
||||||
|
recover() // Ignore panics, let the other call to f panic instead
|
||||||
|
c <- ret
|
||||||
|
}()
|
||||||
|
ret = f.Call(vs)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) {
|
||||||
|
var addr bool
|
||||||
|
var vax, vay reflect.Value // Addressable versions of vx and vy
|
||||||
|
|
||||||
|
var mayForce, mayForceInit bool
|
||||||
|
step := StructField{&structField{}}
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
step.typ = t.Field(i).Type
|
||||||
|
step.vx = vx.Field(i)
|
||||||
|
step.vy = vy.Field(i)
|
||||||
|
step.name = t.Field(i).Name
|
||||||
|
step.idx = i
|
||||||
|
step.unexported = !isExported(step.name)
|
||||||
|
if step.unexported {
|
||||||
|
if step.name == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Defer checking of unexported fields until later to give an
|
||||||
|
// Ignore a chance to ignore the field.
|
||||||
|
if !vax.IsValid() || !vay.IsValid() {
|
||||||
|
// For retrieveUnexportedField to work, the parent struct must
|
||||||
|
// be addressable. Create a new copy of the values if
|
||||||
|
// necessary to make them addressable.
|
||||||
|
addr = vx.CanAddr() || vy.CanAddr()
|
||||||
|
vax = makeAddressable(vx)
|
||||||
|
vay = makeAddressable(vy)
|
||||||
|
}
|
||||||
|
if !mayForceInit {
|
||||||
|
for _, xf := range s.exporters {
|
||||||
|
mayForce = mayForce || xf(t)
|
||||||
|
}
|
||||||
|
mayForceInit = true
|
||||||
|
}
|
||||||
|
step.mayForce = mayForce
|
||||||
|
step.paddr = addr
|
||||||
|
step.pvx = vax
|
||||||
|
step.pvy = vay
|
||||||
|
step.field = t.Field(i)
|
||||||
|
}
|
||||||
|
s.compareAny(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) {
|
||||||
|
isSlice := t.Kind() == reflect.Slice
|
||||||
|
if isSlice && (vx.IsNil() || vy.IsNil()) {
|
||||||
|
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: It is incorrect to call curPtrs.Push on the slice header pointer
|
||||||
|
// since slices represents a list of pointers, rather than a single pointer.
|
||||||
|
// The pointer checking logic must be handled on a per-element basis
|
||||||
|
// in compareAny.
|
||||||
|
//
|
||||||
|
// A slice header (see reflect.SliceHeader) in Go is a tuple of a starting
|
||||||
|
// pointer P, a length N, and a capacity C. Supposing each slice element has
|
||||||
|
// a memory size of M, then the slice is equivalent to the list of pointers:
|
||||||
|
// [P+i*M for i in range(N)]
|
||||||
|
//
|
||||||
|
// For example, v[:0] and v[:1] are slices with the same starting pointer,
|
||||||
|
// but they are clearly different values. Using the slice pointer alone
|
||||||
|
// violates the assumption that equal pointers implies equal values.
|
||||||
|
|
||||||
|
step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}, isSlice: isSlice}}
|
||||||
|
withIndexes := func(ix, iy int) SliceIndex {
|
||||||
|
if ix >= 0 {
|
||||||
|
step.vx, step.xkey = vx.Index(ix), ix
|
||||||
|
} else {
|
||||||
|
step.vx, step.xkey = reflect.Value{}, -1
|
||||||
|
}
|
||||||
|
if iy >= 0 {
|
||||||
|
step.vy, step.ykey = vy.Index(iy), iy
|
||||||
|
} else {
|
||||||
|
step.vy, step.ykey = reflect.Value{}, -1
|
||||||
|
}
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore options are able to ignore missing elements in a slice.
|
||||||
|
// However, detecting these reliably requires an optimal differencing
|
||||||
|
// algorithm, for which diff.Difference is not.
|
||||||
|
//
|
||||||
|
// Instead, we first iterate through both slices to detect which elements
|
||||||
|
// would be ignored if standing alone. The index of non-discarded elements
|
||||||
|
// are stored in a separate slice, which diffing is then performed on.
|
||||||
|
var indexesX, indexesY []int
|
||||||
|
var ignoredX, ignoredY []bool
|
||||||
|
for ix := 0; ix < vx.Len(); ix++ {
|
||||||
|
ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0
|
||||||
|
if !ignored {
|
||||||
|
indexesX = append(indexesX, ix)
|
||||||
|
}
|
||||||
|
ignoredX = append(ignoredX, ignored)
|
||||||
|
}
|
||||||
|
for iy := 0; iy < vy.Len(); iy++ {
|
||||||
|
ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0
|
||||||
|
if !ignored {
|
||||||
|
indexesY = append(indexesY, iy)
|
||||||
|
}
|
||||||
|
ignoredY = append(ignoredY, ignored)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute an edit-script for slices vx and vy (excluding ignored elements).
|
||||||
|
edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result {
|
||||||
|
return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy]))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Replay the ignore-scripts and the edit-script.
|
||||||
|
var ix, iy int
|
||||||
|
for ix < vx.Len() || iy < vy.Len() {
|
||||||
|
var e diff.EditType
|
||||||
|
switch {
|
||||||
|
case ix < len(ignoredX) && ignoredX[ix]:
|
||||||
|
e = diff.UniqueX
|
||||||
|
case iy < len(ignoredY) && ignoredY[iy]:
|
||||||
|
e = diff.UniqueY
|
||||||
|
default:
|
||||||
|
e, edits = edits[0], edits[1:]
|
||||||
|
}
|
||||||
|
switch e {
|
||||||
|
case diff.UniqueX:
|
||||||
|
s.compareAny(withIndexes(ix, -1))
|
||||||
|
ix++
|
||||||
|
case diff.UniqueY:
|
||||||
|
s.compareAny(withIndexes(-1, iy))
|
||||||
|
iy++
|
||||||
|
default:
|
||||||
|
s.compareAny(withIndexes(ix, iy))
|
||||||
|
ix++
|
||||||
|
iy++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) {
|
||||||
|
if vx.IsNil() || vy.IsNil() {
|
||||||
|
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cycle-detection for maps.
|
||||||
|
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||||
|
s.report(eq, reportByCycle)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.curPtrs.Pop(vx, vy)
|
||||||
|
|
||||||
|
// We combine and sort the two map keys so that we can perform the
|
||||||
|
// comparisons in a deterministic order.
|
||||||
|
step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}}
|
||||||
|
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
|
||||||
|
step.vx = vx.MapIndex(k)
|
||||||
|
step.vy = vy.MapIndex(k)
|
||||||
|
step.key = k
|
||||||
|
if !step.vx.IsValid() && !step.vy.IsValid() {
|
||||||
|
// It is possible for both vx and vy to be invalid if the
|
||||||
|
// key contained a NaN value in it.
|
||||||
|
//
|
||||||
|
// Even with the ability to retrieve NaN keys in Go 1.12,
|
||||||
|
// there still isn't a sensible way to compare the values since
|
||||||
|
// a NaN key may map to multiple unordered values.
|
||||||
|
// The most reasonable way to compare NaNs would be to compare the
|
||||||
|
// set of values. However, this is impossible to do efficiently
|
||||||
|
// since set equality is provably an O(n^2) operation given only
|
||||||
|
// an Equal function. If we had a Less function or Hash function,
|
||||||
|
// this could be done in O(n*log(n)) or O(n), respectively.
|
||||||
|
//
|
||||||
|
// Rather than adding complex logic to deal with NaNs, make it
|
||||||
|
// the user's responsibility to compare such obscure maps.
|
||||||
|
const help = "consider providing a Comparer to compare the map"
|
||||||
|
panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help))
|
||||||
|
}
|
||||||
|
s.compareAny(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) {
|
||||||
|
if vx.IsNil() || vy.IsNil() {
|
||||||
|
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cycle-detection for pointers.
|
||||||
|
if eq, visited := s.curPtrs.Push(vx, vy); visited {
|
||||||
|
s.report(eq, reportByCycle)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer s.curPtrs.Pop(vx, vy)
|
||||||
|
|
||||||
|
vx, vy = vx.Elem(), vy.Elem()
|
||||||
|
s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) {
|
||||||
|
if vx.IsNil() || vy.IsNil() {
|
||||||
|
s.report(vx.IsNil() && vy.IsNil(), 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vx, vy = vx.Elem(), vy.Elem()
|
||||||
|
if vx.Type() != vy.Type() {
|
||||||
|
s.report(false, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) report(eq bool, rf resultFlags) {
|
||||||
|
if rf&reportByIgnore == 0 {
|
||||||
|
if eq {
|
||||||
|
s.result.NumSame++
|
||||||
|
rf |= reportEqual
|
||||||
|
} else {
|
||||||
|
s.result.NumDiff++
|
||||||
|
rf |= reportUnequal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range s.reporters {
|
||||||
|
r.Report(Result{flags: rf})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recChecker tracks the state needed to periodically perform checks that
|
||||||
|
// user provided transformers are not stuck in an infinitely recursive cycle.
|
||||||
|
type recChecker struct{ next int }
|
||||||
|
|
||||||
|
// Check scans the Path for any recursive transformers and panics when any
|
||||||
|
// recursive transformers are detected. Note that the presence of a
|
||||||
|
// recursive Transformer does not necessarily imply an infinite cycle.
|
||||||
|
// As such, this check only activates after some minimal number of path steps.
|
||||||
|
func (rc *recChecker) Check(p Path) {
|
||||||
|
const minLen = 1 << 16
|
||||||
|
if rc.next == 0 {
|
||||||
|
rc.next = minLen
|
||||||
|
}
|
||||||
|
if len(p) < rc.next {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rc.next <<= 1
|
||||||
|
|
||||||
|
// Check whether the same transformer has appeared at least twice.
|
||||||
|
var ss []string
|
||||||
|
m := map[Option]int{}
|
||||||
|
for _, ps := range p {
|
||||||
|
if t, ok := ps.(Transform); ok {
|
||||||
|
t := t.Option()
|
||||||
|
if m[t] == 1 { // Transformer was used exactly once before
|
||||||
|
tf := t.(*transformer).fnc.Type()
|
||||||
|
ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0)))
|
||||||
|
}
|
||||||
|
m[t]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ss) > 0 {
|
||||||
|
const warning = "recursive set of Transformers detected"
|
||||||
|
const help = "consider using cmpopts.AcyclicTransformer"
|
||||||
|
set := strings.Join(ss, "\n\t")
|
||||||
|
panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynChecker tracks the state needed to periodically perform checks that
|
||||||
|
// user provided functions are symmetric and deterministic.
|
||||||
|
// The zero value is safe for immediate use.
|
||||||
|
type dynChecker struct{ curr, next int }
|
||||||
|
|
||||||
|
// Next increments the state and reports whether a check should be performed.
|
||||||
|
//
|
||||||
|
// 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 ...
|
||||||
|
// See https://en.wikipedia.org/wiki/Triangular_number
|
||||||
|
//
|
||||||
|
// This sequence ensures that the cost of checks drops significantly as
|
||||||
|
// the number of functions calls grows larger.
|
||||||
|
func (dc *dynChecker) Next() bool {
|
||||||
|
ok := dc.curr == dc.next
|
||||||
|
if ok {
|
||||||
|
dc.curr = 0
|
||||||
|
dc.next++
|
||||||
|
}
|
||||||
|
dc.curr++
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeAddressable returns a value that is always addressable.
|
||||||
|
// It returns the input verbatim if it is already addressable,
|
||||||
|
// otherwise it creates a new value and returns an addressable copy.
|
||||||
|
func makeAddressable(v reflect.Value) reflect.Value {
|
||||||
|
if v.CanAddr() {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
vc := reflect.New(v.Type()).Elem()
|
||||||
|
vc.Set(v)
|
||||||
|
return vc
|
||||||
|
}
|
16
vendor/github.com/google/go-cmp/cmp/export_panic.go
generated
vendored
Normal file
16
vendor/github.com/google/go-cmp/cmp/export_panic.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build purego
|
||||||
|
// +build purego
|
||||||
|
|
||||||
|
package cmp
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const supportExporters = false
|
||||||
|
|
||||||
|
func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value {
|
||||||
|
panic("no support for forcibly accessing unexported fields")
|
||||||
|
}
|
36
vendor/github.com/google/go-cmp/cmp/export_unsafe.go
generated
vendored
Normal file
36
vendor/github.com/google/go-cmp/cmp/export_unsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !purego
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
package cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const supportExporters = true
|
||||||
|
|
||||||
|
// retrieveUnexportedField uses unsafe to forcibly retrieve any field from
|
||||||
|
// a struct such that the value has read-write permissions.
|
||||||
|
//
|
||||||
|
// The parent struct, v, must be addressable, while f must be a StructField
|
||||||
|
// describing the field to retrieve. If addr is false,
|
||||||
|
// then the returned value will be shallowed copied to be non-addressable.
|
||||||
|
func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value {
|
||||||
|
ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem()
|
||||||
|
if !addr {
|
||||||
|
// A field is addressable if and only if the struct is addressable.
|
||||||
|
// If the original parent value was not addressable, shallow copy the
|
||||||
|
// value to make it non-addressable to avoid leaking an implementation
|
||||||
|
// detail of how forcibly exporting a field works.
|
||||||
|
if ve.Kind() == reflect.Interface && ve.IsNil() {
|
||||||
|
return reflect.Zero(f.Type)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(ve.Interface()).Convert(f.Type)
|
||||||
|
}
|
||||||
|
return ve
|
||||||
|
}
|
18
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
18
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !cmp_debug
|
||||||
|
// +build !cmp_debug
|
||||||
|
|
||||||
|
package diff
|
||||||
|
|
||||||
|
var debug debugger
|
||||||
|
|
||||||
|
type debugger struct{}
|
||||||
|
|
||||||
|
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
func (debugger) Update() {}
|
||||||
|
func (debugger) Finish() {}
|
123
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
123
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build cmp_debug
|
||||||
|
// +build cmp_debug
|
||||||
|
|
||||||
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The algorithm can be seen running in real-time by enabling debugging:
|
||||||
|
// go test -tags=cmp_debug -v
|
||||||
|
//
|
||||||
|
// Example output:
|
||||||
|
// === RUN TestDifference/#34
|
||||||
|
// ┌───────────────────────────────┐
|
||||||
|
// │ \ · · · · · · · · · · · · · · │
|
||||||
|
// │ · # · · · · · · · · · · · · · │
|
||||||
|
// │ · \ · · · · · · · · · · · · · │
|
||||||
|
// │ · · \ · · · · · · · · · · · · │
|
||||||
|
// │ · · · X # · · · · · · · · · · │
|
||||||
|
// │ · · · # \ · · · · · · · · · · │
|
||||||
|
// │ · · · · · # # · · · · · · · · │
|
||||||
|
// │ · · · · · # \ · · · · · · · · │
|
||||||
|
// │ · · · · · · · \ · · · · · · · │
|
||||||
|
// │ · · · · · · · · \ · · · · · · │
|
||||||
|
// │ · · · · · · · · · \ · · · · · │
|
||||||
|
// │ · · · · · · · · · · \ · · # · │
|
||||||
|
// │ · · · · · · · · · · · \ # # · │
|
||||||
|
// │ · · · · · · · · · · · # # # · │
|
||||||
|
// │ · · · · · · · · · · # # # # · │
|
||||||
|
// │ · · · · · · · · · # # # # # · │
|
||||||
|
// │ · · · · · · · · · · · · · · \ │
|
||||||
|
// └───────────────────────────────┘
|
||||||
|
// [.Y..M.XY......YXYXY.|]
|
||||||
|
//
|
||||||
|
// The grid represents the edit-graph where the horizontal axis represents
|
||||||
|
// list X and the vertical axis represents list Y. The start of the two lists
|
||||||
|
// is the top-left, while the ends are the bottom-right. The '·' represents
|
||||||
|
// an unexplored node in the graph. The '\' indicates that the two symbols
|
||||||
|
// from list X and Y are equal. The 'X' indicates that two symbols are similar
|
||||||
|
// (but not exactly equal) to each other. The '#' indicates that the two symbols
|
||||||
|
// are different (and not similar). The algorithm traverses this graph trying to
|
||||||
|
// make the paths starting in the top-left and the bottom-right connect.
|
||||||
|
//
|
||||||
|
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
|
||||||
|
// the currently established path from the forward and reverse searches,
|
||||||
|
// separated by a '|' character.
|
||||||
|
|
||||||
|
const (
|
||||||
|
updateDelay = 100 * time.Millisecond
|
||||||
|
finishDelay = 500 * time.Millisecond
|
||||||
|
ansiTerminal = true // ANSI escape codes used to move terminal cursor
|
||||||
|
)
|
||||||
|
|
||||||
|
var debug debugger
|
||||||
|
|
||||||
|
type debugger struct {
|
||||||
|
sync.Mutex
|
||||||
|
p1, p2 EditScript
|
||||||
|
fwdPath, revPath *EditScript
|
||||||
|
grid []byte
|
||||||
|
lines int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
|
||||||
|
dbg.Lock()
|
||||||
|
dbg.fwdPath, dbg.revPath = p1, p2
|
||||||
|
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
|
||||||
|
row := "│ " + strings.Repeat("· ", nx) + "│\n"
|
||||||
|
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
|
||||||
|
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
|
||||||
|
dbg.lines = strings.Count(dbg.String(), "\n")
|
||||||
|
fmt.Print(dbg)
|
||||||
|
|
||||||
|
// Wrap the EqualFunc so that we can intercept each result.
|
||||||
|
return func(ix, iy int) (r Result) {
|
||||||
|
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
|
||||||
|
for i := range cell {
|
||||||
|
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
|
||||||
|
}
|
||||||
|
switch r = f(ix, iy); {
|
||||||
|
case r.Equal():
|
||||||
|
cell[0] = '\\'
|
||||||
|
case r.Similar():
|
||||||
|
cell[0] = 'X'
|
||||||
|
default:
|
||||||
|
cell[0] = '#'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) Update() {
|
||||||
|
dbg.print(updateDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) Finish() {
|
||||||
|
dbg.print(finishDelay)
|
||||||
|
dbg.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) String() string {
|
||||||
|
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
|
||||||
|
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
|
||||||
|
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) print(d time.Duration) {
|
||||||
|
if ansiTerminal {
|
||||||
|
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
|
||||||
|
}
|
||||||
|
fmt.Print(dbg)
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
398
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
398
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go
generated
vendored
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
// 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 diff implements an algorithm for producing edit-scripts.
|
||||||
|
// The edit-script is a sequence of operations needed to transform one list
|
||||||
|
// of symbols into another (or vice-versa). The edits allowed are insertions,
|
||||||
|
// deletions, and modifications. The summation of all edits is called the
|
||||||
|
// Levenshtein distance as this problem is well-known in computer science.
|
||||||
|
//
|
||||||
|
// This package prioritizes performance over accuracy. That is, the run time
|
||||||
|
// is more important than obtaining a minimal Levenshtein distance.
|
||||||
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/flags"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EditType represents a single operation within an edit-script.
|
||||||
|
type EditType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Identity indicates that a symbol pair is identical in both list X and Y.
|
||||||
|
Identity EditType = iota
|
||||||
|
// UniqueX indicates that a symbol only exists in X and not Y.
|
||||||
|
UniqueX
|
||||||
|
// UniqueY indicates that a symbol only exists in Y and not X.
|
||||||
|
UniqueY
|
||||||
|
// Modified indicates that a symbol pair is a modification of each other.
|
||||||
|
Modified
|
||||||
|
)
|
||||||
|
|
||||||
|
// EditScript represents the series of differences between two lists.
|
||||||
|
type EditScript []EditType
|
||||||
|
|
||||||
|
// String returns a human-readable string representing the edit-script where
|
||||||
|
// Identity, UniqueX, UniqueY, and Modified are represented by the
|
||||||
|
// '.', 'X', 'Y', and 'M' characters, respectively.
|
||||||
|
func (es EditScript) String() string {
|
||||||
|
b := make([]byte, len(es))
|
||||||
|
for i, e := range es {
|
||||||
|
switch e {
|
||||||
|
case Identity:
|
||||||
|
b[i] = '.'
|
||||||
|
case UniqueX:
|
||||||
|
b[i] = 'X'
|
||||||
|
case UniqueY:
|
||||||
|
b[i] = 'Y'
|
||||||
|
case Modified:
|
||||||
|
b[i] = 'M'
|
||||||
|
default:
|
||||||
|
panic("invalid edit-type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stats returns a histogram of the number of each type of edit operation.
|
||||||
|
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
|
||||||
|
for _, e := range es {
|
||||||
|
switch e {
|
||||||
|
case Identity:
|
||||||
|
s.NI++
|
||||||
|
case UniqueX:
|
||||||
|
s.NX++
|
||||||
|
case UniqueY:
|
||||||
|
s.NY++
|
||||||
|
case Modified:
|
||||||
|
s.NM++
|
||||||
|
default:
|
||||||
|
panic("invalid edit-type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
|
||||||
|
// lists X and Y are equal.
|
||||||
|
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
|
||||||
|
|
||||||
|
// LenX is the length of the X list.
|
||||||
|
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
|
||||||
|
|
||||||
|
// LenY is the length of the Y list.
|
||||||
|
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
|
||||||
|
|
||||||
|
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
|
||||||
|
// When called by Difference, the index is guaranteed to be within nx and ny.
|
||||||
|
type EqualFunc func(ix int, iy int) Result
|
||||||
|
|
||||||
|
// Result is the result of comparison.
|
||||||
|
// NumSame is the number of sub-elements that are equal.
|
||||||
|
// NumDiff is the number of sub-elements that are not equal.
|
||||||
|
type Result struct{ NumSame, NumDiff int }
|
||||||
|
|
||||||
|
// BoolResult returns a Result that is either Equal or not Equal.
|
||||||
|
func BoolResult(b bool) Result {
|
||||||
|
if b {
|
||||||
|
return Result{NumSame: 1} // Equal, Similar
|
||||||
|
} else {
|
||||||
|
return Result{NumDiff: 2} // Not Equal, not Similar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal indicates whether the symbols are equal. Two symbols are equal
|
||||||
|
// if and only if NumDiff == 0. If Equal, then they are also Similar.
|
||||||
|
func (r Result) Equal() bool { return r.NumDiff == 0 }
|
||||||
|
|
||||||
|
// Similar indicates whether two symbols are similar and may be represented
|
||||||
|
// by using the Modified type. As a special case, we consider binary comparisons
|
||||||
|
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
|
||||||
|
//
|
||||||
|
// The exact ratio of NumSame to NumDiff to determine similarity may change.
|
||||||
|
func (r Result) Similar() bool {
|
||||||
|
// Use NumSame+1 to offset NumSame so that binary comparisons are similar.
|
||||||
|
return r.NumSame+1 >= r.NumDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||||
|
|
||||||
|
// Difference reports whether two lists of lengths nx and ny are equal
|
||||||
|
// given the definition of equality provided as f.
|
||||||
|
//
|
||||||
|
// This function returns an edit-script, which is a sequence of operations
|
||||||
|
// needed to convert one list into the other. The following invariants for
|
||||||
|
// the edit-script are maintained:
|
||||||
|
// • eq == (es.Dist()==0)
|
||||||
|
// • nx == es.LenX()
|
||||||
|
// • ny == es.LenY()
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// favors performance over optimality. The exact output is not guaranteed to
|
||||||
|
// be stable and may change over time.
|
||||||
|
func Difference(nx, ny int, f EqualFunc) (es EditScript) {
|
||||||
|
// This algorithm is based on traversing what is known as an "edit-graph".
|
||||||
|
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
|
||||||
|
// by Eugene W. Myers. Since D can be as large as N itself, this is
|
||||||
|
// effectively O(N^2). Unlike the algorithm from that paper, we are not
|
||||||
|
// interested in the optimal path, but at least some "decent" path.
|
||||||
|
//
|
||||||
|
// For example, let X and Y be lists of symbols:
|
||||||
|
// X = [A B C A B B A]
|
||||||
|
// Y = [C B A B A C]
|
||||||
|
//
|
||||||
|
// The edit-graph can be drawn as the following:
|
||||||
|
// A B C A B B A
|
||||||
|
// ┌─────────────┐
|
||||||
|
// C │_|_|\|_|_|_|_│ 0
|
||||||
|
// B │_|\|_|_|\|\|_│ 1
|
||||||
|
// A │\|_|_|\|_|_|\│ 2
|
||||||
|
// B │_|\|_|_|\|\|_│ 3
|
||||||
|
// A │\|_|_|\|_|_|\│ 4
|
||||||
|
// C │ | |\| | | | │ 5
|
||||||
|
// └─────────────┘ 6
|
||||||
|
// 0 1 2 3 4 5 6 7
|
||||||
|
//
|
||||||
|
// List X is written along the horizontal axis, while list Y is written
|
||||||
|
// along the vertical axis. At any point on this grid, if the symbol in
|
||||||
|
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
|
||||||
|
// The goal of any minimal edit-script algorithm is to find a path from the
|
||||||
|
// top-left corner to the bottom-right corner, while traveling through the
|
||||||
|
// fewest horizontal or vertical edges.
|
||||||
|
// A horizontal edge is equivalent to inserting a symbol from list X.
|
||||||
|
// A vertical edge is equivalent to inserting a symbol from list Y.
|
||||||
|
// A diagonal edge is equivalent to a matching symbol between both X and Y.
|
||||||
|
|
||||||
|
// Invariants:
|
||||||
|
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
|
||||||
|
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
|
||||||
|
//
|
||||||
|
// In general:
|
||||||
|
// • fwdFrontier.X < revFrontier.X
|
||||||
|
// • fwdFrontier.Y < revFrontier.Y
|
||||||
|
// Unless, it is time for the algorithm to terminate.
|
||||||
|
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
|
||||||
|
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
|
||||||
|
fwdFrontier := fwdPath.point // Forward search frontier
|
||||||
|
revFrontier := revPath.point // Reverse search frontier
|
||||||
|
|
||||||
|
// Search budget bounds the cost of searching for better paths.
|
||||||
|
// The longest sequence of non-matching symbols that can be tolerated is
|
||||||
|
// approximately the square-root of the search budget.
|
||||||
|
searchBudget := 4 * (nx + ny) // O(n)
|
||||||
|
|
||||||
|
// Running the tests with the "cmp_debug" build tag prints a visualization
|
||||||
|
// of the algorithm running in real-time. This is educational for
|
||||||
|
// understanding how the algorithm works. See debug_enable.go.
|
||||||
|
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
|
||||||
|
|
||||||
|
// The algorithm below is a greedy, meet-in-the-middle algorithm for
|
||||||
|
// computing sub-optimal edit-scripts between two lists.
|
||||||
|
//
|
||||||
|
// The algorithm is approximately as follows:
|
||||||
|
// • 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 end (the bottom-right corner). The goal of
|
||||||
|
// the search is connect with the search from the opposite corner.
|
||||||
|
// • As we search, we build a path in a greedy manner, where the first
|
||||||
|
// match seen is added to the path (this is sub-optimal, but provides a
|
||||||
|
// decent result in practice). When matches are found, we try the next pair
|
||||||
|
// of symbols in the lists and follow all matches as far as possible.
|
||||||
|
// • When searching for matches, we search along a diagonal going through
|
||||||
|
// through the "frontier" point. If no matches are found, 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
|
||||||
|
// or in the reverse direction. We do both because it is commonly observed
|
||||||
|
// that two lists commonly differ because elements were added to the front
|
||||||
|
// or end of the other list.
|
||||||
|
//
|
||||||
|
// Non-deterministically start with either the forward or reverse direction
|
||||||
|
// to introduce some deliberate instability so that we have the flexibility
|
||||||
|
// to change this algorithm in the future.
|
||||||
|
if flags.Deterministic || randBool {
|
||||||
|
goto forwardSearch
|
||||||
|
} else {
|
||||||
|
goto reverseSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardSearch:
|
||||||
|
{
|
||||||
|
// Forward search from the beginning.
|
||||||
|
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||||
|
goto finishSearch
|
||||||
|
}
|
||||||
|
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||||
|
// Search in a diagonal pattern for a match.
|
||||||
|
z := zigzag(i)
|
||||||
|
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
|
||||||
|
switch {
|
||||||
|
case p.X >= revPath.X || p.Y < fwdPath.Y:
|
||||||
|
stop1 = true // Hit top-right corner
|
||||||
|
case p.Y >= revPath.Y || p.X < fwdPath.X:
|
||||||
|
stop2 = true // Hit bottom-left corner
|
||||||
|
case f(p.X, p.Y).Equal():
|
||||||
|
// Match found, so connect the path to this point.
|
||||||
|
fwdPath.connect(p, f)
|
||||||
|
fwdPath.append(Identity)
|
||||||
|
// Follow sequence of matches as far as possible.
|
||||||
|
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||||
|
if !f(fwdPath.X, fwdPath.Y).Equal() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fwdPath.append(Identity)
|
||||||
|
}
|
||||||
|
fwdFrontier = fwdPath.point
|
||||||
|
stop1, stop2 = true, true
|
||||||
|
default:
|
||||||
|
searchBudget-- // Match not found
|
||||||
|
}
|
||||||
|
debug.Update()
|
||||||
|
}
|
||||||
|
// Advance the frontier towards reverse point.
|
||||||
|
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
|
||||||
|
fwdFrontier.X++
|
||||||
|
} else {
|
||||||
|
fwdFrontier.Y++
|
||||||
|
}
|
||||||
|
goto reverseSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseSearch:
|
||||||
|
{
|
||||||
|
// Reverse search from the end.
|
||||||
|
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||||
|
goto finishSearch
|
||||||
|
}
|
||||||
|
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||||
|
// Search in a diagonal pattern for a match.
|
||||||
|
z := zigzag(i)
|
||||||
|
p := point{revFrontier.X - z, revFrontier.Y + z}
|
||||||
|
switch {
|
||||||
|
case fwdPath.X >= p.X || revPath.Y < p.Y:
|
||||||
|
stop1 = true // Hit bottom-left corner
|
||||||
|
case fwdPath.Y >= p.Y || revPath.X < p.X:
|
||||||
|
stop2 = true // Hit top-right corner
|
||||||
|
case f(p.X-1, p.Y-1).Equal():
|
||||||
|
// Match found, so connect the path to this point.
|
||||||
|
revPath.connect(p, f)
|
||||||
|
revPath.append(Identity)
|
||||||
|
// Follow sequence of matches as far as possible.
|
||||||
|
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||||
|
if !f(revPath.X-1, revPath.Y-1).Equal() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
revPath.append(Identity)
|
||||||
|
}
|
||||||
|
revFrontier = revPath.point
|
||||||
|
stop1, stop2 = true, true
|
||||||
|
default:
|
||||||
|
searchBudget-- // Match not found
|
||||||
|
}
|
||||||
|
debug.Update()
|
||||||
|
}
|
||||||
|
// Advance the frontier towards forward point.
|
||||||
|
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
|
||||||
|
revFrontier.X--
|
||||||
|
} else {
|
||||||
|
revFrontier.Y--
|
||||||
|
}
|
||||||
|
goto forwardSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
finishSearch:
|
||||||
|
// Join the forward and reverse paths and then append the reverse path.
|
||||||
|
fwdPath.connect(revPath.point, f)
|
||||||
|
for i := len(revPath.es) - 1; i >= 0; i-- {
|
||||||
|
t := revPath.es[i]
|
||||||
|
revPath.es = revPath.es[:i]
|
||||||
|
fwdPath.append(t)
|
||||||
|
}
|
||||||
|
debug.Finish()
|
||||||
|
return fwdPath.es
|
||||||
|
}
|
||||||
|
|
||||||
|
type path struct {
|
||||||
|
dir int // +1 if forward, -1 if reverse
|
||||||
|
point // Leading point of the EditScript path
|
||||||
|
es EditScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
|
||||||
|
// to the edit-script to connect p.point to dst.
|
||||||
|
func (p *path) connect(dst point, f EqualFunc) {
|
||||||
|
if p.dir > 0 {
|
||||||
|
// Connect in forward direction.
|
||||||
|
for dst.X > p.X && dst.Y > p.Y {
|
||||||
|
switch r := f(p.X, p.Y); {
|
||||||
|
case r.Equal():
|
||||||
|
p.append(Identity)
|
||||||
|
case r.Similar():
|
||||||
|
p.append(Modified)
|
||||||
|
case dst.X-p.X >= dst.Y-p.Y:
|
||||||
|
p.append(UniqueX)
|
||||||
|
default:
|
||||||
|
p.append(UniqueY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for dst.X > p.X {
|
||||||
|
p.append(UniqueX)
|
||||||
|
}
|
||||||
|
for dst.Y > p.Y {
|
||||||
|
p.append(UniqueY)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Connect in reverse direction.
|
||||||
|
for p.X > dst.X && p.Y > dst.Y {
|
||||||
|
switch r := f(p.X-1, p.Y-1); {
|
||||||
|
case r.Equal():
|
||||||
|
p.append(Identity)
|
||||||
|
case r.Similar():
|
||||||
|
p.append(Modified)
|
||||||
|
case p.Y-dst.Y >= p.X-dst.X:
|
||||||
|
p.append(UniqueY)
|
||||||
|
default:
|
||||||
|
p.append(UniqueX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for p.X > dst.X {
|
||||||
|
p.append(UniqueX)
|
||||||
|
}
|
||||||
|
for p.Y > dst.Y {
|
||||||
|
p.append(UniqueY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) append(t EditType) {
|
||||||
|
p.es = append(p.es, t)
|
||||||
|
switch t {
|
||||||
|
case Identity, Modified:
|
||||||
|
p.add(p.dir, p.dir)
|
||||||
|
case UniqueX:
|
||||||
|
p.add(p.dir, 0)
|
||||||
|
case UniqueY:
|
||||||
|
p.add(0, p.dir)
|
||||||
|
}
|
||||||
|
debug.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
type point struct{ X, Y int }
|
||||||
|
|
||||||
|
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.
|
||||||
|
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
|
||||||
|
func zigzag(x int) int {
|
||||||
|
if x&1 != 0 {
|
||||||
|
x = ^x
|
||||||
|
}
|
||||||
|
return x >> 1
|
||||||
|
}
|
9
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
Normal file
9
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// 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 flags
|
||||||
|
|
||||||
|
// Deterministic controls whether the output of Diff should be deterministic.
|
||||||
|
// This is only used for testing.
|
||||||
|
var Deterministic bool
|
99
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
99
vendor/github.com/google/go-cmp/cmp/internal/function/func.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
// 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 function provides functionality for identifying function types.
|
||||||
|
package function
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type funcType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ funcType = iota
|
||||||
|
|
||||||
|
tbFunc // func(T) bool
|
||||||
|
ttbFunc // func(T, T) bool
|
||||||
|
trbFunc // func(T, R) bool
|
||||||
|
tibFunc // func(T, I) bool
|
||||||
|
trFunc // func(T) R
|
||||||
|
|
||||||
|
Equal = ttbFunc // func(T, T) bool
|
||||||
|
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
|
||||||
|
Transformer = trFunc // func(T) R
|
||||||
|
ValueFilter = ttbFunc // func(T, T) bool
|
||||||
|
Less = ttbFunc // func(T, T) bool
|
||||||
|
ValuePredicate = tbFunc // func(T) bool
|
||||||
|
KeyValuePredicate = trbFunc // func(T, R) bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var boolType = reflect.TypeOf(true)
|
||||||
|
|
||||||
|
// IsType reports whether the reflect.Type is of the specified function type.
|
||||||
|
func IsType(t reflect.Type, ft funcType) bool {
|
||||||
|
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ni, no := t.NumIn(), t.NumOut()
|
||||||
|
switch ft {
|
||||||
|
case tbFunc: // func(T) bool
|
||||||
|
if ni == 1 && no == 1 && t.Out(0) == boolType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case ttbFunc: // func(T, T) bool
|
||||||
|
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case trbFunc: // func(T, R) bool
|
||||||
|
if ni == 2 && no == 1 && t.Out(0) == boolType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case tibFunc: // func(T, I) bool
|
||||||
|
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case trFunc: // func(T) R
|
||||||
|
if ni == 1 && no == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`)
|
||||||
|
|
||||||
|
// NameOf returns the name of the function value.
|
||||||
|
func NameOf(v reflect.Value) string {
|
||||||
|
fnc := runtime.FuncForPC(v.Pointer())
|
||||||
|
if fnc == nil {
|
||||||
|
return "<unknown>"
|
||||||
|
}
|
||||||
|
fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm"
|
||||||
|
|
||||||
|
// Method closures have a "-fm" suffix.
|
||||||
|
fullName = strings.TrimSuffix(fullName, "-fm")
|
||||||
|
|
||||||
|
var name string
|
||||||
|
for len(fullName) > 0 {
|
||||||
|
inParen := strings.HasSuffix(fullName, ")")
|
||||||
|
fullName = strings.TrimSuffix(fullName, ")")
|
||||||
|
|
||||||
|
s := lastIdentRx.FindString(fullName)
|
||||||
|
if s == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
name = s + "." + name
|
||||||
|
fullName = strings.TrimSuffix(fullName, s)
|
||||||
|
|
||||||
|
if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 {
|
||||||
|
fullName = fullName[:i]
|
||||||
|
}
|
||||||
|
fullName = strings.TrimSuffix(fullName, ".")
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(name, ".")
|
||||||
|
}
|
164
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
164
vendor/github.com/google/go-cmp/cmp/internal/value/name.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
// Copyright 2020, 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 (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var anyType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||||
|
|
||||||
|
// TypeString is nearly identical to reflect.Type.String,
|
||||||
|
// but has an additional option to specify that full type names be used.
|
||||||
|
func TypeString(t reflect.Type, qualified bool) string {
|
||||||
|
return string(appendTypeName(nil, t, qualified, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte {
|
||||||
|
// BUG: Go reflection provides no way to disambiguate two named types
|
||||||
|
// of the same name and within the same package,
|
||||||
|
// 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.
|
||||||
|
if t.Name() != "" {
|
||||||
|
if qualified && t.PkgPath() != "" {
|
||||||
|
b = append(b, '"')
|
||||||
|
b = append(b, t.PkgPath()...)
|
||||||
|
b = append(b, '"')
|
||||||
|
b = append(b, '.')
|
||||||
|
b = append(b, t.Name()...)
|
||||||
|
} else {
|
||||||
|
b = append(b, t.String()...)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unnamed type.
|
||||||
|
switch k := t.Kind(); k {
|
||||||
|
case reflect.Bool, reflect.String, reflect.UnsafePointer,
|
||||||
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||||
|
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||||
|
b = append(b, k.String()...)
|
||||||
|
case reflect.Chan:
|
||||||
|
if t.ChanDir() == reflect.RecvDir {
|
||||||
|
b = append(b, "<-"...)
|
||||||
|
}
|
||||||
|
b = append(b, "chan"...)
|
||||||
|
if t.ChanDir() == reflect.SendDir {
|
||||||
|
b = append(b, "<-"...)
|
||||||
|
}
|
||||||
|
b = append(b, ' ')
|
||||||
|
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||||
|
case reflect.Func:
|
||||||
|
if !elideFunc {
|
||||||
|
b = append(b, "func"...)
|
||||||
|
}
|
||||||
|
b = append(b, '(')
|
||||||
|
for i := 0; i < t.NumIn(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, ", "...)
|
||||||
|
}
|
||||||
|
if i == t.NumIn()-1 && t.IsVariadic() {
|
||||||
|
b = append(b, "..."...)
|
||||||
|
b = appendTypeName(b, t.In(i).Elem(), qualified, false)
|
||||||
|
} else {
|
||||||
|
b = appendTypeName(b, t.In(i), qualified, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b = append(b, ')')
|
||||||
|
switch t.NumOut() {
|
||||||
|
case 0:
|
||||||
|
// Do nothing
|
||||||
|
case 1:
|
||||||
|
b = append(b, ' ')
|
||||||
|
b = appendTypeName(b, t.Out(0), qualified, false)
|
||||||
|
default:
|
||||||
|
b = append(b, " ("...)
|
||||||
|
for i := 0; i < t.NumOut(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, ", "...)
|
||||||
|
}
|
||||||
|
b = appendTypeName(b, t.Out(i), qualified, false)
|
||||||
|
}
|
||||||
|
b = append(b, ')')
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
b = append(b, "struct{ "...)
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, "; "...)
|
||||||
|
}
|
||||||
|
sf := t.Field(i)
|
||||||
|
if !sf.Anonymous {
|
||||||
|
if qualified && sf.PkgPath != "" {
|
||||||
|
b = append(b, '"')
|
||||||
|
b = append(b, sf.PkgPath...)
|
||||||
|
b = append(b, '"')
|
||||||
|
b = append(b, '.')
|
||||||
|
}
|
||||||
|
b = append(b, sf.Name...)
|
||||||
|
b = append(b, ' ')
|
||||||
|
}
|
||||||
|
b = appendTypeName(b, sf.Type, qualified, false)
|
||||||
|
if sf.Tag != "" {
|
||||||
|
b = append(b, ' ')
|
||||||
|
b = strconv.AppendQuote(b, string(sf.Tag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b[len(b)-1] == ' ' {
|
||||||
|
b = b[:len(b)-1]
|
||||||
|
} else {
|
||||||
|
b = append(b, ' ')
|
||||||
|
}
|
||||||
|
b = append(b, '}')
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
b = append(b, '[')
|
||||||
|
if k == reflect.Array {
|
||||||
|
b = strconv.AppendUint(b, uint64(t.Len()), 10)
|
||||||
|
}
|
||||||
|
b = append(b, ']')
|
||||||
|
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||||
|
case reflect.Map:
|
||||||
|
b = append(b, "map["...)
|
||||||
|
b = appendTypeName(b, t.Key(), qualified, false)
|
||||||
|
b = append(b, ']')
|
||||||
|
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||||
|
case reflect.Ptr:
|
||||||
|
b = append(b, '*')
|
||||||
|
b = appendTypeName(b, t.Elem(), qualified, false)
|
||||||
|
case reflect.Interface:
|
||||||
|
b = append(b, "interface{ "...)
|
||||||
|
for i := 0; i < t.NumMethod(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
b = append(b, "; "...)
|
||||||
|
}
|
||||||
|
m := t.Method(i)
|
||||||
|
if qualified && m.PkgPath != "" {
|
||||||
|
b = append(b, '"')
|
||||||
|
b = append(b, m.PkgPath...)
|
||||||
|
b = append(b, '"')
|
||||||
|
b = append(b, '.')
|
||||||
|
}
|
||||||
|
b = append(b, m.Name...)
|
||||||
|
b = appendTypeName(b, m.Type, qualified, true)
|
||||||
|
}
|
||||||
|
if b[len(b)-1] == ' ' {
|
||||||
|
b = b[:len(b)-1]
|
||||||
|
} else {
|
||||||
|
b = append(b, ' ')
|
||||||
|
}
|
||||||
|
b = append(b, '}')
|
||||||
|
default:
|
||||||
|
panic("invalid kind: " + k.String())
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
34
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go
generated
vendored
Normal file
34
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build purego
|
||||||
|
// +build purego
|
||||||
|
|
||||||
|
package value
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
|
||||||
|
type Pointer struct {
|
||||||
|
p uintptr
|
||||||
|
t reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// PointerOf returns a Pointer from v, which must be a
|
||||||
|
// reflect.Ptr, reflect.Slice, or reflect.Map.
|
||||||
|
func PointerOf(v reflect.Value) Pointer {
|
||||||
|
// NOTE: Storing a pointer as an uintptr is technically incorrect as it
|
||||||
|
// assumes that the GC implementation does not use a moving collector.
|
||||||
|
return Pointer{v.Pointer(), v.Type()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether the pointer is nil.
|
||||||
|
func (p Pointer) IsNil() bool {
|
||||||
|
return p.p == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uintptr returns the pointer as a uintptr.
|
||||||
|
func (p Pointer) Uintptr() uintptr {
|
||||||
|
return p.p
|
||||||
|
}
|
37
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go
generated
vendored
Normal file
37
vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !purego
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
package value
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pointer is an opaque typed pointer and is guaranteed to be comparable.
|
||||||
|
type Pointer struct {
|
||||||
|
p unsafe.Pointer
|
||||||
|
t reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// PointerOf returns a Pointer from v, which must be a
|
||||||
|
// reflect.Ptr, reflect.Slice, or reflect.Map.
|
||||||
|
func PointerOf(v reflect.Value) Pointer {
|
||||||
|
// The proper representation of a pointer is unsafe.Pointer,
|
||||||
|
// which is necessary if the GC ever uses a moving collector.
|
||||||
|
return Pointer{unsafe.Pointer(v.Pointer()), v.Type()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether the pointer is nil.
|
||||||
|
func (p Pointer) IsNil() bool {
|
||||||
|
return p.p == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uintptr returns the pointer as a uintptr.
|
||||||
|
func (p Pointer) Uintptr() uintptr {
|
||||||
|
return uintptr(p.p)
|
||||||
|
}
|
106
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
106
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SortKeys sorts a list of map keys, deduplicating keys if necessary.
|
||||||
|
// The type of each value must be comparable.
|
||||||
|
func SortKeys(vs []reflect.Value) []reflect.Value {
|
||||||
|
if len(vs) == 0 {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the map keys.
|
||||||
|
sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) })
|
||||||
|
|
||||||
|
// Deduplicate keys (fails for NaNs).
|
||||||
|
vs2 := vs[:1]
|
||||||
|
for _, v := range vs[1:] {
|
||||||
|
if isLess(vs2[len(vs2)-1], v) {
|
||||||
|
vs2 = append(vs2, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs2
|
||||||
|
}
|
||||||
|
|
||||||
|
// isLess is a generic function for sorting arbitrary map keys.
|
||||||
|
// The inputs must be of the same type and must be comparable.
|
||||||
|
func isLess(x, y reflect.Value) bool {
|
||||||
|
switch x.Type().Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !x.Bool() && y.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return x.Int() < y.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return x.Uint() < y.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
// NOTE: This does not sort -0 as less than +0
|
||||||
|
// since Go maps treat -0 and +0 as equal keys.
|
||||||
|
fx, fy := x.Float(), y.Float()
|
||||||
|
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
cx, cy := x.Complex(), y.Complex()
|
||||||
|
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
|
||||||
|
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
|
||||||
|
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
|
||||||
|
}
|
||||||
|
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
|
||||||
|
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
|
||||||
|
return x.Pointer() < y.Pointer()
|
||||||
|
case reflect.String:
|
||||||
|
return x.String() < y.String()
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < x.Len(); i++ {
|
||||||
|
if isLess(x.Index(i), y.Index(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isLess(y.Index(i), x.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < x.NumField(); i++ {
|
||||||
|
if isLess(x.Field(i), y.Field(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isLess(y.Field(i), x.Field(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Interface:
|
||||||
|
vx, vy := x.Elem(), y.Elem()
|
||||||
|
if !vx.IsValid() || !vy.IsValid() {
|
||||||
|
return !vx.IsValid() && vy.IsValid()
|
||||||
|
}
|
||||||
|
tx, ty := vx.Type(), vy.Type()
|
||||||
|
if tx == ty {
|
||||||
|
return isLess(x.Elem(), y.Elem())
|
||||||
|
}
|
||||||
|
if tx.Kind() != ty.Kind() {
|
||||||
|
return vx.Kind() < vy.Kind()
|
||||||
|
}
|
||||||
|
if tx.String() != ty.String() {
|
||||||
|
return tx.String() < ty.String()
|
||||||
|
}
|
||||||
|
if tx.PkgPath() != ty.PkgPath() {
|
||||||
|
return tx.PkgPath() < ty.PkgPath()
|
||||||
|
}
|
||||||
|
// This can happen in rare situations, so we fallback to just comparing
|
||||||
|
// the unique pointer for a reflect.Type. This guarantees deterministic
|
||||||
|
// ordering within a program, but it is obviously not stable.
|
||||||
|
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
|
||||||
|
default:
|
||||||
|
// Must be Func, Map, or Slice; which are not comparable.
|
||||||
|
panic(fmt.Sprintf("%T is not comparable", x.Type()))
|
||||||
|
}
|
||||||
|
}
|
48
vendor/github.com/google/go-cmp/cmp/internal/value/zero.go
generated
vendored
Normal file
48
vendor/github.com/google/go-cmp/cmp/internal/value/zero.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// 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
|
||||||
|
}
|
552
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
552
vendor/github.com/google/go-cmp/cmp/options.go
generated
vendored
Normal file
|
@ -0,0 +1,552 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option configures for specific behavior of Equal and Diff. In particular,
|
||||||
|
// the fundamental Option functions (Ignore, Transformer, and Comparer),
|
||||||
|
// configure how equality is determined.
|
||||||
|
//
|
||||||
|
// The fundamental options may be composed with filters (FilterPath and
|
||||||
|
// FilterValues) to control the scope over which they are applied.
|
||||||
|
//
|
||||||
|
// The cmp/cmpopts package provides helper functions for creating options that
|
||||||
|
// may be used with Equal and Diff.
|
||||||
|
type Option interface {
|
||||||
|
// filter applies all filters and returns the option that remains.
|
||||||
|
// Each option may only read s.curPath and call s.callTTBFunc.
|
||||||
|
//
|
||||||
|
// An Options is returned only if multiple comparers or transformers
|
||||||
|
// can apply simultaneously and will only contain values of those types
|
||||||
|
// or sub-Options containing values of those types.
|
||||||
|
filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// applicableOption represents the following types:
|
||||||
|
// Fundamental: ignore | validator | *comparer | *transformer
|
||||||
|
// Grouping: Options
|
||||||
|
type applicableOption interface {
|
||||||
|
Option
|
||||||
|
|
||||||
|
// apply executes the option, which may mutate s or panic.
|
||||||
|
apply(s *state, vx, vy reflect.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// coreOption represents the following types:
|
||||||
|
// Fundamental: ignore | validator | *comparer | *transformer
|
||||||
|
// Filters: *pathFilter | *valuesFilter
|
||||||
|
type coreOption interface {
|
||||||
|
Option
|
||||||
|
isCore()
|
||||||
|
}
|
||||||
|
|
||||||
|
type core struct{}
|
||||||
|
|
||||||
|
func (core) isCore() {}
|
||||||
|
|
||||||
|
// Options is a list of Option values that also satisfies the Option interface.
|
||||||
|
// Helper comparison packages may return an Options value when packing multiple
|
||||||
|
// Option values into a single Option. When this package processes an Options,
|
||||||
|
// it will be implicitly expanded into a flat list.
|
||||||
|
//
|
||||||
|
// Applying a filter on an Options is equivalent to applying that same filter
|
||||||
|
// on all individual options held within.
|
||||||
|
type Options []Option
|
||||||
|
|
||||||
|
func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) {
|
||||||
|
for _, opt := range opts {
|
||||||
|
switch opt := opt.filter(s, t, vx, vy); opt.(type) {
|
||||||
|
case ignore:
|
||||||
|
return ignore{} // Only ignore can short-circuit evaluation
|
||||||
|
case validator:
|
||||||
|
out = validator{} // Takes precedence over comparer or transformer
|
||||||
|
case *comparer, *transformer, Options:
|
||||||
|
switch out.(type) {
|
||||||
|
case nil:
|
||||||
|
out = opt
|
||||||
|
case validator:
|
||||||
|
// Keep validator
|
||||||
|
case *comparer, *transformer, Options:
|
||||||
|
out = Options{out, opt} // Conflicting comparers or transformers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts Options) apply(s *state, _, _ reflect.Value) {
|
||||||
|
const warning = "ambiguous set of applicable options"
|
||||||
|
const help = "consider using filters to ensure at most one Comparer or Transformer may apply"
|
||||||
|
var ss []string
|
||||||
|
for _, opt := range flattenOptions(nil, opts) {
|
||||||
|
ss = append(ss, fmt.Sprint(opt))
|
||||||
|
}
|
||||||
|
set := strings.Join(ss, "\n\t")
|
||||||
|
panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts Options) String() string {
|
||||||
|
var ss []string
|
||||||
|
for _, opt := range opts {
|
||||||
|
ss = append(ss, fmt.Sprint(opt))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Options{%s}", strings.Join(ss, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterPath returns a new Option where opt is only evaluated if filter f
|
||||||
|
// returns true for the current Path in the value tree.
|
||||||
|
//
|
||||||
|
// This filter is called even if a slice element or map entry is missing and
|
||||||
|
// provides an opportunity to ignore such cases. The filter function must be
|
||||||
|
// symmetric such that the filter result is identical regardless of whether the
|
||||||
|
// missing value is from x or y.
|
||||||
|
//
|
||||||
|
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
|
||||||
|
// a previously filtered Option.
|
||||||
|
func FilterPath(f func(Path) bool, opt Option) Option {
|
||||||
|
if f == nil {
|
||||||
|
panic("invalid path filter function")
|
||||||
|
}
|
||||||
|
if opt := normalizeOption(opt); opt != nil {
|
||||||
|
return &pathFilter{fnc: f, opt: opt}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathFilter struct {
|
||||||
|
core
|
||||||
|
fnc func(Path) bool
|
||||||
|
opt Option
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||||
|
if f.fnc(s.curPath) {
|
||||||
|
return f.opt.filter(s, t, vx, vy)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f pathFilter) String() string {
|
||||||
|
return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterValues returns a new Option where opt is only evaluated if filter f,
|
||||||
|
// which is a function of the form "func(T, T) bool", returns true for the
|
||||||
|
// current pair of values being compared. If either value is invalid or
|
||||||
|
// the type of the values is not assignable to T, then this filter implicitly
|
||||||
|
// returns false.
|
||||||
|
//
|
||||||
|
// The filter function must be
|
||||||
|
// symmetric (i.e., agnostic to the order of the inputs) and
|
||||||
|
// deterministic (i.e., produces the same result when given the same inputs).
|
||||||
|
// If T is an interface, it is possible that f is called with two values with
|
||||||
|
// different concrete types that both implement T.
|
||||||
|
//
|
||||||
|
// The option passed in may be an Ignore, Transformer, Comparer, Options, or
|
||||||
|
// a previously filtered Option.
|
||||||
|
func FilterValues(f interface{}, opt Option) Option {
|
||||||
|
v := reflect.ValueOf(f)
|
||||||
|
if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
|
||||||
|
panic(fmt.Sprintf("invalid values filter function: %T", f))
|
||||||
|
}
|
||||||
|
if opt := normalizeOption(opt); opt != nil {
|
||||||
|
vf := &valuesFilter{fnc: v, opt: opt}
|
||||||
|
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||||
|
vf.typ = ti
|
||||||
|
}
|
||||||
|
return vf
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type valuesFilter struct {
|
||||||
|
core
|
||||||
|
typ reflect.Type // T
|
||||||
|
fnc reflect.Value // func(T, T) bool
|
||||||
|
opt Option
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||||
|
if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
|
||||||
|
return f.opt.filter(s, t, vx, vy)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f valuesFilter) String() string {
|
||||||
|
return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore is an Option that causes all comparisons to be ignored.
|
||||||
|
// This value is intended to be combined with FilterPath or FilterValues.
|
||||||
|
// It is an error to pass an unfiltered Ignore option to Equal.
|
||||||
|
func Ignore() Option { return ignore{} }
|
||||||
|
|
||||||
|
type ignore struct{ core }
|
||||||
|
|
||||||
|
func (ignore) isFiltered() bool { return false }
|
||||||
|
func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} }
|
||||||
|
func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) }
|
||||||
|
func (ignore) String() string { return "Ignore()" }
|
||||||
|
|
||||||
|
// validator is a sentinel Option type to indicate that some options could not
|
||||||
|
// be evaluated due to unexported fields, missing slice elements, or
|
||||||
|
// missing map entries. Both values are validator only for unexported fields.
|
||||||
|
type validator struct{ core }
|
||||||
|
|
||||||
|
func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption {
|
||||||
|
if !vx.IsValid() || !vy.IsValid() {
|
||||||
|
return validator{}
|
||||||
|
}
|
||||||
|
if !vx.CanInterface() || !vy.CanInterface() {
|
||||||
|
return validator{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (validator) apply(s *state, vx, vy reflect.Value) {
|
||||||
|
// Implies missing slice element or map entry.
|
||||||
|
if !vx.IsValid() || !vy.IsValid() {
|
||||||
|
s.report(vx.IsValid() == vy.IsValid(), 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unable to Interface implies unexported field without visibility access.
|
||||||
|
if !vx.CanInterface() || !vy.CanInterface() {
|
||||||
|
help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported"
|
||||||
|
var name string
|
||||||
|
if t := s.curPath.Index(-2).Type(); t.Name() != "" {
|
||||||
|
// Named type with unexported fields.
|
||||||
|
name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
|
||||||
|
if _, ok := reflect.New(t).Interface().(error); ok {
|
||||||
|
help = "consider using cmpopts.EquateErrors to compare error values"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unnamed type with unexported fields. Derive PkgPath from field.
|
||||||
|
var pkgPath string
|
||||||
|
for i := 0; i < t.NumField() && pkgPath == ""; i++ {
|
||||||
|
pkgPath = t.Field(i).PkgPath
|
||||||
|
}
|
||||||
|
name = fmt.Sprintf("%q.(%v)", pkgPath, t.String()) // e.g., "path/to/package".(struct { a int })
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("not reachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// identRx represents a valid identifier according to the Go specification.
|
||||||
|
const identRx = `[_\p{L}][_\p{L}\p{N}]*`
|
||||||
|
|
||||||
|
var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`)
|
||||||
|
|
||||||
|
// Transformer returns an Option that applies a transformation function that
|
||||||
|
// converts values of a certain type into that of another.
|
||||||
|
//
|
||||||
|
// The transformer f must be a function "func(T) R" that converts values of
|
||||||
|
// type T to those of type R and is implicitly filtered to input values
|
||||||
|
// assignable to T. The transformer must not mutate T in any way.
|
||||||
|
//
|
||||||
|
// To help prevent some cases of infinite recursive cycles applying the
|
||||||
|
// same transform to the output of itself (e.g., in the case where the
|
||||||
|
// input and output types are the same), an implicit filter is added such that
|
||||||
|
// a transformer is applicable only if that exact transformer is not already
|
||||||
|
// in the tail of the Path since the last non-Transform step.
|
||||||
|
// For situations where the implicit filter is still insufficient,
|
||||||
|
// consider using cmpopts.AcyclicTransformer, which adds a filter
|
||||||
|
// to prevent the transformer from being recursively applied upon itself.
|
||||||
|
//
|
||||||
|
// The name is a user provided label that is used as the Transform.Name in the
|
||||||
|
// transformation PathStep (and eventually shown in the Diff output).
|
||||||
|
// The name must be a valid identifier or qualified identifier in Go syntax.
|
||||||
|
// If empty, an arbitrary name is used.
|
||||||
|
func Transformer(name string, f interface{}) Option {
|
||||||
|
v := reflect.ValueOf(f)
|
||||||
|
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
|
||||||
|
panic(fmt.Sprintf("invalid transformer function: %T", f))
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
name = function.NameOf(v)
|
||||||
|
if !identsRx.MatchString(name) {
|
||||||
|
name = "λ" // Lambda-symbol as placeholder name
|
||||||
|
}
|
||||||
|
} else if !identsRx.MatchString(name) {
|
||||||
|
panic(fmt.Sprintf("invalid name: %q", name))
|
||||||
|
}
|
||||||
|
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
|
||||||
|
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||||
|
tr.typ = ti
|
||||||
|
}
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
type transformer struct {
|
||||||
|
core
|
||||||
|
name string
|
||||||
|
typ reflect.Type // T
|
||||||
|
fnc reflect.Value // func(T) R
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *transformer) isFiltered() bool { return tr.typ != nil }
|
||||||
|
|
||||||
|
func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption {
|
||||||
|
for i := len(s.curPath) - 1; i >= 0; i-- {
|
||||||
|
if t, ok := s.curPath[i].(Transform); !ok {
|
||||||
|
break // Hit most recent non-Transform step
|
||||||
|
} else if tr == t.trans {
|
||||||
|
return nil // Cannot directly use same Transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tr.typ == nil || t.AssignableTo(tr.typ) {
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *transformer) apply(s *state, vx, vy reflect.Value) {
|
||||||
|
step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}}
|
||||||
|
vvx := s.callTRFunc(tr.fnc, vx, step)
|
||||||
|
vvy := s.callTRFunc(tr.fnc, vy, step)
|
||||||
|
step.vx, step.vy = vvx, vvy
|
||||||
|
s.compareAny(step)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr transformer) String() string {
|
||||||
|
return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparer returns an Option that determines whether two values are equal
|
||||||
|
// to each other.
|
||||||
|
//
|
||||||
|
// The comparer f must be a function "func(T, T) bool" and is implicitly
|
||||||
|
// filtered to input values assignable to T. If T is an interface, it is
|
||||||
|
// possible that f is called with two values of different concrete types that
|
||||||
|
// both implement T.
|
||||||
|
//
|
||||||
|
// The equality function must be:
|
||||||
|
// • Symmetric: equal(x, y) == equal(y, x)
|
||||||
|
// • Deterministic: equal(x, y) == equal(x, y)
|
||||||
|
// • Pure: equal(x, y) does not modify x or y
|
||||||
|
func Comparer(f interface{}) Option {
|
||||||
|
v := reflect.ValueOf(f)
|
||||||
|
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
|
||||||
|
panic(fmt.Sprintf("invalid comparer function: %T", f))
|
||||||
|
}
|
||||||
|
cm := &comparer{fnc: v}
|
||||||
|
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||||
|
cm.typ = ti
|
||||||
|
}
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
|
||||||
|
type comparer struct {
|
||||||
|
core
|
||||||
|
typ reflect.Type // T
|
||||||
|
fnc reflect.Value // func(T, T) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *comparer) isFiltered() bool { return cm.typ != nil }
|
||||||
|
|
||||||
|
func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption {
|
||||||
|
if cm.typ == nil || t.AssignableTo(cm.typ) {
|
||||||
|
return cm
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm *comparer) apply(s *state, vx, vy reflect.Value) {
|
||||||
|
eq := s.callTTBFunc(cm.fnc, vx, vy)
|
||||||
|
s.report(eq, reportByFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cm comparer) String() string {
|
||||||
|
return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exporter returns an Option that specifies whether Equal is allowed to
|
||||||
|
// introspect into the unexported fields of certain struct types.
|
||||||
|
//
|
||||||
|
// Users of this option must understand that comparing on unexported fields
|
||||||
|
// from external packages is not safe since changes in the internal
|
||||||
|
// implementation of some external package may cause the result of Equal
|
||||||
|
// to unexpectedly change. However, it may be valid to use this option on types
|
||||||
|
// defined in an internal package where the semantic meaning of an unexported
|
||||||
|
// field is in the control of the user.
|
||||||
|
//
|
||||||
|
// In many cases, a custom Comparer should be used instead that defines
|
||||||
|
// equality as a function of the public API of a type rather than the underlying
|
||||||
|
// unexported implementation.
|
||||||
|
//
|
||||||
|
// For example, the reflect.Type documentation defines equality to be determined
|
||||||
|
// by the == operator on the interface (essentially performing a shallow pointer
|
||||||
|
// comparison) and most attempts to compare *regexp.Regexp types are interested
|
||||||
|
// in only checking that the regular expression strings are equal.
|
||||||
|
// Both of these are accomplished using Comparers:
|
||||||
|
//
|
||||||
|
// Comparer(func(x, y reflect.Type) bool { return x == y })
|
||||||
|
// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
|
||||||
|
//
|
||||||
|
// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
|
||||||
|
// all unexported fields on specified struct types.
|
||||||
|
func Exporter(f func(reflect.Type) bool) Option {
|
||||||
|
if !supportExporters {
|
||||||
|
panic("Exporter is not supported on purego builds")
|
||||||
|
}
|
||||||
|
return exporter(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
type exporter func(reflect.Type) bool
|
||||||
|
|
||||||
|
func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowUnexported returns an Options that allows Equal to forcibly introspect
|
||||||
|
// unexported fields of the specified struct types.
|
||||||
|
//
|
||||||
|
// See Exporter for the proper use of this option.
|
||||||
|
func AllowUnexported(types ...interface{}) Option {
|
||||||
|
m := make(map[reflect.Type]bool)
|
||||||
|
for _, typ := range types {
|
||||||
|
t := reflect.TypeOf(typ)
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("invalid struct type: %T", typ))
|
||||||
|
}
|
||||||
|
m[t] = true
|
||||||
|
}
|
||||||
|
return exporter(func(t reflect.Type) bool { return m[t] })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result represents the comparison result for a single node and
|
||||||
|
// is provided by cmp when calling Result (see Reporter).
|
||||||
|
type Result struct {
|
||||||
|
_ [0]func() // Make Result incomparable
|
||||||
|
flags resultFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal reports whether the node was determined to be equal or not.
|
||||||
|
// As a special case, ignored nodes are considered equal.
|
||||||
|
func (r Result) Equal() bool {
|
||||||
|
return r.flags&(reportEqual|reportByIgnore) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByIgnore reports whether the node is equal because it was ignored.
|
||||||
|
// This never reports true if Equal reports false.
|
||||||
|
func (r Result) ByIgnore() bool {
|
||||||
|
return r.flags&reportByIgnore != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByMethod reports whether the Equal method determined equality.
|
||||||
|
func (r Result) ByMethod() bool {
|
||||||
|
return r.flags&reportByMethod != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByFunc reports whether a Comparer function determined equality.
|
||||||
|
func (r Result) ByFunc() bool {
|
||||||
|
return r.flags&reportByFunc != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByCycle reports whether a reference cycle was detected.
|
||||||
|
func (r Result) ByCycle() bool {
|
||||||
|
return r.flags&reportByCycle != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type resultFlags uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ resultFlags = (1 << iota) / 2
|
||||||
|
|
||||||
|
reportEqual
|
||||||
|
reportUnequal
|
||||||
|
reportByIgnore
|
||||||
|
reportByMethod
|
||||||
|
reportByFunc
|
||||||
|
reportByCycle
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reporter is an Option that can be passed to Equal. When Equal traverses
|
||||||
|
// the value trees, it calls PushStep as it descends into each node in the
|
||||||
|
// tree and PopStep as it ascend out of the node. The leaves of the tree are
|
||||||
|
// either compared (determined to be equal or not equal) or ignored and reported
|
||||||
|
// as such by calling the Report method.
|
||||||
|
func Reporter(r interface {
|
||||||
|
// PushStep is called when a tree-traversal operation is performed.
|
||||||
|
// The PathStep itself is only valid until the step is popped.
|
||||||
|
// The PathStep.Values are valid for the duration of the entire traversal
|
||||||
|
// and must not be mutated.
|
||||||
|
//
|
||||||
|
// Equal always calls PushStep at the start to provide an operation-less
|
||||||
|
// PathStep used to report the root values.
|
||||||
|
//
|
||||||
|
// Within a slice, the exact set of inserted, removed, or modified elements
|
||||||
|
// is unspecified and may change in future implementations.
|
||||||
|
// The entries of a map are iterated through in an unspecified order.
|
||||||
|
PushStep(PathStep)
|
||||||
|
|
||||||
|
// Report is called exactly once on leaf nodes to report whether the
|
||||||
|
// comparison identified the node as equal, unequal, or ignored.
|
||||||
|
// A leaf node is one that is immediately preceded by and followed by
|
||||||
|
// a pair of PushStep and PopStep calls.
|
||||||
|
Report(Result)
|
||||||
|
|
||||||
|
// PopStep ascends back up the value tree.
|
||||||
|
// There is always a matching pop call for every push call.
|
||||||
|
PopStep()
|
||||||
|
}) Option {
|
||||||
|
return reporter{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
type reporter struct{ reporterIface }
|
||||||
|
type reporterIface interface {
|
||||||
|
PushStep(PathStep)
|
||||||
|
Report(Result)
|
||||||
|
PopStep()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeOption normalizes the input options such that all Options groups
|
||||||
|
// are flattened and groups with a single element are reduced to that element.
|
||||||
|
// Only coreOptions and Options containing coreOptions are allowed.
|
||||||
|
func normalizeOption(src Option) Option {
|
||||||
|
switch opts := flattenOptions(nil, Options{src}); len(opts) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return opts[0]
|
||||||
|
default:
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flattenOptions copies all options in src to dst as a flat list.
|
||||||
|
// Only coreOptions and Options containing coreOptions are allowed.
|
||||||
|
func flattenOptions(dst, src Options) Options {
|
||||||
|
for _, opt := range src {
|
||||||
|
switch opt := opt.(type) {
|
||||||
|
case nil:
|
||||||
|
continue
|
||||||
|
case Options:
|
||||||
|
dst = flattenOptions(dst, opt)
|
||||||
|
case coreOption:
|
||||||
|
dst = append(dst, opt)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid option type: %T", opt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
378
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
378
vendor/github.com/google/go-cmp/cmp/path.go
generated
vendored
Normal file
|
@ -0,0 +1,378 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Path is a list of PathSteps describing the sequence of operations to get
|
||||||
|
// from some root type to the current position in the value tree.
|
||||||
|
// The first Path element is always an operation-less PathStep that exists
|
||||||
|
// simply to identify the initial type.
|
||||||
|
//
|
||||||
|
// When traversing structs with embedded structs, the embedded struct will
|
||||||
|
// always be accessed as a field before traversing the fields of the
|
||||||
|
// embedded struct themselves. That is, an exported field from the
|
||||||
|
// embedded struct will never be accessed directly from the parent struct.
|
||||||
|
type Path []PathStep
|
||||||
|
|
||||||
|
// PathStep is a union-type for specific operations to traverse
|
||||||
|
// a value's tree structure. Users of this package never need to implement
|
||||||
|
// these types as values of this type will be returned by this package.
|
||||||
|
//
|
||||||
|
// Implementations of this interface are
|
||||||
|
// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform.
|
||||||
|
type PathStep interface {
|
||||||
|
String() string
|
||||||
|
|
||||||
|
// Type is the resulting type after performing the path step.
|
||||||
|
Type() reflect.Type
|
||||||
|
|
||||||
|
// Values is the resulting values after performing the path step.
|
||||||
|
// 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:
|
||||||
|
// • For StructField, both are not interface-able if the current field
|
||||||
|
// is unexported and the struct type is not explicitly permitted by
|
||||||
|
// an Exporter to traverse unexported fields.
|
||||||
|
// • For SliceIndex, one may be invalid if an element is missing from
|
||||||
|
// either the x or y slice.
|
||||||
|
// • For MapIndex, one may be invalid if an entry is missing from
|
||||||
|
// either the x or y map.
|
||||||
|
//
|
||||||
|
// The provided values must not be mutated.
|
||||||
|
Values() (vx, vy reflect.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ PathStep = StructField{}
|
||||||
|
_ PathStep = SliceIndex{}
|
||||||
|
_ PathStep = MapIndex{}
|
||||||
|
_ PathStep = Indirect{}
|
||||||
|
_ PathStep = TypeAssertion{}
|
||||||
|
_ PathStep = Transform{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (pa *Path) push(s PathStep) {
|
||||||
|
*pa = append(*pa, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pa *Path) pop() {
|
||||||
|
*pa = (*pa)[:len(*pa)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last returns the last PathStep in the Path.
|
||||||
|
// If the path is empty, this returns a non-nil PathStep that reports a nil Type.
|
||||||
|
func (pa Path) Last() PathStep {
|
||||||
|
return pa.Index(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the ith step in the Path and supports negative indexing.
|
||||||
|
// A negative index starts counting from the tail of the Path such that -1
|
||||||
|
// refers to the last step, -2 refers to the second-to-last step, and so on.
|
||||||
|
// If index is invalid, this returns a non-nil PathStep that reports a nil Type.
|
||||||
|
func (pa Path) Index(i int) PathStep {
|
||||||
|
if i < 0 {
|
||||||
|
i = len(pa) + i
|
||||||
|
}
|
||||||
|
if i < 0 || i >= len(pa) {
|
||||||
|
return pathStep{}
|
||||||
|
}
|
||||||
|
return pa[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the simplified path to a node.
|
||||||
|
// The simplified path only contains struct field accesses.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// MyMap.MySlices.MyField
|
||||||
|
func (pa Path) String() string {
|
||||||
|
var ss []string
|
||||||
|
for _, s := range pa {
|
||||||
|
if _, ok := s.(StructField); ok {
|
||||||
|
ss = append(ss, s.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(strings.Join(ss, ""), ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoString returns the path to a specific node using Go syntax.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
|
||||||
|
func (pa Path) GoString() string {
|
||||||
|
var ssPre, ssPost []string
|
||||||
|
var numIndirect int
|
||||||
|
for i, s := range pa {
|
||||||
|
var nextStep PathStep
|
||||||
|
if i+1 < len(pa) {
|
||||||
|
nextStep = pa[i+1]
|
||||||
|
}
|
||||||
|
switch s := s.(type) {
|
||||||
|
case Indirect:
|
||||||
|
numIndirect++
|
||||||
|
pPre, pPost := "(", ")"
|
||||||
|
switch nextStep.(type) {
|
||||||
|
case Indirect:
|
||||||
|
continue // Next step is indirection, so let them batch up
|
||||||
|
case StructField:
|
||||||
|
numIndirect-- // Automatic indirection on struct fields
|
||||||
|
case nil:
|
||||||
|
pPre, pPost = "", "" // Last step; no need for parenthesis
|
||||||
|
}
|
||||||
|
if numIndirect > 0 {
|
||||||
|
ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
|
||||||
|
ssPost = append(ssPost, pPost)
|
||||||
|
}
|
||||||
|
numIndirect = 0
|
||||||
|
continue
|
||||||
|
case Transform:
|
||||||
|
ssPre = append(ssPre, s.trans.name+"(")
|
||||||
|
ssPost = append(ssPost, ")")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ssPost = append(ssPost, s.String())
|
||||||
|
}
|
||||||
|
for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
|
||||||
|
}
|
||||||
|
return strings.Join(ssPre, "") + strings.Join(ssPost, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathStep struct {
|
||||||
|
typ reflect.Type
|
||||||
|
vx, vy reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps pathStep) Type() reflect.Type { return ps.typ }
|
||||||
|
func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy }
|
||||||
|
func (ps pathStep) String() string {
|
||||||
|
if ps.typ == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
s := ps.typ.String()
|
||||||
|
if s == "" || strings.ContainsAny(s, "{}\n") {
|
||||||
|
return "root" // Type too simple or complex to print
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("{%s}", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructField represents a struct field access on a field called Name.
|
||||||
|
type StructField struct{ *structField }
|
||||||
|
type structField struct {
|
||||||
|
pathStep
|
||||||
|
name string
|
||||||
|
idx int
|
||||||
|
|
||||||
|
// These fields are used for forcibly accessing an unexported field.
|
||||||
|
// pvx, pvy, and field are only valid if unexported is true.
|
||||||
|
unexported bool
|
||||||
|
mayForce bool // Forcibly allow visibility
|
||||||
|
paddr bool // Was parent addressable?
|
||||||
|
pvx, pvy reflect.Value // Parent values (always addressable)
|
||||||
|
field reflect.StructField // Field information
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf StructField) Type() reflect.Type { return sf.typ }
|
||||||
|
func (sf StructField) Values() (vx, vy reflect.Value) {
|
||||||
|
if !sf.unexported {
|
||||||
|
return sf.vx, sf.vy // CanInterface reports true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forcibly obtain read-write access to an unexported struct field.
|
||||||
|
if sf.mayForce {
|
||||||
|
vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr)
|
||||||
|
vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr)
|
||||||
|
return vx, vy // CanInterface reports true
|
||||||
|
}
|
||||||
|
return sf.vx, sf.vy // CanInterface reports false
|
||||||
|
}
|
||||||
|
func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) }
|
||||||
|
|
||||||
|
// Name is the field name.
|
||||||
|
func (sf StructField) Name() string { return sf.name }
|
||||||
|
|
||||||
|
// Index is the index of the field in the parent struct type.
|
||||||
|
// See reflect.Type.Field.
|
||||||
|
func (sf StructField) Index() int { return sf.idx }
|
||||||
|
|
||||||
|
// SliceIndex is an index operation on a slice or array at some index Key.
|
||||||
|
type SliceIndex struct{ *sliceIndex }
|
||||||
|
type sliceIndex struct {
|
||||||
|
pathStep
|
||||||
|
xkey, ykey int
|
||||||
|
isSlice bool // False for reflect.Array
|
||||||
|
}
|
||||||
|
|
||||||
|
func (si SliceIndex) Type() reflect.Type { return si.typ }
|
||||||
|
func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy }
|
||||||
|
func (si SliceIndex) String() string {
|
||||||
|
switch {
|
||||||
|
case si.xkey == si.ykey:
|
||||||
|
return fmt.Sprintf("[%d]", si.xkey)
|
||||||
|
case si.ykey == -1:
|
||||||
|
// [5->?] means "I don't know where X[5] went"
|
||||||
|
return fmt.Sprintf("[%d->?]", si.xkey)
|
||||||
|
case si.xkey == -1:
|
||||||
|
// [?->3] means "I don't know where Y[3] came from"
|
||||||
|
return fmt.Sprintf("[?->%d]", si.ykey)
|
||||||
|
default:
|
||||||
|
// [5->3] means "X[5] moved to Y[3]"
|
||||||
|
return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key is the index key; it may return -1 if in a split state
|
||||||
|
func (si SliceIndex) Key() int {
|
||||||
|
if si.xkey != si.ykey {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return si.xkey
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitKeys are the indexes for indexing into slices in the
|
||||||
|
// x and y values, respectively. These indexes may differ due to the
|
||||||
|
// insertion or removal of an element in one of the slices, causing
|
||||||
|
// all of the indexes to be shifted. If an index is -1, then that
|
||||||
|
// indicates that the element does not exist in the associated slice.
|
||||||
|
//
|
||||||
|
// Key is guaranteed to return -1 if and only if the indexes returned
|
||||||
|
// by SplitKeys are not the same. SplitKeys will never return -1 for
|
||||||
|
// both indexes.
|
||||||
|
func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey }
|
||||||
|
|
||||||
|
// MapIndex is an index operation on a map at some index Key.
|
||||||
|
type MapIndex struct{ *mapIndex }
|
||||||
|
type mapIndex struct {
|
||||||
|
pathStep
|
||||||
|
key reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi MapIndex) Type() reflect.Type { return mi.typ }
|
||||||
|
func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy }
|
||||||
|
func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
|
||||||
|
|
||||||
|
// Key is the value of the map key.
|
||||||
|
func (mi MapIndex) Key() reflect.Value { return mi.key }
|
||||||
|
|
||||||
|
// Indirect represents pointer indirection on the parent type.
|
||||||
|
type Indirect struct{ *indirect }
|
||||||
|
type indirect struct {
|
||||||
|
pathStep
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in Indirect) Type() reflect.Type { return in.typ }
|
||||||
|
func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy }
|
||||||
|
func (in Indirect) String() string { return "*" }
|
||||||
|
|
||||||
|
// TypeAssertion represents a type assertion on an interface.
|
||||||
|
type TypeAssertion struct{ *typeAssertion }
|
||||||
|
type typeAssertion struct {
|
||||||
|
pathStep
|
||||||
|
}
|
||||||
|
|
||||||
|
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) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
|
||||||
|
|
||||||
|
// Transform is a transformation from the parent type to the current type.
|
||||||
|
type Transform struct{ *transform }
|
||||||
|
type transform struct {
|
||||||
|
pathStep
|
||||||
|
trans *transformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tf Transform) Type() reflect.Type { return tf.typ }
|
||||||
|
func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy }
|
||||||
|
func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
|
||||||
|
|
||||||
|
// Name is the name of the Transformer.
|
||||||
|
func (tf Transform) Name() string { return tf.trans.name }
|
||||||
|
|
||||||
|
// Func is the function pointer to the transformer function.
|
||||||
|
func (tf Transform) Func() reflect.Value { return tf.trans.fnc }
|
||||||
|
|
||||||
|
// Option returns the originally constructed Transformer option.
|
||||||
|
// The == operator can be used to detect the exact option used.
|
||||||
|
func (tf Transform) Option() Option { return tf.trans }
|
||||||
|
|
||||||
|
// pointerPath represents a dual-stack of pointers encountered when
|
||||||
|
// recursively traversing the x and y values. This data structure supports
|
||||||
|
// detection of cycles and determining whether the cycles are equal.
|
||||||
|
// In Go, cycles can occur via pointers, slices, and maps.
|
||||||
|
//
|
||||||
|
// The pointerPath uses a map to represent a stack; where descension into a
|
||||||
|
// pointer pushes the address onto the stack, and ascension from a pointer
|
||||||
|
// 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
|
||||||
|
// by checking whether the pointer has already been visited. The cycle detection
|
||||||
|
// uses a separate stack for the x and y values.
|
||||||
|
//
|
||||||
|
// If a cycle is detected we need to determine whether the two pointers
|
||||||
|
// should be considered equal. The definition of equality chosen by Equal
|
||||||
|
// requires two graphs to have the same structure. To determine this, both the
|
||||||
|
// x and y values must have a cycle where the previous pointers were also
|
||||||
|
// encountered together as a pair.
|
||||||
|
//
|
||||||
|
// Semantically, this is equivalent to augmenting Indirect, SliceIndex, and
|
||||||
|
// MapIndex with pointer information for the x and y values.
|
||||||
|
// Suppose px and py are two pointers to compare, we then search the
|
||||||
|
// Path for whether px was ever encountered in the Path history of x, and
|
||||||
|
// similarly so with py. If either side has a cycle, the comparison is only
|
||||||
|
// equal if both px and py have a cycle resulting from the same PathStep.
|
||||||
|
//
|
||||||
|
// Using a map as a stack is more performant as we can perform cycle detection
|
||||||
|
// in O(1) instead of O(N) where N is len(Path).
|
||||||
|
type pointerPath struct {
|
||||||
|
// mx is keyed by x pointers, where the value is the associated y pointer.
|
||||||
|
mx map[value.Pointer]value.Pointer
|
||||||
|
// my is keyed by y pointers, where the value is the associated x pointer.
|
||||||
|
my map[value.Pointer]value.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pointerPath) Init() {
|
||||||
|
p.mx = make(map[value.Pointer]value.Pointer)
|
||||||
|
p.my = make(map[value.Pointer]value.Pointer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push indicates intent to descend into pointers vx and vy where
|
||||||
|
// visited reports whether either has been seen before. If visited before,
|
||||||
|
// equal reports whether both pointers were encountered together.
|
||||||
|
// Pop must be called if and only if the pointers were never visited.
|
||||||
|
//
|
||||||
|
// The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map
|
||||||
|
// and be non-nil.
|
||||||
|
func (p pointerPath) Push(vx, vy reflect.Value) (equal, visited bool) {
|
||||||
|
px := value.PointerOf(vx)
|
||||||
|
py := value.PointerOf(vy)
|
||||||
|
_, ok1 := p.mx[px]
|
||||||
|
_, ok2 := p.my[py]
|
||||||
|
if ok1 || ok2 {
|
||||||
|
equal = p.mx[px] == py && p.my[py] == px // Pointers paired together
|
||||||
|
return equal, true
|
||||||
|
}
|
||||||
|
p.mx[px] = py
|
||||||
|
p.my[py] = px
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop ascends from pointers vx and vy.
|
||||||
|
func (p pointerPath) Pop(vx, vy reflect.Value) {
|
||||||
|
delete(p.mx, value.PointerOf(vx))
|
||||||
|
delete(p.my, value.PointerOf(vy))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExported reports whether the identifier is exported.
|
||||||
|
func isExported(id string) bool {
|
||||||
|
r, _ := utf8.DecodeRuneInString(id)
|
||||||
|
return unicode.IsUpper(r)
|
||||||
|
}
|
54
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
Normal file
54
vendor/github.com/google/go-cmp/cmp/report.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
// defaultReporter implements the reporter interface.
|
||||||
|
//
|
||||||
|
// As Equal serially calls the PushStep, Report, and PopStep methods, the
|
||||||
|
// defaultReporter constructs a tree-based representation of the compared value
|
||||||
|
// and the result of each comparison (see valueNode).
|
||||||
|
//
|
||||||
|
// When the String method is called, the FormatDiff method transforms the
|
||||||
|
// valueNode tree into a textNode tree, which is a tree-based representation
|
||||||
|
// of the textual output (see textNode).
|
||||||
|
//
|
||||||
|
// Lastly, the textNode.String method produces the final report as a string.
|
||||||
|
type defaultReporter struct {
|
||||||
|
root *valueNode
|
||||||
|
curr *valueNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *defaultReporter) PushStep(ps PathStep) {
|
||||||
|
r.curr = r.curr.PushStep(ps)
|
||||||
|
if r.root == nil {
|
||||||
|
r.root = r.curr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (r *defaultReporter) Report(rs Result) {
|
||||||
|
r.curr.Report(rs)
|
||||||
|
}
|
||||||
|
func (r *defaultReporter) PopStep() {
|
||||||
|
r.curr = r.curr.PopStep()
|
||||||
|
}
|
||||||
|
|
||||||
|
// String provides a full report of the differences detected as a structured
|
||||||
|
// literal in pseudo-Go syntax. String may only be called after the entire tree
|
||||||
|
// has been traversed.
|
||||||
|
func (r *defaultReporter) String() string {
|
||||||
|
assert(r.root != nil && r.curr == nil)
|
||||||
|
if r.root.NumDiff == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
ptrs := new(pointerReferences)
|
||||||
|
text := formatOptions{}.FormatDiff(r.root, ptrs)
|
||||||
|
resolveReferences(text)
|
||||||
|
return text.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func assert(ok bool) {
|
||||||
|
if !ok {
|
||||||
|
panic("assertion failure")
|
||||||
|
}
|
||||||
|
}
|
432
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
Normal file
432
vendor/github.com/google/go-cmp/cmp/report_compare.go
generated
vendored
Normal file
|
@ -0,0 +1,432 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
// numContextRecords is the number of surrounding equal records to print.
|
||||||
|
const numContextRecords = 2
|
||||||
|
|
||||||
|
type diffMode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
diffUnknown diffMode = 0
|
||||||
|
diffIdentical diffMode = ' '
|
||||||
|
diffRemoved diffMode = '-'
|
||||||
|
diffInserted diffMode = '+'
|
||||||
|
)
|
||||||
|
|
||||||
|
type typeMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// emitType always prints the type.
|
||||||
|
emitType typeMode = iota
|
||||||
|
// elideType never prints the type.
|
||||||
|
elideType
|
||||||
|
// autoType prints the type only for composite kinds
|
||||||
|
// (i.e., structs, slices, arrays, and maps).
|
||||||
|
autoType
|
||||||
|
)
|
||||||
|
|
||||||
|
type formatOptions struct {
|
||||||
|
// DiffMode controls the output mode of FormatDiff.
|
||||||
|
//
|
||||||
|
// If diffUnknown, then produce a diff of the x and y values.
|
||||||
|
// If diffIdentical, then emit values as if they were equal.
|
||||||
|
// If diffRemoved, then only emit x values (ignoring y values).
|
||||||
|
// If diffInserted, then only emit y values (ignoring x values).
|
||||||
|
DiffMode diffMode
|
||||||
|
|
||||||
|
// TypeMode controls whether to print the type for the current node.
|
||||||
|
//
|
||||||
|
// As a general rule of thumb, we always print the type of the next node
|
||||||
|
// after an interface, and always elide the type of the next node after
|
||||||
|
// a slice or map node.
|
||||||
|
TypeMode typeMode
|
||||||
|
|
||||||
|
// formatValueOptions are options specific to printing reflect.Values.
|
||||||
|
formatValueOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts formatOptions) WithDiffMode(d diffMode) formatOptions {
|
||||||
|
opts.DiffMode = d
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
func (opts formatOptions) WithTypeMode(t typeMode) formatOptions {
|
||||||
|
opts.TypeMode = t
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
func (opts formatOptions) WithVerbosity(level int) formatOptions {
|
||||||
|
opts.VerbosityLevel = level
|
||||||
|
opts.LimitVerbosity = true
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
func (opts formatOptions) verbosity() uint {
|
||||||
|
switch {
|
||||||
|
case opts.VerbosityLevel < 0:
|
||||||
|
return 0
|
||||||
|
case opts.VerbosityLevel > 16:
|
||||||
|
return 16 // some reasonable maximum to avoid shift overflow
|
||||||
|
default:
|
||||||
|
return uint(opts.VerbosityLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxVerbosityPreset = 6
|
||||||
|
|
||||||
|
// verbosityPreset modifies the verbosity settings given an index
|
||||||
|
// between 0 and maxVerbosityPreset, inclusive.
|
||||||
|
func verbosityPreset(opts formatOptions, i int) formatOptions {
|
||||||
|
opts.VerbosityLevel = int(opts.verbosity()) + 2*i
|
||||||
|
if i > 0 {
|
||||||
|
opts.AvoidStringer = true
|
||||||
|
}
|
||||||
|
if i >= maxVerbosityPreset {
|
||||||
|
opts.PrintAddresses = true
|
||||||
|
opts.QualifiedNames = true
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatDiff converts a valueNode tree into a textNode tree, where the later
|
||||||
|
// is a textual representation of the differences detected in the former.
|
||||||
|
func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
|
||||||
|
if opts.DiffMode == diffIdentical {
|
||||||
|
opts = opts.WithVerbosity(1)
|
||||||
|
} else if opts.verbosity() < 3 {
|
||||||
|
opts = opts.WithVerbosity(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether we have specialized formatting for this node.
|
||||||
|
// This is not necessary, but helpful for producing more readable outputs.
|
||||||
|
if opts.CanFormatDiffSlice(v) {
|
||||||
|
return opts.FormatDiffSlice(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentKind reflect.Kind
|
||||||
|
if v.parent != nil && v.parent.TransformerName == "" {
|
||||||
|
parentKind = v.parent.Type.Kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
// For leaf nodes, format the value based on the reflect.Values alone.
|
||||||
|
if v.MaxDepth == 0 {
|
||||||
|
switch opts.DiffMode {
|
||||||
|
case diffUnknown, diffIdentical:
|
||||||
|
// Format Equal.
|
||||||
|
if v.NumDiff == 0 {
|
||||||
|
outx := opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||||
|
outy := opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||||
|
if v.NumIgnored > 0 && v.NumSame == 0 {
|
||||||
|
return textEllipsis
|
||||||
|
} else if outx.Len() < outy.Len() {
|
||||||
|
return outx
|
||||||
|
} else {
|
||||||
|
return outy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format unequal.
|
||||||
|
assert(opts.DiffMode == diffUnknown)
|
||||||
|
var list textList
|
||||||
|
outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs)
|
||||||
|
outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs)
|
||||||
|
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||||
|
opts2 := verbosityPreset(opts, i).WithTypeMode(elideType)
|
||||||
|
outx = opts2.FormatValue(v.ValueX, parentKind, ptrs)
|
||||||
|
outy = opts2.FormatValue(v.ValueY, parentKind, ptrs)
|
||||||
|
}
|
||||||
|
if outx != nil {
|
||||||
|
list = append(list, textRecord{Diff: '-', Value: outx})
|
||||||
|
}
|
||||||
|
if outy != nil {
|
||||||
|
list = append(list, textRecord{Diff: '+', Value: outy})
|
||||||
|
}
|
||||||
|
return opts.WithTypeMode(emitType).FormatType(v.Type, list)
|
||||||
|
case diffRemoved:
|
||||||
|
return opts.FormatValue(v.ValueX, parentKind, ptrs)
|
||||||
|
case diffInserted:
|
||||||
|
return opts.FormatValue(v.ValueY, parentKind, ptrs)
|
||||||
|
default:
|
||||||
|
panic("invalid diff mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register slice element to support cycle detection.
|
||||||
|
if parentKind == reflect.Slice {
|
||||||
|
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true)
|
||||||
|
defer ptrs.Pop()
|
||||||
|
defer func() { out = wrapTrunkReferences(ptrRefs, out) }()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descend into the child value node.
|
||||||
|
if v.TransformerName != "" {
|
||||||
|
out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||||
|
out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"}
|
||||||
|
return opts.FormatType(v.Type, out)
|
||||||
|
} else {
|
||||||
|
switch k := v.Type.Kind(); k {
|
||||||
|
case reflect.Struct, reflect.Array, reflect.Slice:
|
||||||
|
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||||
|
out = opts.FormatType(v.Type, out)
|
||||||
|
case reflect.Map:
|
||||||
|
// Register map to support cycle detection.
|
||||||
|
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||||
|
defer ptrs.Pop()
|
||||||
|
|
||||||
|
out = opts.formatDiffList(v.Records, k, ptrs)
|
||||||
|
out = wrapTrunkReferences(ptrRefs, out)
|
||||||
|
out = opts.FormatType(v.Type, out)
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Register pointer to support cycle detection.
|
||||||
|
ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false)
|
||||||
|
defer ptrs.Pop()
|
||||||
|
|
||||||
|
out = opts.FormatDiff(v.Value, ptrs)
|
||||||
|
out = wrapTrunkReferences(ptrRefs, out)
|
||||||
|
out = &textWrap{Prefix: "&", Value: out}
|
||||||
|
case reflect.Interface:
|
||||||
|
out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("%v cannot have children", k))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode {
|
||||||
|
// Derive record name based on the data structure kind.
|
||||||
|
var name string
|
||||||
|
var formatKey func(reflect.Value) string
|
||||||
|
switch k {
|
||||||
|
case reflect.Struct:
|
||||||
|
name = "field"
|
||||||
|
opts = opts.WithTypeMode(autoType)
|
||||||
|
formatKey = func(v reflect.Value) string { return v.String() }
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
name = "element"
|
||||||
|
opts = opts.WithTypeMode(elideType)
|
||||||
|
formatKey = func(reflect.Value) string { return "" }
|
||||||
|
case reflect.Map:
|
||||||
|
name = "entry"
|
||||||
|
opts = opts.WithTypeMode(elideType)
|
||||||
|
formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) }
|
||||||
|
}
|
||||||
|
|
||||||
|
maxLen := -1
|
||||||
|
if opts.LimitVerbosity {
|
||||||
|
if opts.DiffMode == diffIdentical {
|
||||||
|
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||||
|
} else {
|
||||||
|
maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc...
|
||||||
|
}
|
||||||
|
opts.VerbosityLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle unification.
|
||||||
|
switch opts.DiffMode {
|
||||||
|
case diffIdentical, diffRemoved, diffInserted:
|
||||||
|
var list textList
|
||||||
|
var deferredEllipsis bool // Add final "..." to indicate records were dropped
|
||||||
|
for _, r := range recs {
|
||||||
|
if len(list) == maxLen {
|
||||||
|
deferredEllipsis = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elide struct fields that are zero value.
|
||||||
|
if k == reflect.Struct {
|
||||||
|
var isZero bool
|
||||||
|
switch opts.DiffMode {
|
||||||
|
case diffIdentical:
|
||||||
|
isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY)
|
||||||
|
case diffRemoved:
|
||||||
|
isZero = value.IsZero(r.Value.ValueX)
|
||||||
|
case diffInserted:
|
||||||
|
isZero = value.IsZero(r.Value.ValueY)
|
||||||
|
}
|
||||||
|
if isZero {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Elide ignored nodes.
|
||||||
|
if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 {
|
||||||
|
deferredEllipsis = !(k == reflect.Slice || k == reflect.Array)
|
||||||
|
if !deferredEllipsis {
|
||||||
|
list.AppendEllipsis(diffStats{})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if out := opts.FormatDiff(r.Value, ptrs); out != nil {
|
||||||
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if deferredEllipsis {
|
||||||
|
list.AppendEllipsis(diffStats{})
|
||||||
|
}
|
||||||
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
|
case diffUnknown:
|
||||||
|
default:
|
||||||
|
panic("invalid diff mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle differencing.
|
||||||
|
var numDiffs int
|
||||||
|
var list textList
|
||||||
|
var keys []reflect.Value // invariant: len(list) == len(keys)
|
||||||
|
groups := coalesceAdjacentRecords(name, recs)
|
||||||
|
maxGroup := diffStats{Name: name}
|
||||||
|
for i, ds := range groups {
|
||||||
|
if maxLen >= 0 && numDiffs >= maxLen {
|
||||||
|
maxGroup = maxGroup.Append(ds)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle equal records.
|
||||||
|
if ds.NumDiff() == 0 {
|
||||||
|
// Compute the number of leading and trailing records to print.
|
||||||
|
var numLo, numHi int
|
||||||
|
numEqual := ds.NumIgnored + ds.NumIdentical
|
||||||
|
for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 {
|
||||||
|
if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
numLo++
|
||||||
|
}
|
||||||
|
for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
|
||||||
|
if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
numHi++
|
||||||
|
}
|
||||||
|
if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 {
|
||||||
|
numHi++ // Avoid pointless coalescing of a single equal record
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the equal values.
|
||||||
|
for _, r := range recs[:numLo] {
|
||||||
|
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||||
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
|
}
|
||||||
|
if numEqual > numLo+numHi {
|
||||||
|
ds.NumIdentical -= numLo + numHi
|
||||||
|
list.AppendEllipsis(ds)
|
||||||
|
for len(keys) < len(list) {
|
||||||
|
keys = append(keys, reflect.Value{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range recs[numEqual-numHi : numEqual] {
|
||||||
|
out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs)
|
||||||
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
|
}
|
||||||
|
recs = recs[numEqual:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle unequal records.
|
||||||
|
for _, r := range recs[:ds.NumDiff()] {
|
||||||
|
switch {
|
||||||
|
case opts.CanFormatDiffSlice(r.Value):
|
||||||
|
out := opts.FormatDiffSlice(r.Value)
|
||||||
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
|
case r.Value.NumChildren == r.Value.MaxDepth:
|
||||||
|
outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||||
|
outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||||
|
for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ {
|
||||||
|
opts2 := verbosityPreset(opts, i)
|
||||||
|
outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs)
|
||||||
|
outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs)
|
||||||
|
}
|
||||||
|
if outx != nil {
|
||||||
|
list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
|
}
|
||||||
|
if outy != nil {
|
||||||
|
list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
out := opts.FormatDiff(r.Value, ptrs)
|
||||||
|
list = append(list, textRecord{Key: formatKey(r.Key), Value: out})
|
||||||
|
keys = append(keys, r.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recs = recs[ds.NumDiff():]
|
||||||
|
numDiffs += ds.NumDiff()
|
||||||
|
}
|
||||||
|
if maxGroup.IsZero() {
|
||||||
|
assert(len(recs) == 0)
|
||||||
|
} else {
|
||||||
|
list.AppendEllipsis(maxGroup)
|
||||||
|
for len(keys) < len(list) {
|
||||||
|
keys = append(keys, reflect.Value{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(len(list) == len(keys))
|
||||||
|
|
||||||
|
// For maps, the default formatting logic uses fmt.Stringer which may
|
||||||
|
// produce ambiguous output. Avoid calling String to disambiguate.
|
||||||
|
if k == reflect.Map {
|
||||||
|
var ambiguous bool
|
||||||
|
seenKeys := map[string]reflect.Value{}
|
||||||
|
for i, currKey := range keys {
|
||||||
|
if currKey.IsValid() {
|
||||||
|
strKey := list[i].Key
|
||||||
|
prevKey, seen := seenKeys[strKey]
|
||||||
|
if seen && prevKey.CanInterface() && currKey.CanInterface() {
|
||||||
|
ambiguous = prevKey.Interface() != currKey.Interface()
|
||||||
|
if ambiguous {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seenKeys[strKey] = currKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ambiguous {
|
||||||
|
for i, k := range keys {
|
||||||
|
if k.IsValid() {
|
||||||
|
list[i].Key = formatMapKey(k, true, ptrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// coalesceAdjacentRecords coalesces the list of records into groups of
|
||||||
|
// adjacent equal, or unequal counts.
|
||||||
|
func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) {
|
||||||
|
var prevCase int // Arbitrary index into which case last occurred
|
||||||
|
lastStats := func(i int) *diffStats {
|
||||||
|
if prevCase != i {
|
||||||
|
groups = append(groups, diffStats{Name: name})
|
||||||
|
prevCase = i
|
||||||
|
}
|
||||||
|
return &groups[len(groups)-1]
|
||||||
|
}
|
||||||
|
for _, r := range recs {
|
||||||
|
switch rv := r.Value; {
|
||||||
|
case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0:
|
||||||
|
lastStats(1).NumIgnored++
|
||||||
|
case rv.NumDiff == 0:
|
||||||
|
lastStats(1).NumIdentical++
|
||||||
|
case rv.NumDiff > 0 && !rv.ValueY.IsValid():
|
||||||
|
lastStats(2).NumRemoved++
|
||||||
|
case rv.NumDiff > 0 && !rv.ValueX.IsValid():
|
||||||
|
lastStats(2).NumInserted++
|
||||||
|
default:
|
||||||
|
lastStats(2).NumModified++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
264
vendor/github.com/google/go-cmp/cmp/report_references.go
generated
vendored
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
// Copyright 2020, 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 cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/flags"
|
||||||
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pointerDelimPrefix = "⟪"
|
||||||
|
pointerDelimSuffix = "⟫"
|
||||||
|
)
|
||||||
|
|
||||||
|
// formatPointer prints the address of the pointer.
|
||||||
|
func formatPointer(p value.Pointer, withDelims bool) string {
|
||||||
|
v := p.Uintptr()
|
||||||
|
if flags.Deterministic {
|
||||||
|
v = 0xdeadf00f // Only used for stable testing purposes
|
||||||
|
}
|
||||||
|
if withDelims {
|
||||||
|
return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix
|
||||||
|
}
|
||||||
|
return formatHex(uint64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pointerReferences is a stack of pointers visited so far.
|
||||||
|
type pointerReferences [][2]value.Pointer
|
||||||
|
|
||||||
|
func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) {
|
||||||
|
if deref && vx.IsValid() {
|
||||||
|
vx = vx.Addr()
|
||||||
|
}
|
||||||
|
if deref && vy.IsValid() {
|
||||||
|
vy = vy.Addr()
|
||||||
|
}
|
||||||
|
switch d {
|
||||||
|
case diffUnknown, diffIdentical:
|
||||||
|
pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)}
|
||||||
|
case diffRemoved:
|
||||||
|
pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}}
|
||||||
|
case diffInserted:
|
||||||
|
pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)}
|
||||||
|
}
|
||||||
|
*ps = append(*ps, pp)
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) {
|
||||||
|
p = value.PointerOf(v)
|
||||||
|
for _, pp := range *ps {
|
||||||
|
if p == pp[0] || p == pp[1] {
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*ps = append(*ps, [2]value.Pointer{p, p})
|
||||||
|
return p, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *pointerReferences) Pop() {
|
||||||
|
*ps = (*ps)[:len(*ps)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// trunkReferences is metadata for a textNode indicating that the sub-tree
|
||||||
|
// represents the value for either pointer in a pair of references.
|
||||||
|
type trunkReferences struct{ pp [2]value.Pointer }
|
||||||
|
|
||||||
|
// trunkReference is metadata for a textNode indicating that the sub-tree
|
||||||
|
// represents the value for the given pointer reference.
|
||||||
|
type trunkReference struct{ p value.Pointer }
|
||||||
|
|
||||||
|
// leafReference is metadata for a textNode indicating that the value is
|
||||||
|
// truncated as it refers to another part of the tree (i.e., a trunk).
|
||||||
|
type leafReference struct{ p value.Pointer }
|
||||||
|
|
||||||
|
func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode {
|
||||||
|
switch {
|
||||||
|
case pp[0].IsNil():
|
||||||
|
return &textWrap{Value: s, Metadata: trunkReference{pp[1]}}
|
||||||
|
case pp[1].IsNil():
|
||||||
|
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||||
|
case pp[0] == pp[1]:
|
||||||
|
return &textWrap{Value: s, Metadata: trunkReference{pp[0]}}
|
||||||
|
default:
|
||||||
|
return &textWrap{Value: s, Metadata: trunkReferences{pp}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode {
|
||||||
|
var prefix string
|
||||||
|
if printAddress {
|
||||||
|
prefix = formatPointer(p, true)
|
||||||
|
}
|
||||||
|
return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}}
|
||||||
|
}
|
||||||
|
func makeLeafReference(p value.Pointer, printAddress bool) textNode {
|
||||||
|
out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"}
|
||||||
|
var prefix string
|
||||||
|
if printAddress {
|
||||||
|
prefix = formatPointer(p, true)
|
||||||
|
}
|
||||||
|
return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveReferences walks the textNode tree searching for any leaf reference
|
||||||
|
// metadata and resolves each against the corresponding trunk references.
|
||||||
|
// Since pointer addresses in memory are not particularly readable to the user,
|
||||||
|
// it replaces each pointer value with an arbitrary and unique reference ID.
|
||||||
|
func resolveReferences(s textNode) {
|
||||||
|
var walkNodes func(textNode, func(textNode))
|
||||||
|
walkNodes = func(s textNode, f func(textNode)) {
|
||||||
|
f(s)
|
||||||
|
switch s := s.(type) {
|
||||||
|
case *textWrap:
|
||||||
|
walkNodes(s.Value, f)
|
||||||
|
case textList:
|
||||||
|
for _, r := range s {
|
||||||
|
walkNodes(r.Value, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all trunks and leaves with reference metadata.
|
||||||
|
var trunks, leaves []*textWrap
|
||||||
|
walkNodes(s, func(s textNode) {
|
||||||
|
if s, ok := s.(*textWrap); ok {
|
||||||
|
switch s.Metadata.(type) {
|
||||||
|
case leafReference:
|
||||||
|
leaves = append(leaves, s)
|
||||||
|
case trunkReference, trunkReferences:
|
||||||
|
trunks = append(trunks, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// No leaf references to resolve.
|
||||||
|
if len(leaves) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the set of all leaf references to resolve.
|
||||||
|
leafPtrs := make(map[value.Pointer]bool)
|
||||||
|
for _, leaf := range leaves {
|
||||||
|
leafPtrs[leaf.Metadata.(leafReference).p] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the set of trunk pointers that are always paired together.
|
||||||
|
// This allows us to assign a single ID to both pointers for brevity.
|
||||||
|
// If a pointer in a pair ever occurs by itself or as a different pair,
|
||||||
|
// then the pair is broken.
|
||||||
|
pairedTrunkPtrs := make(map[value.Pointer]value.Pointer)
|
||||||
|
unpair := func(p value.Pointer) {
|
||||||
|
if !pairedTrunkPtrs[p].IsNil() {
|
||||||
|
pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half
|
||||||
|
}
|
||||||
|
pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half
|
||||||
|
}
|
||||||
|
for _, trunk := range trunks {
|
||||||
|
switch p := trunk.Metadata.(type) {
|
||||||
|
case trunkReference:
|
||||||
|
unpair(p.p) // standalone pointer cannot be part of a pair
|
||||||
|
case trunkReferences:
|
||||||
|
p0, ok0 := pairedTrunkPtrs[p.pp[0]]
|
||||||
|
p1, ok1 := pairedTrunkPtrs[p.pp[1]]
|
||||||
|
switch {
|
||||||
|
case !ok0 && !ok1:
|
||||||
|
// Register the newly seen pair.
|
||||||
|
pairedTrunkPtrs[p.pp[0]] = p.pp[1]
|
||||||
|
pairedTrunkPtrs[p.pp[1]] = p.pp[0]
|
||||||
|
case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]:
|
||||||
|
// Exact pair already seen; do nothing.
|
||||||
|
default:
|
||||||
|
// Pair conflicts with some other pair; break all pairs.
|
||||||
|
unpair(p.pp[0])
|
||||||
|
unpair(p.pp[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correlate each pointer referenced by leaves to a unique identifier,
|
||||||
|
// and print the IDs for each trunk that matches those pointers.
|
||||||
|
var nextID uint
|
||||||
|
ptrIDs := make(map[value.Pointer]uint)
|
||||||
|
newID := func() uint {
|
||||||
|
id := nextID
|
||||||
|
nextID++
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
for _, trunk := range trunks {
|
||||||
|
switch p := trunk.Metadata.(type) {
|
||||||
|
case trunkReference:
|
||||||
|
if print := leafPtrs[p.p]; print {
|
||||||
|
id, ok := ptrIDs[p.p]
|
||||||
|
if !ok {
|
||||||
|
id = newID()
|
||||||
|
ptrIDs[p.p] = id
|
||||||
|
}
|
||||||
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||||
|
}
|
||||||
|
case trunkReferences:
|
||||||
|
print0 := leafPtrs[p.pp[0]]
|
||||||
|
print1 := leafPtrs[p.pp[1]]
|
||||||
|
if print0 || print1 {
|
||||||
|
id0, ok0 := ptrIDs[p.pp[0]]
|
||||||
|
id1, ok1 := ptrIDs[p.pp[1]]
|
||||||
|
isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0]
|
||||||
|
if isPair {
|
||||||
|
var id uint
|
||||||
|
assert(ok0 == ok1) // must be seen together or not at all
|
||||||
|
if ok0 {
|
||||||
|
assert(id0 == id1) // must have the same ID
|
||||||
|
id = id0
|
||||||
|
} else {
|
||||||
|
id = newID()
|
||||||
|
ptrIDs[p.pp[0]] = id
|
||||||
|
ptrIDs[p.pp[1]] = id
|
||||||
|
}
|
||||||
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id))
|
||||||
|
} else {
|
||||||
|
if print0 && !ok0 {
|
||||||
|
id0 = newID()
|
||||||
|
ptrIDs[p.pp[0]] = id0
|
||||||
|
}
|
||||||
|
if print1 && !ok1 {
|
||||||
|
id1 = newID()
|
||||||
|
ptrIDs[p.pp[1]] = id1
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case print0 && print1:
|
||||||
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1))
|
||||||
|
case print0:
|
||||||
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0))
|
||||||
|
case print1:
|
||||||
|
trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all leaf references with the unique identifier.
|
||||||
|
for _, leaf := range leaves {
|
||||||
|
if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok {
|
||||||
|
leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatReference(id uint) string {
|
||||||
|
return fmt.Sprintf("ref#%d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateReferencePrefix(prefix, ref string) string {
|
||||||
|
if prefix == "" {
|
||||||
|
return pointerDelimPrefix + ref + pointerDelimSuffix
|
||||||
|
}
|
||||||
|
suffix := strings.TrimPrefix(prefix, pointerDelimPrefix)
|
||||||
|
return pointerDelimPrefix + ref + ": " + suffix
|
||||||
|
}
|
403
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
Normal file
403
vendor/github.com/google/go-cmp/cmp/report_reflect.go
generated
vendored
Normal file
|
@ -0,0 +1,403 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
|
)
|
||||||
|
|
||||||
|
type formatValueOptions struct {
|
||||||
|
// AvoidStringer controls whether to avoid calling custom stringer
|
||||||
|
// methods like error.Error or fmt.Stringer.String.
|
||||||
|
AvoidStringer bool
|
||||||
|
|
||||||
|
// PrintAddresses controls whether to print the address of all pointers,
|
||||||
|
// slice elements, and maps.
|
||||||
|
PrintAddresses bool
|
||||||
|
|
||||||
|
// QualifiedNames controls whether FormatType uses the fully qualified name
|
||||||
|
// (including the full package path as opposed to just the package name).
|
||||||
|
QualifiedNames bool
|
||||||
|
|
||||||
|
// VerbosityLevel controls the amount of output to produce.
|
||||||
|
// A higher value produces more output. A value of zero or lower produces
|
||||||
|
// no output (represented using an ellipsis).
|
||||||
|
// If LimitVerbosity is false, then the level is treated as infinite.
|
||||||
|
VerbosityLevel int
|
||||||
|
|
||||||
|
// LimitVerbosity specifies that formatting should respect VerbosityLevel.
|
||||||
|
LimitVerbosity bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatType prints the type as if it were wrapping s.
|
||||||
|
// This may return s as-is depending on the current type and TypeMode mode.
|
||||||
|
func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode {
|
||||||
|
// Check whether to emit the type or not.
|
||||||
|
switch opts.TypeMode {
|
||||||
|
case autoType:
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map:
|
||||||
|
if s.Equal(textNil) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if opts.DiffMode == diffIdentical {
|
||||||
|
return s // elide type for identical nodes
|
||||||
|
}
|
||||||
|
case elideType:
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the type label, applying special handling for unnamed types.
|
||||||
|
typeName := value.TypeString(t, opts.QualifiedNames)
|
||||||
|
if t.Name() == "" {
|
||||||
|
// According to Go grammar, certain type literals contain symbols that
|
||||||
|
// do not strongly bind to the next lexicographical token (e.g., *T).
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Ptr:
|
||||||
|
typeName = "(" + typeName + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &textWrap{Prefix: typeName, Value: wrapParens(s)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapParens wraps s with a set of parenthesis, but avoids it if the
|
||||||
|
// wrapped node itself is already surrounded by a pair of parenthesis or braces.
|
||||||
|
// It handles unwrapping one level of pointer-reference nodes.
|
||||||
|
func wrapParens(s textNode) textNode {
|
||||||
|
var refNode *textWrap
|
||||||
|
if s2, ok := s.(*textWrap); ok {
|
||||||
|
// Unwrap a single pointer reference node.
|
||||||
|
switch s2.Metadata.(type) {
|
||||||
|
case leafReference, trunkReference, trunkReferences:
|
||||||
|
refNode = s2
|
||||||
|
if s3, ok := refNode.Value.(*textWrap); ok {
|
||||||
|
s2 = s3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already has delimiters that make parenthesis unnecessary.
|
||||||
|
hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")")
|
||||||
|
hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}")
|
||||||
|
if hasParens || hasBraces {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if refNode != nil {
|
||||||
|
refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return &textWrap{Prefix: "(", Value: s, Suffix: ")"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatValue prints the reflect.Value, taking extra care to avoid descending
|
||||||
|
// into pointers already in ptrs. As pointers are visited, ptrs is also updated.
|
||||||
|
func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) {
|
||||||
|
if !v.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
// Check slice element for cycles.
|
||||||
|
if parentKind == reflect.Slice {
|
||||||
|
ptrRef, visited := ptrs.Push(v.Addr())
|
||||||
|
if visited {
|
||||||
|
return makeLeafReference(ptrRef, false)
|
||||||
|
}
|
||||||
|
defer ptrs.Pop()
|
||||||
|
defer func() { out = wrapTrunkReference(ptrRef, false, out) }()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether there is an Error or String method to call.
|
||||||
|
if !opts.AvoidStringer && v.CanInterface() {
|
||||||
|
// Avoid calling Error or String methods on nil receivers since many
|
||||||
|
// implementations crash when doing so.
|
||||||
|
if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() {
|
||||||
|
var prefix, strVal string
|
||||||
|
func() {
|
||||||
|
// Swallow and ignore any panics from String or Error.
|
||||||
|
defer func() { recover() }()
|
||||||
|
switch v := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
strVal = v.Error()
|
||||||
|
prefix = "e"
|
||||||
|
case fmt.Stringer:
|
||||||
|
strVal = v.String()
|
||||||
|
prefix = "s"
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if prefix != "" {
|
||||||
|
return opts.formatString(prefix, strVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether to explicitly wrap the result with the type.
|
||||||
|
var skipType bool
|
||||||
|
defer func() {
|
||||||
|
if !skipType {
|
||||||
|
out = opts.FormatType(t, out)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return textLine(fmt.Sprint(v.Bool()))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return textLine(fmt.Sprint(v.Int()))
|
||||||
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return textLine(fmt.Sprint(v.Uint()))
|
||||||
|
case reflect.Uint8:
|
||||||
|
if parentKind == reflect.Slice || parentKind == reflect.Array {
|
||||||
|
return textLine(formatHex(v.Uint()))
|
||||||
|
}
|
||||||
|
return textLine(fmt.Sprint(v.Uint()))
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return textLine(formatHex(v.Uint()))
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return textLine(fmt.Sprint(v.Float()))
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return textLine(fmt.Sprint(v.Complex()))
|
||||||
|
case reflect.String:
|
||||||
|
return opts.formatString("", v.String())
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
return textLine(formatPointer(value.PointerOf(v), true))
|
||||||
|
case reflect.Struct:
|
||||||
|
var list textList
|
||||||
|
v := makeAddressable(v) // needed for retrieveUnexportedField
|
||||||
|
maxLen := v.NumField()
|
||||||
|
if opts.LimitVerbosity {
|
||||||
|
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||||
|
opts.VerbosityLevel--
|
||||||
|
}
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
vv := v.Field(i)
|
||||||
|
if value.IsZero(vv) {
|
||||||
|
continue // Elide fields with zero values
|
||||||
|
}
|
||||||
|
if len(list) == maxLen {
|
||||||
|
list.AppendEllipsis(diffStats{})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sf := t.Field(i)
|
||||||
|
if supportExporters && !isExported(sf.Name) {
|
||||||
|
vv = retrieveUnexportedField(v, sf, true)
|
||||||
|
}
|
||||||
|
s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs)
|
||||||
|
list = append(list, textRecord{Key: sf.Name, Value: s})
|
||||||
|
}
|
||||||
|
return &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
return textNil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether this is a []byte of text data.
|
||||||
|
if t.Elem() == reflect.TypeOf(byte(0)) {
|
||||||
|
b := v.Bytes()
|
||||||
|
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 {
|
||||||
|
out = opts.formatString("", string(b))
|
||||||
|
skipType = true
|
||||||
|
return opts.WithTypeMode(emitType).FormatType(t, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
case reflect.Array:
|
||||||
|
maxLen := v.Len()
|
||||||
|
if opts.LimitVerbosity {
|
||||||
|
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||||
|
opts.VerbosityLevel--
|
||||||
|
}
|
||||||
|
var list textList
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if len(list) == maxLen {
|
||||||
|
list.AppendEllipsis(diffStats{})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs)
|
||||||
|
list = append(list, textRecord{Value: s})
|
||||||
|
}
|
||||||
|
|
||||||
|
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
|
if t.Kind() == reflect.Slice && opts.PrintAddresses {
|
||||||
|
header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap())
|
||||||
|
out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
case reflect.Map:
|
||||||
|
if v.IsNil() {
|
||||||
|
return textNil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check pointer for cycles.
|
||||||
|
ptrRef, visited := ptrs.Push(v)
|
||||||
|
if visited {
|
||||||
|
return makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||||
|
}
|
||||||
|
defer ptrs.Pop()
|
||||||
|
|
||||||
|
maxLen := v.Len()
|
||||||
|
if opts.LimitVerbosity {
|
||||||
|
maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc...
|
||||||
|
opts.VerbosityLevel--
|
||||||
|
}
|
||||||
|
var list textList
|
||||||
|
for _, k := range value.SortKeys(v.MapKeys()) {
|
||||||
|
if len(list) == maxLen {
|
||||||
|
list.AppendEllipsis(diffStats{})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sk := formatMapKey(k, false, ptrs)
|
||||||
|
sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs)
|
||||||
|
list = append(list, textRecord{Key: sk, Value: sv})
|
||||||
|
}
|
||||||
|
|
||||||
|
out = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
|
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||||
|
return out
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
return textNil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check pointer for cycles.
|
||||||
|
ptrRef, visited := ptrs.Push(v)
|
||||||
|
if visited {
|
||||||
|
out = makeLeafReference(ptrRef, opts.PrintAddresses)
|
||||||
|
return &textWrap{Prefix: "&", Value: out}
|
||||||
|
}
|
||||||
|
defer ptrs.Pop()
|
||||||
|
|
||||||
|
skipType = true // Let the underlying value print the type instead
|
||||||
|
out = opts.FormatValue(v.Elem(), t.Kind(), ptrs)
|
||||||
|
out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out)
|
||||||
|
out = &textWrap{Prefix: "&", Value: out}
|
||||||
|
return out
|
||||||
|
case reflect.Interface:
|
||||||
|
if v.IsNil() {
|
||||||
|
return textNil
|
||||||
|
}
|
||||||
|
// Interfaces accept different concrete types,
|
||||||
|
// 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)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts formatOptions) formatString(prefix, s string) textNode {
|
||||||
|
maxLen := len(s)
|
||||||
|
maxLines := strings.Count(s, "\n") + 1
|
||||||
|
if opts.LimitVerbosity {
|
||||||
|
maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc...
|
||||||
|
maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||||
|
}
|
||||||
|
|
||||||
|
// For multiline strings, use the triple-quote syntax,
|
||||||
|
// but only use it when printing removed or inserted nodes since
|
||||||
|
// we only want the extra verbosity for those cases.
|
||||||
|
lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n")
|
||||||
|
isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+')
|
||||||
|
for i := 0; i < len(lines) && isTripleQuoted; i++ {
|
||||||
|
lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||||
|
isPrintable := func(r rune) bool {
|
||||||
|
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||||
|
}
|
||||||
|
line := lines[i]
|
||||||
|
isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen
|
||||||
|
}
|
||||||
|
if isTripleQuoted {
|
||||||
|
var list textList
|
||||||
|
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||||
|
for i, line := range lines {
|
||||||
|
if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 {
|
||||||
|
comment := commentString(fmt.Sprintf("%d elided lines", numElided))
|
||||||
|
list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true})
|
||||||
|
}
|
||||||
|
list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true})
|
||||||
|
return &textWrap{Prefix: "(", Value: list, Suffix: ")"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the string as a single-line quoted string.
|
||||||
|
if len(s) > maxLen+len(textEllipsis) {
|
||||||
|
return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis))
|
||||||
|
}
|
||||||
|
return textLine(prefix + formatString(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatMapKey formats v as if it were a map key.
|
||||||
|
// The result is guaranteed to be a single line.
|
||||||
|
func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
|
||||||
|
var opts formatOptions
|
||||||
|
opts.DiffMode = diffIdentical
|
||||||
|
opts.TypeMode = elideType
|
||||||
|
opts.PrintAddresses = disambiguate
|
||||||
|
opts.AvoidStringer = disambiguate
|
||||||
|
opts.QualifiedNames = disambiguate
|
||||||
|
opts.VerbosityLevel = maxVerbosityPreset
|
||||||
|
opts.LimitVerbosity = true
|
||||||
|
s := opts.FormatValue(v, reflect.Map, ptrs).String()
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatString prints s as a double-quoted or backtick-quoted string.
|
||||||
|
func formatString(s string) string {
|
||||||
|
// Use quoted string if it the same length as a raw string literal.
|
||||||
|
// Otherwise, attempt to use the raw string form.
|
||||||
|
qs := strconv.Quote(s)
|
||||||
|
if len(qs) == 1+len(s)+1 {
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow newlines to ensure output is a single line.
|
||||||
|
// Only allow printable runes for readability purposes.
|
||||||
|
rawInvalid := func(r rune) bool {
|
||||||
|
return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t')
|
||||||
|
}
|
||||||
|
if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 {
|
||||||
|
return "`" + s + "`"
|
||||||
|
}
|
||||||
|
return qs
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatHex prints u as a hexadecimal integer in Go notation.
|
||||||
|
func formatHex(u uint64) string {
|
||||||
|
var f string
|
||||||
|
switch {
|
||||||
|
case u <= 0xff:
|
||||||
|
f = "0x%02x"
|
||||||
|
case u <= 0xffff:
|
||||||
|
f = "0x%04x"
|
||||||
|
case u <= 0xffffff:
|
||||||
|
f = "0x%06x"
|
||||||
|
case u <= 0xffffffff:
|
||||||
|
f = "0x%08x"
|
||||||
|
case u <= 0xffffffffff:
|
||||||
|
f = "0x%010x"
|
||||||
|
case u <= 0xffffffffffff:
|
||||||
|
f = "0x%012x"
|
||||||
|
case u <= 0xffffffffffffff:
|
||||||
|
f = "0x%014x"
|
||||||
|
case u <= 0xffffffffffffffff:
|
||||||
|
f = "0x%016x"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(f, u)
|
||||||
|
}
|
613
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
Normal file
613
vendor/github.com/google/go-cmp/cmp/report_slices.go
generated
vendored
Normal file
|
@ -0,0 +1,613 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/diff"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CanFormatDiffSlice reports whether we support custom formatting for nodes
|
||||||
|
// that are slices of primitive kinds or strings.
|
||||||
|
func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
|
||||||
|
switch {
|
||||||
|
case opts.DiffMode != diffUnknown:
|
||||||
|
return false // Must be formatting in diff mode
|
||||||
|
case v.NumDiff == 0:
|
||||||
|
return false // No differences detected
|
||||||
|
case !v.ValueX.IsValid() || !v.ValueY.IsValid():
|
||||||
|
return false // Both values must be valid
|
||||||
|
case v.NumIgnored > 0:
|
||||||
|
return false // Some ignore option was used
|
||||||
|
case v.NumTransformed > 0:
|
||||||
|
return false // Some transform option was used
|
||||||
|
case v.NumCompared > 1:
|
||||||
|
return false // More than one comparison was used
|
||||||
|
case v.NumCompared == 1 && v.Type.Name() != "":
|
||||||
|
// The need for cmp to check applicability of options on every element
|
||||||
|
// in a slice is a significant performance detriment for large []byte.
|
||||||
|
// The workaround is to specify Comparer(bytes.Equal),
|
||||||
|
// which enables cmp to compare []byte more efficiently.
|
||||||
|
// If they differ, we still want to provide batched diffing.
|
||||||
|
// The logic disallows named types since they tend to have their own
|
||||||
|
// String method, with nicer formatting than what this provides.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether this is an interface with the same concrete types.
|
||||||
|
t := v.Type
|
||||||
|
vx, vy := v.ValueX, v.ValueY
|
||||||
|
if t.Kind() == reflect.Interface && !vx.IsNil() && !vy.IsNil() && vx.Elem().Type() == vy.Elem().Type() {
|
||||||
|
vx, vy = vx.Elem(), vy.Elem()
|
||||||
|
t = vx.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether we provide specialized diffing for this type.
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
// Only slices of primitive types have specialized handling.
|
||||||
|
switch t.Elem().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||||
|
reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both slice values have to be non-empty.
|
||||||
|
if t.Kind() == reflect.Slice && (vx.Len() == 0 || vy.Len() == 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a sufficient number of elements already differ,
|
||||||
|
// use specialized formatting even if length requirement is not met.
|
||||||
|
if v.NumDiff > v.NumSame {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use specialized string diffing for longer slices or strings.
|
||||||
|
const minLength = 32
|
||||||
|
return vx.Len() >= minLength && vy.Len() >= minLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatDiffSlice prints a diff for the slices (or strings) represented by v.
|
||||||
|
// This provides custom-tailored logic to make printing of differences in
|
||||||
|
// textual strings and slices of primitive kinds more readable.
|
||||||
|
func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
|
||||||
|
assert(opts.DiffMode == diffUnknown)
|
||||||
|
t, vx, vy := v.Type, v.ValueX, v.ValueY
|
||||||
|
if t.Kind() == reflect.Interface {
|
||||||
|
vx, vy = vx.Elem(), vy.Elem()
|
||||||
|
t = vx.Type()
|
||||||
|
opts = opts.WithTypeMode(emitType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-detect the type of the data.
|
||||||
|
var sx, sy string
|
||||||
|
var ssx, ssy []string
|
||||||
|
var isString, isMostlyText, isPureLinedText, isBinary bool
|
||||||
|
switch {
|
||||||
|
case t.Kind() == reflect.String:
|
||||||
|
sx, sy = vx.String(), vy.String()
|
||||||
|
isString = true
|
||||||
|
case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)):
|
||||||
|
sx, sy = string(vx.Bytes()), string(vy.Bytes())
|
||||||
|
isString = true
|
||||||
|
case t.Kind() == reflect.Array:
|
||||||
|
// Arrays need to be addressable for slice operations to work.
|
||||||
|
vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem()
|
||||||
|
vx2.Set(vx)
|
||||||
|
vy2.Set(vy)
|
||||||
|
vx, vy = vx2, vy2
|
||||||
|
}
|
||||||
|
if isString {
|
||||||
|
var numTotalRunes, numValidRunes, numLines, lastLineIdx, maxLineLen int
|
||||||
|
for i, r := range sx + sy {
|
||||||
|
numTotalRunes++
|
||||||
|
if (unicode.IsPrint(r) || unicode.IsSpace(r)) && r != utf8.RuneError {
|
||||||
|
numValidRunes++
|
||||||
|
}
|
||||||
|
if r == '\n' {
|
||||||
|
if maxLineLen < i-lastLineIdx {
|
||||||
|
maxLineLen = i - lastLineIdx
|
||||||
|
}
|
||||||
|
lastLineIdx = i + 1
|
||||||
|
numLines++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isPureText := numValidRunes == numTotalRunes
|
||||||
|
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))
|
||||||
|
isPureLinedText = efficiencyLines < 4*efficiencyBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the string into printable records.
|
||||||
|
var list textList
|
||||||
|
var delim string
|
||||||
|
switch {
|
||||||
|
// If the text appears to be multi-lined text,
|
||||||
|
// then perform differencing across individual lines.
|
||||||
|
case isPureLinedText:
|
||||||
|
list = opts.formatDiffSlice(
|
||||||
|
reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line",
|
||||||
|
func(v reflect.Value, d diffMode) textRecord {
|
||||||
|
s := formatString(v.Index(0).String())
|
||||||
|
return textRecord{Diff: d, Value: textLine(s)}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
delim = "\n"
|
||||||
|
|
||||||
|
// If possible, use a custom triple-quote (""") syntax for printing
|
||||||
|
// differences in a string literal. This format is more readable,
|
||||||
|
// but has edge-cases where differences are visually indistinguishable.
|
||||||
|
// This format is avoided under the following conditions:
|
||||||
|
// • A line starts with `"""`
|
||||||
|
// • A line starts with "..."
|
||||||
|
// • A line contains non-printable characters
|
||||||
|
// • Adjacent different lines differ only by whitespace
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// """
|
||||||
|
// ... // 3 identical lines
|
||||||
|
// foo
|
||||||
|
// bar
|
||||||
|
// - baz
|
||||||
|
// + BAZ
|
||||||
|
// """
|
||||||
|
isTripleQuoted := true
|
||||||
|
prevRemoveLines := map[string]bool{}
|
||||||
|
prevInsertLines := map[string]bool{}
|
||||||
|
var list2 textList
|
||||||
|
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||||
|
for _, r := range list {
|
||||||
|
if !r.Value.Equal(textEllipsis) {
|
||||||
|
line, _ := strconv.Unquote(string(r.Value.(textLine)))
|
||||||
|
line = strings.TrimPrefix(strings.TrimSuffix(line, "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support
|
||||||
|
normLine := strings.Map(func(r rune) rune {
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
return -1 // drop whitespace to avoid visually indistinguishable output
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, line)
|
||||||
|
isPrintable := func(r rune) bool {
|
||||||
|
return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable
|
||||||
|
}
|
||||||
|
isTripleQuoted = !strings.HasPrefix(line, `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == ""
|
||||||
|
switch r.Diff {
|
||||||
|
case diffRemoved:
|
||||||
|
isTripleQuoted = isTripleQuoted && !prevInsertLines[normLine]
|
||||||
|
prevRemoveLines[normLine] = true
|
||||||
|
case diffInserted:
|
||||||
|
isTripleQuoted = isTripleQuoted && !prevRemoveLines[normLine]
|
||||||
|
prevInsertLines[normLine] = true
|
||||||
|
}
|
||||||
|
if !isTripleQuoted {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.Value = textLine(line)
|
||||||
|
r.ElideComma = true
|
||||||
|
}
|
||||||
|
if !(r.Diff == diffRemoved || r.Diff == diffInserted) { // start a new non-adjacent difference group
|
||||||
|
prevRemoveLines = map[string]bool{}
|
||||||
|
prevInsertLines = map[string]bool{}
|
||||||
|
}
|
||||||
|
list2 = append(list2, r)
|
||||||
|
}
|
||||||
|
if r := list2[len(list2)-1]; r.Diff == diffIdentical && len(r.Value.(textLine)) == 0 {
|
||||||
|
list2 = list2[:len(list2)-1] // elide single empty line at the end
|
||||||
|
}
|
||||||
|
list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true})
|
||||||
|
if isTripleQuoted {
|
||||||
|
var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"}
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if t != reflect.TypeOf(string("")) {
|
||||||
|
out = opts.FormatType(t, out)
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
// Always emit type for slices since the triple-quote syntax
|
||||||
|
// looks like a string (not a slice).
|
||||||
|
opts = opts.WithTypeMode(emitType)
|
||||||
|
out = opts.FormatType(t, out)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the text appears to be single-lined text,
|
||||||
|
// then perform differencing in approximately fixed-sized chunks.
|
||||||
|
// The output is printed as quoted strings.
|
||||||
|
case isMostlyText:
|
||||||
|
list = opts.formatDiffSlice(
|
||||||
|
reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte",
|
||||||
|
func(v reflect.Value, d diffMode) textRecord {
|
||||||
|
s := formatString(v.String())
|
||||||
|
return textRecord{Diff: d, Value: textLine(s)}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// If the text appears to be binary data,
|
||||||
|
// then perform differencing in approximately fixed-sized chunks.
|
||||||
|
// The output is inspired by hexdump.
|
||||||
|
case isBinary:
|
||||||
|
list = opts.formatDiffSlice(
|
||||||
|
reflect.ValueOf(sx), reflect.ValueOf(sy), 16, "byte",
|
||||||
|
func(v reflect.Value, d diffMode) textRecord {
|
||||||
|
var ss []string
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||||
|
}
|
||||||
|
s := strings.Join(ss, ", ")
|
||||||
|
comment := commentString(fmt.Sprintf("%c|%v|", d, formatASCII(v.String())))
|
||||||
|
return textRecord{Diff: d, Value: textLine(s), Comment: comment}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// For all other slices of primitive types,
|
||||||
|
// then perform differencing in approximately fixed-sized chunks.
|
||||||
|
// The size of each chunk depends on the width of the element kind.
|
||||||
|
default:
|
||||||
|
var chunkSize int
|
||||||
|
if t.Elem().Kind() == reflect.Bool {
|
||||||
|
chunkSize = 16
|
||||||
|
} else {
|
||||||
|
switch t.Elem().Bits() {
|
||||||
|
case 8:
|
||||||
|
chunkSize = 16
|
||||||
|
case 16:
|
||||||
|
chunkSize = 12
|
||||||
|
case 32:
|
||||||
|
chunkSize = 8
|
||||||
|
default:
|
||||||
|
chunkSize = 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list = opts.formatDiffSlice(
|
||||||
|
vx, vy, chunkSize, t.Elem().Kind().String(),
|
||||||
|
func(v reflect.Value, d diffMode) textRecord {
|
||||||
|
var ss []string
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
switch t.Elem().Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
ss = append(ss, fmt.Sprint(v.Index(i).Int()))
|
||||||
|
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
ss = append(ss, fmt.Sprint(v.Index(i).Uint()))
|
||||||
|
case reflect.Uint8, reflect.Uintptr:
|
||||||
|
ss = append(ss, formatHex(v.Index(i).Uint()))
|
||||||
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
|
||||||
|
ss = append(ss, fmt.Sprint(v.Index(i).Interface()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s := strings.Join(ss, ", ")
|
||||||
|
return textRecord{Diff: d, Value: textLine(s)}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the output with appropriate type information.
|
||||||
|
var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"}
|
||||||
|
if !isMostlyText {
|
||||||
|
// The "{...}" byte-sequence literal is not valid Go syntax for strings.
|
||||||
|
// Emit the type for extra clarity (e.g. "string{...}").
|
||||||
|
if t.Kind() == reflect.String {
|
||||||
|
opts = opts.WithTypeMode(emitType)
|
||||||
|
}
|
||||||
|
return opts.FormatType(t, out)
|
||||||
|
}
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||||
|
if t != reflect.TypeOf(string("")) {
|
||||||
|
out = opts.FormatType(t, out)
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)}
|
||||||
|
if t != reflect.TypeOf([]byte(nil)) {
|
||||||
|
out = opts.FormatType(t, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatASCII formats s as an ASCII string.
|
||||||
|
// This is useful for printing binary strings in a semi-legible way.
|
||||||
|
func formatASCII(s string) string {
|
||||||
|
b := bytes.Repeat([]byte{'.'}, len(s))
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if ' ' <= s[i] && s[i] <= '~' {
|
||||||
|
b[i] = s[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts formatOptions) formatDiffSlice(
|
||||||
|
vx, vy reflect.Value, chunkSize int, name string,
|
||||||
|
makeRec func(reflect.Value, diffMode) textRecord,
|
||||||
|
) (list textList) {
|
||||||
|
eq := func(ix, iy int) bool {
|
||||||
|
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 {
|
||||||
|
n0 := v.Len()
|
||||||
|
for v.Len() > 0 {
|
||||||
|
n := chunkSize
|
||||||
|
if n > v.Len() {
|
||||||
|
n = v.Len()
|
||||||
|
}
|
||||||
|
list = append(list, makeRec(v.Slice(0, n), d))
|
||||||
|
v = v.Slice(n, v.Len())
|
||||||
|
}
|
||||||
|
return n0 - v.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
var numDiffs int
|
||||||
|
maxLen := -1
|
||||||
|
if opts.LimitVerbosity {
|
||||||
|
maxLen = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc...
|
||||||
|
opts.VerbosityLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
groups := coalesceAdjacentEdits(name, es)
|
||||||
|
groups = coalesceInterveningIdentical(groups, chunkSize/4)
|
||||||
|
groups = cleanupSurroundingIdentical(groups, eq)
|
||||||
|
maxGroup := diffStats{Name: name}
|
||||||
|
for i, ds := range groups {
|
||||||
|
if maxLen >= 0 && numDiffs >= maxLen {
|
||||||
|
maxGroup = maxGroup.Append(ds)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print equal.
|
||||||
|
if ds.NumDiff() == 0 {
|
||||||
|
// Compute the number of leading and trailing equal bytes to print.
|
||||||
|
var numLo, numHi int
|
||||||
|
numEqual := ds.NumIgnored + ds.NumIdentical
|
||||||
|
for numLo < chunkSize*numContextRecords && numLo+numHi < numEqual && i != 0 {
|
||||||
|
numLo++
|
||||||
|
}
|
||||||
|
for numHi < chunkSize*numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 {
|
||||||
|
numHi++
|
||||||
|
}
|
||||||
|
if numEqual-(numLo+numHi) <= chunkSize && ds.NumIgnored == 0 {
|
||||||
|
numHi = numEqual - numLo // Avoid pointless coalescing of single equal row
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the equal bytes.
|
||||||
|
appendChunks(vx.Slice(0, numLo), diffIdentical)
|
||||||
|
if numEqual > numLo+numHi {
|
||||||
|
ds.NumIdentical -= numLo + numHi
|
||||||
|
list.AppendEllipsis(ds)
|
||||||
|
}
|
||||||
|
appendChunks(vx.Slice(numEqual-numHi, numEqual), diffIdentical)
|
||||||
|
vx = vx.Slice(numEqual, vx.Len())
|
||||||
|
vy = vy.Slice(numEqual, vy.Len())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print unequal.
|
||||||
|
len0 := len(list)
|
||||||
|
nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved)
|
||||||
|
vx = vx.Slice(nx, vx.Len())
|
||||||
|
ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted)
|
||||||
|
vy = vy.Slice(ny, vy.Len())
|
||||||
|
numDiffs += len(list) - len0
|
||||||
|
}
|
||||||
|
if maxGroup.IsZero() {
|
||||||
|
assert(vx.Len() == 0 && vy.Len() == 0)
|
||||||
|
} else {
|
||||||
|
list.AppendEllipsis(maxGroup)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
// coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
|
||||||
|
// 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) {
|
||||||
|
var prevMode byte
|
||||||
|
lastStats := func(mode byte) *diffStats {
|
||||||
|
if prevMode != mode {
|
||||||
|
groups = append(groups, diffStats{Name: name})
|
||||||
|
prevMode = mode
|
||||||
|
}
|
||||||
|
return &groups[len(groups)-1]
|
||||||
|
}
|
||||||
|
for _, e := range es {
|
||||||
|
switch e {
|
||||||
|
case diff.Identity:
|
||||||
|
lastStats('=').NumIdentical++
|
||||||
|
case diff.UniqueX:
|
||||||
|
lastStats('!').NumRemoved++
|
||||||
|
case diff.UniqueY:
|
||||||
|
lastStats('!').NumInserted++
|
||||||
|
case diff.Modified:
|
||||||
|
lastStats('!').NumModified++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
// coalesceInterveningIdentical coalesces sufficiently short (<= windowSize)
|
||||||
|
// equal groups into adjacent unequal groups that currently result in a
|
||||||
|
// dual inserted/removed printout. This acts as a high-pass filter to smooth
|
||||||
|
// 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 {
|
||||||
|
groups, groupsOrig := groups[:0], groups
|
||||||
|
for i, ds := range groupsOrig {
|
||||||
|
if len(groups) >= 2 && ds.NumDiff() > 0 {
|
||||||
|
prev := &groups[len(groups)-2] // Unequal group
|
||||||
|
curr := &groups[len(groups)-1] // Equal group
|
||||||
|
next := &groupsOrig[i] // Unequal group
|
||||||
|
hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0
|
||||||
|
hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0
|
||||||
|
if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize {
|
||||||
|
*prev = prev.Append(*curr).Append(*next)
|
||||||
|
groups = groups[:len(groups)-1] // Truncate off equal group
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groups = append(groups, ds)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
431
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
Normal file
431
vendor/github.com/google/go-cmp/cmp/report_text.go
generated
vendored
Normal file
|
@ -0,0 +1,431 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/flags"
|
||||||
|
)
|
||||||
|
|
||||||
|
var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
|
||||||
|
|
||||||
|
const maxColumnLength = 80
|
||||||
|
|
||||||
|
type indentMode int
|
||||||
|
|
||||||
|
func (n indentMode) appendIndent(b []byte, d diffMode) []byte {
|
||||||
|
// The output of Diff is documented as being unstable to provide future
|
||||||
|
// flexibility in changing the output for more humanly readable reports.
|
||||||
|
// This logic intentionally introduces instability to the exact output
|
||||||
|
// so that users can detect accidental reliance on stability early on,
|
||||||
|
// rather than much later when an actual change to the format occurs.
|
||||||
|
if flags.Deterministic || randBool {
|
||||||
|
// Use regular spaces (U+0020).
|
||||||
|
switch d {
|
||||||
|
case diffUnknown, diffIdentical:
|
||||||
|
b = append(b, " "...)
|
||||||
|
case diffRemoved:
|
||||||
|
b = append(b, "- "...)
|
||||||
|
case diffInserted:
|
||||||
|
b = append(b, "+ "...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use non-breaking spaces (U+00a0).
|
||||||
|
switch d {
|
||||||
|
case diffUnknown, diffIdentical:
|
||||||
|
b = append(b, " "...)
|
||||||
|
case diffRemoved:
|
||||||
|
b = append(b, "- "...)
|
||||||
|
case diffInserted:
|
||||||
|
b = append(b, "+ "...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repeatCount(n).appendChar(b, '\t')
|
||||||
|
}
|
||||||
|
|
||||||
|
type repeatCount int
|
||||||
|
|
||||||
|
func (n repeatCount) appendChar(b []byte, c byte) []byte {
|
||||||
|
for ; n > 0; n-- {
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// textNode is a simplified tree-based representation of structured text.
|
||||||
|
// Possible node types are textWrap, textList, or textLine.
|
||||||
|
type textNode interface {
|
||||||
|
// Len reports the length in bytes of a single-line version of the tree.
|
||||||
|
// Nested textRecord.Diff and textRecord.Comment fields are ignored.
|
||||||
|
Len() int
|
||||||
|
// Equal reports whether the two trees are structurally identical.
|
||||||
|
// Nested textRecord.Diff and textRecord.Comment fields are compared.
|
||||||
|
Equal(textNode) bool
|
||||||
|
// String returns the string representation of the text tree.
|
||||||
|
// It is not guaranteed that len(x.String()) == x.Len(),
|
||||||
|
// nor that x.String() == y.String() implies that x.Equal(y).
|
||||||
|
String() string
|
||||||
|
|
||||||
|
// formatCompactTo formats the contents of the tree as a single-line string
|
||||||
|
// to the provided buffer. Any nested textRecord.Diff and textRecord.Comment
|
||||||
|
// fields are ignored.
|
||||||
|
//
|
||||||
|
// However, not all nodes in the tree should be collapsed as a single-line.
|
||||||
|
// If a node can be collapsed as a single-line, it is replaced by a textLine
|
||||||
|
// node. Since the top-level node cannot replace itself, this also returns
|
||||||
|
// the current node itself.
|
||||||
|
//
|
||||||
|
// This does not mutate the receiver.
|
||||||
|
formatCompactTo([]byte, diffMode) ([]byte, textNode)
|
||||||
|
// formatExpandedTo formats the contents of the tree as a multi-line string
|
||||||
|
// to the provided buffer. In order for column alignment to operate well,
|
||||||
|
// formatCompactTo must be called before calling formatExpandedTo.
|
||||||
|
formatExpandedTo([]byte, diffMode, indentMode) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// textWrap is a wrapper that concatenates a prefix and/or a suffix
|
||||||
|
// to the underlying node.
|
||||||
|
type textWrap struct {
|
||||||
|
Prefix string // e.g., "bytes.Buffer{"
|
||||||
|
Value textNode // textWrap | textList | textLine
|
||||||
|
Suffix string // e.g., "}"
|
||||||
|
Metadata interface{} // arbitrary metadata; has no effect on formatting
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *textWrap) Len() int {
|
||||||
|
return len(s.Prefix) + s.Value.Len() + len(s.Suffix)
|
||||||
|
}
|
||||||
|
func (s1 *textWrap) Equal(s2 textNode) bool {
|
||||||
|
if s2, ok := s2.(*textWrap); ok {
|
||||||
|
return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (s *textWrap) String() string {
|
||||||
|
var d diffMode
|
||||||
|
var n indentMode
|
||||||
|
_, s2 := s.formatCompactTo(nil, d)
|
||||||
|
b := n.appendIndent(nil, d) // Leading indent
|
||||||
|
b = s2.formatExpandedTo(b, d, n) // Main body
|
||||||
|
b = append(b, '\n') // Trailing newline
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
func (s *textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||||
|
n0 := len(b) // Original buffer length
|
||||||
|
b = append(b, s.Prefix...)
|
||||||
|
b, s.Value = s.Value.formatCompactTo(b, d)
|
||||||
|
b = append(b, s.Suffix...)
|
||||||
|
if _, ok := s.Value.(textLine); ok {
|
||||||
|
return b, textLine(b[n0:])
|
||||||
|
}
|
||||||
|
return b, s
|
||||||
|
}
|
||||||
|
func (s *textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||||
|
b = append(b, s.Prefix...)
|
||||||
|
b = s.Value.formatExpandedTo(b, d, n)
|
||||||
|
b = append(b, s.Suffix...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// textList is a comma-separated list of textWrap or textLine nodes.
|
||||||
|
// The list may be formatted as multi-lines or single-line at the discretion
|
||||||
|
// of the textList.formatCompactTo method.
|
||||||
|
type textList []textRecord
|
||||||
|
type textRecord struct {
|
||||||
|
Diff diffMode // e.g., 0 or '-' or '+'
|
||||||
|
Key string // e.g., "MyField"
|
||||||
|
Value textNode // textWrap | textLine
|
||||||
|
ElideComma bool // avoid trailing comma
|
||||||
|
Comment fmt.Stringer // e.g., "6 identical fields"
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendEllipsis appends a new ellipsis node to the list if none already
|
||||||
|
// exists at the end. If cs is non-zero it coalesces the statistics with the
|
||||||
|
// previous diffStats.
|
||||||
|
func (s *textList) AppendEllipsis(ds diffStats) {
|
||||||
|
hasStats := !ds.IsZero()
|
||||||
|
if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) {
|
||||||
|
if hasStats {
|
||||||
|
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds})
|
||||||
|
} else {
|
||||||
|
*s = append(*s, textRecord{Value: textEllipsis, ElideComma: true})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hasStats {
|
||||||
|
(*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s textList) Len() (n int) {
|
||||||
|
for i, r := range s {
|
||||||
|
n += len(r.Key)
|
||||||
|
if r.Key != "" {
|
||||||
|
n += len(": ")
|
||||||
|
}
|
||||||
|
n += r.Value.Len()
|
||||||
|
if i < len(s)-1 {
|
||||||
|
n += len(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s1 textList) Equal(s2 textNode) bool {
|
||||||
|
if s2, ok := s2.(textList); ok {
|
||||||
|
if len(s1) != len(s2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range s1 {
|
||||||
|
r1, r2 := s1[i], s2[i]
|
||||||
|
if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s textList) String() string {
|
||||||
|
return (&textWrap{Prefix: "{", Value: s, Suffix: "}"}).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||||
|
s = append(textList(nil), s...) // Avoid mutating original
|
||||||
|
|
||||||
|
// Determine whether we can collapse this list as a single line.
|
||||||
|
n0 := len(b) // Original buffer length
|
||||||
|
var multiLine bool
|
||||||
|
for i, r := range s {
|
||||||
|
if r.Diff == diffInserted || r.Diff == diffRemoved {
|
||||||
|
multiLine = true
|
||||||
|
}
|
||||||
|
b = append(b, r.Key...)
|
||||||
|
if r.Key != "" {
|
||||||
|
b = append(b, ": "...)
|
||||||
|
}
|
||||||
|
b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff)
|
||||||
|
if _, ok := s[i].Value.(textLine); !ok {
|
||||||
|
multiLine = true
|
||||||
|
}
|
||||||
|
if r.Comment != nil {
|
||||||
|
multiLine = true
|
||||||
|
}
|
||||||
|
if i < len(s)-1 {
|
||||||
|
b = append(b, ", "...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Force multi-lined output when printing a removed/inserted node that
|
||||||
|
// is sufficiently long.
|
||||||
|
if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength {
|
||||||
|
multiLine = true
|
||||||
|
}
|
||||||
|
if !multiLine {
|
||||||
|
return b, textLine(b[n0:])
|
||||||
|
}
|
||||||
|
return b, s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte {
|
||||||
|
alignKeyLens := s.alignLens(
|
||||||
|
func(r textRecord) bool {
|
||||||
|
_, isLine := r.Value.(textLine)
|
||||||
|
return r.Key == "" || !isLine
|
||||||
|
},
|
||||||
|
func(r textRecord) int { return utf8.RuneCountInString(r.Key) },
|
||||||
|
)
|
||||||
|
alignValueLens := s.alignLens(
|
||||||
|
func(r textRecord) bool {
|
||||||
|
_, isLine := r.Value.(textLine)
|
||||||
|
return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil
|
||||||
|
},
|
||||||
|
func(r textRecord) int { return utf8.RuneCount(r.Value.(textLine)) },
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format lists of simple lists in a batched form.
|
||||||
|
// If the list is sequence of only textLine values,
|
||||||
|
// then batch multiple values on a single line.
|
||||||
|
var isSimple bool
|
||||||
|
for _, r := range s {
|
||||||
|
_, isLine := r.Value.(textLine)
|
||||||
|
isSimple = r.Diff == 0 && r.Key == "" && isLine && r.Comment == nil
|
||||||
|
if !isSimple {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isSimple {
|
||||||
|
n++
|
||||||
|
var batch []byte
|
||||||
|
emitBatch := func() {
|
||||||
|
if len(batch) > 0 {
|
||||||
|
b = n.appendIndent(append(b, '\n'), d)
|
||||||
|
b = append(b, bytes.TrimRight(batch, " ")...)
|
||||||
|
batch = batch[:0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range s {
|
||||||
|
line := r.Value.(textLine)
|
||||||
|
if len(batch)+len(line)+len(", ") > maxColumnLength {
|
||||||
|
emitBatch()
|
||||||
|
}
|
||||||
|
batch = append(batch, line...)
|
||||||
|
batch = append(batch, ", "...)
|
||||||
|
}
|
||||||
|
emitBatch()
|
||||||
|
n--
|
||||||
|
return n.appendIndent(append(b, '\n'), d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the list as a multi-lined output.
|
||||||
|
n++
|
||||||
|
for i, r := range s {
|
||||||
|
b = n.appendIndent(append(b, '\n'), d|r.Diff)
|
||||||
|
if r.Key != "" {
|
||||||
|
b = append(b, r.Key+": "...)
|
||||||
|
}
|
||||||
|
b = alignKeyLens[i].appendChar(b, ' ')
|
||||||
|
|
||||||
|
b = r.Value.formatExpandedTo(b, d|r.Diff, n)
|
||||||
|
if !r.ElideComma {
|
||||||
|
b = append(b, ',')
|
||||||
|
}
|
||||||
|
b = alignValueLens[i].appendChar(b, ' ')
|
||||||
|
|
||||||
|
if r.Comment != nil {
|
||||||
|
b = append(b, " // "+r.Comment.String()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n--
|
||||||
|
|
||||||
|
return n.appendIndent(append(b, '\n'), d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s textList) alignLens(
|
||||||
|
skipFunc func(textRecord) bool,
|
||||||
|
lenFunc func(textRecord) int,
|
||||||
|
) []repeatCount {
|
||||||
|
var startIdx, endIdx, maxLen int
|
||||||
|
lens := make([]repeatCount, len(s))
|
||||||
|
for i, r := range s {
|
||||||
|
if skipFunc(r) {
|
||||||
|
for j := startIdx; j < endIdx && j < len(s); j++ {
|
||||||
|
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
|
||||||
|
}
|
||||||
|
startIdx, endIdx, maxLen = i+1, i+1, 0
|
||||||
|
} else {
|
||||||
|
if maxLen < lenFunc(r) {
|
||||||
|
maxLen = lenFunc(r)
|
||||||
|
}
|
||||||
|
endIdx = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for j := startIdx; j < endIdx && j < len(s); j++ {
|
||||||
|
lens[j] = repeatCount(maxLen - lenFunc(s[j]))
|
||||||
|
}
|
||||||
|
return lens
|
||||||
|
}
|
||||||
|
|
||||||
|
// textLine is a single-line segment of text and is always a leaf node
|
||||||
|
// in the textNode tree.
|
||||||
|
type textLine []byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
textNil = textLine("nil")
|
||||||
|
textEllipsis = textLine("...")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s textLine) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s1 textLine) Equal(s2 textNode) bool {
|
||||||
|
if s2, ok := s2.(textLine); ok {
|
||||||
|
return bytes.Equal([]byte(s1), []byte(s2))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (s textLine) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) {
|
||||||
|
return append(b, s...), s
|
||||||
|
}
|
||||||
|
func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte {
|
||||||
|
return append(b, s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type diffStats struct {
|
||||||
|
Name string
|
||||||
|
NumIgnored int
|
||||||
|
NumIdentical int
|
||||||
|
NumRemoved int
|
||||||
|
NumInserted int
|
||||||
|
NumModified int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s diffStats) IsZero() bool {
|
||||||
|
s.Name = ""
|
||||||
|
return s == diffStats{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s diffStats) NumDiff() int {
|
||||||
|
return s.NumRemoved + s.NumInserted + s.NumModified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s diffStats) Append(ds diffStats) diffStats {
|
||||||
|
assert(s.Name == ds.Name)
|
||||||
|
s.NumIgnored += ds.NumIgnored
|
||||||
|
s.NumIdentical += ds.NumIdentical
|
||||||
|
s.NumRemoved += ds.NumRemoved
|
||||||
|
s.NumInserted += ds.NumInserted
|
||||||
|
s.NumModified += ds.NumModified
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// String prints a humanly-readable summary of coalesced records.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
|
||||||
|
func (s diffStats) String() string {
|
||||||
|
var ss []string
|
||||||
|
var sum int
|
||||||
|
labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"}
|
||||||
|
counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified}
|
||||||
|
for i, n := range counts {
|
||||||
|
if n > 0 {
|
||||||
|
ss = append(ss, fmt.Sprintf("%d %v", n, labels[i]))
|
||||||
|
}
|
||||||
|
sum += n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pluralize the name (adjusting for some obscure English grammar rules).
|
||||||
|
name := s.Name
|
||||||
|
if sum > 1 {
|
||||||
|
name += "s"
|
||||||
|
if strings.HasSuffix(name, "ys") {
|
||||||
|
name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the list according to English grammar (with Oxford comma).
|
||||||
|
switch n := len(ss); n {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
case 1, 2:
|
||||||
|
return strings.Join(ss, " and ") + " " + name
|
||||||
|
default:
|
||||||
|
return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type commentString string
|
||||||
|
|
||||||
|
func (s commentString) String() string { return string(s) }
|
121
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
Normal file
121
vendor/github.com/google/go-cmp/cmp/report_value.go
generated
vendored
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// 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 cmp
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
// valueNode represents a single node within a report, which is a
|
||||||
|
// structured representation of the value tree, containing information
|
||||||
|
// regarding which nodes are equal or not.
|
||||||
|
type valueNode struct {
|
||||||
|
parent *valueNode
|
||||||
|
|
||||||
|
Type reflect.Type
|
||||||
|
ValueX reflect.Value
|
||||||
|
ValueY reflect.Value
|
||||||
|
|
||||||
|
// NumSame is the number of leaf nodes that are equal.
|
||||||
|
// All descendants are equal only if NumDiff is 0.
|
||||||
|
NumSame int
|
||||||
|
// NumDiff is the number of leaf nodes that are not equal.
|
||||||
|
NumDiff int
|
||||||
|
// NumIgnored is the number of leaf nodes that are ignored.
|
||||||
|
NumIgnored int
|
||||||
|
// NumCompared is the number of leaf nodes that were compared
|
||||||
|
// using an Equal method or Comparer function.
|
||||||
|
NumCompared int
|
||||||
|
// NumTransformed is the number of non-leaf nodes that were transformed.
|
||||||
|
NumTransformed int
|
||||||
|
// NumChildren is the number of transitive descendants of this node.
|
||||||
|
// This counts from zero; thus, leaf nodes have no descendants.
|
||||||
|
NumChildren int
|
||||||
|
// MaxDepth is the maximum depth of the tree. This counts from zero;
|
||||||
|
// thus, leaf nodes have a depth of zero.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// Records is a list of struct fields, slice elements, or map entries.
|
||||||
|
Records []reportRecord // If populated, implies Value is not populated
|
||||||
|
|
||||||
|
// Value is the result of a transformation, pointer indirect, of
|
||||||
|
// type assertion.
|
||||||
|
Value *valueNode // If populated, implies Records is not populated
|
||||||
|
|
||||||
|
// TransformerName is the name of the transformer.
|
||||||
|
TransformerName string // If non-empty, implies Value is populated
|
||||||
|
}
|
||||||
|
type reportRecord struct {
|
||||||
|
Key reflect.Value // Invalid for slice element
|
||||||
|
Value *valueNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) {
|
||||||
|
vx, vy := ps.Values()
|
||||||
|
child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy}
|
||||||
|
switch s := ps.(type) {
|
||||||
|
case StructField:
|
||||||
|
assert(parent.Value == nil)
|
||||||
|
parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child})
|
||||||
|
case SliceIndex:
|
||||||
|
assert(parent.Value == nil)
|
||||||
|
parent.Records = append(parent.Records, reportRecord{Value: child})
|
||||||
|
case MapIndex:
|
||||||
|
assert(parent.Value == nil)
|
||||||
|
parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child})
|
||||||
|
case Indirect:
|
||||||
|
assert(parent.Value == nil && parent.Records == nil)
|
||||||
|
parent.Value = child
|
||||||
|
case TypeAssertion:
|
||||||
|
assert(parent.Value == nil && parent.Records == nil)
|
||||||
|
parent.Value = child
|
||||||
|
case Transform:
|
||||||
|
assert(parent.Value == nil && parent.Records == nil)
|
||||||
|
parent.Value = child
|
||||||
|
parent.TransformerName = s.Name()
|
||||||
|
parent.NumTransformed++
|
||||||
|
default:
|
||||||
|
assert(parent == nil) // Must be the root step
|
||||||
|
}
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *valueNode) Report(rs Result) {
|
||||||
|
assert(r.MaxDepth == 0) // May only be called on leaf nodes
|
||||||
|
|
||||||
|
if rs.ByIgnore() {
|
||||||
|
r.NumIgnored++
|
||||||
|
} else {
|
||||||
|
if rs.Equal() {
|
||||||
|
r.NumSame++
|
||||||
|
} else {
|
||||||
|
r.NumDiff++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(r.NumSame+r.NumDiff+r.NumIgnored == 1)
|
||||||
|
|
||||||
|
if rs.ByMethod() {
|
||||||
|
r.NumCompared++
|
||||||
|
}
|
||||||
|
if rs.ByFunc() {
|
||||||
|
r.NumCompared++
|
||||||
|
}
|
||||||
|
assert(r.NumCompared <= 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (child *valueNode) PopStep() (parent *valueNode) {
|
||||||
|
if child.parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
parent = child.parent
|
||||||
|
parent.NumSame += child.NumSame
|
||||||
|
parent.NumDiff += child.NumDiff
|
||||||
|
parent.NumIgnored += child.NumIgnored
|
||||||
|
parent.NumCompared += child.NumCompared
|
||||||
|
parent.NumTransformed += child.NumTransformed
|
||||||
|
parent.NumChildren += child.NumChildren + 1
|
||||||
|
if parent.MaxDepth < child.MaxDepth+1 {
|
||||||
|
parent.MaxDepth = child.MaxDepth + 1
|
||||||
|
}
|
||||||
|
return parent
|
||||||
|
}
|
8
vendor/github.com/josharian/native/doc.go
generated
vendored
Normal file
8
vendor/github.com/josharian/native/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Package native provides easy access to native byte order.
|
||||||
|
//
|
||||||
|
// Usage: use native.Endian where you need the native binary.ByteOrder.
|
||||||
|
//
|
||||||
|
// Please think twice before using this package.
|
||||||
|
// It can break program portability.
|
||||||
|
// Native byte order is usually not the right answer.
|
||||||
|
package native
|
7
vendor/github.com/josharian/native/endian_big.go
generated
vendored
Normal file
7
vendor/github.com/josharian/native/endian_big.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build mips mips64 ppc64 s390x
|
||||||
|
|
||||||
|
package native
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
var Endian = binary.BigEndian
|
26
vendor/github.com/josharian/native/endian_generic.go
generated
vendored
Normal file
26
vendor/github.com/josharian/native/endian_generic.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// +build !mips,!mips64,!ppc64,!s390x,!amd64,!386,!arm,!arm64,!mipsle,!mips64le,!ppc64le,!riscv64,!wasm
|
||||||
|
|
||||||
|
// This file is a fallback, so that package native doesn't break
|
||||||
|
// the instant the Go project adds support for a new architecture.
|
||||||
|
//
|
||||||
|
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"log"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Endian binary.ByteOrder
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
b := uint16(0xff) // one byte
|
||||||
|
if *(*byte)(unsafe.Pointer(&b)) == 0 {
|
||||||
|
Endian = binary.BigEndian
|
||||||
|
} else {
|
||||||
|
Endian = binary.LittleEndian
|
||||||
|
}
|
||||||
|
log.Printf("github.com/josharian/native: unrecognized arch %v (%v), please file an issue", runtime.GOARCH, Endian)
|
||||||
|
}
|
7
vendor/github.com/josharian/native/endian_little.go
generated
vendored
Normal file
7
vendor/github.com/josharian/native/endian_little.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build amd64 386 arm arm64 mipsle mips64le ppc64le riscv64 wasm
|
||||||
|
|
||||||
|
package native
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
var Endian = binary.LittleEndian
|
7
vendor/github.com/josharian/native/license
generated
vendored
Normal file
7
vendor/github.com/josharian/native/license
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Copyright 2020 Josh Bleecher Snyder
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
10
vendor/github.com/josharian/native/readme.md
generated
vendored
Normal file
10
vendor/github.com/josharian/native/readme.md
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Package native provides easy access to native byte order.
|
||||||
|
|
||||||
|
`go get github.com/josharian/native`
|
||||||
|
|
||||||
|
Usage: Use `native.Endian` where you need the native binary.ByteOrder.
|
||||||
|
|
||||||
|
Please think twice before using this package.
|
||||||
|
It can break program portability.
|
||||||
|
Native byte order is usually not the right answer.
|
||||||
|
|
21
vendor/github.com/mdlayher/genetlink/CHANGELOG.md
generated
vendored
Normal file
21
vendor/github.com/mdlayher/genetlink/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.2.0
|
||||||
|
|
||||||
|
**This is the first release of package netlink that only supports Go 1.13+.
|
||||||
|
Users on older versions of Go must use v1.1.0.**
|
||||||
|
|
||||||
|
- [Improvement]: pruned Go module dependencies via package `netlink` v1.6.0 and
|
||||||
|
removing tool version pins.
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
**This is the first release of package genetlink that only supports Go 1.12+.
|
||||||
|
Users on older versions must use v1.0.0.**
|
||||||
|
|
||||||
|
- [Improvement]: modernization of various parts of the code and documentation in
|
||||||
|
prep for future work.
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
- Initial stable commit.
|
10
vendor/github.com/mdlayher/genetlink/LICENSE.md
generated
vendored
Normal file
10
vendor/github.com/mdlayher/genetlink/LICENSE.md
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
MIT License
|
||||||
|
===========
|
||||||
|
|
||||||
|
Copyright (C) 2016-2017 Matt Layher
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
vendor/github.com/mdlayher/genetlink/README.md
generated
vendored
Normal file
26
vendor/github.com/mdlayher/genetlink/README.md
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# genetlink [![Test Status](https://github.com/mdlayher/genetlink/workflows/Linux%20Test/badge.svg)](https://github.com/mdlayher/genetlink/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/genetlink.svg)](https://pkg.go.dev/github.com/mdlayher/genetlink) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/genetlink)](https://goreportcard.com/report/github.com/mdlayher/genetlink)
|
||||||
|
|
||||||
|
Package `genetlink` implements generic netlink interactions and data types.
|
||||||
|
MIT Licensed.
|
||||||
|
|
||||||
|
For more information about how netlink and generic netlink work,
|
||||||
|
check out my blog series on [Linux, Netlink, and Go](https://mdlayher.com/blog/linux-netlink-and-go-part-1-netlink/).
|
||||||
|
|
||||||
|
If you have any questions or you'd like some guidance, please join us on
|
||||||
|
[Gophers Slack](https://invite.slack.golangbridge.org) in the `#networking`
|
||||||
|
channel!
|
||||||
|
|
||||||
|
## Stability
|
||||||
|
|
||||||
|
See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between
|
||||||
|
releases.
|
||||||
|
|
||||||
|
This package has a stable v1 API and any future breaking changes will prompt
|
||||||
|
the release of a new major version. Features and bug fixes will continue to
|
||||||
|
occur in the v1.x.x series.
|
||||||
|
|
||||||
|
In order to reduce the maintenance burden, this package is only supported on
|
||||||
|
Go 1.12+. Older versions of Go lack critical features and APIs which are
|
||||||
|
necessary for this package to function correctly.
|
||||||
|
|
||||||
|
**If you depend on this package in your applications, please use Go modules.**
|
220
vendor/github.com/mdlayher/genetlink/conn.go
generated
vendored
Normal file
220
vendor/github.com/mdlayher/genetlink/conn.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
package genetlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mdlayher/netlink"
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Protocol is the netlink protocol constant used to specify generic netlink.
|
||||||
|
const Protocol = 0x10 // unix.NETLINK_GENERIC
|
||||||
|
|
||||||
|
// A Conn is a generic netlink connection. A Conn can be used to send and
|
||||||
|
// receive generic netlink messages to and from netlink.
|
||||||
|
//
|
||||||
|
// A Conn is safe for concurrent use, but to avoid contention in
|
||||||
|
// high-throughput applications, the caller should almost certainly create a
|
||||||
|
// pool of Conns and distribute them among workers.
|
||||||
|
type Conn struct {
|
||||||
|
// Operating system-specific netlink connection.
|
||||||
|
c *netlink.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial dials a generic netlink connection. Config specifies optional
|
||||||
|
// configuration for the underlying netlink connection. If config is
|
||||||
|
// nil, a default configuration will be used.
|
||||||
|
func Dial(config *netlink.Config) (*Conn, error) {
|
||||||
|
c, err := netlink.Dial(Protocol, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn creates a Conn that wraps an existing *netlink.Conn for
|
||||||
|
// generic netlink communications.
|
||||||
|
//
|
||||||
|
// NewConn is primarily useful for tests. Most applications should use
|
||||||
|
// Dial instead.
|
||||||
|
func NewConn(c *netlink.Conn) *Conn {
|
||||||
|
return &Conn{c: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection and unblocks any pending read operations.
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
return c.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFamily retrieves a generic netlink family with the specified name.
|
||||||
|
//
|
||||||
|
// If the family does not exist, the error value can be checked using
|
||||||
|
// `errors.Is(err, os.ErrNotExist)`.
|
||||||
|
func (c *Conn) GetFamily(name string) (Family, error) {
|
||||||
|
return c.getFamily(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFamilies retrieves all registered generic netlink families.
|
||||||
|
func (c *Conn) ListFamilies() ([]Family, error) {
|
||||||
|
return c.listFamilies()
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinGroup joins a netlink multicast group by its ID.
|
||||||
|
func (c *Conn) JoinGroup(group uint32) error {
|
||||||
|
return c.c.JoinGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaveGroup leaves a netlink multicast group by its ID.
|
||||||
|
func (c *Conn) LeaveGroup(group uint32) error {
|
||||||
|
return c.c.LeaveGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBPF attaches an assembled BPF program to a Conn.
|
||||||
|
func (c *Conn) SetBPF(filter []bpf.RawInstruction) error {
|
||||||
|
return c.c.SetBPF(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveBPF removes a BPF filter from a Conn.
|
||||||
|
func (c *Conn) RemoveBPF() error {
|
||||||
|
return c.c.RemoveBPF()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOption enables or disables a netlink socket option for the Conn.
|
||||||
|
func (c *Conn) SetOption(option netlink.ConnOption, enable bool) error {
|
||||||
|
return c.c.SetOption(option, enable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadBuffer sets the size of the operating system's receive buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
func (c *Conn) SetReadBuffer(bytes int) error {
|
||||||
|
return c.c.SetReadBuffer(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteBuffer sets the size of the operating system's transmit buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
func (c *Conn) SetWriteBuffer(bytes int) error {
|
||||||
|
return c.c.SetWriteBuffer(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyscallConn returns a raw network connection. This implements the
|
||||||
|
// syscall.Conn interface.
|
||||||
|
//
|
||||||
|
// SyscallConn is intended for advanced use cases, such as getting and setting
|
||||||
|
// arbitrary socket options using the netlink socket's file descriptor.
|
||||||
|
//
|
||||||
|
// Once invoked, it is the caller's responsibility to ensure that operations
|
||||||
|
// performed using Conn and the syscall.RawConn do not conflict with
|
||||||
|
// each other.
|
||||||
|
func (c *Conn) SyscallConn() (syscall.RawConn, error) {
|
||||||
|
return c.c.SyscallConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline sets the read and write deadlines associated with the connection.
|
||||||
|
func (c *Conn) SetDeadline(t time.Time) error {
|
||||||
|
return c.c.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline sets the read deadline associated with the connection.
|
||||||
|
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.c.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline sets the write deadline associated with the connection.
|
||||||
|
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return c.c.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends a single Message to netlink, wrapping it in a netlink.Message
|
||||||
|
// using the specified generic netlink family and flags. On success, Send
|
||||||
|
// returns a copy of the netlink.Message with all parameters populated, for
|
||||||
|
// later validation.
|
||||||
|
func (c *Conn) Send(m Message, family uint16, flags netlink.HeaderFlags) (netlink.Message, error) {
|
||||||
|
nm, err := packMessage(m, family, flags)
|
||||||
|
if err != nil {
|
||||||
|
return netlink.Message{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqnm, err := c.c.Send(nm)
|
||||||
|
if err != nil {
|
||||||
|
return netlink.Message{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reqnm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive receives one or more Messages from netlink. The netlink.Messages
|
||||||
|
// used to wrap each Message are available for later validation.
|
||||||
|
func (c *Conn) Receive() ([]Message, []netlink.Message, error) {
|
||||||
|
msgs, err := c.c.Receive()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gmsgs, err := unpackMessages(msgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gmsgs, msgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute sends a single Message to netlink using Send, receives one or more
|
||||||
|
// replies using Receive, and then checks the validity of the replies against
|
||||||
|
// the request using netlink.Validate.
|
||||||
|
//
|
||||||
|
// Execute acquires a lock for the duration of the function call which blocks
|
||||||
|
// concurrent calls to Send and Receive, in order to ensure consistency between
|
||||||
|
// generic netlink request/reply messages.
|
||||||
|
//
|
||||||
|
// See the documentation of Send, Receive, and netlink.Validate for details
|
||||||
|
// about each function.
|
||||||
|
func (c *Conn) Execute(m Message, family uint16, flags netlink.HeaderFlags) ([]Message, error) {
|
||||||
|
nm, err := packMessage(m, family, flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locking behavior handled by netlink.Conn.Execute.
|
||||||
|
msgs, err := c.c.Execute(nm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return unpackMessages(msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// packMessage packs a generic netlink Message into a netlink.Message with the
|
||||||
|
// appropriate generic netlink family and netlink flags.
|
||||||
|
func packMessage(m Message, family uint16, flags netlink.HeaderFlags) (netlink.Message, error) {
|
||||||
|
nm := netlink.Message{
|
||||||
|
Header: netlink.Header{
|
||||||
|
Type: netlink.HeaderType(family),
|
||||||
|
Flags: flags,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mb, err := m.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return netlink.Message{}, err
|
||||||
|
}
|
||||||
|
nm.Data = mb
|
||||||
|
|
||||||
|
return nm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackMessages unpacks generic netlink Messages from a slice of netlink.Messages.
|
||||||
|
func unpackMessages(msgs []netlink.Message) ([]Message, error) {
|
||||||
|
gmsgs := make([]Message, 0, len(msgs))
|
||||||
|
for _, nm := range msgs {
|
||||||
|
var gm Message
|
||||||
|
if err := (&gm).UnmarshalBinary(nm.Data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gmsgs = append(gmsgs, gm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gmsgs, nil
|
||||||
|
}
|
6
vendor/github.com/mdlayher/genetlink/doc.go
generated
vendored
Normal file
6
vendor/github.com/mdlayher/genetlink/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// Package genetlink implements generic netlink interactions and data types.
|
||||||
|
//
|
||||||
|
// If you have any questions or you'd like some guidance, please join us on
|
||||||
|
// Gophers Slack (https://invite.slack.golangbridge.org) in the #networking
|
||||||
|
// channel!
|
||||||
|
package genetlink
|
17
vendor/github.com/mdlayher/genetlink/family.go
generated
vendored
Normal file
17
vendor/github.com/mdlayher/genetlink/family.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package genetlink
|
||||||
|
|
||||||
|
// A Family is a generic netlink family.
|
||||||
|
type Family struct {
|
||||||
|
ID uint16
|
||||||
|
Version uint8
|
||||||
|
Name string
|
||||||
|
Groups []MulticastGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// A MulticastGroup is a generic netlink multicast group, which can be joined
|
||||||
|
// for notifications from generic netlink families when specific events take
|
||||||
|
// place.
|
||||||
|
type MulticastGroup struct {
|
||||||
|
ID uint32
|
||||||
|
Name string
|
||||||
|
}
|
150
vendor/github.com/mdlayher/genetlink/family_linux.go
generated
vendored
Normal file
150
vendor/github.com/mdlayher/genetlink/family_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package genetlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/mdlayher/netlink"
|
||||||
|
"github.com/mdlayher/netlink/nlenc"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errInvalidFamilyVersion is returned when a family's version is greater
|
||||||
|
// than an 8-bit integer.
|
||||||
|
var errInvalidFamilyVersion = errors.New("invalid family version attribute")
|
||||||
|
|
||||||
|
// getFamily retrieves a generic netlink family with the specified name.
|
||||||
|
func (c *Conn) getFamily(name string) (Family, error) {
|
||||||
|
b, err := netlink.MarshalAttributes([]netlink.Attribute{{
|
||||||
|
Type: unix.CTRL_ATTR_FAMILY_NAME,
|
||||||
|
Data: nlenc.Bytes(name),
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
return Family{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := Message{
|
||||||
|
Header: Header{
|
||||||
|
Command: unix.CTRL_CMD_GETFAMILY,
|
||||||
|
// TODO(mdlayher): grab nlctrl version?
|
||||||
|
Version: 1,
|
||||||
|
},
|
||||||
|
Data: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, err := c.Execute(req, unix.GENL_ID_CTRL, netlink.Request)
|
||||||
|
if err != nil {
|
||||||
|
return Family{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdlayher): consider interpreting generic netlink header values
|
||||||
|
|
||||||
|
families, err := buildFamilies(msgs)
|
||||||
|
if err != nil {
|
||||||
|
return Family{}, err
|
||||||
|
}
|
||||||
|
if len(families) != 1 {
|
||||||
|
// If this were to ever happen, netlink must be in a state where
|
||||||
|
// its answers cannot be trusted
|
||||||
|
panic(fmt.Sprintf("netlink returned multiple families for name: %q", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return families[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listFamilies retrieves all registered generic netlink families.
|
||||||
|
func (c *Conn) listFamilies() ([]Family, error) {
|
||||||
|
req := Message{
|
||||||
|
Header: Header{
|
||||||
|
Command: unix.CTRL_CMD_GETFAMILY,
|
||||||
|
// TODO(mdlayher): grab nlctrl version?
|
||||||
|
Version: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, err := c.Execute(req, unix.GENL_ID_CTRL, netlink.Request|netlink.Dump)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildFamilies(msgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildFamilies builds a slice of Families by parsing attributes from the
|
||||||
|
// input Messages.
|
||||||
|
func buildFamilies(msgs []Message) ([]Family, error) {
|
||||||
|
families := make([]Family, 0, len(msgs))
|
||||||
|
for _, m := range msgs {
|
||||||
|
f, err := parseFamily(m.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
families = append(families, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return families, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFamily decodes netlink attributes into a Family.
|
||||||
|
func parseFamily(b []byte) (Family, error) {
|
||||||
|
ad, err := netlink.NewAttributeDecoder(b)
|
||||||
|
if err != nil {
|
||||||
|
return Family{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var f Family
|
||||||
|
for ad.Next() {
|
||||||
|
switch ad.Type() {
|
||||||
|
case unix.CTRL_ATTR_FAMILY_ID:
|
||||||
|
f.ID = ad.Uint16()
|
||||||
|
case unix.CTRL_ATTR_FAMILY_NAME:
|
||||||
|
f.Name = ad.String()
|
||||||
|
case unix.CTRL_ATTR_VERSION:
|
||||||
|
v := ad.Uint32()
|
||||||
|
if v > math.MaxUint8 {
|
||||||
|
return Family{}, errInvalidFamilyVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Version = uint8(v)
|
||||||
|
case unix.CTRL_ATTR_MCAST_GROUPS:
|
||||||
|
ad.Nested(parseMulticastGroups(&f.Groups))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ad.Err(); err != nil {
|
||||||
|
return Family{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMulticastGroups parses an array of multicast group nested attributes
|
||||||
|
// into a slice of MulticastGroups.
|
||||||
|
func parseMulticastGroups(groups *[]MulticastGroup) func(*netlink.AttributeDecoder) error {
|
||||||
|
return func(ad *netlink.AttributeDecoder) error {
|
||||||
|
*groups = make([]MulticastGroup, 0, ad.Len())
|
||||||
|
for ad.Next() {
|
||||||
|
ad.Nested(func(nad *netlink.AttributeDecoder) error {
|
||||||
|
var g MulticastGroup
|
||||||
|
for nad.Next() {
|
||||||
|
switch nad.Type() {
|
||||||
|
case unix.CTRL_ATTR_MCAST_GRP_NAME:
|
||||||
|
g.Name = nad.String()
|
||||||
|
case unix.CTRL_ATTR_MCAST_GRP_ID:
|
||||||
|
g.ID = nad.Uint32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*groups = append(*groups, g)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
24
vendor/github.com/mdlayher/genetlink/family_others.go
generated
vendored
Normal file
24
vendor/github.com/mdlayher/genetlink/family_others.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package genetlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errUnimplemented is returned by all functions on platforms that
|
||||||
|
// cannot make use of generic netlink.
|
||||||
|
var errUnimplemented = fmt.Errorf("generic netlink not implemented on %s/%s",
|
||||||
|
runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
|
// getFamily always returns an error.
|
||||||
|
func (c *Conn) getFamily(name string) (Family, error) {
|
||||||
|
return Family{}, errUnimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// listFamilies always returns an error.
|
||||||
|
func (c *Conn) listFamilies() ([]Family, error) {
|
||||||
|
return nil, errUnimplemented
|
||||||
|
}
|
21
vendor/github.com/mdlayher/genetlink/fuzz.go
generated
vendored
Normal file
21
vendor/github.com/mdlayher/genetlink/fuzz.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//go:build gofuzz
|
||||||
|
// +build gofuzz
|
||||||
|
|
||||||
|
package genetlink
|
||||||
|
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
return fuzzMessage(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fuzzMessage(data []byte) int {
|
||||||
|
var m Message
|
||||||
|
if err := (&m).UnmarshalBinary(data); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := m.MarshalBinary(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
61
vendor/github.com/mdlayher/genetlink/message.go
generated
vendored
Normal file
61
vendor/github.com/mdlayher/genetlink/message.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package genetlink
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// errInvalidMessage is returned when a Message is malformed.
|
||||||
|
var errInvalidMessage = errors.New("generic netlink message is invalid or too short")
|
||||||
|
|
||||||
|
// A Header is a generic netlink header. A Header is sent and received with
|
||||||
|
// each generic netlink message to indicate metadata regarding a Message.
|
||||||
|
type Header struct {
|
||||||
|
// Command specifies a command to issue to netlink.
|
||||||
|
Command uint8
|
||||||
|
|
||||||
|
// Version specifies the version of a command to use.
|
||||||
|
Version uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerLen is the length of a Header.
|
||||||
|
const headerLen = 4 // unix.GENL_HDRLEN
|
||||||
|
|
||||||
|
// A Message is a generic netlink message. It contains a Header and an
|
||||||
|
// arbitrary byte payload, which may be decoded using information from the
|
||||||
|
// Header.
|
||||||
|
//
|
||||||
|
// Data is encoded using the native endianness of the host system. Use
|
||||||
|
// the netlink.AttributeDecoder and netlink.AttributeEncoder types to decode
|
||||||
|
// and encode data.
|
||||||
|
type Message struct {
|
||||||
|
Header Header
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary marshals a Message into a byte slice.
|
||||||
|
func (m Message) MarshalBinary() ([]byte, error) {
|
||||||
|
b := make([]byte, headerLen)
|
||||||
|
|
||||||
|
b[0] = m.Header.Command
|
||||||
|
b[1] = m.Header.Version
|
||||||
|
|
||||||
|
// b[2] and b[3] are padding bytes and set to zero
|
||||||
|
|
||||||
|
return append(b, m.Data...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary unmarshals the contents of a byte slice into a Message.
|
||||||
|
func (m *Message) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) < headerLen {
|
||||||
|
return errInvalidMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow reserved pad bytes to be set
|
||||||
|
if b[2] != 0 || b[3] != 0 {
|
||||||
|
return errInvalidMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Header.Command = b[0]
|
||||||
|
m.Header.Version = b[1]
|
||||||
|
|
||||||
|
m.Data = b[4:]
|
||||||
|
return nil
|
||||||
|
}
|
4
vendor/github.com/mdlayher/netlink/.gitignore
generated
vendored
Normal file
4
vendor/github.com/mdlayher/netlink/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
internal/integration/integration.test
|
||||||
|
netlink.test
|
||||||
|
netlink-fuzz.zip
|
||||||
|
testdata/
|
142
vendor/github.com/mdlayher/netlink/CHANGELOG.md
generated
vendored
Normal file
142
vendor/github.com/mdlayher/netlink/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
## v1.6.0
|
||||||
|
|
||||||
|
**This is the first release of package netlink that only supports Go 1.13+.
|
||||||
|
Users on older versions of Go must use v1.5.0.**
|
||||||
|
|
||||||
|
- [New API] [commit](https://github.com/mdlayher/netlink/commit/ad9e2c41caa993e3f4b68831d6cb2cb05818275d):
|
||||||
|
the `netlink.Config.Strict` field can be used to apply a more strict default
|
||||||
|
set of options to a `netlink.Conn`. This is recommended for applications
|
||||||
|
running on modern Linux kernels, but cannot be enabled by default because the
|
||||||
|
options may require a more recent kernel than the minimum kernel version that
|
||||||
|
Go supports. See the documentation for details.
|
||||||
|
- [Improvement]: broke some integration tests into a separate Go module so the
|
||||||
|
default `go.mod` for package `netlink` has fewer dependencies.
|
||||||
|
|
||||||
|
## v1.5.0
|
||||||
|
|
||||||
|
**This is the last release of package netlink that supports Go 1.12.**
|
||||||
|
|
||||||
|
- [New API] [commit](https://github.com/mdlayher/netlink/commit/53a1c10065e51077659ceedf921c8f0807abe8c0):
|
||||||
|
the `netlink.Config.PID` field can be used to specify an explicit port ID when
|
||||||
|
binding the netlink socket. This is intended for advanced use cases and most
|
||||||
|
callers should leave this field set to 0.
|
||||||
|
- [Improvement]: more low-level functionality ported to
|
||||||
|
`github.com/mdlayher/socket`, reducing package complexity.
|
||||||
|
|
||||||
|
## v1.4.2
|
||||||
|
|
||||||
|
- [Documentation] [commit](https://github.com/mdlayher/netlink/commit/177e6364fb170d465d681c7c8a6283417a6d3e49):
|
||||||
|
the `netlink.Config.DisableNSLockThread` now properly uses Go's deprecated
|
||||||
|
identifier convention. This option has been a noop for a long time and should
|
||||||
|
not be used.
|
||||||
|
- [Improvement] [#189](https://github.com/mdlayher/netlink/pull/189): the
|
||||||
|
package now uses Go 1.17's `//go:build` identifiers. Thanks @tklauser.
|
||||||
|
- [Bug Fix]
|
||||||
|
[commit](https://github.com/mdlayher/netlink/commit/fe6002e030928bd1f2a446c0b6c65e8f2df4ed5e):
|
||||||
|
the `netlink.AttributeEncoder`'s `Bytes`, `String`, and `Do` methods now
|
||||||
|
properly reject byte slices and strings which are too large to fit in the
|
||||||
|
value of a netlink attribute. Thanks @ubiquitousbyte for the report.
|
||||||
|
|
||||||
|
## v1.4.1
|
||||||
|
|
||||||
|
- [Improvement]: significant runtime network poller integration cleanup through
|
||||||
|
the use of `github.com/mdlayher/socket`.
|
||||||
|
|
||||||
|
## v1.4.0
|
||||||
|
|
||||||
|
- [New API] [#185](https://github.com/mdlayher/netlink/pull/185): the
|
||||||
|
`netlink.AttributeDecoder` and `netlink.AttributeEncoder` types now have
|
||||||
|
methods for dealing with signed integers: `Int8`, `Int16`, `Int32`, and
|
||||||
|
`Int64`. These are necessary for working with rtnetlink's XDP APIs. Thanks
|
||||||
|
@fbegyn.
|
||||||
|
|
||||||
|
## v1.3.2
|
||||||
|
|
||||||
|
- [Improvement]
|
||||||
|
[commit](https://github.com/mdlayher/netlink/commit/ebc6e2e28bcf1a0671411288423d8116ff924d6d):
|
||||||
|
`github.com/google/go-cmp` is no longer a (non-test) dependency of this module.
|
||||||
|
|
||||||
|
## v1.3.1
|
||||||
|
|
||||||
|
- [Improvement]: many internal cleanups and simplifications. The library is now
|
||||||
|
slimmer and features less internal indirection. There are no user-facing
|
||||||
|
changes in this release.
|
||||||
|
|
||||||
|
## v1.3.0
|
||||||
|
|
||||||
|
- [New API] [#176](https://github.com/mdlayher/netlink/pull/176):
|
||||||
|
`netlink.OpError` now has `Message` and `Offset` fields which are populated
|
||||||
|
when the kernel returns netlink extended acknowledgement data along with an
|
||||||
|
error code. The caller can turn on this option by using
|
||||||
|
`netlink.Conn.SetOption(netlink.ExtendedAcknowledge, true)`.
|
||||||
|
- [New API]
|
||||||
|
[commit](https://github.com/mdlayher/netlink/commit/beba85e0372133b6d57221191d2c557727cd1499):
|
||||||
|
the `netlink.GetStrictCheck` option can be used to tell the kernel to be more
|
||||||
|
strict when parsing requests. This enables more safety checks and can allow
|
||||||
|
the kernel to perform more advanced request filtering in subsystems such as
|
||||||
|
route netlink.
|
||||||
|
|
||||||
|
## v1.2.1
|
||||||
|
|
||||||
|
- [Bug Fix]
|
||||||
|
[commit](https://github.com/mdlayher/netlink/commit/d81418f81b0bfa2465f33790a85624c63d6afe3d):
|
||||||
|
`netlink.SetBPF` will no longer panic if an empty BPF filter is set.
|
||||||
|
- [Improvement]
|
||||||
|
[commit](https://github.com/mdlayher/netlink/commit/8014f9a7dbf4fd7b84a1783dd7b470db9113ff36):
|
||||||
|
the library now uses https://github.com/josharian/native to provide the
|
||||||
|
system's native endianness at compile time, rather than re-computing it many
|
||||||
|
times at runtime.
|
||||||
|
|
||||||
|
## v1.2.0
|
||||||
|
|
||||||
|
**This is the first release of package netlink that only supports Go 1.12+.
|
||||||
|
Users on older versions of Go must use v1.1.1.**
|
||||||
|
|
||||||
|
- [Improvement] [#173](https://github.com/mdlayher/netlink/pull/173): support
|
||||||
|
for Go 1.11 and below has been dropped. All users are highly recommended to
|
||||||
|
use a stable and supported release of Go for their applications.
|
||||||
|
- [Performance] [#171](https://github.com/mdlayher/netlink/pull/171):
|
||||||
|
`netlink.Conn` no longer requires a locked OS thread for the vast majority of
|
||||||
|
operations, which should result in a significant speedup for highly concurrent
|
||||||
|
callers. Thanks @ti-mo.
|
||||||
|
- [Bug Fix] [#169](https://github.com/mdlayher/netlink/pull/169): calls to
|
||||||
|
`netlink.Conn.Close` are now able to unblock concurrent calls to
|
||||||
|
`netlink.Conn.Receive` and other blocking operations.
|
||||||
|
|
||||||
|
## v1.1.1
|
||||||
|
|
||||||
|
**This is the last release of package netlink that supports Go 1.11.**
|
||||||
|
|
||||||
|
- [Improvement] [#165](https://github.com/mdlayher/netlink/pull/165):
|
||||||
|
`netlink.Conn` `SetReadBuffer` and `SetWriteBuffer` methods now attempt the
|
||||||
|
`SO_*BUFFORCE` socket options to possibly ignore system limits given elevated
|
||||||
|
caller permissions. Thanks @MarkusBauer.
|
||||||
|
- [Note]
|
||||||
|
[commit](https://github.com/mdlayher/netlink/commit/c5f8ab79aa345dcfcf7f14d746659ca1b80a0ecc):
|
||||||
|
`netlink.Conn.Close` has had a long-standing bug
|
||||||
|
[#162](https://github.com/mdlayher/netlink/pull/162) related to internal
|
||||||
|
concurrency handling where a call to `Close` is not sufficient to unblock
|
||||||
|
pending reads. To effectively fix this issue, it is necessary to drop support
|
||||||
|
for Go 1.11 and below. This will be fixed in a future release, but a
|
||||||
|
workaround is noted in the method documentation as of now.
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
|
||||||
|
- [New API] [#157](https://github.com/mdlayher/netlink/pull/157): the
|
||||||
|
`netlink.AttributeDecoder.TypeFlags` method enables retrieval of the type bits
|
||||||
|
stored in a netlink attribute's type field, because the existing `Type` method
|
||||||
|
masks away these bits. Thanks @ti-mo!
|
||||||
|
- [Performance] [#157](https://github.com/mdlayher/netlink/pull/157): `netlink.AttributeDecoder`
|
||||||
|
now decodes netlink attributes on demand, enabling callers who only need a
|
||||||
|
limited number of attributes to exit early from decoding loops. Thanks @ti-mo!
|
||||||
|
- [Improvement] [#161](https://github.com/mdlayher/netlink/pull/161): `netlink.Conn`
|
||||||
|
system calls are now ready for Go 1.14+'s changes to goroutine preemption.
|
||||||
|
See the PR for details.
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
- Initial stable commit.
|
9
vendor/github.com/mdlayher/netlink/LICENSE.md
generated
vendored
Normal file
9
vendor/github.com/mdlayher/netlink/LICENSE.md
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2016-2022 Matt Layher
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
51
vendor/github.com/mdlayher/netlink/README.md
generated
vendored
Normal file
51
vendor/github.com/mdlayher/netlink/README.md
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# netlink [![Test Status](https://github.com/mdlayher/netlink/workflows/Linux%20Test/badge.svg)](https://github.com/mdlayher/netlink/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/netlink.svg)](https://pkg.go.dev/github.com/mdlayher/netlink) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/netlink)](https://goreportcard.com/report/github.com/mdlayher/netlink)
|
||||||
|
|
||||||
|
Package `netlink` provides low-level access to Linux netlink sockets.
|
||||||
|
MIT Licensed.
|
||||||
|
|
||||||
|
For more information about how netlink works, check out my blog series
|
||||||
|
on [Linux, Netlink, and Go](https://mdlayher.com/blog/linux-netlink-and-go-part-1-netlink/).
|
||||||
|
|
||||||
|
If you have any questions or you'd like some guidance, please join us on
|
||||||
|
[Gophers Slack](https://invite.slack.golangbridge.org) in the `#networking`
|
||||||
|
channel!
|
||||||
|
|
||||||
|
## Stability
|
||||||
|
|
||||||
|
See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between
|
||||||
|
releases.
|
||||||
|
|
||||||
|
This package has a stable v1 API and any future breaking changes will prompt
|
||||||
|
the release of a new major version. Features and bug fixes will continue to
|
||||||
|
occur in the v1.x.x series.
|
||||||
|
|
||||||
|
In order to reduce the maintenance burden, this package is only supported on
|
||||||
|
Go 1.12+. Older versions of Go lack critical features and APIs which are
|
||||||
|
necessary for this package to function correctly.
|
||||||
|
|
||||||
|
**If you depend on this package in your applications, please use Go modules.**
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
A [number of netlink packages](https://godoc.org/?q=netlink) are already
|
||||||
|
available for Go, but I wasn't able to find one that aligned with what
|
||||||
|
I wanted in a netlink package:
|
||||||
|
|
||||||
|
- Straightforward, idiomatic API
|
||||||
|
- Well tested
|
||||||
|
- Well documented
|
||||||
|
- Doesn't use package/global variables or state
|
||||||
|
- Doesn't necessarily need root to work
|
||||||
|
|
||||||
|
My goal for this package is to use it as a building block for the creation
|
||||||
|
of other netlink family packages.
|
||||||
|
|
||||||
|
## Ecosystem
|
||||||
|
|
||||||
|
Over time, an ecosystem of Go packages has developed around package `netlink`.
|
||||||
|
Many of these packages provide building blocks for further interactions with
|
||||||
|
various netlink families, such as `NETLINK_GENERIC` or `NETLINK_ROUTE`.
|
||||||
|
|
||||||
|
To have your package included in this diagram, please send a pull request!
|
||||||
|
|
||||||
|
![netlink ecosystem](./netlink.svg)
|
37
vendor/github.com/mdlayher/netlink/align.go
generated
vendored
Normal file
37
vendor/github.com/mdlayher/netlink/align.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
// Functions and values used to properly align netlink messages, headers,
|
||||||
|
// and attributes. Definitions taken from Linux kernel source.
|
||||||
|
|
||||||
|
// #define NLMSG_ALIGNTO 4U
|
||||||
|
const nlmsgAlignTo = 4
|
||||||
|
|
||||||
|
// #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
|
||||||
|
func nlmsgAlign(len int) int {
|
||||||
|
return ((len) + nlmsgAlignTo - 1) & ^(nlmsgAlignTo - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
|
||||||
|
func nlmsgLength(len int) int {
|
||||||
|
return len + nlmsgHeaderLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// #define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
|
||||||
|
var nlmsgHeaderLen = nlmsgAlign(int(unsafe.Sizeof(Header{})))
|
||||||
|
|
||||||
|
// #define NLA_ALIGNTO 4
|
||||||
|
const nlaAlignTo = 4
|
||||||
|
|
||||||
|
// #define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
|
||||||
|
func nlaAlign(len int) int {
|
||||||
|
return ((len) + nlaAlignTo - 1) & ^(nlaAlignTo - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because this package's Attribute type contains a byte slice, unsafe.Sizeof
|
||||||
|
// can't be used to determine the correct length.
|
||||||
|
const sizeofAttribute = 4
|
||||||
|
|
||||||
|
// #define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr)))
|
||||||
|
var nlaHeaderLen = nlaAlign(sizeofAttribute)
|
707
vendor/github.com/mdlayher/netlink/attribute.go
generated
vendored
Normal file
707
vendor/github.com/mdlayher/netlink/attribute.go
generated
vendored
Normal file
|
@ -0,0 +1,707 @@
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/josharian/native"
|
||||||
|
"github.com/mdlayher/netlink/nlenc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errInvalidAttribute specifies if an Attribute's length is incorrect.
|
||||||
|
var errInvalidAttribute = errors.New("invalid attribute; length too short or too large")
|
||||||
|
|
||||||
|
// An Attribute is a netlink attribute. Attributes are packed and unpacked
|
||||||
|
// to and from the Data field of Message for some netlink families.
|
||||||
|
type Attribute struct {
|
||||||
|
// Length of an Attribute, including this field and Type.
|
||||||
|
Length uint16
|
||||||
|
|
||||||
|
// The type of this Attribute, typically matched to a constant. Note that
|
||||||
|
// flags such as Nested and NetByteOrder must be handled manually when
|
||||||
|
// working with Attribute structures directly.
|
||||||
|
Type uint16
|
||||||
|
|
||||||
|
// An arbitrary payload which is specified by Type.
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal marshals the contents of a into b and returns the number of bytes
|
||||||
|
// written to b, including attribute alignment padding.
|
||||||
|
func (a *Attribute) marshal(b []byte) (int, error) {
|
||||||
|
if int(a.Length) < nlaHeaderLen {
|
||||||
|
return 0, errInvalidAttribute
|
||||||
|
}
|
||||||
|
|
||||||
|
nlenc.PutUint16(b[0:2], a.Length)
|
||||||
|
nlenc.PutUint16(b[2:4], a.Type)
|
||||||
|
n := copy(b[nlaHeaderLen:], a.Data)
|
||||||
|
|
||||||
|
return nlaHeaderLen + nlaAlign(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshal unmarshals the contents of a byte slice into an Attribute.
|
||||||
|
func (a *Attribute) unmarshal(b []byte) error {
|
||||||
|
if len(b) < nlaHeaderLen {
|
||||||
|
return errInvalidAttribute
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Length = nlenc.Uint16(b[0:2])
|
||||||
|
a.Type = nlenc.Uint16(b[2:4])
|
||||||
|
|
||||||
|
if int(a.Length) > len(b) {
|
||||||
|
return errInvalidAttribute
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// No length, no data
|
||||||
|
case a.Length == 0:
|
||||||
|
a.Data = make([]byte, 0)
|
||||||
|
// Not enough length for any data
|
||||||
|
case int(a.Length) < nlaHeaderLen:
|
||||||
|
return errInvalidAttribute
|
||||||
|
// Data present
|
||||||
|
case int(a.Length) >= nlaHeaderLen:
|
||||||
|
a.Data = make([]byte, len(b[nlaHeaderLen:a.Length]))
|
||||||
|
copy(a.Data, b[nlaHeaderLen:a.Length])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalAttributes packs a slice of Attributes into a single byte slice.
|
||||||
|
// In most cases, the Length field of each Attribute should be set to 0, so it
|
||||||
|
// can be calculated and populated automatically for each Attribute.
|
||||||
|
//
|
||||||
|
// It is recommend to use the AttributeEncoder type where possible instead of
|
||||||
|
// calling MarshalAttributes and using package nlenc functions directly.
|
||||||
|
func MarshalAttributes(attrs []Attribute) ([]byte, error) {
|
||||||
|
// Count how many bytes we should allocate to store each attribute's contents.
|
||||||
|
var c int
|
||||||
|
for _, a := range attrs {
|
||||||
|
c += nlaHeaderLen + nlaAlign(len(a.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance through b with idx to place attribute data at the correct offset.
|
||||||
|
var idx int
|
||||||
|
b := make([]byte, c)
|
||||||
|
for _, a := range attrs {
|
||||||
|
// Infer the length of attribute if zero.
|
||||||
|
if a.Length == 0 {
|
||||||
|
a.Length = uint16(nlaHeaderLen + len(a.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal a into b and advance idx to show many bytes are occupied.
|
||||||
|
n, err := a.marshal(b[idx:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
idx += n
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalAttributes unpacks a slice of Attributes from a single byte slice.
|
||||||
|
//
|
||||||
|
// It is recommend to use the AttributeDecoder type where possible instead of calling
|
||||||
|
// UnmarshalAttributes and using package nlenc functions directly.
|
||||||
|
func UnmarshalAttributes(b []byte) ([]Attribute, error) {
|
||||||
|
ad, err := NewAttributeDecoder(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a nil slice when there are no attributes to decode.
|
||||||
|
if ad.Len() == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := make([]Attribute, 0, ad.Len())
|
||||||
|
|
||||||
|
for ad.Next() {
|
||||||
|
if ad.a.Length != 0 {
|
||||||
|
attrs = append(attrs, ad.a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ad.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AttributeDecoder provides a safe, iterator-like, API around attribute
|
||||||
|
// decoding.
|
||||||
|
//
|
||||||
|
// It is recommend to use an AttributeDecoder where possible instead of calling
|
||||||
|
// UnmarshalAttributes and using package nlenc functions directly.
|
||||||
|
//
|
||||||
|
// The Err method must be called after the Next method returns false to determine
|
||||||
|
// if any errors occurred during iteration.
|
||||||
|
type AttributeDecoder struct {
|
||||||
|
// ByteOrder defines a specific byte order to use when processing integer
|
||||||
|
// attributes. ByteOrder should be set immediately after creating the
|
||||||
|
// AttributeDecoder: before any attributes are parsed.
|
||||||
|
//
|
||||||
|
// If not set, the native byte order will be used.
|
||||||
|
ByteOrder binary.ByteOrder
|
||||||
|
|
||||||
|
// The current attribute being worked on.
|
||||||
|
a Attribute
|
||||||
|
|
||||||
|
// The slice of input bytes and its iterator index.
|
||||||
|
b []byte
|
||||||
|
i int
|
||||||
|
|
||||||
|
length int
|
||||||
|
|
||||||
|
// Any error encountered while decoding attributes.
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAttributeDecoder creates an AttributeDecoder that unpacks Attributes
|
||||||
|
// from b and prepares the decoder for iteration.
|
||||||
|
func NewAttributeDecoder(b []byte) (*AttributeDecoder, error) {
|
||||||
|
ad := &AttributeDecoder{
|
||||||
|
// By default, use native byte order.
|
||||||
|
ByteOrder: native.Endian,
|
||||||
|
|
||||||
|
b: b,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
ad.length, err = ad.available()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next advances the decoder to the next netlink attribute. It returns false
|
||||||
|
// when no more attributes are present, or an error was encountered.
|
||||||
|
func (ad *AttributeDecoder) Next() bool {
|
||||||
|
if ad.err != nil {
|
||||||
|
// Hit an error, stop iteration.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit if array pointer is at or beyond the end of the slice.
|
||||||
|
if ad.i >= len(ad.b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ad.a.unmarshal(ad.b[ad.i:]); err != nil {
|
||||||
|
ad.err = err
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance the pointer by at least one header's length.
|
||||||
|
if int(ad.a.Length) < nlaHeaderLen {
|
||||||
|
ad.i += nlaHeaderLen
|
||||||
|
} else {
|
||||||
|
ad.i += nlaAlign(int(ad.a.Length))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns the Attribute.Type field of the current netlink attribute
|
||||||
|
// pointed to by the decoder.
|
||||||
|
//
|
||||||
|
// Type masks off the high bits of the netlink attribute type which may contain
|
||||||
|
// the Nested and NetByteOrder flags. These can be obtained by calling TypeFlags.
|
||||||
|
func (ad *AttributeDecoder) Type() uint16 {
|
||||||
|
// Mask off any flags stored in the high bits.
|
||||||
|
return ad.a.Type & attrTypeMask
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeFlags returns the two high bits of the Attribute.Type field of the current
|
||||||
|
// netlink attribute pointed to by the decoder.
|
||||||
|
//
|
||||||
|
// These bits of the netlink attribute type are used for the Nested and NetByteOrder
|
||||||
|
// flags, available as the Nested and NetByteOrder constants in this package.
|
||||||
|
func (ad *AttributeDecoder) TypeFlags() uint16 {
|
||||||
|
return ad.a.Type & ^attrTypeMask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of netlink attributes pointed to by the decoder.
|
||||||
|
func (ad *AttributeDecoder) Len() int { return ad.length }
|
||||||
|
|
||||||
|
// count scans the input slice to count the number of netlink attributes
|
||||||
|
// that could be decoded by Next().
|
||||||
|
func (ad *AttributeDecoder) available() (int, error) {
|
||||||
|
var count int
|
||||||
|
for i := 0; i < len(ad.b); {
|
||||||
|
// Make sure there's at least a header's worth
|
||||||
|
// of data to read on each iteration.
|
||||||
|
if len(ad.b[i:]) < nlaHeaderLen {
|
||||||
|
return 0, errInvalidAttribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the length of the attribute.
|
||||||
|
l := int(nlenc.Uint16(ad.b[i : i+2]))
|
||||||
|
|
||||||
|
// Ignore zero-length attributes.
|
||||||
|
if l != 0 {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance by at least a header's worth of bytes.
|
||||||
|
if l < nlaHeaderLen {
|
||||||
|
l = nlaHeaderLen
|
||||||
|
}
|
||||||
|
|
||||||
|
i += nlaAlign(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// data returns the Data field of the current Attribute pointed to by the decoder.
|
||||||
|
func (ad *AttributeDecoder) data() []byte { return ad.a.Data }
|
||||||
|
|
||||||
|
// Err returns the first error encountered by the decoder.
|
||||||
|
func (ad *AttributeDecoder) Err() error { return ad.err }
|
||||||
|
|
||||||
|
// Bytes returns the raw bytes of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Bytes() []byte {
|
||||||
|
src := ad.data()
|
||||||
|
dest := make([]byte, len(src))
|
||||||
|
copy(dest, src)
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) String() string {
|
||||||
|
if ad.err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return nlenc.String(ad.data())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 returns the uint8 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Uint8() uint8 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 1 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a uint8; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint8(b[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 returns the uint16 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Uint16() uint16 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 2 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a uint16; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad.ByteOrder.Uint16(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 returns the uint32 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Uint32() uint32 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 4 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a uint32; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad.ByteOrder.Uint32(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 returns the uint64 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Uint64() uint64 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 8 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a uint64; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad.ByteOrder.Uint64(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8 returns the Int8 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Int8() int8 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 1 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a int8; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return int8(b[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16 returns the Int16 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Int16() int16 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 2 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a int16; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return int16(ad.ByteOrder.Uint16(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 returns the Int32 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Int32() int32 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 4 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a int32; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return int32(ad.ByteOrder.Uint32(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the Int64 representation of the current Attribute's data.
|
||||||
|
func (ad *AttributeDecoder) Int64() int64 {
|
||||||
|
if ad.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 8 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a int64; length: %d", ad.Type(), len(b))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(ad.ByteOrder.Uint64(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag returns a boolean representing the Attribute.
|
||||||
|
func (ad *AttributeDecoder) Flag() bool {
|
||||||
|
if ad.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if len(b) != 0 {
|
||||||
|
ad.err = fmt.Errorf("netlink: attribute %d is not a flag; length: %d", ad.Type(), len(b))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do is a general purpose function which allows access to the current data
|
||||||
|
// pointed to by the AttributeDecoder.
|
||||||
|
//
|
||||||
|
// Do can be used to allow parsing arbitrary data within the context of the
|
||||||
|
// decoder. Do is most useful when dealing with nested attributes, attribute
|
||||||
|
// arrays, or decoding arbitrary types (such as C structures) which don't fit
|
||||||
|
// cleanly into a typical unsigned integer value.
|
||||||
|
//
|
||||||
|
// The function fn should not retain any reference to the data b outside of the
|
||||||
|
// scope of the function.
|
||||||
|
func (ad *AttributeDecoder) Do(fn func(b []byte) error) {
|
||||||
|
if ad.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := ad.data()
|
||||||
|
if err := fn(b); err != nil {
|
||||||
|
ad.err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested decodes data into a nested AttributeDecoder to handle nested netlink
|
||||||
|
// attributes. When calling Nested, the Err method does not need to be called on
|
||||||
|
// the nested AttributeDecoder.
|
||||||
|
//
|
||||||
|
// The nested AttributeDecoder nad inherits the same ByteOrder setting as the
|
||||||
|
// top-level AttributeDecoder ad.
|
||||||
|
func (ad *AttributeDecoder) Nested(fn func(nad *AttributeDecoder) error) {
|
||||||
|
// Because we are wrapping Do, there is no need to check ad.err immediately.
|
||||||
|
ad.Do(func(b []byte) error {
|
||||||
|
nad, err := NewAttributeDecoder(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nad.ByteOrder = ad.ByteOrder
|
||||||
|
|
||||||
|
if err := fn(nad); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nad.Err()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AttributeEncoder provides a safe way to encode attributes.
|
||||||
|
//
|
||||||
|
// It is recommended to use an AttributeEncoder where possible instead of
|
||||||
|
// calling MarshalAttributes or using package nlenc directly.
|
||||||
|
//
|
||||||
|
// Errors from intermediate encoding steps are returned in the call to
|
||||||
|
// Encode.
|
||||||
|
type AttributeEncoder struct {
|
||||||
|
// ByteOrder defines a specific byte order to use when processing integer
|
||||||
|
// attributes. ByteOrder should be set immediately after creating the
|
||||||
|
// AttributeEncoder: before any attributes are encoded.
|
||||||
|
//
|
||||||
|
// If not set, the native byte order will be used.
|
||||||
|
ByteOrder binary.ByteOrder
|
||||||
|
|
||||||
|
attrs []Attribute
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAttributeEncoder creates an AttributeEncoder that encodes Attributes.
|
||||||
|
func NewAttributeEncoder() *AttributeEncoder {
|
||||||
|
return &AttributeEncoder{ByteOrder: native.Endian}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 encodes uint8 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Uint8(typ uint16, v uint8) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: []byte{v},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 encodes uint16 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Uint16(typ uint16, v uint16) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 2)
|
||||||
|
ae.ByteOrder.PutUint16(b, v)
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 encodes uint32 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Uint32(typ uint16, v uint32) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 4)
|
||||||
|
ae.ByteOrder.PutUint32(b, v)
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 encodes uint64 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Uint64(typ uint16, v uint64) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 8)
|
||||||
|
ae.ByteOrder.PutUint64(b, v)
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int8 encodes int8 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Int8(typ uint16, v int8) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: []byte{uint8(v)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int16 encodes int16 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Int16(typ uint16, v int16) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 2)
|
||||||
|
ae.ByteOrder.PutUint16(b, uint16(v))
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 encodes int32 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Int32(typ uint16, v int32) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 4)
|
||||||
|
ae.ByteOrder.PutUint32(b, uint32(v))
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 encodes int64 data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Int64(typ uint16, v int64) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 8)
|
||||||
|
ae.ByteOrder.PutUint64(b, uint64(v))
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag encodes a flag into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Flag(typ uint16, v bool) {
|
||||||
|
// Only set flag on no previous error or v == true.
|
||||||
|
if ae.err != nil || !v {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags have no length or data fields.
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{Type: typ})
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes string s as a null-terminated string into an Attribute
|
||||||
|
// specified by typ.
|
||||||
|
func (ae *AttributeEncoder) String(typ uint16, s string) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length checking, thanks ubiquitousbyte on GitHub.
|
||||||
|
if len(s) > math.MaxUint16-nlaHeaderLen {
|
||||||
|
ae.err = errors.New("string is too large to fit in a netlink attribute")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: nlenc.Bytes(s),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes embeds raw byte data into an Attribute specified by typ.
|
||||||
|
func (ae *AttributeEncoder) Bytes(typ uint16, b []byte) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) > math.MaxUint16-nlaHeaderLen {
|
||||||
|
ae.err = errors.New("byte slice is too large to fit in a netlink attribute")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do is a general purpose function to encode arbitrary data into an attribute
|
||||||
|
// specified by typ.
|
||||||
|
//
|
||||||
|
// Do is especially helpful in encoding nested attributes, attribute arrays,
|
||||||
|
// or encoding arbitrary types (such as C structures) which don't fit cleanly
|
||||||
|
// into an unsigned integer value.
|
||||||
|
func (ae *AttributeEncoder) Do(typ uint16, fn func() ([]byte, error)) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := fn()
|
||||||
|
if err != nil {
|
||||||
|
ae.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) > math.MaxUint16-nlaHeaderLen {
|
||||||
|
ae.err = errors.New("byte slice produced by Do is too large to fit in a netlink attribute")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ae.attrs = append(ae.attrs, Attribute{
|
||||||
|
Type: typ,
|
||||||
|
Data: b,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested embeds data produced by a nested AttributeEncoder and flags that data
|
||||||
|
// with the Nested flag. When calling Nested, the Encode method should not be
|
||||||
|
// called on the nested AttributeEncoder.
|
||||||
|
//
|
||||||
|
// The nested AttributeEncoder nae inherits the same ByteOrder setting as the
|
||||||
|
// top-level AttributeEncoder ae.
|
||||||
|
func (ae *AttributeEncoder) Nested(typ uint16, fn func(nae *AttributeEncoder) error) {
|
||||||
|
// Because we are wrapping Do, there is no need to check ae.err immediately.
|
||||||
|
ae.Do(Nested|typ, func() ([]byte, error) {
|
||||||
|
nae := NewAttributeEncoder()
|
||||||
|
nae.ByteOrder = ae.ByteOrder
|
||||||
|
|
||||||
|
if err := fn(nae); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nae.Encode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode returns the encoded bytes representing the attributes.
|
||||||
|
func (ae *AttributeEncoder) Encode() ([]byte, error) {
|
||||||
|
if ae.err != nil {
|
||||||
|
return nil, ae.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarshalAttributes(ae.attrs)
|
||||||
|
}
|
589
vendor/github.com/mdlayher/netlink/conn.go
generated
vendored
Normal file
589
vendor/github.com/mdlayher/netlink/conn.go
generated
vendored
Normal file
|
@ -0,0 +1,589 @@
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Conn is a connection to netlink. A Conn can be used to send and
|
||||||
|
// receives messages to and from netlink.
|
||||||
|
//
|
||||||
|
// A Conn is safe for concurrent use, but to avoid contention in
|
||||||
|
// high-throughput applications, the caller should almost certainly create a
|
||||||
|
// pool of Conns and distribute them among workers.
|
||||||
|
//
|
||||||
|
// A Conn is capable of manipulating netlink subsystems from within a specific
|
||||||
|
// Linux network namespace, but special care must be taken when doing so. See
|
||||||
|
// the documentation of Config for details.
|
||||||
|
type Conn struct {
|
||||||
|
// Atomics must come first.
|
||||||
|
//
|
||||||
|
// seq is an atomically incremented integer used to provide sequence
|
||||||
|
// numbers when Conn.Send is called.
|
||||||
|
seq uint32
|
||||||
|
|
||||||
|
// mu serializes access to the netlink socket for the request/response
|
||||||
|
// transaction within Execute.
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
// sock is the operating system-specific implementation of
|
||||||
|
// a netlink sockets connection.
|
||||||
|
sock Socket
|
||||||
|
|
||||||
|
// pid is the PID assigned by netlink.
|
||||||
|
pid uint32
|
||||||
|
|
||||||
|
// d provides debugging capabilities for a Conn if not nil.
|
||||||
|
d *debugger
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Socket is an operating-system specific implementation of netlink
|
||||||
|
// sockets used by Conn.
|
||||||
|
type Socket interface {
|
||||||
|
Close() error
|
||||||
|
Send(m Message) error
|
||||||
|
SendMessages(m []Message) error
|
||||||
|
Receive() ([]Message, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial dials a connection to netlink, using the specified netlink family.
|
||||||
|
// Config specifies optional configuration for Conn. If config is nil, a default
|
||||||
|
// configuration will be used.
|
||||||
|
func Dial(family int, config *Config) (*Conn, error) {
|
||||||
|
// TODO(mdlayher): plumb in netlink.OpError wrapping?
|
||||||
|
|
||||||
|
// Use OS-specific dial() to create Socket.
|
||||||
|
c, pid, err := dial(family, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewConn(c, pid), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn creates a Conn using the specified Socket and PID for netlink
|
||||||
|
// communications.
|
||||||
|
//
|
||||||
|
// NewConn is primarily useful for tests. Most applications should use
|
||||||
|
// Dial instead.
|
||||||
|
func NewConn(sock Socket, pid uint32) *Conn {
|
||||||
|
// Seed the sequence number using a random number generator.
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
seq := r.Uint32()
|
||||||
|
|
||||||
|
// Configure a debugger if arguments are set.
|
||||||
|
var d *debugger
|
||||||
|
if len(debugArgs) > 0 {
|
||||||
|
d = newDebugger(debugArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Conn{
|
||||||
|
seq: seq,
|
||||||
|
sock: sock,
|
||||||
|
pid: pid,
|
||||||
|
d: d,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug executes fn with the debugger if the debugger is not nil.
|
||||||
|
func (c *Conn) debug(fn func(d *debugger)) {
|
||||||
|
if c.d == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fn(c.d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection and unblocks any pending read operations.
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
// Close does not acquire a lock because it must be able to interrupt any
|
||||||
|
// blocked system calls, such as when Receive is waiting on a multicast
|
||||||
|
// group message.
|
||||||
|
//
|
||||||
|
// We rely on the kernel to deal with concurrent operations to the netlink
|
||||||
|
// socket itself.
|
||||||
|
return newOpError("close", c.sock.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute sends a single Message to netlink using Send, receives one or more
|
||||||
|
// replies using Receive, and then checks the validity of the replies against
|
||||||
|
// the request using Validate.
|
||||||
|
//
|
||||||
|
// Execute acquires a lock for the duration of the function call which blocks
|
||||||
|
// concurrent calls to Send, SendMessages, and Receive, in order to ensure
|
||||||
|
// consistency between netlink request/reply messages.
|
||||||
|
//
|
||||||
|
// See the documentation of Send, Receive, and Validate for details about
|
||||||
|
// each function.
|
||||||
|
func (c *Conn) Execute(m Message) ([]Message, error) {
|
||||||
|
// Acquire the write lock and invoke the internal implementations of Send
|
||||||
|
// and Receive which require the lock already be held.
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
req, err := c.lockedSend(m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.lockedReceive()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Validate(req, res); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessages sends multiple Messages to netlink. The handling of
|
||||||
|
// a Header's Length, Sequence and PID fields is the same as when
|
||||||
|
// calling Send.
|
||||||
|
func (c *Conn) SendMessages(msgs []Message) ([]Message, error) {
|
||||||
|
// Wait for any concurrent calls to Execute to finish before proceeding.
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
|
||||||
|
for i := range msgs {
|
||||||
|
c.fixMsg(&msgs[i], nlmsgLength(len(msgs[i].Data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.debug(func(d *debugger) {
|
||||||
|
for _, m := range msgs {
|
||||||
|
d.debugf(1, "send msgs: %+v", m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := c.sock.SendMessages(msgs); err != nil {
|
||||||
|
c.debug(func(d *debugger) {
|
||||||
|
d.debugf(1, "send msgs: err: %v", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil, newOpError("send-messages", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends a single Message to netlink. In most cases, a Header's Length,
|
||||||
|
// Sequence, and PID fields should be set to 0, so they can be populated
|
||||||
|
// automatically before the Message is sent. On success, Send returns a copy
|
||||||
|
// of the Message with all parameters populated, for later validation.
|
||||||
|
//
|
||||||
|
// If Header.Length is 0, it will be automatically populated using the
|
||||||
|
// correct length for the Message, including its payload.
|
||||||
|
//
|
||||||
|
// If Header.Sequence is 0, it will be automatically populated using the
|
||||||
|
// next sequence number for this connection.
|
||||||
|
//
|
||||||
|
// If Header.PID is 0, it will be automatically populated using a PID
|
||||||
|
// assigned by netlink.
|
||||||
|
func (c *Conn) Send(m Message) (Message, error) {
|
||||||
|
// Wait for any concurrent calls to Execute to finish before proceeding.
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
|
||||||
|
return c.lockedSend(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lockedSend implements Send, but must be called with c.mu acquired for reading.
|
||||||
|
// We rely on the kernel to deal with concurrent reads and writes to the netlink
|
||||||
|
// socket itself.
|
||||||
|
func (c *Conn) lockedSend(m Message) (Message, error) {
|
||||||
|
c.fixMsg(&m, nlmsgLength(len(m.Data)))
|
||||||
|
|
||||||
|
c.debug(func(d *debugger) {
|
||||||
|
d.debugf(1, "send: %+v", m)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := c.sock.Send(m); err != nil {
|
||||||
|
c.debug(func(d *debugger) {
|
||||||
|
d.debugf(1, "send: err: %v", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Message{}, newOpError("send", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive receives one or more messages from netlink. Multi-part messages are
|
||||||
|
// handled transparently and returned as a single slice of Messages, with the
|
||||||
|
// final empty "multi-part done" message removed.
|
||||||
|
//
|
||||||
|
// If any of the messages indicate a netlink error, that error will be returned.
|
||||||
|
func (c *Conn) Receive() ([]Message, error) {
|
||||||
|
// Wait for any concurrent calls to Execute to finish before proceeding.
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
|
||||||
|
return c.lockedReceive()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lockedReceive implements Receive, but must be called with c.mu acquired for reading.
|
||||||
|
// We rely on the kernel to deal with concurrent reads and writes to the netlink
|
||||||
|
// socket itself.
|
||||||
|
func (c *Conn) lockedReceive() ([]Message, error) {
|
||||||
|
msgs, err := c.receive()
|
||||||
|
if err != nil {
|
||||||
|
c.debug(func(d *debugger) {
|
||||||
|
d.debugf(1, "recv: err: %v", err)
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.debug(func(d *debugger) {
|
||||||
|
for _, m := range msgs {
|
||||||
|
d.debugf(1, "recv: %+v", m)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// When using nltest, it's possible for zero messages to be returned by receive.
|
||||||
|
if len(msgs) == 0 {
|
||||||
|
return msgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim the final message with multi-part done indicator if
|
||||||
|
// present.
|
||||||
|
if m := msgs[len(msgs)-1]; m.Header.Flags&Multi != 0 && m.Header.Type == Done {
|
||||||
|
return msgs[:len(msgs)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive is the internal implementation of Conn.Receive, which can be called
|
||||||
|
// recursively to handle multi-part messages.
|
||||||
|
func (c *Conn) receive() ([]Message, error) {
|
||||||
|
// NB: All non-nil errors returned from this function *must* be of type
|
||||||
|
// OpError in order to maintain the appropriate contract with callers of
|
||||||
|
// this package.
|
||||||
|
//
|
||||||
|
// This contract also applies to functions called within this function,
|
||||||
|
// such as checkMessage.
|
||||||
|
|
||||||
|
var res []Message
|
||||||
|
for {
|
||||||
|
msgs, err := c.sock.Receive()
|
||||||
|
if err != nil {
|
||||||
|
return nil, newOpError("receive", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this message is multi-part, we will need to continue looping to
|
||||||
|
// drain all the messages from the socket.
|
||||||
|
var multi bool
|
||||||
|
|
||||||
|
for _, m := range msgs {
|
||||||
|
if err := checkMessage(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does this message indicate a multi-part message?
|
||||||
|
if m.Header.Flags&Multi == 0 {
|
||||||
|
// No, check the next messages.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does this message indicate the last message in a series of
|
||||||
|
// multi-part messages from a single read?
|
||||||
|
multi = m.Header.Type != Done
|
||||||
|
}
|
||||||
|
|
||||||
|
res = append(res, msgs...)
|
||||||
|
|
||||||
|
if !multi {
|
||||||
|
// No more messages coming.
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A groupJoinLeaver is a Socket that supports joining and leaving
|
||||||
|
// netlink multicast groups.
|
||||||
|
type groupJoinLeaver interface {
|
||||||
|
Socket
|
||||||
|
JoinGroup(group uint32) error
|
||||||
|
LeaveGroup(group uint32) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinGroup joins a netlink multicast group by its ID.
|
||||||
|
func (c *Conn) JoinGroup(group uint32) error {
|
||||||
|
conn, ok := c.sock.(groupJoinLeaver)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("join-group")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("join-group", conn.JoinGroup(group))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaveGroup leaves a netlink multicast group by its ID.
|
||||||
|
func (c *Conn) LeaveGroup(group uint32) error {
|
||||||
|
conn, ok := c.sock.(groupJoinLeaver)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("leave-group")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("leave-group", conn.LeaveGroup(group))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bpfSetter is a Socket that supports setting and removing BPF filters.
|
||||||
|
type bpfSetter interface {
|
||||||
|
Socket
|
||||||
|
bpf.Setter
|
||||||
|
RemoveBPF() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBPF attaches an assembled BPF program to a Conn.
|
||||||
|
func (c *Conn) SetBPF(filter []bpf.RawInstruction) error {
|
||||||
|
conn, ok := c.sock.(bpfSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("set-bpf")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("set-bpf", conn.SetBPF(filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveBPF removes a BPF filter from a Conn.
|
||||||
|
func (c *Conn) RemoveBPF() error {
|
||||||
|
conn, ok := c.sock.(bpfSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("remove-bpf")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("remove-bpf", conn.RemoveBPF())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A deadlineSetter is a Socket that supports setting deadlines.
|
||||||
|
type deadlineSetter interface {
|
||||||
|
Socket
|
||||||
|
SetDeadline(time.Time) error
|
||||||
|
SetReadDeadline(time.Time) error
|
||||||
|
SetWriteDeadline(time.Time) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline sets the read and write deadlines associated with the connection.
|
||||||
|
func (c *Conn) SetDeadline(t time.Time) error {
|
||||||
|
conn, ok := c.sock.(deadlineSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("set-deadline")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("set-deadline", conn.SetDeadline(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline sets the read deadline associated with the connection.
|
||||||
|
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||||
|
conn, ok := c.sock.(deadlineSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("set-read-deadline")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("set-read-deadline", conn.SetReadDeadline(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline sets the write deadline associated with the connection.
|
||||||
|
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
||||||
|
conn, ok := c.sock.(deadlineSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("set-write-deadline")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("set-write-deadline", conn.SetWriteDeadline(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ConnOption is a boolean option that may be set for a Conn.
|
||||||
|
type ConnOption int
|
||||||
|
|
||||||
|
// Possible ConnOption values. These constants are equivalent to the Linux
|
||||||
|
// setsockopt boolean options for netlink sockets.
|
||||||
|
const (
|
||||||
|
PacketInfo ConnOption = iota
|
||||||
|
BroadcastError
|
||||||
|
NoENOBUFS
|
||||||
|
ListenAllNSID
|
||||||
|
CapAcknowledge
|
||||||
|
ExtendedAcknowledge
|
||||||
|
GetStrictCheck
|
||||||
|
)
|
||||||
|
|
||||||
|
// An optionSetter is a Socket that supports setting netlink options.
|
||||||
|
type optionSetter interface {
|
||||||
|
Socket
|
||||||
|
SetOption(option ConnOption, enable bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOption enables or disables a netlink socket option for the Conn.
|
||||||
|
func (c *Conn) SetOption(option ConnOption, enable bool) error {
|
||||||
|
conn, ok := c.sock.(optionSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("set-option")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("set-option", conn.SetOption(option, enable))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bufferSetter is a Socket that supports setting connection buffer sizes.
|
||||||
|
type bufferSetter interface {
|
||||||
|
Socket
|
||||||
|
SetReadBuffer(bytes int) error
|
||||||
|
SetWriteBuffer(bytes int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadBuffer sets the size of the operating system's receive buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
func (c *Conn) SetReadBuffer(bytes int) error {
|
||||||
|
conn, ok := c.sock.(bufferSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("set-read-buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("set-read-buffer", conn.SetReadBuffer(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteBuffer sets the size of the operating system's transmit buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
func (c *Conn) SetWriteBuffer(bytes int) error {
|
||||||
|
conn, ok := c.sock.(bufferSetter)
|
||||||
|
if !ok {
|
||||||
|
return notSupported("set-write-buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOpError("set-write-buffer", conn.SetWriteBuffer(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A syscallConner is a Socket that supports syscall.Conn.
|
||||||
|
type syscallConner interface {
|
||||||
|
Socket
|
||||||
|
syscall.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ syscall.Conn = &Conn{}
|
||||||
|
|
||||||
|
// SyscallConn returns a raw network connection. This implements the
|
||||||
|
// syscall.Conn interface.
|
||||||
|
//
|
||||||
|
// SyscallConn is intended for advanced use cases, such as getting and setting
|
||||||
|
// arbitrary socket options using the netlink socket's file descriptor.
|
||||||
|
//
|
||||||
|
// Once invoked, it is the caller's responsibility to ensure that operations
|
||||||
|
// performed using Conn and the syscall.RawConn do not conflict with
|
||||||
|
// each other.
|
||||||
|
func (c *Conn) SyscallConn() (syscall.RawConn, error) {
|
||||||
|
sc, ok := c.sock.(syscallConner)
|
||||||
|
if !ok {
|
||||||
|
return nil, notSupported("syscall-conn")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdlayher): mutex or similar to enforce syscall.RawConn contract of
|
||||||
|
// FD remaining valid for duration of calls?
|
||||||
|
|
||||||
|
return sc.SyscallConn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixMsg updates the fields of m using the logic specified in Send.
|
||||||
|
func (c *Conn) fixMsg(m *Message, ml int) {
|
||||||
|
if m.Header.Length == 0 {
|
||||||
|
m.Header.Length = uint32(nlmsgAlign(ml))
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Header.Sequence == 0 {
|
||||||
|
m.Header.Sequence = c.nextSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Header.PID == 0 {
|
||||||
|
m.Header.PID = c.pid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextSequence atomically increments Conn's sequence number and returns
|
||||||
|
// the incremented value.
|
||||||
|
func (c *Conn) nextSequence() uint32 {
|
||||||
|
return atomic.AddUint32(&c.seq, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates one or more reply Messages against a request Message,
|
||||||
|
// ensuring that they contain matching sequence numbers and PIDs.
|
||||||
|
func Validate(request Message, replies []Message) error {
|
||||||
|
for _, m := range replies {
|
||||||
|
// Check for mismatched sequence, unless:
|
||||||
|
// - request had no sequence, meaning we are probably validating
|
||||||
|
// a multicast reply
|
||||||
|
if m.Header.Sequence != request.Header.Sequence && request.Header.Sequence != 0 {
|
||||||
|
return newOpError("validate", errMismatchedSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for mismatched PID, unless:
|
||||||
|
// - request had no PID, meaning we are either:
|
||||||
|
// - validating a multicast reply
|
||||||
|
// - netlink has not yet assigned us a PID
|
||||||
|
// - response had no PID, meaning it's from the kernel as a multicast reply
|
||||||
|
if m.Header.PID != request.Header.PID && request.Header.PID != 0 && m.Header.PID != 0 {
|
||||||
|
return newOpError("validate", errMismatchedPID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config contains options for a Conn.
|
||||||
|
type Config struct {
|
||||||
|
// Groups is a bitmask which specifies multicast groups. If set to 0,
|
||||||
|
// no multicast group subscriptions will be made.
|
||||||
|
Groups uint32
|
||||||
|
|
||||||
|
// NetNS specifies the network namespace the Conn will operate in.
|
||||||
|
//
|
||||||
|
// If set (non-zero), Conn will enter the specified network namespace and
|
||||||
|
// an error will occur in Dial if the operation fails.
|
||||||
|
//
|
||||||
|
// If not set (zero), a best-effort attempt will be made to enter the
|
||||||
|
// network namespace of the calling thread: this means that any changes made
|
||||||
|
// to the calling thread's network namespace will also be reflected in Conn.
|
||||||
|
// If this operation fails (due to lack of permissions or because network
|
||||||
|
// namespaces are disabled by kernel configuration), Dial will not return
|
||||||
|
// an error, and the Conn will operate in the default network namespace of
|
||||||
|
// the process. This enables non-privileged use of Conn in applications
|
||||||
|
// which do not require elevated privileges.
|
||||||
|
//
|
||||||
|
// Entering a network namespace is a privileged operation (root or
|
||||||
|
// CAP_SYS_ADMIN are required), and most applications should leave this set
|
||||||
|
// to 0.
|
||||||
|
NetNS int
|
||||||
|
|
||||||
|
// DisableNSLockThread is a no-op.
|
||||||
|
//
|
||||||
|
// Deprecated: internal changes have made this option obsolete and it has no
|
||||||
|
// effect. Do not use.
|
||||||
|
DisableNSLockThread bool
|
||||||
|
|
||||||
|
// PID specifies the port ID used to bind the netlink socket. If set to 0,
|
||||||
|
// the kernel will assign a port ID on the caller's behalf.
|
||||||
|
//
|
||||||
|
// Most callers should leave this field set to 0. This option is intended
|
||||||
|
// for advanced use cases where the kernel expects a fixed unicast address
|
||||||
|
// destination for netlink messages.
|
||||||
|
PID uint32
|
||||||
|
|
||||||
|
// Strict applies a more strict default set of options to the Conn,
|
||||||
|
// including:
|
||||||
|
// - ExtendedAcknowledge: true
|
||||||
|
// - provides more useful error messages when supported by the kernel
|
||||||
|
// - GetStrictCheck: true
|
||||||
|
// - more strictly enforces request validation for some families such
|
||||||
|
// as rtnetlink which were historically misused
|
||||||
|
//
|
||||||
|
// If any of the options specified by Strict cannot be configured due to an
|
||||||
|
// outdated kernel or similar, an error will be returned.
|
||||||
|
//
|
||||||
|
// When possible, setting Strict to true is recommended for applications
|
||||||
|
// running on modern Linux kernels.
|
||||||
|
Strict bool
|
||||||
|
}
|
248
vendor/github.com/mdlayher/netlink/conn_linux.go
generated
vendored
Normal file
248
vendor/github.com/mdlayher/netlink/conn_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mdlayher/socket"
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Socket = &conn{}
|
||||||
|
|
||||||
|
// A conn is the Linux implementation of a netlink sockets connection.
|
||||||
|
type conn struct {
|
||||||
|
s *socket.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// dial is the entry point for Dial. dial opens a netlink socket using
|
||||||
|
// system calls, and returns its PID.
|
||||||
|
func dial(family int, config *Config) (*conn, uint32, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the netlink socket.
|
||||||
|
s, err := socket.Socket(
|
||||||
|
unix.AF_NETLINK,
|
||||||
|
unix.SOCK_RAW,
|
||||||
|
family,
|
||||||
|
"netlink",
|
||||||
|
&socket.Config{NetNS: config.NetNS},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newConn(s, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newConn binds a connection to netlink using the input *socket.Conn.
|
||||||
|
func newConn(s *socket.Conn, config *Config) (*conn, uint32, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &unix.SockaddrNetlink{
|
||||||
|
Family: unix.AF_NETLINK,
|
||||||
|
Groups: config.Groups,
|
||||||
|
Pid: config.PID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Socket must be closed in the event of any system call errors, to avoid
|
||||||
|
// leaking file descriptors.
|
||||||
|
|
||||||
|
if err := s.Bind(addr); err != nil {
|
||||||
|
_ = s.Close()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err := s.Getsockname()
|
||||||
|
if err != nil {
|
||||||
|
_ = s.Close()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &conn{s: s}
|
||||||
|
if config.Strict {
|
||||||
|
// The caller has requested the strict option set. Historically we have
|
||||||
|
// recommended checking for ENOPROTOOPT if the kernel does not support
|
||||||
|
// the option in question, but that may result in a silent failure and
|
||||||
|
// unexpected behavior for the user.
|
||||||
|
//
|
||||||
|
// Treat any error here as a fatal error, and require the caller to deal
|
||||||
|
// with it.
|
||||||
|
for _, o := range []ConnOption{ExtendedAcknowledge, GetStrictCheck} {
|
||||||
|
if err := c.SetOption(o, true); err != nil {
|
||||||
|
_ = c.Close()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, sa.(*unix.SockaddrNetlink).Pid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessages serializes multiple Messages and sends them to netlink.
|
||||||
|
func (c *conn) SendMessages(messages []Message) error {
|
||||||
|
var buf []byte
|
||||||
|
for _, m := range messages {
|
||||||
|
b, err := m.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = append(buf, b...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sa := &unix.SockaddrNetlink{Family: unix.AF_NETLINK}
|
||||||
|
return c.s.Sendmsg(buf, nil, sa, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends a single Message to netlink.
|
||||||
|
func (c *conn) Send(m Message) error {
|
||||||
|
b, err := m.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sa := &unix.SockaddrNetlink{Family: unix.AF_NETLINK}
|
||||||
|
return c.s.Sendmsg(b, nil, sa, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive receives one or more Messages from netlink.
|
||||||
|
func (c *conn) Receive() ([]Message, error) {
|
||||||
|
b := make([]byte, os.Getpagesize())
|
||||||
|
for {
|
||||||
|
// Peek at the buffer to see how many bytes are available.
|
||||||
|
//
|
||||||
|
// TODO(mdlayher): deal with OOB message data if available, such as
|
||||||
|
// when PacketInfo ConnOption is true.
|
||||||
|
n, _, _, _, err := c.s.Recvmsg(b, nil, unix.MSG_PEEK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break when we can read all messages
|
||||||
|
if n < len(b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double in size if not enough bytes
|
||||||
|
b = make([]byte, len(b)*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read out all available messages
|
||||||
|
n, _, _, _, err := c.s.Recvmsg(b, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := syscall.ParseNetlinkMessage(b[:nlmsgAlign(n)])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := make([]Message, 0, len(raw))
|
||||||
|
for _, r := range raw {
|
||||||
|
m := Message{
|
||||||
|
Header: sysToHeader(r.Header),
|
||||||
|
Data: r.Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs = append(msgs, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection.
|
||||||
|
func (c *conn) Close() error { return c.s.Close() }
|
||||||
|
|
||||||
|
// JoinGroup joins a multicast group by ID.
|
||||||
|
func (c *conn) JoinGroup(group uint32) error {
|
||||||
|
return c.s.SetsockoptInt(unix.SOL_NETLINK, unix.NETLINK_ADD_MEMBERSHIP, int(group))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LeaveGroup leaves a multicast group by ID.
|
||||||
|
func (c *conn) LeaveGroup(group uint32) error {
|
||||||
|
return c.s.SetsockoptInt(unix.SOL_NETLINK, unix.NETLINK_DROP_MEMBERSHIP, int(group))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBPF attaches an assembled BPF program to a conn.
|
||||||
|
func (c *conn) SetBPF(filter []bpf.RawInstruction) error { return c.s.SetBPF(filter) }
|
||||||
|
|
||||||
|
// RemoveBPF removes a BPF filter from a conn.
|
||||||
|
func (c *conn) RemoveBPF() error { return c.s.RemoveBPF() }
|
||||||
|
|
||||||
|
// SetOption enables or disables a netlink socket option for the Conn.
|
||||||
|
func (c *conn) SetOption(option ConnOption, enable bool) error {
|
||||||
|
o, ok := linuxOption(option)
|
||||||
|
if !ok {
|
||||||
|
// Return the typical Linux error for an unknown ConnOption.
|
||||||
|
return os.NewSyscallError("setsockopt", unix.ENOPROTOOPT)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v int
|
||||||
|
if enable {
|
||||||
|
v = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.s.SetsockoptInt(unix.SOL_NETLINK, o, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *conn) SetDeadline(t time.Time) error { return c.s.SetDeadline(t) }
|
||||||
|
func (c *conn) SetReadDeadline(t time.Time) error { return c.s.SetReadDeadline(t) }
|
||||||
|
func (c *conn) SetWriteDeadline(t time.Time) error { return c.s.SetWriteDeadline(t) }
|
||||||
|
|
||||||
|
// SetReadBuffer sets the size of the operating system's receive buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
func (c *conn) SetReadBuffer(bytes int) error { return c.s.SetReadBuffer(bytes) }
|
||||||
|
|
||||||
|
// SetReadBuffer sets the size of the operating system's transmit buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
func (c *conn) SetWriteBuffer(bytes int) error { return c.s.SetWriteBuffer(bytes) }
|
||||||
|
|
||||||
|
// SyscallConn returns a raw network connection.
|
||||||
|
func (c *conn) SyscallConn() (syscall.RawConn, error) { return c.s.SyscallConn() }
|
||||||
|
|
||||||
|
// linuxOption converts a ConnOption to its Linux value.
|
||||||
|
func linuxOption(o ConnOption) (int, bool) {
|
||||||
|
switch o {
|
||||||
|
case PacketInfo:
|
||||||
|
return unix.NETLINK_PKTINFO, true
|
||||||
|
case BroadcastError:
|
||||||
|
return unix.NETLINK_BROADCAST_ERROR, true
|
||||||
|
case NoENOBUFS:
|
||||||
|
return unix.NETLINK_NO_ENOBUFS, true
|
||||||
|
case ListenAllNSID:
|
||||||
|
return unix.NETLINK_LISTEN_ALL_NSID, true
|
||||||
|
case CapAcknowledge:
|
||||||
|
return unix.NETLINK_CAP_ACK, true
|
||||||
|
case ExtendedAcknowledge:
|
||||||
|
return unix.NETLINK_EXT_ACK, true
|
||||||
|
case GetStrictCheck:
|
||||||
|
return unix.NETLINK_GET_STRICT_CHK, true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysToHeader converts a syscall.NlMsghdr to a Header.
|
||||||
|
func sysToHeader(r syscall.NlMsghdr) Header {
|
||||||
|
// NB: the memory layout of Header and syscall.NlMsgHdr must be
|
||||||
|
// exactly the same for this unsafe cast to work
|
||||||
|
return *(*Header)(unsafe.Pointer(&r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newError converts an error number from netlink into the appropriate
|
||||||
|
// system call error for Linux.
|
||||||
|
func newError(errno int) error {
|
||||||
|
return syscall.Errno(errno)
|
||||||
|
}
|
30
vendor/github.com/mdlayher/netlink/conn_others.go
generated
vendored
Normal file
30
vendor/github.com/mdlayher/netlink/conn_others.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errUnimplemented is returned by all functions on platforms that
|
||||||
|
// cannot make use of netlink sockets.
|
||||||
|
var errUnimplemented = fmt.Errorf("netlink: not implemented on %s/%s",
|
||||||
|
runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
|
var _ Socket = &conn{}
|
||||||
|
|
||||||
|
// A conn is the no-op implementation of a netlink sockets connection.
|
||||||
|
type conn struct{}
|
||||||
|
|
||||||
|
// All cross-platform functions and Socket methods are unimplemented outside
|
||||||
|
// of Linux.
|
||||||
|
|
||||||
|
func dial(_ int, _ *Config) (*conn, uint32, error) { return nil, 0, errUnimplemented }
|
||||||
|
func newError(_ int) error { return errUnimplemented }
|
||||||
|
|
||||||
|
func (c *conn) Send(_ Message) error { return errUnimplemented }
|
||||||
|
func (c *conn) SendMessages(_ []Message) error { return errUnimplemented }
|
||||||
|
func (c *conn) Receive() ([]Message, error) { return nil, errUnimplemented }
|
||||||
|
func (c *conn) Close() error { return errUnimplemented }
|
69
vendor/github.com/mdlayher/netlink/debug.go
generated
vendored
Normal file
69
vendor/github.com/mdlayher/netlink/debug.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Arguments used to create a debugger.
|
||||||
|
var debugArgs []string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Is netlink debugging enabled?
|
||||||
|
s := os.Getenv("NLDEBUG")
|
||||||
|
if s == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debugArgs = strings.Split(s, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A debugger is used to provide debugging information about a netlink connection.
|
||||||
|
type debugger struct {
|
||||||
|
Log *log.Logger
|
||||||
|
Level int
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDebugger creates a debugger by parsing key=value arguments.
|
||||||
|
func newDebugger(args []string) *debugger {
|
||||||
|
d := &debugger{
|
||||||
|
Log: log.New(os.Stderr, "nl: ", 0),
|
||||||
|
Level: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range args {
|
||||||
|
kv := strings.Split(a, "=")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
// Ignore malformed pairs and assume callers wants defaults.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kv[0] {
|
||||||
|
// Select the log level for the debugger.
|
||||||
|
case "level":
|
||||||
|
level, err := strconv.Atoi(kv[1])
|
||||||
|
if err != nil {
|
||||||
|
panicf("netlink: invalid NLDEBUG level: %q", a)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Level = level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// debugf prints debugging information at the specified level, if d.Level is
|
||||||
|
// high enough to print the message.
|
||||||
|
func (d *debugger) debugf(level int, format string, v ...interface{}) {
|
||||||
|
if d.Level >= level {
|
||||||
|
d.Log.Printf(format, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func panicf(format string, a ...interface{}) {
|
||||||
|
panic(fmt.Sprintf(format, a...))
|
||||||
|
}
|
36
vendor/github.com/mdlayher/netlink/doc.go
generated
vendored
Normal file
36
vendor/github.com/mdlayher/netlink/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Package netlink provides low-level access to Linux netlink sockets.
|
||||||
|
//
|
||||||
|
// If you have any questions or you'd like some guidance, please join us on
|
||||||
|
// Gophers Slack (https://invite.slack.golangbridge.org) in the #networking
|
||||||
|
// channel!
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Network namespaces
|
||||||
|
//
|
||||||
|
// This package is aware of Linux network namespaces, and can enter different
|
||||||
|
// network namespaces either implicitly or explicitly, depending on
|
||||||
|
// configuration. The Config structure passed to Dial to create a Conn controls
|
||||||
|
// these behaviors. See the documentation of Config.NetNS for details.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Debugging
|
||||||
|
//
|
||||||
|
// This package supports rudimentary netlink connection debugging support.
|
||||||
|
// To enable this, run your binary with the NLDEBUG environment variable set.
|
||||||
|
// Debugging information will be output to stderr with a prefix of "nl:".
|
||||||
|
//
|
||||||
|
// To use the debugging defaults, use:
|
||||||
|
//
|
||||||
|
// $ NLDEBUG=1 ./nlctl
|
||||||
|
//
|
||||||
|
// To configure individual aspects of the debugger, pass key/value options such
|
||||||
|
// as:
|
||||||
|
//
|
||||||
|
// $ NLDEBUG=level=1 ./nlctl
|
||||||
|
//
|
||||||
|
// Available key/value debugger options include:
|
||||||
|
//
|
||||||
|
// level=N: specify the debugging level (only "1" is currently supported)
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
//go:generate dot netlink.dot -T svg -o netlink.svg
|
138
vendor/github.com/mdlayher/netlink/errors.go
generated
vendored
Normal file
138
vendor/github.com/mdlayher/netlink/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error messages which can be returned by Validate.
|
||||||
|
var (
|
||||||
|
errMismatchedSequence = errors.New("mismatched sequence in netlink reply")
|
||||||
|
errMismatchedPID = errors.New("mismatched PID in netlink reply")
|
||||||
|
errShortErrorMessage = errors.New("not enough data for netlink error code")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errors which can be returned by a Socket that does not implement
|
||||||
|
// all exposed methods of Conn.
|
||||||
|
|
||||||
|
var errNotSupported = errors.New("operation not supported")
|
||||||
|
|
||||||
|
// notSupported provides a concise constructor for "not supported" errors.
|
||||||
|
func notSupported(op string) error {
|
||||||
|
return newOpError(op, errNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotExist determines if an error is produced as the result of querying some
|
||||||
|
// file, object, resource, etc. which does not exist.
|
||||||
|
//
|
||||||
|
// Deprecated: use errors.Unwrap and/or `errors.Is(err, os.Permission)` in Go
|
||||||
|
// 1.13+.
|
||||||
|
func IsNotExist(err error) bool {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *OpError:
|
||||||
|
// Unwrap the inner error and use the stdlib's logic.
|
||||||
|
return os.IsNotExist(err.Err)
|
||||||
|
default:
|
||||||
|
return os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ error = &OpError{}
|
||||||
|
_ net.Error = &OpError{}
|
||||||
|
// Ensure compatibility with Go 1.13+ errors package.
|
||||||
|
_ interface{ Unwrap() error } = &OpError{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// An OpError is an error produced as the result of a failed netlink operation.
|
||||||
|
type OpError struct {
|
||||||
|
// Op is the operation which caused this OpError, such as "send"
|
||||||
|
// or "receive".
|
||||||
|
Op string
|
||||||
|
|
||||||
|
// Err is the underlying error which caused this OpError.
|
||||||
|
//
|
||||||
|
// If Err was produced by a system call error, Err will be of type
|
||||||
|
// *os.SyscallError. If Err was produced by an error code in a netlink
|
||||||
|
// message, Err will contain a raw error value type such as a unix.Errno.
|
||||||
|
//
|
||||||
|
// Most callers should inspect Err using errors.Is from the standard
|
||||||
|
// library.
|
||||||
|
Err error
|
||||||
|
|
||||||
|
// Message and Offset contain additional error information provided by the
|
||||||
|
// kernel when the ExtendedAcknowledge option is set on a Conn and the
|
||||||
|
// kernel indicates the AcknowledgeTLVs flag in a response. If this option
|
||||||
|
// is not set, both of these fields will be empty.
|
||||||
|
Message string
|
||||||
|
Offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
// newOpError is a small wrapper for creating an OpError. As a convenience, it
|
||||||
|
// returns nil if the input err is nil: akin to os.NewSyscallError.
|
||||||
|
func newOpError(op string, err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OpError{
|
||||||
|
Op: op,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *OpError) Error() string {
|
||||||
|
if e == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
_, _ = sb.WriteString(fmt.Sprintf("netlink %s: %v", e.Op, e.Err))
|
||||||
|
|
||||||
|
if e.Message != "" || e.Offset != 0 {
|
||||||
|
_, _ = sb.WriteString(fmt.Sprintf(", offset: %d, message: %q",
|
||||||
|
e.Offset, e.Message))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap unwraps the internal Err field for use with errors.Unwrap.
|
||||||
|
func (e *OpError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
// Portions of this code taken from the Go standard library:
|
||||||
|
//
|
||||||
|
// Copyright 2009 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.
|
||||||
|
|
||||||
|
type timeout interface {
|
||||||
|
Timeout() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout reports whether the error was caused by an I/O timeout.
|
||||||
|
func (e *OpError) Timeout() bool {
|
||||||
|
if ne, ok := e.Err.(*os.SyscallError); ok {
|
||||||
|
t, ok := ne.Err.(timeout)
|
||||||
|
return ok && t.Timeout()
|
||||||
|
}
|
||||||
|
t, ok := e.Err.(timeout)
|
||||||
|
return ok && t.Timeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
type temporary interface {
|
||||||
|
Temporary() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporary reports whether an operation may succeed if retried.
|
||||||
|
func (e *OpError) Temporary() bool {
|
||||||
|
if ne, ok := e.Err.(*os.SyscallError); ok {
|
||||||
|
t, ok := ne.Err.(temporary)
|
||||||
|
return ok && t.Temporary()
|
||||||
|
}
|
||||||
|
t, ok := e.Err.(temporary)
|
||||||
|
return ok && t.Temporary()
|
||||||
|
}
|
82
vendor/github.com/mdlayher/netlink/fuzz.go
generated
vendored
Normal file
82
vendor/github.com/mdlayher/netlink/fuzz.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
//go:build gofuzz
|
||||||
|
// +build gofuzz
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import "github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
|
func fuzz(b1 []byte) int {
|
||||||
|
// 1. unmarshal, marshal, unmarshal again to check m1 and m2 for equality
|
||||||
|
// after a round trip. checkMessage is also used because there is a fair
|
||||||
|
// amount of tricky logic around testing for presence of error headers and
|
||||||
|
// extended acknowledgement attributes.
|
||||||
|
var m1 Message
|
||||||
|
if err := m1.UnmarshalBinary(b1); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkMessage(m1); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
b2, err := m1.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panicf("failed to marshal m1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m2 Message
|
||||||
|
if err := m2.UnmarshalBinary(b2); err != nil {
|
||||||
|
panicf("failed to unmarshal m2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkMessage(m2); err != nil {
|
||||||
|
panicf("failed to check m2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(m1, m2); diff != "" {
|
||||||
|
panicf("unexpected Message (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. marshal again and compare b2 and b3 (b1 may have reserved bytes set
|
||||||
|
// which we ignore and fill with zeros when marshaling) for equality.
|
||||||
|
b3, err := m2.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
panicf("failed to marshal m2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(b2, b3); diff != "" {
|
||||||
|
panicf("unexpected message bytes (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. unmarshal any possible attributes from m1's data and marshal them
|
||||||
|
// again for comparison.
|
||||||
|
a1, err := UnmarshalAttributes(m1.Data)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ab1, err := MarshalAttributes(a1)
|
||||||
|
if err != nil {
|
||||||
|
panicf("failed to marshal a1: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a2, err := UnmarshalAttributes(ab1)
|
||||||
|
if err != nil {
|
||||||
|
panicf("failed to unmarshal a2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(a1, a2); diff != "" {
|
||||||
|
panicf("unexpected Attributes (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
ab2, err := MarshalAttributes(a2)
|
||||||
|
if err != nil {
|
||||||
|
panicf("failed to marshal a2: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(ab1, ab2); diff != "" {
|
||||||
|
panicf("unexpected attribute bytes (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
347
vendor/github.com/mdlayher/netlink/message.go
generated
vendored
Normal file
347
vendor/github.com/mdlayher/netlink/message.go
generated
vendored
Normal file
|
@ -0,0 +1,347 @@
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/mdlayher/netlink/nlenc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flags which may apply to netlink attribute types when communicating with
|
||||||
|
// certain netlink families.
|
||||||
|
const (
|
||||||
|
Nested uint16 = 0x8000
|
||||||
|
NetByteOrder uint16 = 0x4000
|
||||||
|
|
||||||
|
// attrTypeMask masks off Type bits used for the above flags.
|
||||||
|
attrTypeMask uint16 = 0x3fff
|
||||||
|
)
|
||||||
|
|
||||||
|
// Various errors which may occur when attempting to marshal or unmarshal
|
||||||
|
// a Message to and from its binary form.
|
||||||
|
var (
|
||||||
|
errIncorrectMessageLength = errors.New("netlink message header length incorrect")
|
||||||
|
errShortMessage = errors.New("not enough data to create a netlink message")
|
||||||
|
errUnalignedMessage = errors.New("input data is not properly aligned for netlink message")
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderFlags specify flags which may be present in a Header.
|
||||||
|
type HeaderFlags uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// General netlink communication flags.
|
||||||
|
|
||||||
|
// Request indicates a request to netlink.
|
||||||
|
Request HeaderFlags = 1
|
||||||
|
|
||||||
|
// Multi indicates a multi-part message, terminated by Done on the
|
||||||
|
// last message.
|
||||||
|
Multi HeaderFlags = 2
|
||||||
|
|
||||||
|
// Acknowledge requests that netlink reply with an acknowledgement
|
||||||
|
// using Error and, if needed, an error code.
|
||||||
|
Acknowledge HeaderFlags = 4
|
||||||
|
|
||||||
|
// Echo requests that netlink echo this request back to the sender.
|
||||||
|
Echo HeaderFlags = 8
|
||||||
|
|
||||||
|
// DumpInterrupted indicates that a dump was inconsistent due to a
|
||||||
|
// sequence change.
|
||||||
|
DumpInterrupted HeaderFlags = 16
|
||||||
|
|
||||||
|
// DumpFiltered indicates that a dump was filtered as requested.
|
||||||
|
DumpFiltered HeaderFlags = 32
|
||||||
|
|
||||||
|
// Flags used to retrieve data from netlink.
|
||||||
|
|
||||||
|
// Root requests that netlink return a complete table instead of a
|
||||||
|
// single entry.
|
||||||
|
Root HeaderFlags = 0x100
|
||||||
|
|
||||||
|
// Match requests that netlink return a list of all matching entries.
|
||||||
|
Match HeaderFlags = 0x200
|
||||||
|
|
||||||
|
// Atomic requests that netlink send an atomic snapshot of its entries.
|
||||||
|
// Requires CAP_NET_ADMIN or an effective UID of 0.
|
||||||
|
Atomic HeaderFlags = 0x400
|
||||||
|
|
||||||
|
// Dump requests that netlink return a complete list of all entries.
|
||||||
|
Dump HeaderFlags = Root | Match
|
||||||
|
|
||||||
|
// Flags used to create objects.
|
||||||
|
|
||||||
|
// Replace indicates request replaces an existing matching object.
|
||||||
|
Replace HeaderFlags = 0x100
|
||||||
|
|
||||||
|
// Excl indicates request does not replace the object if it already exists.
|
||||||
|
Excl HeaderFlags = 0x200
|
||||||
|
|
||||||
|
// Create indicates request creates an object if it doesn't already exist.
|
||||||
|
Create HeaderFlags = 0x400
|
||||||
|
|
||||||
|
// Append indicates request adds to the end of the object list.
|
||||||
|
Append HeaderFlags = 0x800
|
||||||
|
|
||||||
|
// Flags for extended acknowledgements.
|
||||||
|
|
||||||
|
// Capped indicates the size of a request was capped in an extended
|
||||||
|
// acknowledgement.
|
||||||
|
Capped HeaderFlags = 0x100
|
||||||
|
|
||||||
|
// AcknowledgeTLVs indicates the presence of netlink extended
|
||||||
|
// acknowledgement TLVs in a response.
|
||||||
|
AcknowledgeTLVs HeaderFlags = 0x200
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the string representation of a HeaderFlags.
|
||||||
|
func (f HeaderFlags) String() string {
|
||||||
|
names := []string{
|
||||||
|
"request",
|
||||||
|
"multi",
|
||||||
|
"acknowledge",
|
||||||
|
"echo",
|
||||||
|
"dumpinterrupted",
|
||||||
|
"dumpfiltered",
|
||||||
|
}
|
||||||
|
|
||||||
|
var s string
|
||||||
|
|
||||||
|
left := uint(f)
|
||||||
|
|
||||||
|
for i, name := range names {
|
||||||
|
if f&(1<<uint(i)) != 0 {
|
||||||
|
if s != "" {
|
||||||
|
s += "|"
|
||||||
|
}
|
||||||
|
|
||||||
|
s += name
|
||||||
|
|
||||||
|
left ^= (1 << uint(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "" && left == 0 {
|
||||||
|
s = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
if left > 0 {
|
||||||
|
if s != "" {
|
||||||
|
s += "|"
|
||||||
|
}
|
||||||
|
s += fmt.Sprintf("%#x", left)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderType specifies the type of a Header.
|
||||||
|
type HeaderType uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Noop indicates that no action was taken.
|
||||||
|
Noop HeaderType = 0x1
|
||||||
|
|
||||||
|
// Error indicates an error code is present, which is also used to indicate
|
||||||
|
// success when the code is 0.
|
||||||
|
Error HeaderType = 0x2
|
||||||
|
|
||||||
|
// Done indicates the end of a multi-part message.
|
||||||
|
Done HeaderType = 0x3
|
||||||
|
|
||||||
|
// Overrun indicates that data was lost from this message.
|
||||||
|
Overrun HeaderType = 0x4
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the string representation of a HeaderType.
|
||||||
|
func (t HeaderType) String() string {
|
||||||
|
switch t {
|
||||||
|
case Noop:
|
||||||
|
return "noop"
|
||||||
|
case Error:
|
||||||
|
return "error"
|
||||||
|
case Done:
|
||||||
|
return "done"
|
||||||
|
case Overrun:
|
||||||
|
return "overrun"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown(%d)", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: the memory layout of Header and Linux's syscall.NlMsgHdr must be
|
||||||
|
// exactly the same. Cannot reorder, change data type, add, or remove fields.
|
||||||
|
// Named types of the same size (e.g. HeaderFlags is a uint16) are okay.
|
||||||
|
|
||||||
|
// A Header is a netlink header. A Header is sent and received with each
|
||||||
|
// Message to indicate metadata regarding a Message.
|
||||||
|
type Header struct {
|
||||||
|
// Length of a Message, including this Header.
|
||||||
|
Length uint32
|
||||||
|
|
||||||
|
// Contents of a Message.
|
||||||
|
Type HeaderType
|
||||||
|
|
||||||
|
// Flags which may be used to modify a request or response.
|
||||||
|
Flags HeaderFlags
|
||||||
|
|
||||||
|
// The sequence number of a Message.
|
||||||
|
Sequence uint32
|
||||||
|
|
||||||
|
// The port ID of the sending process.
|
||||||
|
PID uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Message is a netlink message. It contains a Header and an arbitrary
|
||||||
|
// byte payload, which may be decoded using information from the Header.
|
||||||
|
//
|
||||||
|
// Data is often populated with netlink attributes. For easy encoding and
|
||||||
|
// decoding of attributes, see the AttributeDecoder and AttributeEncoder types.
|
||||||
|
type Message struct {
|
||||||
|
Header Header
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary marshals a Message into a byte slice.
|
||||||
|
func (m Message) MarshalBinary() ([]byte, error) {
|
||||||
|
ml := nlmsgAlign(int(m.Header.Length))
|
||||||
|
if ml < nlmsgHeaderLen || ml != int(m.Header.Length) {
|
||||||
|
return nil, errIncorrectMessageLength
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, ml)
|
||||||
|
|
||||||
|
nlenc.PutUint32(b[0:4], m.Header.Length)
|
||||||
|
nlenc.PutUint16(b[4:6], uint16(m.Header.Type))
|
||||||
|
nlenc.PutUint16(b[6:8], uint16(m.Header.Flags))
|
||||||
|
nlenc.PutUint32(b[8:12], m.Header.Sequence)
|
||||||
|
nlenc.PutUint32(b[12:16], m.Header.PID)
|
||||||
|
copy(b[16:], m.Data)
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary unmarshals the contents of a byte slice into a Message.
|
||||||
|
func (m *Message) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) < nlmsgHeaderLen {
|
||||||
|
return errShortMessage
|
||||||
|
}
|
||||||
|
if len(b) != nlmsgAlign(len(b)) {
|
||||||
|
return errUnalignedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow misleading length
|
||||||
|
m.Header.Length = nlenc.Uint32(b[0:4])
|
||||||
|
if int(m.Header.Length) != len(b) {
|
||||||
|
return errShortMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Header.Type = HeaderType(nlenc.Uint16(b[4:6]))
|
||||||
|
m.Header.Flags = HeaderFlags(nlenc.Uint16(b[6:8]))
|
||||||
|
m.Header.Sequence = nlenc.Uint32(b[8:12])
|
||||||
|
m.Header.PID = nlenc.Uint32(b[12:16])
|
||||||
|
m.Data = b[16:]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkMessage checks a single Message for netlink errors.
|
||||||
|
func checkMessage(m Message) error {
|
||||||
|
// NB: All non-nil errors returned from this function *must* be of type
|
||||||
|
// OpError in order to maintain the appropriate contract with callers of
|
||||||
|
// this package.
|
||||||
|
|
||||||
|
// The libnl documentation indicates that type error can
|
||||||
|
// contain error codes:
|
||||||
|
// https://www.infradead.org/~tgr/libnl/doc/core.html#core_errmsg.
|
||||||
|
//
|
||||||
|
// However, rtnetlink at least seems to also allow errors to occur at the
|
||||||
|
// end of a multipart message with done/multi and an error number.
|
||||||
|
var hasHeader bool
|
||||||
|
switch {
|
||||||
|
case m.Header.Type == Error:
|
||||||
|
// Error code followed by nlmsghdr/ext ack attributes.
|
||||||
|
hasHeader = true
|
||||||
|
case m.Header.Type == Done && m.Header.Flags&Multi != 0:
|
||||||
|
// If no data, there must be no error number so just exit early. Some
|
||||||
|
// of the unit tests hard-coded this but I don't actually know if this
|
||||||
|
// case occurs in the wild.
|
||||||
|
if len(m.Data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done|Multi potentially followed by ext ack attributes.
|
||||||
|
default:
|
||||||
|
// Neither, nothing to do.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errno occupies 4 bytes.
|
||||||
|
const endErrno = 4
|
||||||
|
if len(m.Data) < endErrno {
|
||||||
|
return newOpError("receive", errShortErrorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := nlenc.Int32(m.Data[:endErrno])
|
||||||
|
if c == 0 {
|
||||||
|
// 0 indicates no error.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
oerr := &OpError{
|
||||||
|
Op: "receive",
|
||||||
|
// Error code is a negative integer, convert it into an OS-specific raw
|
||||||
|
// system call error, but do not wrap with os.NewSyscallError to signify
|
||||||
|
// that this error was produced by a netlink message; not a system call.
|
||||||
|
Err: newError(-1 * int(c)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdlayher): investigate the Capped flag.
|
||||||
|
|
||||||
|
if m.Header.Flags&AcknowledgeTLVs == 0 {
|
||||||
|
// No extended acknowledgement.
|
||||||
|
return oerr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags indicate an extended acknowledgement. The type/flags combination
|
||||||
|
// checked above determines the offset where the TLVs occur.
|
||||||
|
var off int
|
||||||
|
if hasHeader {
|
||||||
|
// There is an nlmsghdr preceding the TLVs.
|
||||||
|
if len(m.Data) < endErrno+nlmsgHeaderLen {
|
||||||
|
return newOpError("receive", errShortErrorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The TLVs should be at the offset indicated by the nlmsghdr.length,
|
||||||
|
// plus the offset where the header began. But make sure the calculated
|
||||||
|
// offset is still in-bounds.
|
||||||
|
h := *(*Header)(unsafe.Pointer(&m.Data[endErrno : endErrno+nlmsgHeaderLen][0]))
|
||||||
|
off = endErrno + int(h.Length)
|
||||||
|
|
||||||
|
if len(m.Data) < off {
|
||||||
|
return newOpError("receive", errShortErrorMessage)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// There is no nlmsghdr preceding the TLVs, parse them directly.
|
||||||
|
off = endErrno
|
||||||
|
}
|
||||||
|
|
||||||
|
ad, err := NewAttributeDecoder(m.Data[off:])
|
||||||
|
if err != nil {
|
||||||
|
// Malformed TLVs, just return the OpError with the info we have.
|
||||||
|
return oerr
|
||||||
|
}
|
||||||
|
|
||||||
|
for ad.Next() {
|
||||||
|
switch ad.Type() {
|
||||||
|
case 1: // unix.NLMSGERR_ATTR_MSG
|
||||||
|
oerr.Message = ad.String()
|
||||||
|
case 2: // unix.NLMSGERR_ATTR_OFFS
|
||||||
|
oerr.Offset = int(ad.Uint32())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly ignore ad.Err: malformed TLVs, just return the OpError with
|
||||||
|
// the info we have.
|
||||||
|
return oerr
|
||||||
|
}
|
87
vendor/github.com/mdlayher/netlink/netlink.dot
generated
vendored
Normal file
87
vendor/github.com/mdlayher/netlink/netlink.dot
generated
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
digraph {
|
||||||
|
rankdir = LR
|
||||||
|
|
||||||
|
subgraph cluster_netlink {
|
||||||
|
"github.com/mdlayher/netlink" [URL="https://github.com/mdlayher/netlink"]
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster_connector {
|
||||||
|
label = "NETLINK_CONNECTOR";
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/fearful-symmetry/garlic" [URL="https://github.com/fearful-symmetry/garlic"]
|
||||||
|
} -> "github.com/mdlayher/netlink"
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster_crypto {
|
||||||
|
label = "NETLINK_CRYPTO";
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/mdlayher/cryptonl" [URL="https://github.com/mdlayher/cryptonl"]
|
||||||
|
} -> "github.com/mdlayher/netlink"
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster_generic {
|
||||||
|
label = "NETLINK_GENERIC (genetlink)";
|
||||||
|
"github.com/mdlayher/genetlink" [URL="https://github.com/mdlayher/genetlink"]
|
||||||
|
"github.com/mdlayher/genetlink" -> "github.com/mdlayher/netlink"
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/axatrax/l2tp" [URL="https://github.com/axatrax/l2tp"]
|
||||||
|
"github.com/digitalocean/go-openvswitch" [URL="https://github.com/digitalocean/go-openvswitch"]
|
||||||
|
"github.com/mdlayher/devlink" [URL="https://github.com/mdlayher/devlink"]
|
||||||
|
"github.com/mdlayher/ethtool" [URL="https://github.com/mdlayher/ethtool"]
|
||||||
|
"github.com/mdlayher/quota" [URL="https://github.com/mdlayher/quota"]
|
||||||
|
"github.com/mdlayher/taskstats" [URL="https://github.com/mdlayher/taskstats"]
|
||||||
|
"github.com/mdlayher/wifi" [URL="https://github.com/mdlayher/wifi"]
|
||||||
|
"github.com/Merovius/nbd" [URL="https://github.com/Merovius/nbd"]
|
||||||
|
"github.com/rtr7/router7" [URL="https://github.com/rtr7/router7"]
|
||||||
|
"github.com/u-root/u-bmc" [URL="https://github.com/u-root/u-bmc"]
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl" [URL="https://golang.zx2c4.com/wireguard/wgctrl"]
|
||||||
|
} -> "github.com/mdlayher/genetlink"
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster_kobject_uevent {
|
||||||
|
label = "NETLINK_KOBJECT_UEVENT";
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/mdlayher/kobject" [URL="https://github.com/mdlayher/kobject"]
|
||||||
|
} -> "github.com/mdlayher/netlink"
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster_netfilter {
|
||||||
|
label = "NETLINK_NETFILTER (nfnetlink)";
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/florianl/go-conntrack" [URL="https://github.com/florianl/go-conntrack"]
|
||||||
|
"github.com/florianl/go-nflog" [URL="https://github.com/florianl/go-nflog"]
|
||||||
|
"github.com/florianl/go-nfqueue" [URL="https://github.com/florianl/go-nfqueue"]
|
||||||
|
"github.com/google/nftables" [URL="https://github.com/google/nftables"]
|
||||||
|
"github.com/ti-mo/netfilter" [URL="https://github.com/ti-mo/netfilter"]
|
||||||
|
} -> "github.com/mdlayher/netlink"
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/ti-mo/conntrack" [URL="https://github.com/ti-mo/conntrack"]
|
||||||
|
} -> "github.com/ti-mo/netfilter"
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster_route {
|
||||||
|
label = "NETLINK_ROUTE (rtnetlink)";
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/ema/qdisc" [URL="https://github.com/ema/qdisc"]
|
||||||
|
"github.com/florianl/go-tc" [URL="https://github.com/florianl/go-tc"]
|
||||||
|
"github.com/jsimonetti/rtnetlink" [URL="https://github.com/jsimonetti/rtnetlink"]
|
||||||
|
"gitlab.com/mergetb/tech/rtnl" [URL="https://gitlab.com/mergetb/tech/rtnl"]
|
||||||
|
} -> "github.com/mdlayher/netlink"
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster_w1 {
|
||||||
|
label = "NETLINK_W1";
|
||||||
|
|
||||||
|
{
|
||||||
|
"github.com/SpComb/go-onewire" [URL="https://github.com/SpComb/go-onewire"]
|
||||||
|
} -> "github.com/mdlayher/netlink"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
451
vendor/github.com/mdlayher/netlink/netlink.svg
generated
vendored
Normal file
451
vendor/github.com/mdlayher/netlink/netlink.svg
generated
vendored
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Generated by graphviz version 2.43.0 (0)
|
||||||
|
-->
|
||||||
|
<!-- Title: %3 Pages: 1 -->
|
||||||
|
<svg width="1148pt" height="1515pt"
|
||||||
|
viewBox="0.00 0.00 1148.01 1515.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1511)">
|
||||||
|
<title>%3</title>
|
||||||
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-1511 1144.01,-1511 1144.01,4 -4,4"/>
|
||||||
|
<g id="clust1" class="cluster">
|
||||||
|
<title>cluster_netlink</title>
|
||||||
|
<polygon fill="none" stroke="black" points="826.13,-417 826.13,-469 1132.01,-469 1132.01,-417 826.13,-417"/>
|
||||||
|
</g>
|
||||||
|
<g id="clust2" class="cluster">
|
||||||
|
<title>cluster_connector</title>
|
||||||
|
<polygon fill="none" stroke="black" points="439.16,-1424 439.16,-1499 806.13,-1499 806.13,-1424 439.16,-1424"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-1483.8" font-family="Times,serif" font-size="14.00">NETLINK_CONNECTOR</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust4" class="cluster">
|
||||||
|
<title>cluster_crypto</title>
|
||||||
|
<polygon fill="none" stroke="black" points="463.21,-1341 463.21,-1416 782.09,-1416 782.09,-1341 463.21,-1341"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-1400.8" font-family="Times,serif" font-size="14.00">NETLINK_CRYPTO</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust6" class="cluster">
|
||||||
|
<title>cluster_generic</title>
|
||||||
|
<polygon fill="none" stroke="black" points="8,-718 8,-1333 786.64,-1333 786.64,-718 8,-718"/>
|
||||||
|
<text text-anchor="middle" x="397.32" y="-1317.8" font-family="Times,serif" font-size="14.00">NETLINK_GENERIC (genetlink)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust8" class="cluster">
|
||||||
|
<title>cluster_kobject_uevent</title>
|
||||||
|
<polygon fill="none" stroke="black" points="468.41,-635 468.41,-710 776.89,-710 776.89,-635 468.41,-635"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-694.8" font-family="Times,serif" font-size="14.00">NETLINK_KOBJECT_UEVENT</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust10" class="cluster">
|
||||||
|
<title>cluster_netfilter</title>
|
||||||
|
<polygon fill="none" stroke="black" points="44.4,-336 44.4,-627 760.64,-627 760.64,-336 44.4,-336"/>
|
||||||
|
<text text-anchor="middle" x="402.52" y="-611.8" font-family="Times,serif" font-size="14.00">NETLINK_NETFILTER (nfnetlink)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust13" class="cluster">
|
||||||
|
<title>cluster_route</title>
|
||||||
|
<polygon fill="none" stroke="black" points="458.66,-91 458.66,-328 786.64,-328 786.64,-91 458.66,-91"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-312.8" font-family="Times,serif" font-size="14.00">NETLINK_ROUTE (rtnetlink)</text>
|
||||||
|
</g>
|
||||||
|
<g id="clust15" class="cluster">
|
||||||
|
<title>cluster_w1</title>
|
||||||
|
<polygon fill="none" stroke="black" points="455.41,-8 455.41,-83 789.89,-83 789.89,-8 455.41,-8"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-67.8" font-family="Times,serif" font-size="14.00">NETLINK_W1</text>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/netlink -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>github.com/mdlayher/netlink</title>
|
||||||
|
<g id="a_node1"><a xlink:href="https://github.com/mdlayher/netlink" xlink:title="github.com/mdlayher/netlink">
|
||||||
|
<ellipse fill="none" stroke="black" cx="979.07" cy="-443" rx="144.87" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="979.07" y="-439.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/netlink</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/fearful-symmetry/garlic -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>github.com/fearful-symmetry/garlic</title>
|
||||||
|
<g id="a_node2"><a xlink:href="https://github.com/fearful-symmetry/garlic" xlink:title="github.com/fearful-symmetry/garlic">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-1450" rx="175.47" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-1446.3" font-family="Times,serif" font-size="14.00">github.com/fearful-symmetry/garlic</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/fearful-symmetry/garlic->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>github.com/fearful-symmetry/garlic->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M775.09,-1441.05C786.54,-1435.8 797.12,-1428.92 806.13,-1420 945.48,-1282.21 972.95,-617.34 977.34,-471.37"/>
|
||||||
|
<polygon fill="black" stroke="black" points="980.84,-471.13 977.63,-461.04 973.85,-470.93 980.84,-471.13"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/cryptonl -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>github.com/mdlayher/cryptonl</title>
|
||||||
|
<g id="a_node3"><a xlink:href="https://github.com/mdlayher/cryptonl" xlink:title="github.com/mdlayher/cryptonl">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-1367" rx="151.37" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-1363.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/cryptonl</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/cryptonl->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>github.com/mdlayher/cryptonl->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M766.97,-1361.23C781.49,-1355.68 794.97,-1347.86 806.13,-1337 934.19,-1212.36 970.17,-610.39 976.82,-471.59"/>
|
||||||
|
<polygon fill="black" stroke="black" points="980.33,-471.5 977.3,-461.35 973.34,-471.18 980.33,-471.5"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/genetlink -->
|
||||||
|
<g id="node4" class="node">
|
||||||
|
<title>github.com/mdlayher/genetlink</title>
|
||||||
|
<g id="a_node4"><a xlink:href="https://github.com/mdlayher/genetlink" xlink:title="github.com/mdlayher/genetlink">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-987" rx="155.97" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-983.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/genetlink</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/genetlink->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge3" class="edge">
|
||||||
|
<title>github.com/mdlayher/genetlink->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M635.97,-968.89C665.62,-925.25 743.13,-810.74 806.13,-714 863.98,-625.18 930.8,-518.71 961.32,-469.88"/>
|
||||||
|
<polygon fill="black" stroke="black" points="964.42,-471.51 966.75,-461.17 958.48,-467.8 964.42,-471.51"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/axatrax/l2tp -->
|
||||||
|
<g id="node5" class="node">
|
||||||
|
<title>github.com/axatrax/l2tp</title>
|
||||||
|
<g id="a_node5"><a xlink:href="https://github.com/axatrax/l2tp" xlink:title="github.com/axatrax/l2tp">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-1284" rx="122.38" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-1280.3" font-family="Times,serif" font-size="14.00">github.com/axatrax/l2tp</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/axatrax/l2tp->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge4" class="edge">
|
||||||
|
<title>github.com/axatrax/l2tp->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M335.31,-1282.19C361.59,-1277.86 388.33,-1270.15 411.16,-1257 512.47,-1198.65 582.36,-1070.58 609.3,-1014.37"/>
|
||||||
|
<polygon fill="black" stroke="black" points="612.6,-1015.59 613.69,-1005.05 606.26,-1012.61 612.6,-1015.59"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/digitalocean/go-openvswitch -->
|
||||||
|
<g id="node6" class="node">
|
||||||
|
<title>github.com/digitalocean/go-openvswitch</title>
|
||||||
|
<g id="a_node6"><a xlink:href="https://github.com/digitalocean/go-openvswitch" xlink:title="github.com/digitalocean/go-openvswitch">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-1230" rx="197.66" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-1226.3" font-family="Times,serif" font-size="14.00">github.com/digitalocean/go-openvswitch</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/digitalocean/go-openvswitch->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge5" class="edge">
|
||||||
|
<title>github.com/digitalocean/go-openvswitch->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M369.18,-1218.82C383.78,-1214.81 398.02,-1209.64 411.16,-1203 500.09,-1158.12 573.09,-1060.66 604.72,-1013.55"/>
|
||||||
|
<polygon fill="black" stroke="black" points="607.72,-1015.37 610.32,-1005.1 601.88,-1011.51 607.72,-1015.37"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/devlink -->
|
||||||
|
<g id="node7" class="node">
|
||||||
|
<title>github.com/mdlayher/devlink</title>
|
||||||
|
<g id="a_node7"><a xlink:href="https://github.com/mdlayher/devlink" xlink:title="github.com/mdlayher/devlink">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-1176" rx="146.47" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-1172.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/devlink</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/devlink->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge6" class="edge">
|
||||||
|
<title>github.com/mdlayher/devlink->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M344.3,-1167.91C367.14,-1163.77 390.27,-1157.74 411.16,-1149 488.01,-1116.85 561.22,-1049.4 597.65,-1012.49"/>
|
||||||
|
<polygon fill="black" stroke="black" points="600.38,-1014.7 604.86,-1005.1 595.37,-1009.81 600.38,-1014.7"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/ethtool -->
|
||||||
|
<g id="node8" class="node">
|
||||||
|
<title>github.com/mdlayher/ethtool</title>
|
||||||
|
<g id="a_node8"><a xlink:href="https://github.com/mdlayher/ethtool" xlink:title="github.com/mdlayher/ethtool">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-1122" rx="145.67" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-1118.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/ethtool</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/ethtool->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge7" class="edge">
|
||||||
|
<title>github.com/mdlayher/ethtool->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M335.04,-1112C360.53,-1108.08 387.02,-1102.61 411.16,-1095 475.99,-1074.57 544.96,-1035.42 585.63,-1010.3"/>
|
||||||
|
<polygon fill="black" stroke="black" points="587.73,-1013.11 594.36,-1004.85 584.02,-1007.17 587.73,-1013.11"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/quota -->
|
||||||
|
<g id="node9" class="node">
|
||||||
|
<title>github.com/mdlayher/quota</title>
|
||||||
|
<g id="a_node9"><a xlink:href="https://github.com/mdlayher/quota" xlink:title="github.com/mdlayher/quota">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-1068" rx="139.18" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-1064.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/quota</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/quota->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge8" class="edge">
|
||||||
|
<title>github.com/mdlayher/quota->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M318,-1056.06C348.15,-1051.97 381.08,-1046.91 411.16,-1041 459.39,-1031.54 513.03,-1017.72 554.09,-1006.43"/>
|
||||||
|
<polygon fill="black" stroke="black" points="555.28,-1009.74 563.98,-1003.7 553.41,-1002.99 555.28,-1009.74"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/taskstats -->
|
||||||
|
<g id="node10" class="node">
|
||||||
|
<title>github.com/mdlayher/taskstats</title>
|
||||||
|
<g id="a_node10"><a xlink:href="https://github.com/mdlayher/taskstats" xlink:title="github.com/mdlayher/taskstats">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-1014" rx="155.17" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-1010.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/taskstats</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/taskstats->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge9" class="edge">
|
||||||
|
<title>github.com/mdlayher/taskstats->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M348.72,-1005.1C389.7,-1002.38 434.95,-999.38 476.62,-996.62"/>
|
||||||
|
<polygon fill="black" stroke="black" points="477.1,-1000.1 486.85,-995.94 476.64,-993.11 477.1,-1000.1"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/wifi -->
|
||||||
|
<g id="node11" class="node">
|
||||||
|
<title>github.com/mdlayher/wifi</title>
|
||||||
|
<g id="a_node11"><a xlink:href="https://github.com/mdlayher/wifi" xlink:title="github.com/mdlayher/wifi">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-960" rx="129.18" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-956.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/wifi</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/wifi->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge10" class="edge">
|
||||||
|
<title>github.com/mdlayher/wifi->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M330.69,-967.7C376.21,-970.72 428.94,-974.22 476.9,-977.4"/>
|
||||||
|
<polygon fill="black" stroke="black" points="476.85,-980.9 487.06,-978.07 477.31,-973.92 476.85,-980.9"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/Merovius/nbd -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>github.com/Merovius/nbd</title>
|
||||||
|
<g id="a_node12"><a xlink:href="https://github.com/Merovius/nbd" xlink:title="github.com/Merovius/nbd">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-906" rx="129.98" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-902.3" font-family="Times,serif" font-size="14.00">github.com/Merovius/nbd</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/Merovius/nbd->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge11" class="edge">
|
||||||
|
<title>github.com/Merovius/nbd->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M314.2,-917.43C345.37,-921.61 379.81,-926.85 411.16,-933 459.39,-942.46 513.03,-956.28 554.09,-967.57"/>
|
||||||
|
<polygon fill="black" stroke="black" points="553.41,-971.01 563.98,-970.3 555.28,-964.26 553.41,-971.01"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/rtr7/router7 -->
|
||||||
|
<g id="node13" class="node">
|
||||||
|
<title>github.com/rtr7/router7</title>
|
||||||
|
<g id="a_node13"><a xlink:href="https://github.com/rtr7/router7" xlink:title="github.com/rtr7/router7">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-852" rx="122.38" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-848.3" font-family="Times,serif" font-size="14.00">github.com/rtr7/router7</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/rtr7/router7->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge12" class="edge">
|
||||||
|
<title>github.com/rtr7/router7->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M322.65,-860.2C351.82,-864.2 383.07,-870.15 411.16,-879 475.99,-899.43 544.96,-938.58 585.63,-963.7"/>
|
||||||
|
<polygon fill="black" stroke="black" points="584.02,-966.83 594.36,-969.15 587.73,-960.89 584.02,-966.83"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/u-root/u-bmc -->
|
||||||
|
<g id="node14" class="node">
|
||||||
|
<title>github.com/u-root/u-bmc</title>
|
||||||
|
<g id="a_node14"><a xlink:href="https://github.com/u-root/u-bmc" xlink:title="github.com/u-root/u-bmc">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-798" rx="124.58" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-794.3" font-family="Times,serif" font-size="14.00">github.com/u-root/u-bmc</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/u-root/u-bmc->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge13" class="edge">
|
||||||
|
<title>github.com/u-root/u-bmc->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M331.58,-803.97C358.44,-808.07 386.38,-814.63 411.16,-825 488.01,-857.15 561.22,-924.6 597.65,-961.51"/>
|
||||||
|
<polygon fill="black" stroke="black" points="595.37,-964.19 604.86,-968.9 600.38,-959.3 595.37,-964.19"/>
|
||||||
|
</g>
|
||||||
|
<!-- golang.zx2c4.com/wireguard/wgctrl -->
|
||||||
|
<g id="node15" class="node">
|
||||||
|
<title>golang.zx2c4.com/wireguard/wgctrl</title>
|
||||||
|
<g id="a_node15"><a xlink:href="https://golang.zx2c4.com/wireguard/wgctrl" xlink:title="golang.zx2c4.com/wireguard/wgctrl">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-744" rx="176.57" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-740.3" font-family="Times,serif" font-size="14.00">golang.zx2c4.com/wireguard/wgctrl</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- golang.zx2c4.com/wireguard/wgctrl->github.com/mdlayher/genetlink -->
|
||||||
|
<g id="edge14" class="edge">
|
||||||
|
<title>golang.zx2c4.com/wireguard/wgctrl->github.com/mdlayher/genetlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M363.27,-753.62C379.93,-757.83 396.25,-763.47 411.16,-771 500.09,-815.88 573.09,-913.34 604.72,-960.45"/>
|
||||||
|
<polygon fill="black" stroke="black" points="601.88,-962.49 610.32,-968.9 607.72,-958.63 601.88,-962.49"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/kobject -->
|
||||||
|
<g id="node16" class="node">
|
||||||
|
<title>github.com/mdlayher/kobject</title>
|
||||||
|
<g id="a_node16"><a xlink:href="https://github.com/mdlayher/kobject" xlink:title="github.com/mdlayher/kobject">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-661" rx="146.47" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-657.3" font-family="Times,serif" font-size="14.00">github.com/mdlayher/kobject</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/mdlayher/kobject->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge15" class="edge">
|
||||||
|
<title>github.com/mdlayher/kobject->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M749.05,-651.76C768.9,-647.13 788.63,-640.48 806.13,-631 877.87,-592.14 935.5,-511.67 962.1,-469.73"/>
|
||||||
|
<polygon fill="black" stroke="black" points="965.13,-471.49 967.45,-461.15 959.19,-467.79 965.13,-471.49"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-conntrack -->
|
||||||
|
<g id="node17" class="node">
|
||||||
|
<title>github.com/florianl/go-conntrack</title>
|
||||||
|
<g id="a_node17"><a xlink:href="https://github.com/florianl/go-conntrack" xlink:title="github.com/florianl/go-conntrack">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-524" rx="161.37" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-520.3" font-family="Times,serif" font-size="14.00">github.com/florianl/go-conntrack</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-conntrack->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge16" class="edge">
|
||||||
|
<title>github.com/florianl/go-conntrack->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M346.97,-513.88C467.01,-504.18 648.72,-488.2 806.13,-469 830.23,-466.06 856.16,-462.4 880.39,-458.77"/>
|
||||||
|
<polygon fill="black" stroke="black" points="880.93,-462.22 890.29,-457.27 879.88,-455.3 880.93,-462.22"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-nflog -->
|
||||||
|
<g id="node18" class="node">
|
||||||
|
<title>github.com/florianl/go-nflog</title>
|
||||||
|
<g id="a_node18"><a xlink:href="https://github.com/florianl/go-nflog" xlink:title="github.com/florianl/go-nflog">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-470" rx="138.38" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-466.3" font-family="Times,serif" font-size="14.00">github.com/florianl/go-nflog</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-nflog->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge17" class="edge">
|
||||||
|
<title>github.com/florianl/go-nflog->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M347.57,-465.3C481.99,-460.54 689.29,-453.21 829.15,-448.27"/>
|
||||||
|
<polygon fill="black" stroke="black" points="829.65,-451.75 839.52,-447.9 829.4,-444.76 829.65,-451.75"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-nfqueue -->
|
||||||
|
<g id="node19" class="node">
|
||||||
|
<title>github.com/florianl/go-nfqueue</title>
|
||||||
|
<g id="a_node19"><a xlink:href="https://github.com/florianl/go-nfqueue" xlink:title="github.com/florianl/go-nfqueue">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-416" rx="153.27" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-412.3" font-family="Times,serif" font-size="14.00">github.com/florianl/go-nfqueue</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-nfqueue->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge18" class="edge">
|
||||||
|
<title>github.com/florianl/go-nfqueue->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M360.89,-421.17C495.43,-425.93 693.98,-432.95 829.3,-437.74"/>
|
||||||
|
<polygon fill="black" stroke="black" points="829.22,-441.24 839.34,-438.09 829.47,-434.24 829.22,-441.24"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/google/nftables -->
|
||||||
|
<g id="node20" class="node">
|
||||||
|
<title>github.com/google/nftables</title>
|
||||||
|
<g id="a_node20"><a xlink:href="https://github.com/google/nftables" xlink:title="github.com/google/nftables">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-362" rx="137.28" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-358.3" font-family="Times,serif" font-size="14.00">github.com/google/nftables</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/google/nftables->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge19" class="edge">
|
||||||
|
<title>github.com/google/nftables->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M329.52,-371.61C448.83,-381.94 640.78,-399.5 806.13,-419 828.43,-421.63 852.3,-424.76 874.95,-427.88"/>
|
||||||
|
<polygon fill="black" stroke="black" points="874.53,-431.35 884.92,-429.26 875.49,-424.42 874.53,-431.35"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/ti-mo/netfilter -->
|
||||||
|
<g id="node21" class="node">
|
||||||
|
<title>github.com/ti-mo/netfilter</title>
|
||||||
|
<g id="a_node21"><a xlink:href="https://github.com/ti-mo/netfilter" xlink:title="github.com/ti-mo/netfilter">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-572" rx="129.98" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-568.3" font-family="Times,serif" font-size="14.00">github.com/ti-mo/netfilter</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/ti-mo/netfilter->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge20" class="edge">
|
||||||
|
<title>github.com/ti-mo/netfilter->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M703.9,-557.91C736.18,-551.12 773.43,-541.84 806.13,-530 854.07,-512.65 905.86,-485.13 939.93,-465.69"/>
|
||||||
|
<polygon fill="black" stroke="black" points="941.7,-468.71 948.62,-460.69 938.21,-462.64 941.7,-468.71"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/ti-mo/conntrack -->
|
||||||
|
<g id="node22" class="node">
|
||||||
|
<title>github.com/ti-mo/conntrack</title>
|
||||||
|
<g id="a_node22"><a xlink:href="https://github.com/ti-mo/conntrack" xlink:title="github.com/ti-mo/conntrack">
|
||||||
|
<ellipse fill="none" stroke="black" cx="213.58" cy="-578" rx="138.38" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="213.58" y="-574.3" font-family="Times,serif" font-size="14.00">github.com/ti-mo/conntrack</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/ti-mo/conntrack->github.com/ti-mo/netfilter -->
|
||||||
|
<g id="edge21" class="edge">
|
||||||
|
<title>github.com/ti-mo/conntrack->github.com/ti-mo/netfilter</title>
|
||||||
|
<path fill="none" stroke="black" d="M351.26,-575.99C393.71,-575.36 440.52,-574.67 483.09,-574.04"/>
|
||||||
|
<polygon fill="black" stroke="black" points="483.29,-577.54 493.24,-573.89 483.19,-570.54 483.29,-577.54"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/ema/qdisc -->
|
||||||
|
<g id="node23" class="node">
|
||||||
|
<title>github.com/ema/qdisc</title>
|
||||||
|
<g id="a_node23"><a xlink:href="https://github.com/ema/qdisc" xlink:title="github.com/ema/qdisc">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-279" rx="112.38" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-275.3" font-family="Times,serif" font-size="14.00">github.com/ema/qdisc</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/ema/qdisc->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge22" class="edge">
|
||||||
|
<title>github.com/ema/qdisc->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M690.34,-293.42C725.65,-302.27 769.22,-315.16 806.13,-332 858.51,-355.89 913.46,-394 946.74,-418.77"/>
|
||||||
|
<polygon fill="black" stroke="black" points="945.03,-421.86 955.13,-425.06 949.24,-416.26 945.03,-421.86"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-tc -->
|
||||||
|
<g id="node24" class="node">
|
||||||
|
<title>github.com/florianl/go-tc</title>
|
||||||
|
<g id="a_node24"><a xlink:href="https://github.com/florianl/go-tc" xlink:title="github.com/florianl/go-tc">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-225" rx="124.28" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-221.3" font-family="Times,serif" font-size="14.00">github.com/florianl/go-tc</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/florianl/go-tc->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge23" class="edge">
|
||||||
|
<title>github.com/florianl/go-tc->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M741.38,-230.26C763.78,-234.58 786.38,-241.4 806.13,-252 879.02,-291.1 936.52,-373.74 962.68,-416.35"/>
|
||||||
|
<polygon fill="black" stroke="black" points="959.77,-418.3 967.93,-425.05 965.76,-414.68 959.77,-418.3"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/jsimonetti/rtnetlink -->
|
||||||
|
<g id="node25" class="node">
|
||||||
|
<title>github.com/jsimonetti/rtnetlink</title>
|
||||||
|
<g id="a_node25"><a xlink:href="https://github.com/jsimonetti/rtnetlink" xlink:title="github.com/jsimonetti/rtnetlink">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-171" rx="155.97" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-167.3" font-family="Times,serif" font-size="14.00">github.com/jsimonetti/rtnetlink</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/jsimonetti/rtnetlink->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge24" class="edge">
|
||||||
|
<title>github.com/jsimonetti/rtnetlink->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M761.97,-179.11C777.55,-183.56 792.64,-189.67 806.13,-198 891.51,-250.68 945.9,-363.24 967.48,-415.41"/>
|
||||||
|
<polygon fill="black" stroke="black" points="964.25,-416.75 971.25,-424.71 970.74,-414.12 964.25,-416.75"/>
|
||||||
|
</g>
|
||||||
|
<!-- gitlab.com/mergetb/tech/rtnl -->
|
||||||
|
<g id="node26" class="node">
|
||||||
|
<title>gitlab.com/mergetb/tech/rtnl</title>
|
||||||
|
<g id="a_node26"><a xlink:href="https://gitlab.com/mergetb/tech/rtnl" xlink:title="gitlab.com/mergetb/tech/rtnl">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-117" rx="144.87" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-113.3" font-family="Times,serif" font-size="14.00">gitlab.com/mergetb/tech/rtnl</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- gitlab.com/mergetb/tech/rtnl->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge25" class="edge">
|
||||||
|
<title>gitlab.com/mergetb/tech/rtnl->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M759.44,-122.98C776.06,-127.69 792.1,-134.45 806.13,-144 904.62,-211.07 953.88,-354.53 970.94,-415.1"/>
|
||||||
|
<polygon fill="black" stroke="black" points="967.56,-416.02 973.58,-424.75 974.32,-414.18 967.56,-416.02"/>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/SpComb/go-onewire -->
|
||||||
|
<g id="node27" class="node">
|
||||||
|
<title>github.com/SpComb/go-onewire</title>
|
||||||
|
<g id="a_node27"><a xlink:href="https://github.com/SpComb/go-onewire" xlink:title="github.com/SpComb/go-onewire">
|
||||||
|
<ellipse fill="none" stroke="black" cx="622.65" cy="-34" rx="159.47" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="622.65" y="-30.3" font-family="Times,serif" font-size="14.00">github.com/SpComb/go-onewire</text>
|
||||||
|
</a>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<!-- github.com/SpComb/go-onewire->github.com/mdlayher/netlink -->
|
||||||
|
<g id="edge26" class="edge">
|
||||||
|
<title>github.com/SpComb/go-onewire->github.com/mdlayher/netlink</title>
|
||||||
|
<path fill="none" stroke="black" d="M725.73,-47.78C754.17,-55.59 783.46,-67.83 806.13,-87 912.36,-176.8 958.15,-347.52 972.62,-414.77"/>
|
||||||
|
<polygon fill="black" stroke="black" points="969.21,-415.57 974.68,-424.64 976.07,-414.14 969.21,-415.57"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 26 KiB |
15
vendor/github.com/mdlayher/netlink/nlenc/doc.go
generated
vendored
Normal file
15
vendor/github.com/mdlayher/netlink/nlenc/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Package nlenc implements encoding and decoding functions for netlink
|
||||||
|
// messages and attributes.
|
||||||
|
package nlenc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/josharian/native"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NativeEndian returns the native byte order of this system.
|
||||||
|
func NativeEndian() binary.ByteOrder {
|
||||||
|
// TODO(mdlayher): consider deprecating and removing this function for v2.
|
||||||
|
return native.Endian
|
||||||
|
}
|
150
vendor/github.com/mdlayher/netlink/nlenc/int.go
generated
vendored
Normal file
150
vendor/github.com/mdlayher/netlink/nlenc/int.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
package nlenc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PutUint8 encodes a uint8 into b.
|
||||||
|
// If b is not exactly 1 byte in length, PutUint8 will panic.
|
||||||
|
func PutUint8(b []byte, v uint8) {
|
||||||
|
if l := len(b); l != 1 {
|
||||||
|
panic(fmt.Sprintf("PutUint8: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutUint16 encodes a uint16 into b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 2 bytes in length, PutUint16 will panic.
|
||||||
|
func PutUint16(b []byte, v uint16) {
|
||||||
|
if l := len(b); l != 2 {
|
||||||
|
panic(fmt.Sprintf("PutUint16: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
*(*uint16)(unsafe.Pointer(&b[0])) = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutUint32 encodes a uint32 into b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 4 bytes in length, PutUint32 will panic.
|
||||||
|
func PutUint32(b []byte, v uint32) {
|
||||||
|
if l := len(b); l != 4 {
|
||||||
|
panic(fmt.Sprintf("PutUint32: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
*(*uint32)(unsafe.Pointer(&b[0])) = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutUint64 encodes a uint64 into b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 8 bytes in length, PutUint64 will panic.
|
||||||
|
func PutUint64(b []byte, v uint64) {
|
||||||
|
if l := len(b); l != 8 {
|
||||||
|
panic(fmt.Sprintf("PutUint64: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
*(*uint64)(unsafe.Pointer(&b[0])) = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutInt32 encodes a int32 into b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 4 bytes in length, PutInt32 will panic.
|
||||||
|
func PutInt32(b []byte, v int32) {
|
||||||
|
if l := len(b); l != 4 {
|
||||||
|
panic(fmt.Sprintf("PutInt32: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
*(*int32)(unsafe.Pointer(&b[0])) = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8 decodes a uint8 from b.
|
||||||
|
// If b is not exactly 1 byte in length, Uint8 will panic.
|
||||||
|
func Uint8(b []byte) uint8 {
|
||||||
|
if l := len(b); l != 1 {
|
||||||
|
panic(fmt.Sprintf("Uint8: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 decodes a uint16 from b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 2 bytes in length, Uint16 will panic.
|
||||||
|
func Uint16(b []byte) uint16 {
|
||||||
|
if l := len(b); l != 2 {
|
||||||
|
panic(fmt.Sprintf("Uint16: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
return *(*uint16)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 decodes a uint32 from b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 4 bytes in length, Uint32 will panic.
|
||||||
|
func Uint32(b []byte) uint32 {
|
||||||
|
if l := len(b); l != 4 {
|
||||||
|
panic(fmt.Sprintf("Uint32: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 decodes a uint64 from b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 8 bytes in length, Uint64 will panic.
|
||||||
|
func Uint64(b []byte) uint64 {
|
||||||
|
if l := len(b); l != 8 {
|
||||||
|
panic(fmt.Sprintf("Uint64: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
return *(*uint64)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 decodes an int32 from b using the host machine's native endianness.
|
||||||
|
// If b is not exactly 4 bytes in length, Int32 will panic.
|
||||||
|
func Int32(b []byte) int32 {
|
||||||
|
if l := len(b); l != 4 {
|
||||||
|
panic(fmt.Sprintf("Int32: unexpected byte slice length: %d", l))
|
||||||
|
}
|
||||||
|
|
||||||
|
return *(*int32)(unsafe.Pointer(&b[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint8Bytes encodes a uint8 into a newly-allocated byte slice. It is a
|
||||||
|
// shortcut for allocating a new byte slice and filling it using PutUint8.
|
||||||
|
func Uint8Bytes(v uint8) []byte {
|
||||||
|
b := make([]byte, 1)
|
||||||
|
PutUint8(b, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16Bytes encodes a uint16 into a newly-allocated byte slice using the
|
||||||
|
// host machine's native endianness. It is a shortcut for allocating a new
|
||||||
|
// byte slice and filling it using PutUint16.
|
||||||
|
func Uint16Bytes(v uint16) []byte {
|
||||||
|
b := make([]byte, 2)
|
||||||
|
PutUint16(b, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32Bytes encodes a uint32 into a newly-allocated byte slice using the
|
||||||
|
// host machine's native endianness. It is a shortcut for allocating a new
|
||||||
|
// byte slice and filling it using PutUint32.
|
||||||
|
func Uint32Bytes(v uint32) []byte {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
PutUint32(b, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64Bytes encodes a uint64 into a newly-allocated byte slice using the
|
||||||
|
// host machine's native endianness. It is a shortcut for allocating a new
|
||||||
|
// byte slice and filling it using PutUint64.
|
||||||
|
func Uint64Bytes(v uint64) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
PutUint64(b, v)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32Bytes encodes a int32 into a newly-allocated byte slice using the
|
||||||
|
// host machine's native endianness. It is a shortcut for allocating a new
|
||||||
|
// byte slice and filling it using PutInt32.
|
||||||
|
func Int32Bytes(v int32) []byte {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
PutInt32(b, v)
|
||||||
|
return b
|
||||||
|
}
|
18
vendor/github.com/mdlayher/netlink/nlenc/string.go
generated
vendored
Normal file
18
vendor/github.com/mdlayher/netlink/nlenc/string.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package nlenc
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
// Bytes returns a null-terminated byte slice with the contents of s.
|
||||||
|
func Bytes(s string) []byte {
|
||||||
|
return append([]byte(s), 0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string with the contents of b from a null-terminated
|
||||||
|
// byte slice.
|
||||||
|
func String(b []byte) string {
|
||||||
|
// If the string has more than one NULL terminator byte, we want to remove
|
||||||
|
// all of them before returning the string to the caller; hence the use of
|
||||||
|
// strings.TrimRight instead of strings.TrimSuffix (which previously only
|
||||||
|
// removed a single NULL).
|
||||||
|
return string(bytes.TrimRight(b, "\x00"))
|
||||||
|
}
|
12
vendor/github.com/mdlayher/socket/CHANGELOG.md
generated
vendored
Normal file
12
vendor/github.com/mdlayher/socket/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v0.1.1
|
||||||
|
|
||||||
|
- [New API]: `socket.Conn` now has `CloseRead`, `CloseWrite`, and `Shutdown`
|
||||||
|
methods.
|
||||||
|
- [Improvement]: internal rework to more robustly handle various errors.
|
||||||
|
|
||||||
|
## v0.1.0
|
||||||
|
|
||||||
|
- Initial unstable release. Most functionality has been developed and ported
|
||||||
|
from package [`netlink`](https://github.com/mdlayher/netlink).
|
9
vendor/github.com/mdlayher/socket/LICENSE.md
generated
vendored
Normal file
9
vendor/github.com/mdlayher/socket/LICENSE.md
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# MIT License
|
||||||
|
|
||||||
|
Copyright (C) 2021 Matt Layher
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
14
vendor/github.com/mdlayher/socket/README.md
generated
vendored
Normal file
14
vendor/github.com/mdlayher/socket/README.md
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# socket [![Test Status](https://github.com/mdlayher/socket/workflows/Test/badge.svg)](https://github.com/mdlayher/socket/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/socket.svg)](https://pkg.go.dev/github.com/mdlayher/socket) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/socket)](https://goreportcard.com/report/github.com/mdlayher/socket)
|
||||||
|
|
||||||
|
Package `socket` provides a low-level network connection type which integrates
|
||||||
|
with Go's runtime network poller to provide asynchronous I/O and deadline
|
||||||
|
support. MIT Licensed.
|
||||||
|
|
||||||
|
This package focuses on UNIX-like operating systems which make use of BSD
|
||||||
|
sockets system call APIs. It is meant to be used as a foundation for the
|
||||||
|
creation of operating system-specific socket packages, for socket families such
|
||||||
|
as Linux's `AF_NETLINK`, `AF_PACKET`, or `AF_VSOCK`. This package should not be
|
||||||
|
used directly in end user applications.
|
||||||
|
|
||||||
|
Any use of package socket should be guarded by build tags, as one would also
|
||||||
|
use when importing the `syscall` or `golang.org/x/sys` packages.
|
23
vendor/github.com/mdlayher/socket/accept.go
generated
vendored
Normal file
23
vendor/github.com/mdlayher/socket/accept.go
generated
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//go:build !dragonfly && !freebsd && !illumos && !linux
|
||||||
|
// +build !dragonfly,!freebsd,!illumos,!linux
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sysAccept = "accept"
|
||||||
|
|
||||||
|
// accept wraps accept(2).
|
||||||
|
func accept(fd, flags int) (int, unix.Sockaddr, error) {
|
||||||
|
if flags != 0 {
|
||||||
|
// These operating systems have no support for flags to accept(2).
|
||||||
|
return 0, nil, fmt.Errorf("socket: Conn.Accept flags are ineffective on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
return unix.Accept(fd)
|
||||||
|
}
|
15
vendor/github.com/mdlayher/socket/accept4.go
generated
vendored
Normal file
15
vendor/github.com/mdlayher/socket/accept4.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//go:build dragonfly || freebsd || illumos || linux
|
||||||
|
// +build dragonfly freebsd illumos linux
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sysAccept = "accept4"
|
||||||
|
|
||||||
|
// accept wraps accept4(2).
|
||||||
|
func accept(fd, flags int) (int, unix.Sockaddr, error) {
|
||||||
|
return unix.Accept4(fd, flags)
|
||||||
|
}
|
567
vendor/github.com/mdlayher/socket/conn.go
generated
vendored
Normal file
567
vendor/github.com/mdlayher/socket/conn.go
generated
vendored
Normal file
|
@ -0,0 +1,567 @@
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Conn is a low-level network connection which integrates with Go's runtime
|
||||||
|
// network poller to provide asynchronous I/O and deadline support.
|
||||||
|
type Conn struct {
|
||||||
|
// Indicates whether or not Conn.Close has been called. Must be accessed
|
||||||
|
// atomically. Atomics definitions must come first in the Conn struct.
|
||||||
|
closed uint32
|
||||||
|
|
||||||
|
// A unique name for the Conn which is also associated with derived file
|
||||||
|
// descriptors such as those created by accept(2).
|
||||||
|
name string
|
||||||
|
|
||||||
|
// Provides access to the underlying file registered with the runtime
|
||||||
|
// network poller, and arbitrary raw I/O calls.
|
||||||
|
fd *os.File
|
||||||
|
rc syscall.RawConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Config contains options for a Conn.
|
||||||
|
type Config struct {
|
||||||
|
// NetNS specifies the Linux network namespace the Conn will operate in.
|
||||||
|
// This option is unsupported on other operating systems.
|
||||||
|
//
|
||||||
|
// If set (non-zero), Conn will enter the specified network namespace and an
|
||||||
|
// error will occur in Socket if the operation fails.
|
||||||
|
//
|
||||||
|
// If not set (zero), a best-effort attempt will be made to enter the
|
||||||
|
// network namespace of the calling thread: this means that any changes made
|
||||||
|
// to the calling thread's network namespace will also be reflected in Conn.
|
||||||
|
// If this operation fails (due to lack of permissions or because network
|
||||||
|
// namespaces are disabled by kernel configuration), Socket will not return
|
||||||
|
// an error, and the Conn will operate in the default network namespace of
|
||||||
|
// the process. This enables non-privileged use of Conn in applications
|
||||||
|
// which do not require elevated privileges.
|
||||||
|
//
|
||||||
|
// Entering a network namespace is a privileged operation (root or
|
||||||
|
// CAP_SYS_ADMIN are required), and most applications should leave this set
|
||||||
|
// to 0.
|
||||||
|
NetNS int
|
||||||
|
}
|
||||||
|
|
||||||
|
// High-level methods which provide convenience over raw system calls.
|
||||||
|
|
||||||
|
// Close closes the underlying file descriptor for the Conn, which also causes
|
||||||
|
// all in-flight I/O operations to immediately unblock and return errors. Any
|
||||||
|
// subsequent uses of Conn will result in EBADF.
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
// The caller has expressed an intent to close the socket, so immediately
|
||||||
|
// increment s.closed to force further calls to result in EBADF before also
|
||||||
|
// closing the file descriptor to unblock any outstanding operations.
|
||||||
|
//
|
||||||
|
// Because other operations simply check for s.closed != 0, we will permit
|
||||||
|
// double Close, which would increment s.closed beyond 1.
|
||||||
|
if atomic.AddUint32(&c.closed, 1) != 1 {
|
||||||
|
// Multiple Close calls.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError("close", c.fd.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseRead shuts down the reading side of the Conn. Most callers should just
|
||||||
|
// use Close.
|
||||||
|
func (c *Conn) CloseRead() error { return c.Shutdown(unix.SHUT_RD) }
|
||||||
|
|
||||||
|
// CloseWrite shuts down the writing side of the Conn. Most callers should just
|
||||||
|
// use Close.
|
||||||
|
func (c *Conn) CloseWrite() error { return c.Shutdown(unix.SHUT_WR) }
|
||||||
|
|
||||||
|
// Read implements io.Reader by reading directly from the underlying file
|
||||||
|
// descriptor.
|
||||||
|
func (c *Conn) Read(b []byte) (int, error) { return c.fd.Read(b) }
|
||||||
|
|
||||||
|
// Write implements io.Writer by writing directly to the underlying file
|
||||||
|
// descriptor.
|
||||||
|
func (c *Conn) Write(b []byte) (int, error) { return c.fd.Write(b) }
|
||||||
|
|
||||||
|
// SetDeadline sets both the read and write deadlines associated with the Conn.
|
||||||
|
func (c *Conn) SetDeadline(t time.Time) error { return c.fd.SetDeadline(t) }
|
||||||
|
|
||||||
|
// SetReadDeadline sets the read deadline associated with the Conn.
|
||||||
|
func (c *Conn) SetReadDeadline(t time.Time) error { return c.fd.SetReadDeadline(t) }
|
||||||
|
|
||||||
|
// SetWriteDeadline sets the write deadline associated with the Conn.
|
||||||
|
func (c *Conn) SetWriteDeadline(t time.Time) error { return c.fd.SetWriteDeadline(t) }
|
||||||
|
|
||||||
|
// ReadBuffer gets the size of the operating system's receive buffer associated
|
||||||
|
// with the Conn.
|
||||||
|
func (c *Conn) ReadBuffer() (int, error) {
|
||||||
|
return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBuffer gets the size of the operating system's transmit buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
func (c *Conn) WriteBuffer() (int, error) {
|
||||||
|
return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadBuffer sets the size of the operating system's receive buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
//
|
||||||
|
// When called with elevated privileges on Linux, the SO_RCVBUFFORCE option will
|
||||||
|
// be used to override operating system limits. Otherwise SO_RCVBUF is used
|
||||||
|
// (which obeys operating system limits).
|
||||||
|
func (c *Conn) SetReadBuffer(bytes int) error { return c.setReadBuffer(bytes) }
|
||||||
|
|
||||||
|
// SetWriteBuffer sets the size of the operating system's transmit buffer
|
||||||
|
// associated with the Conn.
|
||||||
|
//
|
||||||
|
// When called with elevated privileges on Linux, the SO_SNDBUFFORCE option will
|
||||||
|
// be used to override operating system limits. Otherwise SO_SNDBUF is used
|
||||||
|
// (which obeys operating system limits).
|
||||||
|
func (c *Conn) SetWriteBuffer(bytes int) error { return c.setWriteBuffer(bytes) }
|
||||||
|
|
||||||
|
// SyscallConn returns a raw network connection. This implements the
|
||||||
|
// syscall.Conn interface.
|
||||||
|
//
|
||||||
|
// SyscallConn is intended for advanced use cases, such as getting and setting
|
||||||
|
// arbitrary socket options using the socket's file descriptor. If possible,
|
||||||
|
// those operations should be performed using methods on Conn instead.
|
||||||
|
//
|
||||||
|
// Once invoked, it is the caller's responsibility to ensure that operations
|
||||||
|
// performed using Conn and the syscall.RawConn do not conflict with each other.
|
||||||
|
func (c *Conn) SyscallConn() (syscall.RawConn, error) {
|
||||||
|
if atomic.LoadUint32(&c.closed) != 0 {
|
||||||
|
return nil, os.NewSyscallError("syscallconn", unix.EBADF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdlayher): mutex or similar to enforce syscall.RawConn contract of
|
||||||
|
// FD remaining valid for duration of calls?
|
||||||
|
return c.rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Socket wraps the socket(2) system call to produce a Conn. domain, typ, and
|
||||||
|
// proto are passed directly to socket(2), and name should be a unique name for
|
||||||
|
// the socket type such as "netlink" or "vsock".
|
||||||
|
//
|
||||||
|
// The cfg parameter specifies optional configuration for the Conn. If nil, no
|
||||||
|
// additional configuration will be applied.
|
||||||
|
//
|
||||||
|
// If the operating system supports SOCK_CLOEXEC and SOCK_NONBLOCK, they are
|
||||||
|
// automatically applied to typ to mirror the standard library's socket flag
|
||||||
|
// behaviors.
|
||||||
|
func Socket(domain, typ, proto int, name string, cfg *Config) (*Conn, error) {
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = &Config{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.NetNS == 0 {
|
||||||
|
// Non-Linux or no network namespace.
|
||||||
|
return socket(domain, typ, proto, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linux only: create Conn in the specified network namespace.
|
||||||
|
return withNetNS(cfg.NetNS, func() (*Conn, error) {
|
||||||
|
return socket(domain, typ, proto, name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// socket is the internal, cross-platform entry point for socket(2).
|
||||||
|
func socket(domain, typ, proto int, name string) (*Conn, error) {
|
||||||
|
var (
|
||||||
|
fd int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
fd, err = unix.Socket(domain, typ|socketFlags, proto)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
// Some OSes already set CLOEXEC with typ.
|
||||||
|
if !flagCLOEXEC {
|
||||||
|
unix.CloseOnExec(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No error, prepare the Conn.
|
||||||
|
return newConn(fd, name)
|
||||||
|
case !ready(err):
|
||||||
|
// System call interrupted or not ready, try again.
|
||||||
|
continue
|
||||||
|
case err == unix.EINVAL, err == unix.EPROTONOSUPPORT:
|
||||||
|
// On Linux, SOCK_NONBLOCK and SOCK_CLOEXEC were introduced in
|
||||||
|
// 2.6.27. On FreeBSD, both flags were introduced in FreeBSD 10.
|
||||||
|
// EINVAL and EPROTONOSUPPORT check for earlier versions of these
|
||||||
|
// OSes respectively.
|
||||||
|
//
|
||||||
|
// Mirror what the standard library does when creating file
|
||||||
|
// descriptors: avoid racing a fork/exec with the creation of new
|
||||||
|
// file descriptors, so that child processes do not inherit socket
|
||||||
|
// file descriptors unexpectedly.
|
||||||
|
//
|
||||||
|
// For a more thorough explanation, see similar work in the Go tree:
|
||||||
|
// func sysSocket in net/sock_cloexec.go, as well as the detailed
|
||||||
|
// comment in syscall/exec_unix.go.
|
||||||
|
syscall.ForkLock.RLock()
|
||||||
|
fd, err = unix.Socket(domain, typ, proto)
|
||||||
|
if err != nil {
|
||||||
|
syscall.ForkLock.RUnlock()
|
||||||
|
return nil, os.NewSyscallError("socket", err)
|
||||||
|
}
|
||||||
|
unix.CloseOnExec(fd)
|
||||||
|
syscall.ForkLock.RUnlock()
|
||||||
|
|
||||||
|
return newConn(fd, name)
|
||||||
|
default:
|
||||||
|
// Unhandled error.
|
||||||
|
return nil, os.NewSyscallError("socket", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdlayher): consider exporting newConn as New?
|
||||||
|
|
||||||
|
// newConn wraps an existing file descriptor to create a Conn. name should be a
|
||||||
|
// unique name for the socket type such as "netlink" or "vsock".
|
||||||
|
func newConn(fd int, name string) (*Conn, error) {
|
||||||
|
// All Conn I/O is nonblocking for integration with Go's runtime network
|
||||||
|
// poller. Depending on the OS this might already be set but it can't hurt
|
||||||
|
// to set it again.
|
||||||
|
if err := unix.SetNonblock(fd, true); err != nil {
|
||||||
|
return nil, os.NewSyscallError("setnonblock", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// os.NewFile registers the non-blocking file descriptor with the runtime
|
||||||
|
// poller, which is then used for most subsequent operations except those
|
||||||
|
// that require raw I/O via SyscallConn.
|
||||||
|
//
|
||||||
|
// See also: https://golang.org/pkg/os/#NewFile
|
||||||
|
f := os.NewFile(uintptr(fd), name)
|
||||||
|
rc, err := f.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Conn{
|
||||||
|
name: name,
|
||||||
|
fd: f,
|
||||||
|
rc: rc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Low-level methods which provide raw system call access.
|
||||||
|
|
||||||
|
// Accept wraps accept(2) or accept4(2) depending on the operating system, but
|
||||||
|
// returns a Conn for the accepted connection rather than a raw file descriptor.
|
||||||
|
//
|
||||||
|
// If the operating system supports accept4(2) (which allows flags),
|
||||||
|
// SOCK_CLOEXEC and SOCK_NONBLOCK are automatically applied to flags to mirror
|
||||||
|
// the standard library's socket flag behaviors.
|
||||||
|
//
|
||||||
|
// If the operating system only supports accept(2) (which does not allow flags)
|
||||||
|
// and flags is not zero, an error will be returned.
|
||||||
|
func (c *Conn) Accept(flags int) (*Conn, unix.Sockaddr, error) {
|
||||||
|
var (
|
||||||
|
nfd int
|
||||||
|
sa unix.Sockaddr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
doErr := c.read(sysAccept, func(fd int) error {
|
||||||
|
// Either accept(2) or accept4(2) depending on the OS.
|
||||||
|
nfd, sa, err = accept(fd, flags|socketFlags)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return nil, nil, doErr
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// sysAccept is either "accept" or "accept4" depending on the OS.
|
||||||
|
return nil, nil, os.NewSyscallError(sysAccept, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successfully accepted a connection, wrap it in a Conn for use by the
|
||||||
|
// caller.
|
||||||
|
ac, err := newConn(nfd, c.name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ac, sa, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind wraps bind(2).
|
||||||
|
func (c *Conn) Bind(sa unix.Sockaddr) error {
|
||||||
|
const op = "bind"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
err = unix.Bind(fd, sa)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect wraps connect(2).
|
||||||
|
func (c *Conn) Connect(sa unix.Sockaddr) error {
|
||||||
|
const op = "connect"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.write(op, func(fd int) error {
|
||||||
|
err = unix.Connect(fd, sa)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == unix.EISCONN {
|
||||||
|
// EISCONN is reported if the socket is already established and should
|
||||||
|
// not be treated as an error.
|
||||||
|
// - Darwin reports this for at least TCP sockets
|
||||||
|
// - Linux reports this for at least AF_VSOCK sockets
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getsockname wraps getsockname(2).
|
||||||
|
func (c *Conn) Getsockname() (unix.Sockaddr, error) {
|
||||||
|
const op = "getsockname"
|
||||||
|
|
||||||
|
var (
|
||||||
|
sa unix.Sockaddr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
sa, err = unix.Getsockname(fd)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return nil, doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return sa, os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetsockoptInt wraps getsockopt(2) for integer values.
|
||||||
|
func (c *Conn) GetsockoptInt(level, opt int) (int, error) {
|
||||||
|
const op = "getsockopt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
value int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
value, err = unix.GetsockoptInt(fd, level, opt)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return 0, doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen wraps listen(2).
|
||||||
|
func (c *Conn) Listen(n int) error {
|
||||||
|
const op = "listen"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
err = unix.Listen(fd, n)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recvmsg wraps recvmsg(2).
|
||||||
|
func (c *Conn) Recvmsg(p, oob []byte, flags int) (int, int, int, unix.Sockaddr, error) {
|
||||||
|
const op = "recvmsg"
|
||||||
|
|
||||||
|
var (
|
||||||
|
n, oobn, recvflags int
|
||||||
|
from unix.Sockaddr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
doErr := c.read(op, func(fd int) error {
|
||||||
|
n, oobn, recvflags, from, err = unix.Recvmsg(fd, p, oob, flags)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return 0, 0, 0, nil, doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, oobn, recvflags, from, os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recvfrom wraps recvfrom(2)
|
||||||
|
func (c *Conn) Recvfrom(p []byte, flags int) (int, unix.Sockaddr, error) {
|
||||||
|
const op = "recvfrom"
|
||||||
|
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
addr unix.Sockaddr
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
doErr := c.read(op, func(fd int) error {
|
||||||
|
n, addr, err = unix.Recvfrom(fd, p, flags)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return 0, nil, doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, addr, os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sendmsg wraps sendmsg(2).
|
||||||
|
func (c *Conn) Sendmsg(p, oob []byte, to unix.Sockaddr, flags int) error {
|
||||||
|
const op = "sendmsg"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.write(op, func(fd int) error {
|
||||||
|
err = unix.Sendmsg(fd, p, oob, to, flags)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sendto wraps sendto(2).
|
||||||
|
func (c *Conn) Sendto(b []byte, to unix.Sockaddr, flags int) error {
|
||||||
|
const op = "sendto"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.write(op, func(fd int) error {
|
||||||
|
err = unix.Sendto(fd, b, flags, to)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetsockoptInt wraps setsockopt(2) for integer values.
|
||||||
|
func (c *Conn) SetsockoptInt(level, opt, value int) error {
|
||||||
|
const op = "setsockopt"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
err = unix.SetsockoptInt(fd, level, opt, value)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown wraps shutdown(2).
|
||||||
|
func (c *Conn) Shutdown(how int) error {
|
||||||
|
const op = "shutdown"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
err = unix.Shutdown(fd, how)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conn low-level read/write/control functions. These functions mirror the
|
||||||
|
// syscall.RawConn APIs but the input closures return errors rather than
|
||||||
|
// booleans. Any syscalls invoked within f should return their error to allow
|
||||||
|
// the Conn to check for readiness with the runtime network poller, or to retry
|
||||||
|
// operations which may have been interrupted by EINTR or similar.
|
||||||
|
//
|
||||||
|
// Note that errors from the input closure functions are not propagated to the
|
||||||
|
// error return values of read/write/control, and the caller is still
|
||||||
|
// responsible for error handling.
|
||||||
|
|
||||||
|
// read executes f, a read function, against the associated file descriptor.
|
||||||
|
// op is used to create an *os.SyscallError if the file descriptor is closed.
|
||||||
|
func (c *Conn) read(op string, f func(fd int) error) error {
|
||||||
|
if atomic.LoadUint32(&c.closed) != 0 {
|
||||||
|
return os.NewSyscallError(op, unix.EBADF)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.rc.Read(func(fd uintptr) bool {
|
||||||
|
return ready(f(int(fd)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// write executes f, a write function, against the associated file descriptor.
|
||||||
|
// op is used to create an *os.SyscallError if the file descriptor is closed.
|
||||||
|
func (c *Conn) write(op string, f func(fd int) error) error {
|
||||||
|
if atomic.LoadUint32(&c.closed) != 0 {
|
||||||
|
return os.NewSyscallError(op, unix.EBADF)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.rc.Write(func(fd uintptr) bool {
|
||||||
|
return ready(f(int(fd)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// control executes f, a control function, against the associated file
|
||||||
|
// descriptor. op is used to create an *os.SyscallError if the file descriptor
|
||||||
|
// is closed.
|
||||||
|
func (c *Conn) control(op string, f func(fd int) error) error {
|
||||||
|
if atomic.LoadUint32(&c.closed) != 0 {
|
||||||
|
return os.NewSyscallError(op, unix.EBADF)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.rc.Control(func(fd uintptr) {
|
||||||
|
// Repeatedly attempt the syscall(s) invoked by f until completion is
|
||||||
|
// indicated by the return value of ready.
|
||||||
|
for {
|
||||||
|
if ready(f(int(fd))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ready indicates readiness based on the value of err.
|
||||||
|
func ready(err error) bool {
|
||||||
|
// When a socket is in non-blocking mode, we might see a variety of errors:
|
||||||
|
// - EAGAIN: most common case for a socket read not being ready
|
||||||
|
// - EALREADY: reported on connect after EINPROGRESS for AF_VSOCK at least
|
||||||
|
// - EINPROGRESS: reported by some sockets when first calling connect
|
||||||
|
// - EINTR: system call interrupted, more frequently occurs in Go 1.14+
|
||||||
|
// because goroutines can be asynchronously preempted
|
||||||
|
//
|
||||||
|
// Return false to let the poller wait for readiness. See the source code
|
||||||
|
// for internal/poll.FD.RawRead for more details.
|
||||||
|
switch err {
|
||||||
|
case unix.EAGAIN, unix.EALREADY, unix.EINPROGRESS, unix.EINTR:
|
||||||
|
// Not ready.
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
// Ready regardless of whether there was an error or no error.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
88
vendor/github.com/mdlayher/socket/conn_linux.go
generated
vendored
Normal file
88
vendor/github.com/mdlayher/socket/conn_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetBPF attaches an assembled BPF program to a Conn.
|
||||||
|
func (c *Conn) SetBPF(filter []bpf.RawInstruction) error {
|
||||||
|
// We can't point to the first instruction in the array if no instructions
|
||||||
|
// are present.
|
||||||
|
if len(filter) == 0 {
|
||||||
|
return os.NewSyscallError("setsockopt", unix.EINVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
prog := unix.SockFprog{
|
||||||
|
Len: uint16(len(filter)),
|
||||||
|
Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])),
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SetsockoptSockFprog(unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, &prog)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveBPF removes a BPF filter from a Conn.
|
||||||
|
func (c *Conn) RemoveBPF() error {
|
||||||
|
// 0 argument is ignored.
|
||||||
|
return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetsockoptSockFprog wraps setsockopt(2) for unix.SockFprog values.
|
||||||
|
func (c *Conn) SetsockoptSockFprog(level, opt int, fprog *unix.SockFprog) error {
|
||||||
|
const op = "setsockopt"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
err = unix.SetsockoptSockFprog(fd, level, opt, fprog)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return doErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSockoptTpacketStats wraps getsockopt(2) for getting TpacketStats
|
||||||
|
func (c *Conn) GetSockoptTpacketStats(level, name int) (*unix.TpacketStats, error) {
|
||||||
|
const op = "getsockopt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
stats *unix.TpacketStats
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
stats, err = unix.GetsockoptTpacketStats(fd, level, name)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return stats, doErr
|
||||||
|
}
|
||||||
|
return stats, os.NewSyscallError(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSockoptTpacketStatsV3 wraps getsockopt(2) for getting TpacketStatsV3
|
||||||
|
func (c *Conn) GetSockoptTpacketStatsV3(level, name int) (*unix.TpacketStatsV3, error) {
|
||||||
|
const op = "getsockopt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
stats *unix.TpacketStatsV3
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
doErr := c.control(op, func(fd int) error {
|
||||||
|
stats, err = unix.GetsockoptTpacketStatsV3(fd, level, name)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if doErr != nil {
|
||||||
|
return stats, doErr
|
||||||
|
}
|
||||||
|
return stats, os.NewSyscallError(op, err)
|
||||||
|
}
|
13
vendor/github.com/mdlayher/socket/doc.go
generated
vendored
Normal file
13
vendor/github.com/mdlayher/socket/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Package socket provides a low-level network connection type which integrates
|
||||||
|
// with Go's runtime network poller to provide asynchronous I/O and deadline
|
||||||
|
// support.
|
||||||
|
//
|
||||||
|
// This package focuses on UNIX-like operating systems which make use of BSD
|
||||||
|
// sockets system call APIs. It is meant to be used as a foundation for the
|
||||||
|
// creation of operating system-specific socket packages, for socket families
|
||||||
|
// such as Linux's AF_NETLINK, AF_PACKET, or AF_VSOCK. This package should not
|
||||||
|
// be used directly in end user applications.
|
||||||
|
//
|
||||||
|
// Any use of package socket should be guarded by build tags, as one would also
|
||||||
|
// use when importing the syscall or golang.org/x/sys packages.
|
||||||
|
package socket
|
150
vendor/github.com/mdlayher/socket/netns_linux.go
generated
vendored
Normal file
150
vendor/github.com/mdlayher/socket/netns_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errNetNSDisabled is returned when network namespaces are unavailable on
|
||||||
|
// a given system.
|
||||||
|
var errNetNSDisabled = errors.New("socket: Linux network namespaces are not enabled on this system")
|
||||||
|
|
||||||
|
// withNetNS invokes fn within the context of the network namespace specified by
|
||||||
|
// fd, while also managing the logic required to safely do so by manipulating
|
||||||
|
// thread-local state.
|
||||||
|
func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) {
|
||||||
|
var (
|
||||||
|
eg errgroup.Group
|
||||||
|
conn *Conn
|
||||||
|
)
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
// Retrieve and store the calling OS thread's network namespace so the
|
||||||
|
// thread can be reassigned to it after creating a socket in another network
|
||||||
|
// namespace.
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
ns, err := threadNetNS()
|
||||||
|
if err != nil {
|
||||||
|
// No thread-local manipulation, unlock.
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ns.Close()
|
||||||
|
|
||||||
|
// Beyond this point, the thread's network namespace is poisoned. Do not
|
||||||
|
// unlock the OS thread until all network namespace manipulation completes
|
||||||
|
// to avoid returning to the caller with altered thread-local state.
|
||||||
|
|
||||||
|
// Assign the current OS thread the goroutine is locked to to the given
|
||||||
|
// network namespace.
|
||||||
|
if err := ns.Set(fd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt Conn creation and unconditionally restore the original namespace.
|
||||||
|
c, err := fn()
|
||||||
|
if nerr := ns.Restore(); nerr != nil {
|
||||||
|
// Failed to restore original namespace. Return an error and allow the
|
||||||
|
// runtime to terminate the thread.
|
||||||
|
if err == nil {
|
||||||
|
_ = c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nerr
|
||||||
|
}
|
||||||
|
|
||||||
|
// No more thread-local state manipulation; return the new Conn.
|
||||||
|
runtime.UnlockOSThread()
|
||||||
|
conn = c
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A netNS is a handle that can manipulate network namespaces.
|
||||||
|
//
|
||||||
|
// Operations performed on a netNS must use runtime.LockOSThread before
|
||||||
|
// manipulating any network namespaces.
|
||||||
|
type netNS struct {
|
||||||
|
// The handle to a network namespace.
|
||||||
|
f *os.File
|
||||||
|
|
||||||
|
// Indicates if network namespaces are disabled on this system, and thus
|
||||||
|
// operations should become a no-op or return errors.
|
||||||
|
disabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// threadNetNS constructs a netNS using the network namespace of the calling
|
||||||
|
// thread. If the namespace is not the default namespace, runtime.LockOSThread
|
||||||
|
// should be invoked first.
|
||||||
|
func threadNetNS() (*netNS, error) {
|
||||||
|
return fileNetNS(fmt.Sprintf("/proc/self/task/%d/ns/net", unix.Gettid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileNetNS opens file and creates a netNS. fileNetNS should only be called
|
||||||
|
// directly in tests.
|
||||||
|
func fileNetNS(file string) (*netNS, error) {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return &netNS{f: f}, nil
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
// Network namespaces are not enabled on this system. Use this signal
|
||||||
|
// to return errors elsewhere if the caller explicitly asks for a
|
||||||
|
// network namespace to be set.
|
||||||
|
return &netNS{disabled: true}, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases the handle to a network namespace.
|
||||||
|
func (n *netNS) Close() error {
|
||||||
|
return n.do(func() error { return n.f.Close() })
|
||||||
|
}
|
||||||
|
|
||||||
|
// FD returns a file descriptor which represents the network namespace.
|
||||||
|
func (n *netNS) FD() int {
|
||||||
|
if n.disabled {
|
||||||
|
// No reasonable file descriptor value in this case, so specify a
|
||||||
|
// non-existent one.
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(n.f.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the original network namespace for the calling thread.
|
||||||
|
func (n *netNS) Restore() error {
|
||||||
|
return n.do(func() error { return n.Set(n.FD()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a new network namespace for the current thread using fd.
|
||||||
|
func (n *netNS) Set(fd int) error {
|
||||||
|
return n.do(func() error {
|
||||||
|
return os.NewSyscallError("setns", unix.Setns(fd, unix.CLONE_NEWNET))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// do runs fn if network namespaces are enabled on this system.
|
||||||
|
func (n *netNS) do(fn func() error) error {
|
||||||
|
if n.disabled {
|
||||||
|
return errNetNSDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn()
|
||||||
|
}
|
14
vendor/github.com/mdlayher/socket/netns_others.go
generated
vendored
Normal file
14
vendor/github.com/mdlayher/socket/netns_others.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// withNetNS returns an error on non-Linux systems.
|
||||||
|
func withNetNS(_ int, _ func() (*Conn, error)) (*Conn, error) {
|
||||||
|
return nil, fmt.Errorf("socket: Linux network namespace support is not available on %s", runtime.GOOS)
|
||||||
|
}
|
24
vendor/github.com/mdlayher/socket/setbuffer_linux.go
generated
vendored
Normal file
24
vendor/github.com/mdlayher/socket/setbuffer_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// setReadBuffer wraps the SO_RCVBUF{,FORCE} setsockopt(2) options.
|
||||||
|
func (c *Conn) setReadBuffer(bytes int) error {
|
||||||
|
err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes)
|
||||||
|
if err != nil {
|
||||||
|
err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setWriteBuffer wraps the SO_SNDBUF{,FORCE} setsockopt(2) options.
|
||||||
|
func (c *Conn) setWriteBuffer(bytes int) error {
|
||||||
|
err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes)
|
||||||
|
if err != nil {
|
||||||
|
err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
16
vendor/github.com/mdlayher/socket/setbuffer_others.go
generated
vendored
Normal file
16
vendor/github.com/mdlayher/socket/setbuffer_others.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !linux
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// setReadBuffer wraps the SO_RCVBUF setsockopt(2) option.
|
||||||
|
func (c *Conn) setReadBuffer(bytes int) error {
|
||||||
|
return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setWriteBuffer wraps the SO_SNDBUF setsockopt(2) option.
|
||||||
|
func (c *Conn) setWriteBuffer(bytes int) error {
|
||||||
|
return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes)
|
||||||
|
}
|
12
vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go
generated
vendored
Normal file
12
vendor/github.com/mdlayher/socket/typ_cloexec_nonblock.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build !darwin
|
||||||
|
// +build !darwin
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// These operating systems support CLOEXEC and NONBLOCK socket options.
|
||||||
|
flagCLOEXEC = true
|
||||||
|
socketFlags = unix.SOCK_CLOEXEC | unix.SOCK_NONBLOCK
|
||||||
|
)
|
11
vendor/github.com/mdlayher/socket/typ_none.go
generated
vendored
Normal file
11
vendor/github.com/mdlayher/socket/typ_none.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build darwin
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
const (
|
||||||
|
// These operating systems do not support CLOEXEC and NONBLOCK socket
|
||||||
|
// options.
|
||||||
|
flagCLOEXEC = false
|
||||||
|
socketFlags = 0
|
||||||
|
)
|
27
vendor/github.com/pmezard/go-difflib/LICENSE
generated
vendored
Normal file
27
vendor/github.com/pmezard/go-difflib/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2013, Patrick Mezard
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
The names of its contributors may not be used to endorse or promote
|
||||||
|
products derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||||
|
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
772
vendor/github.com/pmezard/go-difflib/difflib/difflib.go
generated
vendored
Normal file
772
vendor/github.com/pmezard/go-difflib/difflib/difflib.go
generated
vendored
Normal file
|
@ -0,0 +1,772 @@
|
||||||
|
// Package difflib is a partial port of Python difflib module.
|
||||||
|
//
|
||||||
|
// It provides tools to compare sequences of strings and generate textual diffs.
|
||||||
|
//
|
||||||
|
// The following class and functions have been ported:
|
||||||
|
//
|
||||||
|
// - SequenceMatcher
|
||||||
|
//
|
||||||
|
// - unified_diff
|
||||||
|
//
|
||||||
|
// - context_diff
|
||||||
|
//
|
||||||
|
// Getting unified diffs was the main goal of the port. Keep in mind this code
|
||||||
|
// is mostly suitable to output text differences in a human friendly way, there
|
||||||
|
// are no guarantees generated diffs are consumable by patch(1).
|
||||||
|
package difflib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateRatio(matches, length int) float64 {
|
||||||
|
if length > 0 {
|
||||||
|
return 2.0 * float64(matches) / float64(length)
|
||||||
|
}
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
type Match struct {
|
||||||
|
A int
|
||||||
|
B int
|
||||||
|
Size int
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpCode struct {
|
||||||
|
Tag byte
|
||||||
|
I1 int
|
||||||
|
I2 int
|
||||||
|
J1 int
|
||||||
|
J2 int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SequenceMatcher compares sequence of strings. The basic
|
||||||
|
// algorithm predates, and is a little fancier than, an algorithm
|
||||||
|
// published in the late 1980's by Ratcliff and Obershelp under the
|
||||||
|
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
||||||
|
// the longest contiguous matching subsequence that contains no "junk"
|
||||||
|
// elements (R-O doesn't address junk). The same idea is then applied
|
||||||
|
// recursively to the pieces of the sequences to the left and to the right
|
||||||
|
// of the matching subsequence. This does not yield minimal edit
|
||||||
|
// sequences, but does tend to yield matches that "look right" to people.
|
||||||
|
//
|
||||||
|
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
||||||
|
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
||||||
|
// longest *contiguous* & junk-free matching subsequence. That's what
|
||||||
|
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
||||||
|
// notion, pairing up elements that appear uniquely in each sequence.
|
||||||
|
// That, and the method here, appear to yield more intuitive difference
|
||||||
|
// reports than does diff. This method appears to be the least vulnerable
|
||||||
|
// to synching up on blocks of "junk lines", though (like blank lines in
|
||||||
|
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
||||||
|
// because this is the only method of the 3 that has a *concept* of
|
||||||
|
// "junk" <wink>.
|
||||||
|
//
|
||||||
|
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
||||||
|
// case. SequenceMatcher is quadratic time for the worst case and has
|
||||||
|
// expected-case behavior dependent in a complicated way on how many
|
||||||
|
// elements the sequences have in common; best case time is linear.
|
||||||
|
type SequenceMatcher struct {
|
||||||
|
a []string
|
||||||
|
b []string
|
||||||
|
b2j map[string][]int
|
||||||
|
IsJunk func(string) bool
|
||||||
|
autoJunk bool
|
||||||
|
bJunk map[string]struct{}
|
||||||
|
matchingBlocks []Match
|
||||||
|
fullBCount map[string]int
|
||||||
|
bPopular map[string]struct{}
|
||||||
|
opCodes []OpCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMatcher(a, b []string) *SequenceMatcher {
|
||||||
|
m := SequenceMatcher{autoJunk: true}
|
||||||
|
m.SetSeqs(a, b)
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
||||||
|
isJunk func(string) bool) *SequenceMatcher {
|
||||||
|
|
||||||
|
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
||||||
|
m.SetSeqs(a, b)
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set two sequences to be compared.
|
||||||
|
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
||||||
|
m.SetSeq1(a)
|
||||||
|
m.SetSeq2(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the first sequence to be compared. The second sequence to be compared is
|
||||||
|
// not changed.
|
||||||
|
//
|
||||||
|
// SequenceMatcher computes and caches detailed information about the second
|
||||||
|
// sequence, so if you want to compare one sequence S against many sequences,
|
||||||
|
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
||||||
|
// sequences.
|
||||||
|
//
|
||||||
|
// See also SetSeqs() and SetSeq2().
|
||||||
|
func (m *SequenceMatcher) SetSeq1(a []string) {
|
||||||
|
if &a == &m.a {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.a = a
|
||||||
|
m.matchingBlocks = nil
|
||||||
|
m.opCodes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the second sequence to be compared. The first sequence to be compared is
|
||||||
|
// not changed.
|
||||||
|
func (m *SequenceMatcher) SetSeq2(b []string) {
|
||||||
|
if &b == &m.b {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.b = b
|
||||||
|
m.matchingBlocks = nil
|
||||||
|
m.opCodes = nil
|
||||||
|
m.fullBCount = nil
|
||||||
|
m.chainB()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SequenceMatcher) chainB() {
|
||||||
|
// Populate line -> index mapping
|
||||||
|
b2j := map[string][]int{}
|
||||||
|
for i, s := range m.b {
|
||||||
|
indices := b2j[s]
|
||||||
|
indices = append(indices, i)
|
||||||
|
b2j[s] = indices
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge junk elements
|
||||||
|
m.bJunk = map[string]struct{}{}
|
||||||
|
if m.IsJunk != nil {
|
||||||
|
junk := m.bJunk
|
||||||
|
for s, _ := range b2j {
|
||||||
|
if m.IsJunk(s) {
|
||||||
|
junk[s] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s, _ := range junk {
|
||||||
|
delete(b2j, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge remaining popular elements
|
||||||
|
popular := map[string]struct{}{}
|
||||||
|
n := len(m.b)
|
||||||
|
if m.autoJunk && n >= 200 {
|
||||||
|
ntest := n/100 + 1
|
||||||
|
for s, indices := range b2j {
|
||||||
|
if len(indices) > ntest {
|
||||||
|
popular[s] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for s, _ := range popular {
|
||||||
|
delete(b2j, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.bPopular = popular
|
||||||
|
m.b2j = b2j
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SequenceMatcher) isBJunk(s string) bool {
|
||||||
|
_, ok := m.bJunk[s]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||||
|
//
|
||||||
|
// If IsJunk is not defined:
|
||||||
|
//
|
||||||
|
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||||
|
// alo <= i <= i+k <= ahi
|
||||||
|
// blo <= j <= j+k <= bhi
|
||||||
|
// and for all (i',j',k') meeting those conditions,
|
||||||
|
// k >= k'
|
||||||
|
// i <= i'
|
||||||
|
// and if i == i', j <= j'
|
||||||
|
//
|
||||||
|
// In other words, of all maximal matching blocks, return one that
|
||||||
|
// starts earliest in a, and of all those maximal matching blocks that
|
||||||
|
// start earliest in a, return the one that starts earliest in b.
|
||||||
|
//
|
||||||
|
// If IsJunk is defined, first the longest matching block is
|
||||||
|
// determined as above, but with the additional restriction that no
|
||||||
|
// junk element appears in the block. Then that block is extended as
|
||||||
|
// far as possible by matching (only) junk elements on both sides. So
|
||||||
|
// the resulting block never matches on junk except as identical junk
|
||||||
|
// happens to be adjacent to an "interesting" match.
|
||||||
|
//
|
||||||
|
// If no blocks match, return (alo, blo, 0).
|
||||||
|
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
||||||
|
// CAUTION: stripping common prefix or suffix would be incorrect.
|
||||||
|
// E.g.,
|
||||||
|
// ab
|
||||||
|
// acab
|
||||||
|
// Longest matching block is "ab", but if common prefix is
|
||||||
|
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
||||||
|
// strip, so ends up claiming that ab is changed to acab by
|
||||||
|
// inserting "ca" in the middle. That's minimal but unintuitive:
|
||||||
|
// "it's obvious" that someone inserted "ac" at the front.
|
||||||
|
// Windiff ends up at the same place as diff, but by pairing up
|
||||||
|
// the unique 'b's and then matching the first two 'a's.
|
||||||
|
besti, bestj, bestsize := alo, blo, 0
|
||||||
|
|
||||||
|
// find longest junk-free match
|
||||||
|
// during an iteration of the loop, j2len[j] = length of longest
|
||||||
|
// junk-free match ending with a[i-1] and b[j]
|
||||||
|
j2len := map[int]int{}
|
||||||
|
for i := alo; i != ahi; i++ {
|
||||||
|
// look at all instances of a[i] in b; note that because
|
||||||
|
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
||||||
|
newj2len := map[int]int{}
|
||||||
|
for _, j := range m.b2j[m.a[i]] {
|
||||||
|
// a[i] matches b[j]
|
||||||
|
if j < blo {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if j >= bhi {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
k := j2len[j-1] + 1
|
||||||
|
newj2len[j] = k
|
||||||
|
if k > bestsize {
|
||||||
|
besti, bestj, bestsize = i-k+1, j-k+1, k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j2len = newj2len
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend the best by non-junk elements on each end. In particular,
|
||||||
|
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
||||||
|
// the inner loop above, but also means "the best" match so far
|
||||||
|
// doesn't contain any junk *or* popular non-junk elements.
|
||||||
|
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
||||||
|
m.a[besti-1] == m.b[bestj-1] {
|
||||||
|
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||||
|
}
|
||||||
|
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||||
|
!m.isBJunk(m.b[bestj+bestsize]) &&
|
||||||
|
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||||
|
bestsize += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have a wholly interesting match (albeit possibly
|
||||||
|
// empty!), we may as well suck up the matching junk on each
|
||||||
|
// side of it too. Can't think of a good reason not to, and it
|
||||||
|
// saves post-processing the (possibly considerable) expense of
|
||||||
|
// figuring out what to do with it. In the case of an empty
|
||||||
|
// interesting match, this is clearly the right thing to do,
|
||||||
|
// because no other kind of match is possible in the regions.
|
||||||
|
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
||||||
|
m.a[besti-1] == m.b[bestj-1] {
|
||||||
|
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||||
|
}
|
||||||
|
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||||
|
m.isBJunk(m.b[bestj+bestsize]) &&
|
||||||
|
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||||
|
bestsize += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return Match{A: besti, B: bestj, Size: bestsize}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return list of triples describing matching subsequences.
|
||||||
|
//
|
||||||
|
// Each triple is of the form (i, j, n), and means that
|
||||||
|
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
||||||
|
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
||||||
|
// adjacent triples in the list, and the second is not the last triple in the
|
||||||
|
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
||||||
|
// adjacent equal blocks.
|
||||||
|
//
|
||||||
|
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
||||||
|
// triple with n==0.
|
||||||
|
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
||||||
|
if m.matchingBlocks != nil {
|
||||||
|
return m.matchingBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
||||||
|
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
||||||
|
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
||||||
|
i, j, k := match.A, match.B, match.Size
|
||||||
|
if match.Size > 0 {
|
||||||
|
if alo < i && blo < j {
|
||||||
|
matched = matchBlocks(alo, i, blo, j, matched)
|
||||||
|
}
|
||||||
|
matched = append(matched, match)
|
||||||
|
if i+k < ahi && j+k < bhi {
|
||||||
|
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matched
|
||||||
|
}
|
||||||
|
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
||||||
|
|
||||||
|
// It's possible that we have adjacent equal blocks in the
|
||||||
|
// matching_blocks list now.
|
||||||
|
nonAdjacent := []Match{}
|
||||||
|
i1, j1, k1 := 0, 0, 0
|
||||||
|
for _, b := range matched {
|
||||||
|
// Is this block adjacent to i1, j1, k1?
|
||||||
|
i2, j2, k2 := b.A, b.B, b.Size
|
||||||
|
if i1+k1 == i2 && j1+k1 == j2 {
|
||||||
|
// Yes, so collapse them -- this just increases the length of
|
||||||
|
// the first block by the length of the second, and the first
|
||||||
|
// block so lengthened remains the block to compare against.
|
||||||
|
k1 += k2
|
||||||
|
} else {
|
||||||
|
// Not adjacent. Remember the first block (k1==0 means it's
|
||||||
|
// the dummy we started with), and make the second block the
|
||||||
|
// new block to compare against.
|
||||||
|
if k1 > 0 {
|
||||||
|
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||||
|
}
|
||||||
|
i1, j1, k1 = i2, j2, k2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if k1 > 0 {
|
||||||
|
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||||
|
}
|
||||||
|
|
||||||
|
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
||||||
|
m.matchingBlocks = nonAdjacent
|
||||||
|
return m.matchingBlocks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return list of 5-tuples describing how to turn a into b.
|
||||||
|
//
|
||||||
|
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
||||||
|
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
||||||
|
// tuple preceding it, and likewise for j1 == the previous j2.
|
||||||
|
//
|
||||||
|
// The tags are characters, with these meanings:
|
||||||
|
//
|
||||||
|
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
||||||
|
//
|
||||||
|
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
||||||
|
//
|
||||||
|
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
||||||
|
//
|
||||||
|
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
||||||
|
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
||||||
|
if m.opCodes != nil {
|
||||||
|
return m.opCodes
|
||||||
|
}
|
||||||
|
i, j := 0, 0
|
||||||
|
matching := m.GetMatchingBlocks()
|
||||||
|
opCodes := make([]OpCode, 0, len(matching))
|
||||||
|
for _, m := range matching {
|
||||||
|
// invariant: we've pumped out correct diffs to change
|
||||||
|
// a[:i] into b[:j], and the next matching block is
|
||||||
|
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
||||||
|
// out a diff to change a[i:ai] into b[j:bj], pump out
|
||||||
|
// the matching block, and move (i,j) beyond the match
|
||||||
|
ai, bj, size := m.A, m.B, m.Size
|
||||||
|
tag := byte(0)
|
||||||
|
if i < ai && j < bj {
|
||||||
|
tag = 'r'
|
||||||
|
} else if i < ai {
|
||||||
|
tag = 'd'
|
||||||
|
} else if j < bj {
|
||||||
|
tag = 'i'
|
||||||
|
}
|
||||||
|
if tag > 0 {
|
||||||
|
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
||||||
|
}
|
||||||
|
i, j = ai+size, bj+size
|
||||||
|
// the list of matching blocks is terminated by a
|
||||||
|
// sentinel with size 0
|
||||||
|
if size > 0 {
|
||||||
|
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.opCodes = opCodes
|
||||||
|
return m.opCodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Isolate change clusters by eliminating ranges with no changes.
|
||||||
|
//
|
||||||
|
// Return a generator of groups with up to n lines of context.
|
||||||
|
// Each group is in the same format as returned by GetOpCodes().
|
||||||
|
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
||||||
|
if n < 0 {
|
||||||
|
n = 3
|
||||||
|
}
|
||||||
|
codes := m.GetOpCodes()
|
||||||
|
if len(codes) == 0 {
|
||||||
|
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
|
||||||
|
}
|
||||||
|
// Fixup leading and trailing groups if they show no changes.
|
||||||
|
if codes[0].Tag == 'e' {
|
||||||
|
c := codes[0]
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
||||||
|
}
|
||||||
|
if codes[len(codes)-1].Tag == 'e' {
|
||||||
|
c := codes[len(codes)-1]
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
||||||
|
}
|
||||||
|
nn := n + n
|
||||||
|
groups := [][]OpCode{}
|
||||||
|
group := []OpCode{}
|
||||||
|
for _, c := range codes {
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
// End the current group and start a new one whenever
|
||||||
|
// there is a large range with no changes.
|
||||||
|
if c.Tag == 'e' && i2-i1 > nn {
|
||||||
|
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
|
||||||
|
j1, min(j2, j1+n)})
|
||||||
|
groups = append(groups, group)
|
||||||
|
group = []OpCode{}
|
||||||
|
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
||||||
|
}
|
||||||
|
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
||||||
|
}
|
||||||
|
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
||||||
|
groups = append(groups, group)
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a measure of the sequences' similarity (float in [0,1]).
|
||||||
|
//
|
||||||
|
// Where T is the total number of elements in both sequences, and
|
||||||
|
// M is the number of matches, this is 2.0*M / T.
|
||||||
|
// Note that this is 1 if the sequences are identical, and 0 if
|
||||||
|
// they have nothing in common.
|
||||||
|
//
|
||||||
|
// .Ratio() is expensive to compute if you haven't already computed
|
||||||
|
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
||||||
|
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
||||||
|
// upper bound.
|
||||||
|
func (m *SequenceMatcher) Ratio() float64 {
|
||||||
|
matches := 0
|
||||||
|
for _, m := range m.GetMatchingBlocks() {
|
||||||
|
matches += m.Size
|
||||||
|
}
|
||||||
|
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an upper bound on ratio() relatively quickly.
|
||||||
|
//
|
||||||
|
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||||
|
// is faster to compute.
|
||||||
|
func (m *SequenceMatcher) QuickRatio() float64 {
|
||||||
|
// viewing a and b as multisets, set matches to the cardinality
|
||||||
|
// of their intersection; this counts the number of matches
|
||||||
|
// without regard to order, so is clearly an upper bound
|
||||||
|
if m.fullBCount == nil {
|
||||||
|
m.fullBCount = map[string]int{}
|
||||||
|
for _, s := range m.b {
|
||||||
|
m.fullBCount[s] = m.fullBCount[s] + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// avail[x] is the number of times x appears in 'b' less the
|
||||||
|
// number of times we've seen it in 'a' so far ... kinda
|
||||||
|
avail := map[string]int{}
|
||||||
|
matches := 0
|
||||||
|
for _, s := range m.a {
|
||||||
|
n, ok := avail[s]
|
||||||
|
if !ok {
|
||||||
|
n = m.fullBCount[s]
|
||||||
|
}
|
||||||
|
avail[s] = n - 1
|
||||||
|
if n > 0 {
|
||||||
|
matches += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an upper bound on ratio() very quickly.
|
||||||
|
//
|
||||||
|
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||||
|
// is faster to compute than either .Ratio() or .QuickRatio().
|
||||||
|
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
||||||
|
la, lb := len(m.a), len(m.b)
|
||||||
|
return calculateRatio(min(la, lb), la+lb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert range to the "ed" format
|
||||||
|
func formatRangeUnified(start, stop int) string {
|
||||||
|
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
beginning := start + 1 // lines start numbering with one
|
||||||
|
length := stop - start
|
||||||
|
if length == 1 {
|
||||||
|
return fmt.Sprintf("%d", beginning)
|
||||||
|
}
|
||||||
|
if length == 0 {
|
||||||
|
beginning -= 1 // empty ranges begin at line just before the range
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d,%d", beginning, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unified diff parameters
|
||||||
|
type UnifiedDiff struct {
|
||||||
|
A []string // First sequence lines
|
||||||
|
FromFile string // First file name
|
||||||
|
FromDate string // First file time
|
||||||
|
B []string // Second sequence lines
|
||||||
|
ToFile string // Second file name
|
||||||
|
ToDate string // Second file time
|
||||||
|
Eol string // Headers end of line, defaults to LF
|
||||||
|
Context int // Number of context lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare two sequences of lines; generate the delta as a unified diff.
|
||||||
|
//
|
||||||
|
// Unified diffs are a compact way of showing line changes and a few
|
||||||
|
// lines of context. The number of context lines is set by 'n' which
|
||||||
|
// defaults to three.
|
||||||
|
//
|
||||||
|
// By default, the diff control lines (those with ---, +++, or @@) are
|
||||||
|
// created with a trailing newline. This is helpful so that inputs
|
||||||
|
// created from file.readlines() result in diffs that are suitable for
|
||||||
|
// file.writelines() since both the inputs and outputs have trailing
|
||||||
|
// newlines.
|
||||||
|
//
|
||||||
|
// For inputs that do not have trailing newlines, set the lineterm
|
||||||
|
// argument to "" so that the output will be uniformly newline free.
|
||||||
|
//
|
||||||
|
// The unidiff format normally has a header for filenames and modification
|
||||||
|
// times. Any or all of these may be specified using strings for
|
||||||
|
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
||||||
|
// The modification times are normally expressed in the ISO 8601 format.
|
||||||
|
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
||||||
|
buf := bufio.NewWriter(writer)
|
||||||
|
defer buf.Flush()
|
||||||
|
wf := func(format string, args ...interface{}) error {
|
||||||
|
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ws := func(s string) error {
|
||||||
|
_, err := buf.WriteString(s)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(diff.Eol) == 0 {
|
||||||
|
diff.Eol = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
started := false
|
||||||
|
m := NewMatcher(diff.A, diff.B)
|
||||||
|
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||||
|
if !started {
|
||||||
|
started = true
|
||||||
|
fromDate := ""
|
||||||
|
if len(diff.FromDate) > 0 {
|
||||||
|
fromDate = "\t" + diff.FromDate
|
||||||
|
}
|
||||||
|
toDate := ""
|
||||||
|
if len(diff.ToDate) > 0 {
|
||||||
|
toDate = "\t" + diff.ToDate
|
||||||
|
}
|
||||||
|
if diff.FromFile != "" || diff.ToFile != "" {
|
||||||
|
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first, last := g[0], g[len(g)-1]
|
||||||
|
range1 := formatRangeUnified(first.I1, last.I2)
|
||||||
|
range2 := formatRangeUnified(first.J1, last.J2)
|
||||||
|
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, c := range g {
|
||||||
|
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||||
|
if c.Tag == 'e' {
|
||||||
|
for _, line := range diff.A[i1:i2] {
|
||||||
|
if err := ws(" " + line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.Tag == 'r' || c.Tag == 'd' {
|
||||||
|
for _, line := range diff.A[i1:i2] {
|
||||||
|
if err := ws("-" + line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.Tag == 'r' || c.Tag == 'i' {
|
||||||
|
for _, line := range diff.B[j1:j2] {
|
||||||
|
if err := ws("+" + line); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like WriteUnifiedDiff but returns the diff a string.
|
||||||
|
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
err := WriteUnifiedDiff(w, diff)
|
||||||
|
return string(w.Bytes()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert range to the "ed" format.
|
||||||
|
func formatRangeContext(start, stop int) string {
|
||||||
|
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
beginning := start + 1 // lines start numbering with one
|
||||||
|
length := stop - start
|
||||||
|
if length == 0 {
|
||||||
|
beginning -= 1 // empty ranges begin at line just before the range
|
||||||
|
}
|
||||||
|
if length <= 1 {
|
||||||
|
return fmt.Sprintf("%d", beginning)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextDiff UnifiedDiff
|
||||||
|
|
||||||
|
// Compare two sequences of lines; generate the delta as a context diff.
|
||||||
|
//
|
||||||
|
// Context diffs are a compact way of showing line changes and a few
|
||||||
|
// lines of context. The number of context lines is set by diff.Context
|
||||||
|
// which defaults to three.
|
||||||
|
//
|
||||||
|
// By default, the diff control lines (those with *** or ---) are
|
||||||
|
// created with a trailing newline.
|
||||||
|
//
|
||||||
|
// For inputs that do not have trailing newlines, set the diff.Eol
|
||||||
|
// argument to "" so that the output will be uniformly newline free.
|
||||||
|
//
|
||||||
|
// The context diff format normally has a header for filenames and
|
||||||
|
// modification times. Any or all of these may be specified using
|
||||||
|
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
|
||||||
|
// The modification times are normally expressed in the ISO 8601 format.
|
||||||
|
// If not specified, the strings default to blanks.
|
||||||
|
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
|
||||||
|
buf := bufio.NewWriter(writer)
|
||||||
|
defer buf.Flush()
|
||||||
|
var diffErr error
|
||||||
|
wf := func(format string, args ...interface{}) {
|
||||||
|
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||||
|
if diffErr == nil && err != nil {
|
||||||
|
diffErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws := func(s string) {
|
||||||
|
_, err := buf.WriteString(s)
|
||||||
|
if diffErr == nil && err != nil {
|
||||||
|
diffErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(diff.Eol) == 0 {
|
||||||
|
diff.Eol = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := map[byte]string{
|
||||||
|
'i': "+ ",
|
||||||
|
'd': "- ",
|
||||||
|
'r': "! ",
|
||||||
|
'e': " ",
|
||||||
|
}
|
||||||
|
|
||||||
|
started := false
|
||||||
|
m := NewMatcher(diff.A, diff.B)
|
||||||
|
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||||
|
if !started {
|
||||||
|
started = true
|
||||||
|
fromDate := ""
|
||||||
|
if len(diff.FromDate) > 0 {
|
||||||
|
fromDate = "\t" + diff.FromDate
|
||||||
|
}
|
||||||
|
toDate := ""
|
||||||
|
if len(diff.ToDate) > 0 {
|
||||||
|
toDate = "\t" + diff.ToDate
|
||||||
|
}
|
||||||
|
if diff.FromFile != "" || diff.ToFile != "" {
|
||||||
|
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||||
|
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
first, last := g[0], g[len(g)-1]
|
||||||
|
ws("***************" + diff.Eol)
|
||||||
|
|
||||||
|
range1 := formatRangeContext(first.I1, last.I2)
|
||||||
|
wf("*** %s ****%s", range1, diff.Eol)
|
||||||
|
for _, c := range g {
|
||||||
|
if c.Tag == 'r' || c.Tag == 'd' {
|
||||||
|
for _, cc := range g {
|
||||||
|
if cc.Tag == 'i' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, line := range diff.A[cc.I1:cc.I2] {
|
||||||
|
ws(prefix[cc.Tag] + line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
range2 := formatRangeContext(first.J1, last.J2)
|
||||||
|
wf("--- %s ----%s", range2, diff.Eol)
|
||||||
|
for _, c := range g {
|
||||||
|
if c.Tag == 'r' || c.Tag == 'i' {
|
||||||
|
for _, cc := range g {
|
||||||
|
if cc.Tag == 'd' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, line := range diff.B[cc.J1:cc.J2] {
|
||||||
|
ws(prefix[cc.Tag] + line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diffErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like WriteContextDiff but returns the diff a string.
|
||||||
|
func GetContextDiffString(diff ContextDiff) (string, error) {
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
err := WriteContextDiff(w, diff)
|
||||||
|
return string(w.Bytes()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split a string on "\n" while preserving them. The output can be used
|
||||||
|
// as input for UnifiedDiff and ContextDiff structures.
|
||||||
|
func SplitLines(s string) []string {
|
||||||
|
lines := strings.SplitAfter(s, "\n")
|
||||||
|
lines[len(lines)-1] += "\n"
|
||||||
|
return lines
|
||||||
|
}
|
21
vendor/github.com/stretchr/testify/LICENSE
generated
vendored
Normal file
21
vendor/github.com/stretchr/testify/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
394
vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
Normal file
394
vendor/github.com/stretchr/testify/assert/assertion_compare.go
generated
vendored
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompareType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
compareLess CompareType = iota - 1
|
||||||
|
compareEqual
|
||||||
|
compareGreater
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
intType = reflect.TypeOf(int(1))
|
||||||
|
int8Type = reflect.TypeOf(int8(1))
|
||||||
|
int16Type = reflect.TypeOf(int16(1))
|
||||||
|
int32Type = reflect.TypeOf(int32(1))
|
||||||
|
int64Type = reflect.TypeOf(int64(1))
|
||||||
|
|
||||||
|
uintType = reflect.TypeOf(uint(1))
|
||||||
|
uint8Type = reflect.TypeOf(uint8(1))
|
||||||
|
uint16Type = reflect.TypeOf(uint16(1))
|
||||||
|
uint32Type = reflect.TypeOf(uint32(1))
|
||||||
|
uint64Type = reflect.TypeOf(uint64(1))
|
||||||
|
|
||||||
|
float32Type = reflect.TypeOf(float32(1))
|
||||||
|
float64Type = reflect.TypeOf(float64(1))
|
||||||
|
|
||||||
|
stringType = reflect.TypeOf("")
|
||||||
|
)
|
||||||
|
|
||||||
|
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
||||||
|
obj1Value := reflect.ValueOf(obj1)
|
||||||
|
obj2Value := reflect.ValueOf(obj2)
|
||||||
|
|
||||||
|
// throughout this switch we try and avoid calling .Convert() if possible,
|
||||||
|
// as this has a pretty big performance impact
|
||||||
|
switch kind {
|
||||||
|
case reflect.Int:
|
||||||
|
{
|
||||||
|
intobj1, ok := obj1.(int)
|
||||||
|
if !ok {
|
||||||
|
intobj1 = obj1Value.Convert(intType).Interface().(int)
|
||||||
|
}
|
||||||
|
intobj2, ok := obj2.(int)
|
||||||
|
if !ok {
|
||||||
|
intobj2 = obj2Value.Convert(intType).Interface().(int)
|
||||||
|
}
|
||||||
|
if intobj1 > intobj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if intobj1 == intobj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if intobj1 < intobj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int8:
|
||||||
|
{
|
||||||
|
int8obj1, ok := obj1.(int8)
|
||||||
|
if !ok {
|
||||||
|
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
|
||||||
|
}
|
||||||
|
int8obj2, ok := obj2.(int8)
|
||||||
|
if !ok {
|
||||||
|
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
|
||||||
|
}
|
||||||
|
if int8obj1 > int8obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int8obj1 == int8obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int8obj1 < int8obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
{
|
||||||
|
int16obj1, ok := obj1.(int16)
|
||||||
|
if !ok {
|
||||||
|
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
|
||||||
|
}
|
||||||
|
int16obj2, ok := obj2.(int16)
|
||||||
|
if !ok {
|
||||||
|
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
|
||||||
|
}
|
||||||
|
if int16obj1 > int16obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int16obj1 == int16obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int16obj1 < int16obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
{
|
||||||
|
int32obj1, ok := obj1.(int32)
|
||||||
|
if !ok {
|
||||||
|
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
|
||||||
|
}
|
||||||
|
int32obj2, ok := obj2.(int32)
|
||||||
|
if !ok {
|
||||||
|
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
|
||||||
|
}
|
||||||
|
if int32obj1 > int32obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int32obj1 == int32obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int32obj1 < int32obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
{
|
||||||
|
int64obj1, ok := obj1.(int64)
|
||||||
|
if !ok {
|
||||||
|
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
|
||||||
|
}
|
||||||
|
int64obj2, ok := obj2.(int64)
|
||||||
|
if !ok {
|
||||||
|
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
|
||||||
|
}
|
||||||
|
if int64obj1 > int64obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if int64obj1 == int64obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if int64obj1 < int64obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint:
|
||||||
|
{
|
||||||
|
uintobj1, ok := obj1.(uint)
|
||||||
|
if !ok {
|
||||||
|
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
|
||||||
|
}
|
||||||
|
uintobj2, ok := obj2.(uint)
|
||||||
|
if !ok {
|
||||||
|
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
|
||||||
|
}
|
||||||
|
if uintobj1 > uintobj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uintobj1 == uintobj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uintobj1 < uintobj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint8:
|
||||||
|
{
|
||||||
|
uint8obj1, ok := obj1.(uint8)
|
||||||
|
if !ok {
|
||||||
|
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
|
||||||
|
}
|
||||||
|
uint8obj2, ok := obj2.(uint8)
|
||||||
|
if !ok {
|
||||||
|
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
|
||||||
|
}
|
||||||
|
if uint8obj1 > uint8obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint8obj1 == uint8obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint8obj1 < uint8obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
{
|
||||||
|
uint16obj1, ok := obj1.(uint16)
|
||||||
|
if !ok {
|
||||||
|
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
|
||||||
|
}
|
||||||
|
uint16obj2, ok := obj2.(uint16)
|
||||||
|
if !ok {
|
||||||
|
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
|
||||||
|
}
|
||||||
|
if uint16obj1 > uint16obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint16obj1 == uint16obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint16obj1 < uint16obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
{
|
||||||
|
uint32obj1, ok := obj1.(uint32)
|
||||||
|
if !ok {
|
||||||
|
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
|
||||||
|
}
|
||||||
|
uint32obj2, ok := obj2.(uint32)
|
||||||
|
if !ok {
|
||||||
|
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
|
||||||
|
}
|
||||||
|
if uint32obj1 > uint32obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint32obj1 == uint32obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint32obj1 < uint32obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
{
|
||||||
|
uint64obj1, ok := obj1.(uint64)
|
||||||
|
if !ok {
|
||||||
|
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
|
||||||
|
}
|
||||||
|
uint64obj2, ok := obj2.(uint64)
|
||||||
|
if !ok {
|
||||||
|
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
|
||||||
|
}
|
||||||
|
if uint64obj1 > uint64obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if uint64obj1 == uint64obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if uint64obj1 < uint64obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float32:
|
||||||
|
{
|
||||||
|
float32obj1, ok := obj1.(float32)
|
||||||
|
if !ok {
|
||||||
|
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
|
||||||
|
}
|
||||||
|
float32obj2, ok := obj2.(float32)
|
||||||
|
if !ok {
|
||||||
|
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
|
||||||
|
}
|
||||||
|
if float32obj1 > float32obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if float32obj1 == float32obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if float32obj1 < float32obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Float64:
|
||||||
|
{
|
||||||
|
float64obj1, ok := obj1.(float64)
|
||||||
|
if !ok {
|
||||||
|
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
|
||||||
|
}
|
||||||
|
float64obj2, ok := obj2.(float64)
|
||||||
|
if !ok {
|
||||||
|
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
|
||||||
|
}
|
||||||
|
if float64obj1 > float64obj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if float64obj1 == float64obj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if float64obj1 < float64obj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
{
|
||||||
|
stringobj1, ok := obj1.(string)
|
||||||
|
if !ok {
|
||||||
|
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
|
||||||
|
}
|
||||||
|
stringobj2, ok := obj2.(string)
|
||||||
|
if !ok {
|
||||||
|
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
|
||||||
|
}
|
||||||
|
if stringobj1 > stringobj2 {
|
||||||
|
return compareGreater, true
|
||||||
|
}
|
||||||
|
if stringobj1 == stringobj2 {
|
||||||
|
return compareEqual, true
|
||||||
|
}
|
||||||
|
if stringobj1 < stringobj2 {
|
||||||
|
return compareLess, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareEqual, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greater asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// assert.Greater(t, 2, 1)
|
||||||
|
// assert.Greater(t, float64(2), float64(1))
|
||||||
|
// assert.Greater(t, "b", "a")
|
||||||
|
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.GreaterOrEqual(t, 2, 1)
|
||||||
|
// assert.GreaterOrEqual(t, 2, 2)
|
||||||
|
// assert.GreaterOrEqual(t, "b", "a")
|
||||||
|
// assert.GreaterOrEqual(t, "b", "b")
|
||||||
|
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// assert.Less(t, 1, 2)
|
||||||
|
// assert.Less(t, float64(1), float64(2))
|
||||||
|
// assert.Less(t, "a", "b")
|
||||||
|
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.LessOrEqual(t, 1, 2)
|
||||||
|
// assert.LessOrEqual(t, 2, 2)
|
||||||
|
// assert.LessOrEqual(t, "a", "b")
|
||||||
|
// assert.LessOrEqual(t, "b", "b")
|
||||||
|
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positive asserts that the specified element is positive
|
||||||
|
//
|
||||||
|
// assert.Positive(t, 1)
|
||||||
|
// assert.Positive(t, 1.23)
|
||||||
|
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
zero := reflect.Zero(reflect.TypeOf(e))
|
||||||
|
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative asserts that the specified element is negative
|
||||||
|
//
|
||||||
|
// assert.Negative(t, -1)
|
||||||
|
// assert.Negative(t, -1.23)
|
||||||
|
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
zero := reflect.Zero(reflect.TypeOf(e))
|
||||||
|
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
e1Kind := reflect.ValueOf(e1).Kind()
|
||||||
|
e2Kind := reflect.ValueOf(e2).Kind()
|
||||||
|
if e1Kind != e2Kind {
|
||||||
|
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
compareResult, isComparable := compare(e1, e2, e1Kind)
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containsValue(allowedComparesResults, compareResult) {
|
||||||
|
return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsValue(values []CompareType, value CompareType) bool {
|
||||||
|
for _, v := range values {
|
||||||
|
if v == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
741
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
Normal file
741
vendor/github.com/stretchr/testify/assert/assertion_format.go
generated
vendored
Normal file
|
@ -0,0 +1,741 @@
|
||||||
|
/*
|
||||||
|
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||||
|
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
|
*/
|
||||||
|
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
http "net/http"
|
||||||
|
url "net/url"
|
||||||
|
time "time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Conditionf uses a Comparison to assert a complex condition.
|
||||||
|
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Condition(t, comp, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||||
|
// specified substring or element.
|
||||||
|
//
|
||||||
|
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
|
||||||
|
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||||
|
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||||
|
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirExistsf checks whether a directory exists in the given path. It also fails
|
||||||
|
// if the path is a file rather a directory or there is an error checking whether it exists.
|
||||||
|
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return DirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||||
|
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||||
|
// the number of appearances of each of them in both lists should match.
|
||||||
|
//
|
||||||
|
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||||
|
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||||
|
// a slice or a channel with len == 0.
|
||||||
|
//
|
||||||
|
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||||
|
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Empty(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equalf asserts that two objects are equal.
|
||||||
|
//
|
||||||
|
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Pointer variable equality is determined based on the equality of the
|
||||||
|
// referenced values (as opposed to the memory addresses). Function equality
|
||||||
|
// cannot be determined and will always fail.
|
||||||
|
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||||
|
// and that it is equal to the provided error.
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||||
|
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||||
|
// and equal.
|
||||||
|
//
|
||||||
|
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
|
||||||
|
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
||||||
|
// assert.Equal(t, expectedErrorf, err)
|
||||||
|
// }
|
||||||
|
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Error(t, err, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
|
||||||
|
// This is a wrapper for errors.As.
|
||||||
|
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
|
||||||
|
// This is a wrapper for errors.Is.
|
||||||
|
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eventuallyf asserts that given condition will be met in waitFor time,
|
||||||
|
// periodically checking target function each tick.
|
||||||
|
//
|
||||||
|
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||||
|
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exactlyf asserts that two objects are equal in value and type.
|
||||||
|
//
|
||||||
|
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
|
||||||
|
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failf reports a failure through
|
||||||
|
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailNowf fails test
|
||||||
|
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Falsef asserts that the specified value is false.
|
||||||
|
//
|
||||||
|
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||||
|
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return False(t, value, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileExistsf checks whether a file exists in the given path. It also fails if
|
||||||
|
// the path points to a directory or there is an error when trying to check the file.
|
||||||
|
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greaterf asserts that the first element is greater than the second
|
||||||
|
//
|
||||||
|
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||||
|
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
|
||||||
|
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||||
|
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
|
||||||
|
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||||
|
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||||
|
// body that contains a string.
|
||||||
|
//
|
||||||
|
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||||
|
// body that does not contain a string.
|
||||||
|
//
|
||||||
|
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||||
|
//
|
||||||
|
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||||
|
//
|
||||||
|
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||||
|
//
|
||||||
|
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||||
|
//
|
||||||
|
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Returns whether the assertion was successful (true) or not (false).
|
||||||
|
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementsf asserts that an object is implemented by the specified interface.
|
||||||
|
//
|
||||||
|
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||||
|
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||||
|
//
|
||||||
|
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
|
||||||
|
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||||
|
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||||
|
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||||
|
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||||
|
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDecreasingf asserts that the collection is decreasing
|
||||||
|
//
|
||||||
|
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
|
||||||
|
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||||
|
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||||
|
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIncreasingf asserts that the collection is increasing
|
||||||
|
//
|
||||||
|
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
|
||||||
|
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||||
|
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||||
|
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonDecreasingf asserts that the collection is not decreasing
|
||||||
|
//
|
||||||
|
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||||
|
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonIncreasingf asserts that the collection is not increasing
|
||||||
|
//
|
||||||
|
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||||
|
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||||
|
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTypef asserts that the specified objects are of the same type.
|
||||||
|
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONEqf asserts that two JSON strings are equivalent.
|
||||||
|
//
|
||||||
|
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||||
|
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lenf asserts that the specified object has specific length.
|
||||||
|
// Lenf also fails if the object has a type that len() not accept.
|
||||||
|
//
|
||||||
|
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||||
|
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lessf asserts that the first element is less than the second
|
||||||
|
//
|
||||||
|
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||||
|
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
|
||||||
|
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||||
|
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LessOrEqualf asserts that the first element is less than or equal to the second
|
||||||
|
//
|
||||||
|
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
|
||||||
|
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||||
|
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negativef asserts that the specified element is negative
|
||||||
|
//
|
||||||
|
// assert.Negativef(t, -1, "error message %s", "formatted")
|
||||||
|
// assert.Negativef(t, -1.23, "error message %s", "formatted")
|
||||||
|
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Negative(t, e, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
|
||||||
|
// periodically checking the target function each tick.
|
||||||
|
//
|
||||||
|
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||||
|
func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Never(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nilf asserts that the specified object is nil.
|
||||||
|
//
|
||||||
|
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||||
|
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Nil(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoDirExistsf checks whether a directory does not exist in the given path.
|
||||||
|
// It fails if the path points to an existing _directory_ only.
|
||||||
|
func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NoDirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||||
|
//
|
||||||
|
// actualObj, err := SomeFunction()
|
||||||
|
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
|
||||||
|
// assert.Equal(t, expectedObj, actualObj)
|
||||||
|
// }
|
||||||
|
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NoError(t, err, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoFileExistsf checks whether a file does not exist in a given path. It fails
|
||||||
|
// if the path points to an existing _file_ only.
|
||||||
|
func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NoFileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||||
|
// specified substring or element.
|
||||||
|
//
|
||||||
|
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
|
||||||
|
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||||
|
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||||
|
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||||
|
// a slice or a channel with len == 0.
|
||||||
|
//
|
||||||
|
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||||
|
// assert.Equal(t, "two", obj[1])
|
||||||
|
// }
|
||||||
|
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEqualf asserts that the specified values are NOT equal.
|
||||||
|
//
|
||||||
|
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Pointer variable equality is determined based on the equality of the
|
||||||
|
// referenced values (as opposed to the memory addresses).
|
||||||
|
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||||
|
//
|
||||||
|
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
|
||||||
|
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
|
||||||
|
// This is a wrapper for errors.Is.
|
||||||
|
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotNilf asserts that the specified object is not nil.
|
||||||
|
//
|
||||||
|
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||||
|
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotNil(t, object, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||||
|
//
|
||||||
|
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||||
|
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||||
|
//
|
||||||
|
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||||
|
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||||
|
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotSamef asserts that two pointers do not reference the same object.
|
||||||
|
//
|
||||||
|
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||||
|
// elements given in the specified subset(array, slice...).
|
||||||
|
//
|
||||||
|
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||||
|
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotZerof asserts that i is not the zero value for its type.
|
||||||
|
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return NotZero(t, i, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||||
|
//
|
||||||
|
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||||
|
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Panics(t, f, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
|
||||||
|
// panics, and that the recovered panic value is an error that satisfies the
|
||||||
|
// EqualError comparison.
|
||||||
|
//
|
||||||
|
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||||
|
func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||||
|
// the recovered panic value equals the expected panic value.
|
||||||
|
//
|
||||||
|
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||||
|
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Positivef asserts that the specified element is positive
|
||||||
|
//
|
||||||
|
// assert.Positivef(t, 1, "error message %s", "formatted")
|
||||||
|
// assert.Positivef(t, 1.23, "error message %s", "formatted")
|
||||||
|
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Positive(t, e, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regexpf asserts that a specified regexp matches a string.
|
||||||
|
//
|
||||||
|
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||||
|
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||||
|
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Samef asserts that two pointers reference the same object.
|
||||||
|
//
|
||||||
|
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||||
|
//
|
||||||
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||||
|
// determined based on the equality of both type and value.
|
||||||
|
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||||
|
// elements given in the specified subset(array, slice...).
|
||||||
|
//
|
||||||
|
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||||
|
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truef asserts that the specified value is true.
|
||||||
|
//
|
||||||
|
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||||
|
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return True(t, value, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||||
|
//
|
||||||
|
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||||
|
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||||
|
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zerof asserts that i is the zero value for its type.
|
||||||
|
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok {
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
return Zero(t, i, append([]interface{}{msg}, args...)...)
|
||||||
|
}
|
5
vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
generated
vendored
Normal file
5
vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{.CommentFormat}}
|
||||||
|
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
|
||||||
|
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||||
|
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
|
||||||
|
}
|
1470
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
Normal file
1470
vendor/github.com/stretchr/testify/assert/assertion_forward.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
5
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
5
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{.CommentWithoutT "a"}}
|
||||||
|
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
||||||
|
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||||
|
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||||
|
}
|
81
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
Normal file
81
vendor/github.com/stretchr/testify/assert/assertion_order.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// isOrdered checks that collection contains orderable elements.
|
||||||
|
func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
|
||||||
|
objKind := reflect.TypeOf(object).Kind()
|
||||||
|
if objKind != reflect.Slice && objKind != reflect.Array {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
objValue := reflect.ValueOf(object)
|
||||||
|
objLen := objValue.Len()
|
||||||
|
|
||||||
|
if objLen <= 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
value := objValue.Index(0)
|
||||||
|
valueInterface := value.Interface()
|
||||||
|
firstValueKind := value.Kind()
|
||||||
|
|
||||||
|
for i := 1; i < objLen; i++ {
|
||||||
|
prevValue := value
|
||||||
|
prevValueInterface := valueInterface
|
||||||
|
|
||||||
|
value = objValue.Index(i)
|
||||||
|
valueInterface = value.Interface()
|
||||||
|
|
||||||
|
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
|
||||||
|
|
||||||
|
if !isComparable {
|
||||||
|
return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containsValue(allowedComparesResults, compareResult) {
|
||||||
|
return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIncreasing asserts that the collection is increasing
|
||||||
|
//
|
||||||
|
// assert.IsIncreasing(t, []int{1, 2, 3})
|
||||||
|
// assert.IsIncreasing(t, []float{1, 2})
|
||||||
|
// assert.IsIncreasing(t, []string{"a", "b"})
|
||||||
|
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonIncreasing asserts that the collection is not increasing
|
||||||
|
//
|
||||||
|
// assert.IsNonIncreasing(t, []int{2, 1, 1})
|
||||||
|
// assert.IsNonIncreasing(t, []float{2, 1})
|
||||||
|
// assert.IsNonIncreasing(t, []string{"b", "a"})
|
||||||
|
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDecreasing asserts that the collection is decreasing
|
||||||
|
//
|
||||||
|
// assert.IsDecreasing(t, []int{2, 1, 0})
|
||||||
|
// assert.IsDecreasing(t, []float{2, 1})
|
||||||
|
// assert.IsDecreasing(t, []string{"b", "a"})
|
||||||
|
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNonDecreasing asserts that the collection is not decreasing
|
||||||
|
//
|
||||||
|
// assert.IsNonDecreasing(t, []int{1, 1, 2})
|
||||||
|
// assert.IsNonDecreasing(t, []float{1, 2})
|
||||||
|
// assert.IsNonDecreasing(t, []string{"a", "b"})
|
||||||
|
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||||
|
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
|
||||||
|
}
|
1774
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
Normal file
1774
vendor/github.com/stretchr/testify/assert/assertions.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
45
vendor/github.com/stretchr/testify/assert/doc.go
generated
vendored
Normal file
45
vendor/github.com/stretchr/testify/assert/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||||
|
//
|
||||||
|
// Example Usage
|
||||||
|
//
|
||||||
|
// The following is a complete example using assert in a standard test function:
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "github.com/stretchr/testify/assert"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func TestSomething(t *testing.T) {
|
||||||
|
//
|
||||||
|
// var a string = "Hello"
|
||||||
|
// var b string = "Hello"
|
||||||
|
//
|
||||||
|
// assert.Equal(t, a, b, "The two words should be the same.")
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if you assert many times, use the format below:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "testing"
|
||||||
|
// "github.com/stretchr/testify/assert"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func TestSomething(t *testing.T) {
|
||||||
|
// assert := assert.New(t)
|
||||||
|
//
|
||||||
|
// var a string = "Hello"
|
||||||
|
// var b string = "Hello"
|
||||||
|
//
|
||||||
|
// assert.Equal(a, b, "The two words should be the same.")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Assertions
|
||||||
|
//
|
||||||
|
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
||||||
|
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
||||||
|
// testing framework. This allows the assertion funcs to write the failings and other details to
|
||||||
|
// the correct place.
|
||||||
|
//
|
||||||
|
// Every assertion function also takes an optional string message as the final argument,
|
||||||
|
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||||
|
package assert
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue