Merge branch 'master' into single-stat-polish
commit
4a82b57134
|
@ -5,6 +5,7 @@
|
|||
1. [#2708](https://github.com/influxdata/chronograf/pull/2708): Link to specified kapacitor config panel from rule builder alert handlers
|
||||
1. [#2722](https://github.com/influxdata/chronograf/pull/2722): Add auto refresh widget to hosts list page
|
||||
1. [#2765](https://github.com/influxdata/chronograf/pull/2765): Update to go 1.9.3 and node 6.12.3 for releases
|
||||
1. [#2784](https://github.com/influxdata/chronograf/pull/2784): Update to go 1.9.4
|
||||
1. [#2703](https://github.com/influxdata/chronograf/pull/2703): Add global users page visible only to super admins
|
||||
1. [#2777](https://github.com/influxdata/chronograf/pull/2777): Allow user to delete themselves
|
||||
|
||||
|
@ -17,8 +18,10 @@
|
|||
### Bug Fixes
|
||||
1. [#2684](https://github.com/influxdata/chronograf/pull/2684): Fix TICKscript Sensu alerts when no group by tags selected
|
||||
1. [#2735](https://github.com/influxdata/chronograf/pull/2735): Remove cli options from systemd service file
|
||||
1. [#2757](https://github.com/influxdata/chronograf/pull/2757): Added "TO" field to kapacitor SMTP config, and improved error messages for config saving and testing
|
||||
1. [#2761](https://github.com/influxdata/chronograf/pull/2761): Remove cli options from sysvinit service file
|
||||
1. [#2788](https://github.com/influxdata/chronograf/pull/2788): Fix disappearance of text in Single Stat graphs during editing
|
||||
1. [#2780](https://github.com/influxdata/chronograf/pull/2780): Fix routing on alert save
|
||||
|
||||
## v1.4.0.1 [2017-1-9]
|
||||
### Features
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
branch = "master"
|
||||
name = "github.com/dustin/go-humanize"
|
||||
packages = ["."]
|
||||
revision = "259d2a102b871d17f30e3cd9881a642961a1e486"
|
||||
revision = "bb3d318650d48840a39aa21a027c6630e198e626"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/elazarl/go-bindata-assetfs"
|
||||
|
@ -39,18 +39,53 @@
|
|||
|
||||
[[projects]]
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = ["gogoproto","jsonpb","plugin/compare","plugin/defaultcheck","plugin/description","plugin/embedcheck","plugin/enumstringer","plugin/equal","plugin/face","plugin/gostring","plugin/marshalto","plugin/oneofcheck","plugin/populate","plugin/size","plugin/stringer","plugin/testgen","plugin/union","plugin/unmarshal","proto","protoc-gen-gogo","protoc-gen-gogo/descriptor","protoc-gen-gogo/generator","protoc-gen-gogo/grpc","protoc-gen-gogo/plugin","vanity","vanity/command"]
|
||||
packages = [
|
||||
"gogoproto",
|
||||
"jsonpb",
|
||||
"plugin/compare",
|
||||
"plugin/defaultcheck",
|
||||
"plugin/description",
|
||||
"plugin/embedcheck",
|
||||
"plugin/enumstringer",
|
||||
"plugin/equal",
|
||||
"plugin/face",
|
||||
"plugin/gostring",
|
||||
"plugin/marshalto",
|
||||
"plugin/oneofcheck",
|
||||
"plugin/populate",
|
||||
"plugin/size",
|
||||
"plugin/stringer",
|
||||
"plugin/testgen",
|
||||
"plugin/union",
|
||||
"plugin/unmarshal",
|
||||
"proto",
|
||||
"protoc-gen-gogo",
|
||||
"protoc-gen-gogo/descriptor",
|
||||
"protoc-gen-gogo/generator",
|
||||
"protoc-gen-gogo/grpc",
|
||||
"protoc-gen-gogo/plugin",
|
||||
"vanity",
|
||||
"vanity/command"
|
||||
]
|
||||
revision = "6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "8ee79997227bf9b34611aee7946ae64735e6fd93"
|
||||
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/google/go-cmp"
|
||||
packages = ["cmp","cmp/cmpopts"]
|
||||
revision = "79b2d888f100ec053545168aa94bcfb322e8bfc8"
|
||||
packages = [
|
||||
"cmp",
|
||||
"cmp/cmpopts",
|
||||
"cmp/internal/diff",
|
||||
"cmp/internal/function",
|
||||
"cmp/internal/value"
|
||||
]
|
||||
revision = "8099a9787ce5dc5984ed879a3bda47dc730a8e97"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/google/go-github"
|
||||
|
@ -58,19 +93,35 @@
|
|||
revision = "1bc362c7737e51014af7299e016444b654095ad9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/google/go-querystring"
|
||||
packages = ["query"]
|
||||
revision = "9235644dd9e52eeae6fa48efd539fdc351a0af53"
|
||||
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/influxdata/influxdb"
|
||||
packages = ["influxql","influxql/internal","influxql/neldermead","models","pkg/escape"]
|
||||
packages = [
|
||||
"influxql",
|
||||
"influxql/internal",
|
||||
"influxql/neldermead",
|
||||
"models",
|
||||
"pkg/escape"
|
||||
]
|
||||
revision = "cd9363b52cac452113b95554d98a6be51beda24e"
|
||||
version = "v1.1.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/influxdata/kapacitor"
|
||||
packages = ["client/v1","pipeline","pipeline/tick","services/k8s/client","tick","tick/ast","tick/stateful","udf/agent"]
|
||||
packages = [
|
||||
"client/v1",
|
||||
"pipeline",
|
||||
"pipeline/tick",
|
||||
"services/k8s/client",
|
||||
"tick",
|
||||
"tick/ast",
|
||||
"tick/stateful",
|
||||
"udf/agent"
|
||||
]
|
||||
revision = "6de30070b39afde111fea5e041281126fe8aae31"
|
||||
|
||||
[[projects]]
|
||||
|
@ -84,15 +135,15 @@
|
|||
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/jteeuwen/go-bindata"
|
||||
name = "github.com/kevinburke/go-bindata"
|
||||
packages = ["."]
|
||||
revision = "a0ff2567cfb70903282db057e799fd826784d41d"
|
||||
revision = "46eb4c183bfc1ebb527d9d19bcded39476302eb8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "ff09b135c25aae272398c51a07235b90a75aa4f0"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/satori/go.uuid"
|
||||
|
@ -107,39 +158,60 @@
|
|||
[[projects]]
|
||||
name = "github.com/tylerb/graceful"
|
||||
packages = ["."]
|
||||
revision = "50a48b6e73fcc75b45e22c05b79629a67c79e938"
|
||||
version = "v1.2.13"
|
||||
revision = "4654dfbb6ad53cb5e27f37d99b02e16c1872fbbb"
|
||||
version = "v1.2.15"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context","context/ctxhttp"]
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp"
|
||||
]
|
||||
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [".","github","heroku","internal"]
|
||||
packages = [
|
||||
".",
|
||||
"github",
|
||||
"heroku",
|
||||
"internal"
|
||||
]
|
||||
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "f3918c30c5c2cb527c0b071a27c35120a6c0719a"
|
||||
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/api"
|
||||
packages = ["gensupport","googleapi","googleapi/internal/uritemplates","oauth2/v2"]
|
||||
packages = [
|
||||
"gensupport",
|
||||
"googleapi",
|
||||
"googleapi/internal/uritemplates",
|
||||
"oauth2/v2"
|
||||
]
|
||||
revision = "bc20c61134e1d25265dd60049f5735381e79b631"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"]
|
||||
packages = [
|
||||
"internal",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch"
|
||||
]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "a5bd1aa82919723ff8ec5dd9d520329862de8181ca9dba75c6acb3a34df5f1a4"
|
||||
inputs-digest = "11df631364d11bc05c8f71af1aa735360b5a40a793d32d47d1f1d8c694a55f6f"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
required = ["github.com/jteeuwen/go-bindata","github.com/gogo/protobuf/proto","github.com/gogo/protobuf/jsonpb","github.com/gogo/protobuf/protoc-gen-gogo","github.com/gogo/protobuf/gogoproto"]
|
||||
required = ["github.com/kevinburke/go-bindata","github.com/gogo/protobuf/proto","github.com/gogo/protobuf/jsonpb","github.com/gogo/protobuf/protoc-gen-gogo","github.com/gogo/protobuf/gogoproto"]
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/NYTimes/gziphandler"
|
||||
|
@ -41,8 +41,8 @@ required = ["github.com/jteeuwen/go-bindata","github.com/gogo/protobuf/proto","g
|
|||
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/jteeuwen/go-bindata"
|
||||
revision = "a0ff2567cfb70903282db057e799fd826784d41d"
|
||||
name = "github.com/kevinburke/go-bindata"
|
||||
revision = "46eb4c183bfc1ebb527d9d19bcded39476302eb8"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/satori/go.uuid"
|
||||
|
|
4
Makefile
4
Makefile
|
@ -2,7 +2,7 @@
|
|||
|
||||
VERSION ?= $(shell git describe --always --tags)
|
||||
COMMIT ?= $(shell git rev-parse --short=8 HEAD)
|
||||
GOBINDATA := $(shell go list -f {{.Root}} github.com/jteeuwen/go-bindata 2> /dev/null)
|
||||
GOBINDATA := $(shell go list -f {{.Root}} github.com/kevinburke/go-bindata 2> /dev/null)
|
||||
YARN := $(shell command -v yarn 2> /dev/null)
|
||||
|
||||
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go' -not -path "./vendor/*" )
|
||||
|
@ -73,7 +73,7 @@ dep: .jsdep .godep
|
|||
.godep:
|
||||
ifndef GOBINDATA
|
||||
@echo "Installing go-bindata"
|
||||
go get -u github.com/jteeuwen/go-bindata/...
|
||||
go get -u github.com/kevinburke/go-bindata/...
|
||||
endif
|
||||
@touch .godep
|
||||
|
||||
|
|
|
@ -70,6 +70,97 @@ func (m *Source) String() string { return proto.CompactTextString(m)
|
|||
func (*Source) ProtoMessage() {}
|
||||
func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
|
||||
|
||||
func (m *Source) GetID() int64 {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Source) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetUsername() string {
|
||||
if m != nil {
|
||||
return m.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetPassword() string {
|
||||
if m != nil {
|
||||
return m.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetURL() string {
|
||||
if m != nil {
|
||||
return m.URL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetDefault() bool {
|
||||
if m != nil {
|
||||
return m.Default
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Source) GetTelegraf() string {
|
||||
if m != nil {
|
||||
return m.Telegraf
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetInsecureSkipVerify() bool {
|
||||
if m != nil {
|
||||
return m.InsecureSkipVerify
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Source) GetMetaURL() string {
|
||||
if m != nil {
|
||||
return m.MetaURL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetSharedSecret() string {
|
||||
if m != nil {
|
||||
return m.SharedSecret
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetOrganization() string {
|
||||
if m != nil {
|
||||
return m.Organization
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Source) GetRole() string {
|
||||
if m != nil {
|
||||
return m.Role
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Dashboard struct {
|
||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
|
@ -83,6 +174,20 @@ func (m *Dashboard) String() string { return proto.CompactTextString(
|
|||
func (*Dashboard) ProtoMessage() {}
|
||||
func (*Dashboard) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} }
|
||||
|
||||
func (m *Dashboard) GetID() int64 {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Dashboard) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Dashboard) GetCells() []*DashboardCell {
|
||||
if m != nil {
|
||||
return m.Cells
|
||||
|
@ -97,6 +202,13 @@ func (m *Dashboard) GetTemplates() []*Template {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Dashboard) GetOrganization() string {
|
||||
if m != nil {
|
||||
return m.Organization
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DashboardCell struct {
|
||||
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
||||
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"`
|
||||
|
@ -116,6 +228,34 @@ func (m *DashboardCell) String() string { return proto.CompactTextStr
|
|||
func (*DashboardCell) ProtoMessage() {}
|
||||
func (*DashboardCell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{2} }
|
||||
|
||||
func (m *DashboardCell) GetX() int32 {
|
||||
if m != nil {
|
||||
return m.X
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetY() int32 {
|
||||
if m != nil {
|
||||
return m.Y
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetW() int32 {
|
||||
if m != nil {
|
||||
return m.W
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetH() int32 {
|
||||
if m != nil {
|
||||
return m.H
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetQueries() []*Query {
|
||||
if m != nil {
|
||||
return m.Queries
|
||||
|
@ -123,6 +263,27 @@ func (m *DashboardCell) GetQueries() []*Query {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetID() string {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *DashboardCell) GetAxes() map[string]*Axis {
|
||||
if m != nil {
|
||||
return m.Axes
|
||||
|
@ -157,6 +318,41 @@ func (m *Color) String() string { return proto.CompactTextString(m) }
|
|||
func (*Color) ProtoMessage() {}
|
||||
func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
|
||||
|
||||
func (m *Color) GetID() string {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Color) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Color) GetHex() string {
|
||||
if m != nil {
|
||||
return m.Hex
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Color) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Color) GetValue() string {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Legend struct {
|
||||
Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"`
|
||||
Orientation string `protobuf:"bytes,2,opt,name=Orientation,proto3" json:"Orientation,omitempty"`
|
||||
|
@ -167,8 +363,22 @@ func (m *Legend) String() string { return proto.CompactTextString(m)
|
|||
func (*Legend) ProtoMessage() {}
|
||||
func (*Legend) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
|
||||
|
||||
func (m *Legend) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Legend) GetOrientation() string {
|
||||
if m != nil {
|
||||
return m.Orientation
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Axis struct {
|
||||
LegacyBounds []int64 `protobuf:"varint,1,rep,name=legacyBounds" json:"legacyBounds,omitempty"`
|
||||
LegacyBounds []int64 `protobuf:"varint,1,rep,packed,name=legacyBounds" json:"legacyBounds,omitempty"`
|
||||
Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"`
|
||||
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
|
||||
Prefix string `protobuf:"bytes,4,opt,name=prefix,proto3" json:"prefix,omitempty"`
|
||||
|
@ -182,6 +392,55 @@ func (m *Axis) String() string { return proto.CompactTextString(m) }
|
|||
func (*Axis) ProtoMessage() {}
|
||||
func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
|
||||
|
||||
func (m *Axis) GetLegacyBounds() []int64 {
|
||||
if m != nil {
|
||||
return m.LegacyBounds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Axis) GetBounds() []string {
|
||||
if m != nil {
|
||||
return m.Bounds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Axis) GetLabel() string {
|
||||
if m != nil {
|
||||
return m.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Axis) GetPrefix() string {
|
||||
if m != nil {
|
||||
return m.Prefix
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Axis) GetSuffix() string {
|
||||
if m != nil {
|
||||
return m.Suffix
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Axis) GetBase() string {
|
||||
if m != nil {
|
||||
return m.Base
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Axis) GetScale() string {
|
||||
if m != nil {
|
||||
return m.Scale
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Template struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
TempVar string `protobuf:"bytes,2,opt,name=temp_var,json=tempVar,proto3" json:"temp_var,omitempty"`
|
||||
|
@ -196,6 +455,20 @@ func (m *Template) String() string { return proto.CompactTextString(m
|
|||
func (*Template) ProtoMessage() {}
|
||||
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
|
||||
|
||||
func (m *Template) GetID() string {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Template) GetTempVar() string {
|
||||
if m != nil {
|
||||
return m.TempVar
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Template) GetValues() []*TemplateValue {
|
||||
if m != nil {
|
||||
return m.Values
|
||||
|
@ -203,6 +476,20 @@ func (m *Template) GetValues() []*TemplateValue {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Template) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Template) GetLabel() string {
|
||||
if m != nil {
|
||||
return m.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Template) GetQuery() *TemplateQuery {
|
||||
if m != nil {
|
||||
return m.Query
|
||||
|
@ -221,6 +508,27 @@ func (m *TemplateValue) String() string { return proto.CompactTextStr
|
|||
func (*TemplateValue) ProtoMessage() {}
|
||||
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
|
||||
|
||||
func (m *TemplateValue) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TemplateValue) GetValue() string {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TemplateValue) GetSelected() bool {
|
||||
if m != nil {
|
||||
return m.Selected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type TemplateQuery struct {
|
||||
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
|
||||
Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"`
|
||||
|
@ -235,6 +543,48 @@ func (m *TemplateQuery) String() string { return proto.CompactTextStr
|
|||
func (*TemplateQuery) ProtoMessage() {}
|
||||
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
|
||||
|
||||
func (m *TemplateQuery) GetCommand() string {
|
||||
if m != nil {
|
||||
return m.Command
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TemplateQuery) GetDb() string {
|
||||
if m != nil {
|
||||
return m.Db
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TemplateQuery) GetRp() string {
|
||||
if m != nil {
|
||||
return m.Rp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TemplateQuery) GetMeasurement() string {
|
||||
if m != nil {
|
||||
return m.Measurement
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TemplateQuery) GetTagKey() string {
|
||||
if m != nil {
|
||||
return m.TagKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TemplateQuery) GetFieldKey() string {
|
||||
if m != nil {
|
||||
return m.FieldKey
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
|
@ -251,6 +601,62 @@ func (m *Server) String() string { return proto.CompactTextString(m)
|
|||
func (*Server) ProtoMessage() {}
|
||||
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
|
||||
|
||||
func (m *Server) GetID() int64 {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Server) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Server) GetUsername() string {
|
||||
if m != nil {
|
||||
return m.Username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Server) GetPassword() string {
|
||||
if m != nil {
|
||||
return m.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Server) GetURL() string {
|
||||
if m != nil {
|
||||
return m.URL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Server) GetSrcID() int64 {
|
||||
if m != nil {
|
||||
return m.SrcID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Server) GetActive() bool {
|
||||
if m != nil {
|
||||
return m.Active
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Server) GetOrganization() string {
|
||||
if m != nil {
|
||||
return m.Organization
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Layout struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"`
|
||||
|
@ -264,6 +670,27 @@ func (m *Layout) String() string { return proto.CompactTextString(m)
|
|||
func (*Layout) ProtoMessage() {}
|
||||
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
|
||||
|
||||
func (m *Layout) GetID() string {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Layout) GetApplication() string {
|
||||
if m != nil {
|
||||
return m.Application
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Layout) GetMeasurement() string {
|
||||
if m != nil {
|
||||
return m.Measurement
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Layout) GetCells() []*Cell {
|
||||
if m != nil {
|
||||
return m.Cells
|
||||
|
@ -271,6 +698,13 @@ func (m *Layout) GetCells() []*Cell {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Layout) GetAutoflow() bool {
|
||||
if m != nil {
|
||||
return m.Autoflow
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Cell struct {
|
||||
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
||||
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"`
|
||||
|
@ -279,7 +713,7 @@ type Cell struct {
|
|||
Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"`
|
||||
I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,omitempty"`
|
||||
Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Yranges []int64 `protobuf:"varint,8,rep,name=yranges" json:"yranges,omitempty"`
|
||||
Yranges []int64 `protobuf:"varint,8,rep,packed,name=yranges" json:"yranges,omitempty"`
|
||||
Ylabels []string `protobuf:"bytes,9,rep,name=ylabels" json:"ylabels,omitempty"`
|
||||
Type string `protobuf:"bytes,10,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Axes map[string]*Axis `protobuf:"bytes,11,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"`
|
||||
|
@ -290,6 +724,34 @@ func (m *Cell) String() string { return proto.CompactTextString(m) }
|
|||
func (*Cell) ProtoMessage() {}
|
||||
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
|
||||
|
||||
func (m *Cell) GetX() int32 {
|
||||
if m != nil {
|
||||
return m.X
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Cell) GetY() int32 {
|
||||
if m != nil {
|
||||
return m.Y
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Cell) GetW() int32 {
|
||||
if m != nil {
|
||||
return m.W
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Cell) GetH() int32 {
|
||||
if m != nil {
|
||||
return m.H
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Cell) GetQueries() []*Query {
|
||||
if m != nil {
|
||||
return m.Queries
|
||||
|
@ -297,6 +759,41 @@ func (m *Cell) GetQueries() []*Query {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Cell) GetI() string {
|
||||
if m != nil {
|
||||
return m.I
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Cell) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Cell) GetYranges() []int64 {
|
||||
if m != nil {
|
||||
return m.Yranges
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Cell) GetYlabels() []string {
|
||||
if m != nil {
|
||||
return m.Ylabels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Cell) GetType() string {
|
||||
if m != nil {
|
||||
return m.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Cell) GetAxes() map[string]*Axis {
|
||||
if m != nil {
|
||||
return m.Axes
|
||||
|
@ -321,6 +818,48 @@ func (m *Query) String() string { return proto.CompactTextString(m) }
|
|||
func (*Query) ProtoMessage() {}
|
||||
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
|
||||
|
||||
func (m *Query) GetCommand() string {
|
||||
if m != nil {
|
||||
return m.Command
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Query) GetDB() string {
|
||||
if m != nil {
|
||||
return m.DB
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Query) GetRP() string {
|
||||
if m != nil {
|
||||
return m.RP
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Query) GetGroupBys() []string {
|
||||
if m != nil {
|
||||
return m.GroupBys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Query) GetWheres() []string {
|
||||
if m != nil {
|
||||
return m.Wheres
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Query) GetLabel() string {
|
||||
if m != nil {
|
||||
return m.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Query) GetRange() *Range {
|
||||
if m != nil {
|
||||
return m.Range
|
||||
|
@ -328,6 +867,13 @@ func (m *Query) GetRange() *Range {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Query) GetSource() string {
|
||||
if m != nil {
|
||||
return m.Source
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Query) GetShifts() []*TimeShift {
|
||||
if m != nil {
|
||||
return m.Shifts
|
||||
|
@ -346,6 +892,27 @@ func (m *TimeShift) String() string { return proto.CompactTextString(
|
|||
func (*TimeShift) ProtoMessage() {}
|
||||
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} }
|
||||
|
||||
func (m *TimeShift) GetLabel() string {
|
||||
if m != nil {
|
||||
return m.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TimeShift) GetUnit() string {
|
||||
if m != nil {
|
||||
return m.Unit
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TimeShift) GetQuantity() string {
|
||||
if m != nil {
|
||||
return m.Quantity
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Range struct {
|
||||
Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"`
|
||||
Lower int64 `protobuf:"varint,2,opt,name=Lower,proto3" json:"Lower,omitempty"`
|
||||
|
@ -356,6 +923,20 @@ func (m *Range) String() string { return proto.CompactTextString(m) }
|
|||
func (*Range) ProtoMessage() {}
|
||||
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} }
|
||||
|
||||
func (m *Range) GetUpper() int64 {
|
||||
if m != nil {
|
||||
return m.Upper
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Range) GetLower() int64 {
|
||||
if m != nil {
|
||||
return m.Lower
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type AlertRule struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
JSON string `protobuf:"bytes,2,opt,name=JSON,proto3" json:"JSON,omitempty"`
|
||||
|
@ -368,6 +949,34 @@ func (m *AlertRule) String() string { return proto.CompactTextString(
|
|||
func (*AlertRule) ProtoMessage() {}
|
||||
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
|
||||
|
||||
func (m *AlertRule) GetID() string {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AlertRule) GetJSON() string {
|
||||
if m != nil {
|
||||
return m.JSON
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AlertRule) GetSrcID() int64 {
|
||||
if m != nil {
|
||||
return m.SrcID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *AlertRule) GetKapaID() int64 {
|
||||
if m != nil {
|
||||
return m.KapaID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
|
@ -382,6 +991,34 @@ func (m *User) String() string { return proto.CompactTextString(m) }
|
|||
func (*User) ProtoMessage() {}
|
||||
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} }
|
||||
|
||||
func (m *User) GetID() uint64 {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *User) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetProvider() string {
|
||||
if m != nil {
|
||||
return m.Provider
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetScheme() string {
|
||||
if m != nil {
|
||||
return m.Scheme
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *User) GetRoles() []*Role {
|
||||
if m != nil {
|
||||
return m.Roles
|
||||
|
@ -389,6 +1026,13 @@ func (m *User) GetRoles() []*Role {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *User) GetSuperAdmin() bool {
|
||||
if m != nil {
|
||||
return m.SuperAdmin
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
Organization string `protobuf:"bytes,1,opt,name=Organization,proto3" json:"Organization,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
|
@ -399,6 +1043,20 @@ func (m *Role) String() string { return proto.CompactTextString(m) }
|
|||
func (*Role) ProtoMessage() {}
|
||||
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} }
|
||||
|
||||
func (m *Role) GetOrganization() string {
|
||||
if m != nil {
|
||||
return m.Organization
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Role) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"`
|
||||
|
@ -411,6 +1069,34 @@ func (m *Organization) String() string { return proto.CompactTextStri
|
|||
func (*Organization) ProtoMessage() {}
|
||||
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} }
|
||||
|
||||
func (m *Organization) GetID() string {
|
||||
if m != nil {
|
||||
return m.ID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Organization) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Organization) GetDefaultRole() string {
|
||||
if m != nil {
|
||||
return m.DefaultRole
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Organization) GetPublic() bool {
|
||||
if m != nil {
|
||||
return m.Public
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Auth *AuthConfig `protobuf:"bytes,1,opt,name=Auth" json:"Auth,omitempty"`
|
||||
}
|
||||
|
@ -436,6 +1122,13 @@ func (m *AuthConfig) String() string { return proto.CompactTextString
|
|||
func (*AuthConfig) ProtoMessage() {}
|
||||
func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} }
|
||||
|
||||
func (m *AuthConfig) GetSuperAdminNewUsers() bool {
|
||||
if m != nil {
|
||||
return m.SuperAdminNewUsers
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"`
|
||||
Commit string `protobuf:"bytes,2,opt,name=Commit,proto3" json:"Commit,omitempty"`
|
||||
|
@ -446,6 +1139,20 @@ func (m *BuildInfo) String() string { return proto.CompactTextString(
|
|||
func (*BuildInfo) ProtoMessage() {}
|
||||
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} }
|
||||
|
||||
func (m *BuildInfo) GetVersion() string {
|
||||
if m != nil {
|
||||
return m.Version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *BuildInfo) GetCommit() string {
|
||||
if m != nil {
|
||||
return m.Commit
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Source)(nil), "internal.Source")
|
||||
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
|
||||
|
@ -475,7 +1182,7 @@ func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
|||
|
||||
var fileDescriptorInternal = []byte{
|
||||
// 1379 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44,
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44,
|
||||
0x10, 0x97, 0xe3, 0x38, 0x89, 0x27, 0xd7, 0x52, 0x99, 0x8a, 0x9a, 0x22, 0xa1, 0x60, 0x81, 0x08,
|
||||
0x82, 0x1e, 0xe8, 0x2a, 0x24, 0x84, 0xa0, 0x52, 0xee, 0x82, 0xca, 0xd1, 0x6b, 0x7b, 0xdd, 0xdc,
|
||||
0x1d, 0x4f, 0xa8, 0xda, 0x38, 0x93, 0xc4, 0xaa, 0x63, 0x9b, 0xb5, 0x7d, 0x17, 0xf3, 0x61, 0x90,
|
||||
|
|
|
@ -3,7 +3,7 @@ machine:
|
|||
services:
|
||||
- docker
|
||||
environment:
|
||||
DOCKER_TAG: chronograf-20180206
|
||||
DOCKER_TAG: chronograf-20180207
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
VERSION ?= $(shell git describe --always --tags)
|
||||
COMMIT ?= $(shell git rev-parse --short=8 HEAD)
|
||||
GDM := $(shell command -v gdm 2> /dev/null)
|
||||
GOBINDATA := $(shell go list -f {{.Root}} github.com/jteeuwen/go-bindata 2> /dev/null)
|
||||
GOBINDATA := $(shell go list -f {{.Root}} github.com/kevinburke/go-bindata 2> /dev/null)
|
||||
YARN := $(shell command -v yarn 2> /dev/null)
|
||||
|
||||
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go')
|
||||
|
@ -63,7 +63,7 @@ ifndef GDM
|
|||
endif
|
||||
ifndef GOBINDATA
|
||||
@echo "Installing go-bindata"
|
||||
go get -u github.com/jteeuwen/go-bindata/...
|
||||
go get -u github.com/kevinburke/go-bindata/...
|
||||
endif
|
||||
gdm restore
|
||||
@touch .godep
|
||||
|
|
|
@ -37,7 +37,7 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
|||
|
||||
# Install go
|
||||
ENV GOPATH /root/go
|
||||
ENV GO_VERSION 1.9.3
|
||||
ENV GO_VERSION 1.9.4
|
||||
ENV GO_ARCH amd64
|
||||
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
|
||||
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \
|
||||
|
|
|
@ -10,3 +10,6 @@ After updating the Dockerfile_build run
|
|||
|
||||
and push to quay with:
|
||||
`docker push quay.io/influxdb/builder:chronograf-$(date "+%Y%m%d")`
|
||||
|
||||
### Update circle
|
||||
Update DOCKER_TAG in circle.yml to the new container.
|
||||
|
|
|
@ -128,14 +128,6 @@ class AllUsersTable extends Component {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isCreatingUser
|
||||
? <AllUsersTableRowNew
|
||||
organizations={organizations}
|
||||
onBlur={this.handleBlurCreateUserRow}
|
||||
onCreateUser={onCreateUser}
|
||||
notify={notify}
|
||||
/>
|
||||
: null}
|
||||
{users.length
|
||||
? users.map(user =>
|
||||
<AllUsersTableRow
|
||||
|
@ -156,6 +148,14 @@ class AllUsersTable extends Component {
|
|||
<p>No Users to display</p>
|
||||
</th>
|
||||
</tr>}
|
||||
{isCreatingUser
|
||||
? <AllUsersTableRowNew
|
||||
organizations={organizations}
|
||||
onBlur={this.handleBlurCreateUserRow}
|
||||
onCreateUser={onCreateUser}
|
||||
notify={notify}
|
||||
/>
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,8 @@ const {
|
|||
colActions,
|
||||
} = ALL_USERS_TABLE
|
||||
|
||||
const nullOrganization = {id: null, name: 'None'}
|
||||
const nullOrganization = {id: undefined, name: 'None'}
|
||||
const nullRole = {name: '*', organization: undefined}
|
||||
|
||||
class AllUsersTableRowNew extends Component {
|
||||
constructor(props) {
|
||||
|
@ -21,11 +22,9 @@ class AllUsersTableRowNew extends Component {
|
|||
name: '',
|
||||
provider: '',
|
||||
scheme: 'oauth2',
|
||||
roles: [
|
||||
{
|
||||
...nullOrganization,
|
||||
},
|
||||
],
|
||||
role: {
|
||||
...nullRole,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,16 +34,17 @@ class AllUsersTableRowNew extends Component {
|
|||
|
||||
handleConfirmCreateUser = () => {
|
||||
const {onBlur, onCreateUser} = this.props
|
||||
const {name, provider, scheme, roles, superAdmin} = this.state
|
||||
|
||||
const {name, provider, scheme, role, superAdmin} = this.state
|
||||
const newUser = {
|
||||
name,
|
||||
provider,
|
||||
scheme,
|
||||
superAdmin,
|
||||
roles: roles[0].id === null ? [] : roles,
|
||||
// since you can only choose one organization, there is only one role in a new row
|
||||
// if no organization is selected ie the "None" organization,
|
||||
// then set roles to an empty array
|
||||
roles: role.organization === undefined ? [] : [role],
|
||||
}
|
||||
|
||||
onCreateUser(newUser)
|
||||
onBlur()
|
||||
}
|
||||
|
@ -54,17 +54,18 @@ class AllUsersTableRowNew extends Component {
|
|||
}
|
||||
|
||||
handleSelectOrganization = newOrganization => {
|
||||
const newRoles = [
|
||||
newOrganization.id === null
|
||||
// if "None" was selected for organization, create a "null role" from the predefined null role
|
||||
// else create a new role with the organization as the newOrganization's id
|
||||
const newRole =
|
||||
newOrganization.id === undefined
|
||||
? {
|
||||
...nullOrganization,
|
||||
...nullRole,
|
||||
}
|
||||
: {
|
||||
id: newOrganization.id,
|
||||
organization: newOrganization.id,
|
||||
name: '*', // '*' causes the server to determine the current defaultRole of the selected organization
|
||||
},
|
||||
]
|
||||
this.setState({roles: newRoles})
|
||||
}
|
||||
this.setState({role: newRole})
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
|
@ -88,7 +89,7 @@ class AllUsersTableRowNew extends Component {
|
|||
|
||||
render() {
|
||||
const {organizations, onBlur} = this.props
|
||||
const {name, provider, scheme, roles} = this.state
|
||||
const {name, provider, scheme, role} = this.state
|
||||
|
||||
const dropdownOrganizationsItems = [
|
||||
{...nullOrganization},
|
||||
|
@ -98,7 +99,7 @@ class AllUsersTableRowNew extends Component {
|
|||
text: o.name,
|
||||
}))
|
||||
const selectedRole = dropdownOrganizationsItems.find(
|
||||
o => roles[0].id === o.id
|
||||
o => role.organization === o.id
|
||||
)
|
||||
|
||||
const preventCreate = !name || !provider
|
||||
|
|
|
@ -24,7 +24,7 @@ class AllUsersPage extends Component {
|
|||
|
||||
handleCreateUser = user => {
|
||||
const {links, actionsAdmin: {createUserAsync}} = this.props
|
||||
createUserAsync(links.users, user)
|
||||
createUserAsync(links.allUsers, user)
|
||||
}
|
||||
|
||||
handleUpdateUserRoles = (user, roles, successMessage) => {
|
||||
|
|
|
@ -20,7 +20,7 @@ const adminChronograf = (state = initialState, action) => {
|
|||
|
||||
case 'CHRONOGRAF_ADD_USER': {
|
||||
const {user} = action.payload
|
||||
return {...state, users: [user, ...state.users]}
|
||||
return {...state, users: [...state.users, user]}
|
||||
}
|
||||
|
||||
case 'CHRONOGRAF_UPDATE_USER': {
|
||||
|
|
|
@ -84,11 +84,14 @@ class AlertTabs extends Component {
|
|||
type: 'success',
|
||||
text: `Alert configuration for ${section} successfully saved.`,
|
||||
})
|
||||
} catch (error) {
|
||||
return true
|
||||
} catch ({data: {error}}) {
|
||||
const errorMsg = _.join(_.drop(_.split(error, ': '), 2), ': ')
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
text: `There was an error saving the alert configuration for ${section}.`,
|
||||
text: `There was an error saving the alert configuration for ${section}: ${errorMsg}`,
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,11 +100,18 @@ class AlertTabs extends Component {
|
|||
e.preventDefault()
|
||||
|
||||
try {
|
||||
await testAlertOutput(this.props.kapacitor, section)
|
||||
this.props.addFlashMessage({
|
||||
type: 'success',
|
||||
text: `Successfully triggered an alert to ${section}. If the alert does not reach its destination, please check your configuration settings.`,
|
||||
})
|
||||
const {data} = await testAlertOutput(this.props.kapacitor, section)
|
||||
if (data.success) {
|
||||
this.props.addFlashMessage({
|
||||
type: 'success',
|
||||
text: `Successfully triggered an alert to ${section}. If the alert does not reach its destination, please check your configuration settings.`,
|
||||
})
|
||||
} else {
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
text: `There was an error sending an alert to ${section}: ${data.message}`,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
|
|
|
@ -25,7 +25,7 @@ class KapacitorRule extends Component {
|
|||
this.setState({timeRange})
|
||||
}
|
||||
|
||||
handleCreate = link => {
|
||||
handleCreate = pathname => {
|
||||
const {
|
||||
addFlashMessage,
|
||||
queryConfigs,
|
||||
|
@ -42,7 +42,7 @@ class KapacitorRule extends Component {
|
|||
|
||||
createRule(kapacitor, newRule)
|
||||
.then(() => {
|
||||
router.push(link || `/sources/${source.id}/alert-rules`)
|
||||
router.push(pathname || `/sources/${source.id}/alert-rules`)
|
||||
addFlashMessage({type: 'success', text: 'Rule successfully created'})
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -53,7 +53,7 @@ class KapacitorRule extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleEdit = link => {
|
||||
handleEdit = pathname => {
|
||||
const {addFlashMessage, queryConfigs, rule, router, source} = this.props
|
||||
const updatedRule = Object.assign({}, rule, {
|
||||
query: queryConfigs[rule.queryID],
|
||||
|
@ -61,7 +61,7 @@ class KapacitorRule extends Component {
|
|||
|
||||
editRule(updatedRule)
|
||||
.then(() => {
|
||||
router.push(link || `/sources/${source.id}/alert-rules`)
|
||||
router.push(pathname || `/sources/${source.id}/alert-rules`)
|
||||
addFlashMessage({
|
||||
type: 'success',
|
||||
text: `${rule.name} successfully saved!`,
|
||||
|
@ -75,16 +75,28 @@ class KapacitorRule extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
handleSave = () => {
|
||||
const {rule} = this.props
|
||||
if (rule.id === DEFAULT_RULE_ID) {
|
||||
this.handleCreate()
|
||||
} else {
|
||||
this.handleEdit()
|
||||
}
|
||||
}
|
||||
|
||||
handleSaveToConfig = configName => () => {
|
||||
const {rule, configLink, router} = this.props
|
||||
const pathname = `${configLink}#${configName}`
|
||||
if (this.validationError()) {
|
||||
router.push({
|
||||
pathname: `${configLink}#${configName}`,
|
||||
pathname,
|
||||
})
|
||||
} else if (rule.id === DEFAULT_RULE_ID) {
|
||||
this.handleCreate(configLink)
|
||||
return
|
||||
}
|
||||
if (rule.id === DEFAULT_RULE_ID) {
|
||||
this.handleCreate(pathname)
|
||||
} else {
|
||||
this.handleEdit(configLink)
|
||||
this.handleEdit(pathname)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,13 +171,12 @@ class KapacitorRule extends Component {
|
|||
} = this.props
|
||||
const {chooseTrigger, updateRuleValues} = ruleActions
|
||||
const {timeRange} = this.state
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<RuleHeader
|
||||
source={source}
|
||||
onSave={
|
||||
rule.id === DEFAULT_RULE_ID ? this.handleCreate : this.handleEdit
|
||||
}
|
||||
onSave={this.handleSave}
|
||||
validationError={this.validationError()}
|
||||
/>
|
||||
<FancyScrollbar className="page-contents fancy-scroll--kapacitor">
|
||||
|
|
|
@ -10,7 +10,7 @@ class AlertaConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -20,8 +20,10 @@ class AlertaConfig extends Component {
|
|||
url: this.url.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -12,7 +12,7 @@ class HipchatConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -21,8 +21,10 @@ class HipchatConfig extends Component {
|
|||
token: this.token.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -16,7 +16,7 @@ class OpsGenieConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -25,8 +25,10 @@ class OpsGenieConfig extends Component {
|
|||
recipients: this.state.currentRecipients,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -9,7 +9,7 @@ class PagerDutyConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -17,8 +17,10 @@ class PagerDutyConfig extends Component {
|
|||
url: this.url.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -13,7 +13,7 @@ class PushoverConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -22,8 +22,10 @@ class PushoverConfig extends Component {
|
|||
'user-key': this.userKey.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -8,19 +8,21 @@ class SMTPConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
host: this.host.value,
|
||||
port: this.port.value,
|
||||
from: this.from.value,
|
||||
to: this.to.value ? [this.to.value] : [],
|
||||
username: this.username.value,
|
||||
password: this.password.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
@ -28,7 +30,7 @@ class SMTPConfig extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {host, port, from, username, password} = this.props.config.options
|
||||
const {host, port, from, username, password, to} = this.props.config.options
|
||||
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
|
@ -56,7 +58,7 @@ class SMTPConfig extends Component {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group col-xs-12">
|
||||
<div className="form-group col-xs-6">
|
||||
<label htmlFor="smtp-from">From Email</label>
|
||||
<input
|
||||
className="form-control"
|
||||
|
@ -69,6 +71,19 @@ class SMTPConfig extends Component {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group col-xs-12 col-md-6">
|
||||
<label htmlFor="smtp-to">To Email</label>
|
||||
<input
|
||||
className="form-control"
|
||||
id="smtp-to"
|
||||
placeholder="email@domain.com"
|
||||
type="text"
|
||||
ref={r => (this.to = r)}
|
||||
defaultValue={to || ''}
|
||||
onChange={this.disableTest}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group col-xs-12 col-md-6">
|
||||
<label htmlFor="smtp-user">User</label>
|
||||
<input
|
||||
|
|
|
@ -8,7 +8,7 @@ class SensuConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -16,8 +16,10 @@ class SensuConfig extends Component {
|
|||
addr: this.addr.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -10,14 +10,16 @@ class SlackConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
const properties = {
|
||||
url: this.url.value,
|
||||
channel: this.channel.value,
|
||||
}
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
disableTest = () => {
|
||||
this.setState({testEnabled: false})
|
||||
|
|
|
@ -10,7 +10,7 @@ class TalkConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -18,8 +18,10 @@ class TalkConfig extends Component {
|
|||
author_name: this.author.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -12,7 +12,7 @@ class TelegramConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
let parseMode
|
||||
|
@ -31,8 +31,10 @@ class TelegramConfig extends Component {
|
|||
token: this.token.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -10,7 +10,7 @@ class VictorOpsConfig extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
handleSubmit = async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const properties = {
|
||||
|
@ -19,8 +19,10 @@ class VictorOpsConfig extends Component {
|
|||
url: this.url.value,
|
||||
}
|
||||
|
||||
this.props.onSave(properties)
|
||||
this.setState({testEnabled: true})
|
||||
const success = await this.props.onSave(properties)
|
||||
if (success) {
|
||||
this.setState({testEnabled: true})
|
||||
}
|
||||
}
|
||||
|
||||
disableTest = () => {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
#*
|
||||
*.[568]
|
||||
*.a
|
||||
*~
|
||||
[568].out
|
||||
_*
|
|
@ -1,13 +1,16 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.3.3
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- tip
|
||||
- 1.3.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- master
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
- go: master
|
||||
fast_finish: true
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
|
|
|
@ -88,5 +88,37 @@ Example:
|
|||
humanize.SI(0.00000000223, "M") // 2.23 nM
|
||||
```
|
||||
|
||||
## English-specific functions
|
||||
|
||||
The following functions are in the `humanize/english` subpackage.
|
||||
|
||||
### Plurals
|
||||
|
||||
Simple English pluralization
|
||||
|
||||
```go
|
||||
english.PluralWord(1, "object", "") // object
|
||||
english.PluralWord(42, "object", "") // objects
|
||||
english.PluralWord(2, "bus", "") // buses
|
||||
english.PluralWord(99, "locus", "loci") // loci
|
||||
|
||||
english.Plural(1, "object", "") // 1 object
|
||||
english.Plural(42, "object", "") // 42 objects
|
||||
english.Plural(2, "bus", "") // 2 buses
|
||||
english.Plural(99, "locus", "loci") // 99 loci
|
||||
```
|
||||
|
||||
### Word series
|
||||
|
||||
Format comma-separated words lists with conjuctions:
|
||||
|
||||
```go
|
||||
english.WordSeries([]string{"foo"}, "and") // foo
|
||||
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
||||
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
||||
|
||||
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
||||
```
|
||||
|
||||
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
func Comma(v int64) string {
|
||||
sign := ""
|
||||
|
||||
// minin64 can't be negated to a usable value, so it has to be special cased.
|
||||
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||
if v == math.MinInt64 {
|
||||
return "-9,223,372,036,854,775,808"
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnit
|
|||
}
|
||||
|
||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||
return magnitudes[i].D >= diff
|
||||
return magnitudes[i].D > diff
|
||||
})
|
||||
|
||||
if n >= len(magnitudes) {
|
||||
|
|
|
@ -37,6 +37,17 @@ func TestPast(t *testing.T) {
|
|||
}.validate(t)
|
||||
}
|
||||
|
||||
func TestReltimeOffbyone(t *testing.T) {
|
||||
testList{
|
||||
{"1w-1", RelTime(time.Unix(0, 0), time.Unix(7*24*60*60, -1), "ago", ""), "6 days ago"},
|
||||
{"1w±0", RelTime(time.Unix(0, 0), time.Unix(7*24*60*60, 0), "ago", ""), "1 week ago"},
|
||||
{"1w+1", RelTime(time.Unix(0, 0), time.Unix(7*24*60*60, 1), "ago", ""), "1 week ago"},
|
||||
{"2w-1", RelTime(time.Unix(0, 0), time.Unix(14*24*60*60, -1), "ago", ""), "1 week ago"},
|
||||
{"2w±0", RelTime(time.Unix(0, 0), time.Unix(14*24*60*60, 0), "ago", ""), "2 weeks ago"},
|
||||
{"2w+1", RelTime(time.Unix(0, 0), time.Unix(14*24*60*60, 1), "ago", ""), "2 weeks ago"},
|
||||
}.validate(t)
|
||||
}
|
||||
|
||||
func TestFuture(t *testing.T) {
|
||||
// Add a little time so that these things properly line up in
|
||||
// the future.
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
|
||||
install:
|
||||
- go get -v -d -t github.com/golang/protobuf/...
|
||||
- curl -L https://github.com/google/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip -o /tmp/protoc.zip
|
||||
- unzip /tmp/protoc.zip -d $HOME/protoc
|
||||
|
||||
env:
|
||||
- PATH=$HOME/protoc/bin:$PATH
|
||||
|
||||
script:
|
||||
- make all test
|
|
@ -1,5 +1,8 @@
|
|||
# Go support for Protocol Buffers
|
||||
|
||||
[![Build Status](https://travis-ci.org/golang/protobuf.svg?branch=master)](https://travis-ci.org/golang/protobuf)
|
||||
[![GoDoc](https://godoc.org/github.com/golang/protobuf?status.svg)](https://godoc.org/github.com/golang/protobuf)
|
||||
|
||||
Google's data interchange format.
|
||||
Copyright 2010 The Go Authors.
|
||||
https://github.com/golang/protobuf
|
||||
|
@ -22,7 +25,7 @@ To use this software, you must:
|
|||
for details or, if you are using gccgo, follow the instructions at
|
||||
https://golang.org/doc/install/gccgo
|
||||
- Grab the code from the repository and install the proto package.
|
||||
The simplest way is to run `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}`.
|
||||
The simplest way is to run `go get -u github.com/golang/protobuf/protoc-gen-go`.
|
||||
The compiler plugin, protoc-gen-go, will be installed in $GOBIN,
|
||||
defaulting to $GOPATH/bin. It must be in your $PATH for the protocol
|
||||
compiler, protoc, to find it.
|
||||
|
@ -104,12 +107,12 @@ for a protocol buffer variable v:
|
|||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Getters are only generated for message and oneof fields.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
Consider file test.proto, containing
|
||||
|
||||
```proto
|
||||
syntax = "proto2";
|
||||
package example;
|
||||
|
||||
enum FOO { X = 17; };
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// 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.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DiscardUnknown recursively discards all unknown fields from this message
|
||||
// and all embedded messages.
|
||||
//
|
||||
// When unmarshaling a message with unrecognized fields, the tags and values
|
||||
// of such fields are preserved in the Message. This allows a later call to
|
||||
// marshal to be able to produce a message that continues to have those
|
||||
// unrecognized fields. To avoid this, DiscardUnknown is used to
|
||||
// explicitly clear the unknown fields after unmarshaling.
|
||||
//
|
||||
// For proto2 messages, the unknown fields of message extensions are only
|
||||
// discarded from messages that have been accessed via GetExtension.
|
||||
func DiscardUnknown(m Message) {
|
||||
discardLegacy(m)
|
||||
}
|
||||
|
||||
func discardLegacy(m Message) {
|
||||
v := reflect.ValueOf(m)
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return
|
||||
}
|
||||
v = v.Elem()
|
||||
if v.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
t := v.Type()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
vf := v.Field(i)
|
||||
tf := f.Type
|
||||
|
||||
// Unwrap tf to get its most basic type.
|
||||
var isPointer, isSlice bool
|
||||
if tf.Kind() == reflect.Slice && tf.Elem().Kind() != reflect.Uint8 {
|
||||
isSlice = true
|
||||
tf = tf.Elem()
|
||||
}
|
||||
if tf.Kind() == reflect.Ptr {
|
||||
isPointer = true
|
||||
tf = tf.Elem()
|
||||
}
|
||||
if isPointer && isSlice && tf.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("%T.%s cannot be a slice of pointers to primitive types", m, f.Name))
|
||||
}
|
||||
|
||||
switch tf.Kind() {
|
||||
case reflect.Struct:
|
||||
switch {
|
||||
case !isPointer:
|
||||
panic(fmt.Sprintf("%T.%s cannot be a direct struct value", m, f.Name))
|
||||
case isSlice: // E.g., []*pb.T
|
||||
for j := 0; j < vf.Len(); j++ {
|
||||
discardLegacy(vf.Index(j).Interface().(Message))
|
||||
}
|
||||
default: // E.g., *pb.T
|
||||
discardLegacy(vf.Interface().(Message))
|
||||
}
|
||||
case reflect.Map:
|
||||
switch {
|
||||
case isPointer || isSlice:
|
||||
panic(fmt.Sprintf("%T.%s cannot be a pointer to a map or a slice of map values", m, f.Name))
|
||||
default: // E.g., map[K]V
|
||||
tv := vf.Type().Elem()
|
||||
if tv.Kind() == reflect.Ptr && tv.Implements(protoMessageType) { // Proto struct (e.g., *T)
|
||||
for _, key := range vf.MapKeys() {
|
||||
val := vf.MapIndex(key)
|
||||
discardLegacy(val.Interface().(Message))
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Interface:
|
||||
// Must be oneof field.
|
||||
switch {
|
||||
case isPointer || isSlice:
|
||||
panic(fmt.Sprintf("%T.%s cannot be a pointer to a interface or a slice of interface values", m, f.Name))
|
||||
default: // E.g., test_proto.isCommunique_Union interface
|
||||
if !vf.IsNil() && f.Tag.Get("protobuf_oneof") != "" {
|
||||
vf = vf.Elem() // E.g., *test_proto.Communique_Msg
|
||||
if !vf.IsNil() {
|
||||
vf = vf.Elem() // E.g., test_proto.Communique_Msg
|
||||
vf = vf.Field(0) // E.g., Proto struct (e.g., *T) or primitive value
|
||||
if vf.Kind() == reflect.Ptr {
|
||||
discardLegacy(vf.Interface().(Message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if vf := v.FieldByName("XXX_unrecognized"); vf.IsValid() {
|
||||
if vf.Type() != reflect.TypeOf([]byte{}) {
|
||||
panic("expected XXX_unrecognized to be of type []byte")
|
||||
}
|
||||
vf.Set(reflect.ValueOf([]byte(nil)))
|
||||
}
|
||||
|
||||
// For proto2 messages, only discard unknown fields in message extensions
|
||||
// that have been accessed via GetExtension.
|
||||
if em, ok := extendable(m); ok {
|
||||
// Ignore lock since discardLegacy is not concurrency safe.
|
||||
emm, _ := em.extensionsRead()
|
||||
for _, mx := range emm {
|
||||
if m, ok := mx.value.(Message); ok {
|
||||
discardLegacy(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -174,11 +174,11 @@ func sizeFixed32(x uint64) int {
|
|||
// This is the format used for the sint64 protocol buffer type.
|
||||
func (p *Buffer) EncodeZigzag64(x uint64) error {
|
||||
// use signed number to get arithmetic right shift.
|
||||
return p.EncodeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
return p.EncodeVarint((x << 1) ^ uint64((int64(x) >> 63)))
|
||||
}
|
||||
|
||||
func sizeZigzag64(x uint64) int {
|
||||
return sizeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
return sizeVarint((x << 1) ^ uint64((int64(x) >> 63)))
|
||||
}
|
||||
|
||||
// EncodeZigzag32 writes a zigzag-encoded 32-bit integer
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
|
|
|
@ -73,7 +73,6 @@ for a protocol buffer variable v:
|
|||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Getters are only generated for message and oneof fields.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
The simplest way to describe this is to see an example.
|
||||
|
|
|
@ -865,7 +865,7 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
|||
return p.readStruct(fv, terminator)
|
||||
case reflect.Uint32:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||
fv.SetUint(uint64(x))
|
||||
fv.SetUint(x)
|
||||
return nil
|
||||
}
|
||||
case reflect.Uint64:
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
This package is intended to be a more powerful and safer alternative to
|
||||
`reflect.DeepEqual` for comparing whether two values are semantically equal.
|
||||
|
||||
The primary features of cmp are:
|
||||
The primary features of `cmp` are:
|
||||
|
||||
* When the default behavior of equality does not suit the needs of the test,
|
||||
custom equality functions can override the equality operation.
|
||||
|
@ -20,8 +20,11 @@ The primary features of cmp are:
|
|||
* 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; they result in panics unless suppressed by using
|
||||
an `Ignore` option.
|
||||
fields are not compared by default; they result in panics unless suppressed
|
||||
by using an `Ignore` option (see `cmpopts.IgnoreUnexported`) or explictly
|
||||
compared using the `AllowUnexported` option.
|
||||
|
||||
See the [GoDoc documentation][godoc] for more information.
|
||||
|
||||
This is not an official Google product.
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"reflect"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
)
|
||||
|
||||
// SortSlices returns a Transformer option that sorts all []V.
|
||||
|
@ -26,7 +27,7 @@ import (
|
|||
// SortSlices can be used in conjuction with EquateEmpty.
|
||||
func SortSlices(less interface{}) cmp.Option {
|
||||
vf := reflect.ValueOf(less)
|
||||
if !isTTBoolFunc(vf.Type()) || vf.IsNil() {
|
||||
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
|
||||
panic(fmt.Sprintf("invalid less function: %T", less))
|
||||
}
|
||||
ss := sliceSorter{vf.Type().In(0), vf}
|
||||
|
@ -97,7 +98,7 @@ func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
|
|||
// SortMaps can be used in conjuction with EquateEmpty.
|
||||
func SortMaps(less interface{}) cmp.Option {
|
||||
vf := reflect.ValueOf(less)
|
||||
if !isTTBoolFunc(vf.Type()) || vf.IsNil() {
|
||||
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
|
||||
panic(fmt.Sprintf("invalid less function: %T", less))
|
||||
}
|
||||
ms := mapSorter{vf.Type().In(0), vf}
|
||||
|
@ -143,13 +144,3 @@ func (ms mapSorter) less(v reflect.Value, i, j int) bool {
|
|||
}
|
||||
return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
|
||||
}
|
||||
|
||||
var boolType = reflect.TypeOf(true)
|
||||
|
||||
// isTTBoolFunc reports whether f is of the form: func(T, T) bool.
|
||||
func isTTBoolFunc(t reflect.Type) bool {
|
||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||
return false
|
||||
}
|
||||
return t.NumIn() == 2 && t.NumOut() == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ package cmp
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/diff"
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
// BUG: Maps with keys containing NaN values cannot be properly compared due to
|
||||
|
@ -37,6 +41,8 @@ import (
|
|||
//
|
||||
// See https://golang.org/issue/11104 for more details.
|
||||
|
||||
var nothing = reflect.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:
|
||||
//
|
||||
|
@ -48,11 +54,10 @@ import (
|
|||
// 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 apply that transformer on the
|
||||
// current values and recursively call Equal on the transformed output values.
|
||||
// If S contains a single Comparer, then use that Comparer to determine whether
|
||||
// the current values are equal or not.
|
||||
// Otherwise, S is empty and evaluation proceeds to the next rule.
|
||||
// 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
|
||||
|
@ -79,7 +84,7 @@ import (
|
|||
func Equal(x, y interface{}, opts ...Option) bool {
|
||||
s := newState(opts)
|
||||
s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))
|
||||
return s.eq
|
||||
return s.result.Equal()
|
||||
}
|
||||
|
||||
// Diff returns a human-readable report of the differences between two values.
|
||||
|
@ -91,7 +96,7 @@ func Equal(x, y interface{}, opts ...Option) bool {
|
|||
// Do not depend on this output being stable.
|
||||
func Diff(x, y interface{}, opts ...Option) string {
|
||||
r := new(defaultReporter)
|
||||
opts = append(opts[:len(opts):len(opts)], r) // Force copy when appending
|
||||
opts = Options{Options(opts), r}
|
||||
eq := Equal(x, y, opts...)
|
||||
d := r.String()
|
||||
if (d == "") != eq {
|
||||
|
@ -101,48 +106,44 @@ func Diff(x, y interface{}, opts ...Option) string {
|
|||
}
|
||||
|
||||
type state struct {
|
||||
eq bool // Current result of comparison
|
||||
curPath Path // The current path in the value tree
|
||||
// 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
|
||||
reporter reporter // Optional reporter used for difference formatting
|
||||
|
||||
// dsCheck tracks the state needed to periodically perform checks that
|
||||
// user provided func(T, T) bool functions are symmetric and deterministic.
|
||||
//
|
||||
// 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.
|
||||
dsCheck struct{ curr, next int }
|
||||
// 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 map[reflect.Type]bool // Set of structs with unexported field visibility
|
||||
optsIgn []option // List of all ignore options without value filters
|
||||
opts []option // List of all other options
|
||||
reporter reporter // Optional reporter used for difference formatting
|
||||
opts Options // List of all fundamental and filter options
|
||||
}
|
||||
|
||||
func newState(opts []Option) *state {
|
||||
s := &state{eq: true}
|
||||
s := new(state)
|
||||
for _, opt := range opts {
|
||||
s.processOption(opt)
|
||||
}
|
||||
// Move Ignore options to the front so that they are evaluated first.
|
||||
for i, j := 0, 0; i < len(s.opts); i++ {
|
||||
if s.opts[i].op == nil {
|
||||
s.opts[i], s.opts[j] = s.opts[j], s.opts[i]
|
||||
j++
|
||||
}
|
||||
}
|
||||
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 visibleStructs:
|
||||
if s.exporters == nil {
|
||||
s.exporters = make(map[reflect.Type]bool)
|
||||
|
@ -150,15 +151,6 @@ func (s *state) processOption(opt Option) {
|
|||
for t := range opt {
|
||||
s.exporters[t] = true
|
||||
}
|
||||
case option:
|
||||
if opt.typeFilter == nil && len(opt.pathFilters)+len(opt.valueFilters) == 0 {
|
||||
panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt))
|
||||
}
|
||||
if opt.op == nil && len(opt.valueFilters) == 0 {
|
||||
s.optsIgn = append(s.optsIgn, opt)
|
||||
} else {
|
||||
s.opts = append(s.opts, opt)
|
||||
}
|
||||
case reporter:
|
||||
if s.reporter != nil {
|
||||
panic("difference reporter already registered")
|
||||
|
@ -169,6 +161,24 @@ func (s *state) processOption(opt Option) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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(vx, vy reflect.Value) diff.Result {
|
||||
// We do not save and restore the curPath because all of the compareX
|
||||
// methods should properly push and pop from the path.
|
||||
// It is an implementation bug if the contents of curPath differs from
|
||||
// when calling this function to when returning from it.
|
||||
|
||||
oldResult, oldReporter := s.result, s.reporter
|
||||
s.result = diff.Result{} // Reset result
|
||||
s.reporter = nil // Remove reporter to avoid spurious printouts
|
||||
s.compareAny(vx, vy)
|
||||
res := s.result
|
||||
s.result, s.reporter = oldResult, oldReporter
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *state) compareAny(vx, vy reflect.Value) {
|
||||
// TODO: Support cyclic data structures.
|
||||
|
||||
|
@ -184,10 +194,12 @@ func (s *state) compareAny(vx, vy reflect.Value) {
|
|||
t := vx.Type()
|
||||
if len(s.curPath) == 0 {
|
||||
s.curPath.push(&pathStep{typ: t})
|
||||
defer s.curPath.pop()
|
||||
}
|
||||
vx, vy = s.tryExporting(vx, vy)
|
||||
|
||||
// Rule 1: Check whether an option applies on this node in the value tree.
|
||||
if s.tryOptions(&vx, &vy, t) {
|
||||
if s.tryOptions(vx, vy, t) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -264,143 +276,144 @@ func (s *state) compareAny(vx, vy reflect.Value) {
|
|||
}
|
||||
}
|
||||
|
||||
// tryOptions iterates through all of the options and evaluates whether any
|
||||
// of them can be applied. This may modify the underlying values vx and vy
|
||||
// if an unexported field is being forcibly exported.
|
||||
func (s *state) tryOptions(vx, vy *reflect.Value, t reflect.Type) bool {
|
||||
// Try all ignore options that do not depend on the value first.
|
||||
// This avoids possible panics when processing unexported fields.
|
||||
for _, opt := range s.optsIgn {
|
||||
var v reflect.Value // Dummy value; should never be used
|
||||
if s.applyFilters(v, v, t, opt) {
|
||||
return true // Ignore option applied
|
||||
}
|
||||
}
|
||||
|
||||
// Since the values must be used after this point, verify that the values
|
||||
// are either exported or can be forcibly exported.
|
||||
func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) {
|
||||
if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
|
||||
if !sf.force {
|
||||
const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
|
||||
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
|
||||
if sf.force {
|
||||
// Use unsafe pointer arithmetic to get read-write access to an
|
||||
// unexported field in the struct.
|
||||
vx = unsafeRetrieveField(sf.pvx, sf.field)
|
||||
vy = unsafeRetrieveField(sf.pvy, sf.field)
|
||||
} else {
|
||||
// We are not allowed to export the value, so invalidate them
|
||||
// so that tryOptions can panic later if not explicitly ignored.
|
||||
vx = nothing
|
||||
vy = nothing
|
||||
}
|
||||
}
|
||||
return vx, vy
|
||||
}
|
||||
|
||||
// Use unsafe pointer arithmetic to get read-write access to an
|
||||
// unexported field in the struct.
|
||||
*vx = unsafeRetrieveField(sf.pvx, sf.field)
|
||||
*vy = unsafeRetrieveField(sf.pvy, sf.field)
|
||||
func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool {
|
||||
// If there were no FilterValues, we will not detect invalid inputs,
|
||||
// so manually check for them and append invalid if necessary.
|
||||
// We still evaluate the options since an ignore can override invalid.
|
||||
opts := s.opts
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
opts = Options{opts, invalid{}}
|
||||
}
|
||||
|
||||
// Try all other options now.
|
||||
optIdx := -1 // Index of Option to apply
|
||||
for i, opt := range s.opts {
|
||||
if !s.applyFilters(*vx, *vy, t, opt) {
|
||||
continue
|
||||
}
|
||||
if opt.op == nil {
|
||||
return true // Ignored comparison
|
||||
}
|
||||
if optIdx >= 0 {
|
||||
panic(fmt.Sprintf("ambiguous set of options at %#v\n\n%v\n\n%v\n", s.curPath, s.opts[optIdx], opt))
|
||||
}
|
||||
optIdx = i
|
||||
}
|
||||
if optIdx >= 0 {
|
||||
s.applyOption(*vx, *vy, t, s.opts[optIdx])
|
||||
return true
|
||||
// Evaluate all filters and apply the remaining options.
|
||||
if opt := opts.filter(s, vx, vy, t); opt != nil {
|
||||
return opt.apply(s, vx, vy)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) applyFilters(vx, vy reflect.Value, t reflect.Type, opt option) bool {
|
||||
if opt.typeFilter != nil {
|
||||
if !t.AssignableTo(opt.typeFilter) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, f := range opt.pathFilters {
|
||||
if !f(s.curPath) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, f := range opt.valueFilters {
|
||||
if !t.AssignableTo(f.in) || !s.callFunc(f.fnc, vx, vy) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *state) applyOption(vx, vy reflect.Value, t reflect.Type, opt option) {
|
||||
switch op := opt.op.(type) {
|
||||
case *transformer:
|
||||
vx = op.fnc.Call([]reflect.Value{vx})[0]
|
||||
vy = op.fnc.Call([]reflect.Value{vy})[0]
|
||||
s.curPath.push(&transform{pathStep{op.fnc.Type().Out(0)}, op})
|
||||
defer s.curPath.pop()
|
||||
s.compareAny(vx, vy)
|
||||
return
|
||||
case *comparer:
|
||||
eq := s.callFunc(op.fnc, vx, vy)
|
||||
s.report(eq, vx, vy)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
|
||||
// Check if this type even has an Equal method.
|
||||
m, ok := t.MethodByName("Equal")
|
||||
ft := functionType(m.Type)
|
||||
if !ok || (ft != equalFunc && ft != equalIfaceFunc) {
|
||||
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
|
||||
return false
|
||||
}
|
||||
|
||||
eq := s.callFunc(m.Func, vx, vy)
|
||||
eq := s.callTTBFunc(m.Func, vx, vy)
|
||||
s.report(eq, vx, vy)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *state) callFunc(f, x, y reflect.Value) bool {
|
||||
got := f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
if s.dsCheck.curr == s.dsCheck.next {
|
||||
// Swapping the input arguments is sufficient to check that
|
||||
// f is symmetric and deterministic.
|
||||
want := f.Call([]reflect.Value{y, x})[0].Bool()
|
||||
if got != want {
|
||||
fn := getFuncName(f.Pointer())
|
||||
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
|
||||
}
|
||||
s.dsCheck.curr = 0
|
||||
s.dsCheck.next++
|
||||
func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
|
||||
if !s.dynChecker.Next() {
|
||||
return f.Call([]reflect.Value{v})[0]
|
||||
}
|
||||
s.dsCheck.curr++
|
||||
return got
|
||||
|
||||
// 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)
|
||||
want := f.Call([]reflect.Value{v})[0]
|
||||
if got := <-c; !s.statelessCompare(got, want).Equal() {
|
||||
// To avoid false-positives with non-reflexive equality operations,
|
||||
// we sanity check whether a value is equal to itself.
|
||||
if !s.statelessCompare(want, want).Equal() {
|
||||
return want
|
||||
}
|
||||
fn := getFuncName(f.Pointer())
|
||||
panic(fmt.Sprintf("non-deterministic function detected: %s", fn))
|
||||
}
|
||||
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)
|
||||
want := f.Call([]reflect.Value{x, y})[0].Bool()
|
||||
if got := <-c; !got.IsValid() || got.Bool() != want {
|
||||
fn := getFuncName(f.Pointer())
|
||||
panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", fn))
|
||||
}
|
||||
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) compareArray(vx, vy reflect.Value, t reflect.Type) {
|
||||
step := &sliceIndex{pathStep{t.Elem()}, 0}
|
||||
step := &sliceIndex{pathStep{t.Elem()}, 0, 0}
|
||||
s.curPath.push(step)
|
||||
defer s.curPath.pop()
|
||||
|
||||
// Regardless of the lengths, we always try to compare the elements.
|
||||
// If one slice is longer, we will report the elements of the longer
|
||||
// slice as different (relative to an invalid reflect.Value).
|
||||
nmin := vx.Len()
|
||||
if nmin > vy.Len() {
|
||||
nmin = vy.Len()
|
||||
// Compute an edit-script for slices vx and vy.
|
||||
eq, es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
|
||||
step.xkey, step.ykey = ix, iy
|
||||
return s.statelessCompare(vx.Index(ix), vy.Index(iy))
|
||||
})
|
||||
|
||||
// Equal or no edit-script, so report entire slices as is.
|
||||
if eq || es == nil {
|
||||
s.curPath.pop() // Pop first since we are reporting the whole slice
|
||||
s.report(eq, vx, vy)
|
||||
return
|
||||
}
|
||||
for i := 0; i < nmin; i++ {
|
||||
step.key = i
|
||||
s.compareAny(vx.Index(i), vy.Index(i))
|
||||
}
|
||||
for i := nmin; i < vx.Len(); i++ {
|
||||
step.key = i
|
||||
s.report(false, vx.Index(i), reflect.Value{})
|
||||
}
|
||||
for i := nmin; i < vy.Len(); i++ {
|
||||
step.key = i
|
||||
s.report(false, reflect.Value{}, vy.Index(i))
|
||||
|
||||
// Replay the edit-script.
|
||||
var ix, iy int
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case diff.UniqueX:
|
||||
step.xkey, step.ykey = ix, -1
|
||||
s.report(false, vx.Index(ix), nothing)
|
||||
ix++
|
||||
case diff.UniqueY:
|
||||
step.xkey, step.ykey = -1, iy
|
||||
s.report(false, nothing, vy.Index(iy))
|
||||
iy++
|
||||
default:
|
||||
step.xkey, step.ykey = ix, iy
|
||||
if e == diff.Identity {
|
||||
s.report(true, vx.Index(ix), vy.Index(iy))
|
||||
} else {
|
||||
s.compareAny(vx.Index(ix), vy.Index(iy))
|
||||
}
|
||||
ix++
|
||||
iy++
|
||||
}
|
||||
}
|
||||
s.curPath.pop()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
|
||||
|
@ -414,7 +427,7 @@ func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
|
|||
step := &mapIndex{pathStep: pathStep{t.Elem()}}
|
||||
s.curPath.push(step)
|
||||
defer s.curPath.pop()
|
||||
for _, k := range sortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
|
||||
for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) {
|
||||
step.key = k
|
||||
vvx := vx.MapIndex(k)
|
||||
vvy := vy.MapIndex(k)
|
||||
|
@ -422,9 +435,9 @@ func (s *state) compareMap(vx, vy reflect.Value, t reflect.Type) {
|
|||
case vvx.IsValid() && vvy.IsValid():
|
||||
s.compareAny(vvx, vvy)
|
||||
case vvx.IsValid() && !vvy.IsValid():
|
||||
s.report(false, vvx, reflect.Value{})
|
||||
s.report(false, vvx, nothing)
|
||||
case !vvx.IsValid() && vvy.IsValid():
|
||||
s.report(false, reflect.Value{}, vvy)
|
||||
s.report(false, nothing, vvy)
|
||||
default:
|
||||
// It is possible for both vvx and vvy to be invalid if the
|
||||
// key contained a NaN value in it. There is no way in
|
||||
|
@ -470,12 +483,39 @@ func (s *state) compareStruct(vx, vy reflect.Value, t reflect.Type) {
|
|||
// report records the result of a single comparison.
|
||||
// It also calls Report if any reporter is registered.
|
||||
func (s *state) report(eq bool, vx, vy reflect.Value) {
|
||||
s.eq = s.eq && eq
|
||||
if eq {
|
||||
s.result.NSame++
|
||||
} else {
|
||||
s.result.NDiff++
|
||||
}
|
||||
if s.reporter != nil {
|
||||
s.reporter.Report(vx, vy, eq, s.curPath)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -487,33 +527,3 @@ func makeAddressable(v reflect.Value) reflect.Value {
|
|||
vc.Set(v)
|
||||
return vc
|
||||
}
|
||||
|
||||
type funcType int
|
||||
|
||||
const (
|
||||
invalidFunc funcType = iota
|
||||
equalFunc // func(T, T) bool
|
||||
equalIfaceFunc // func(T, I) bool
|
||||
transformFunc // func(T) R
|
||||
valueFilterFunc = equalFunc // func(T, T) bool
|
||||
)
|
||||
|
||||
var boolType = reflect.TypeOf(true)
|
||||
|
||||
// functionType identifies which type of function signature this is.
|
||||
func functionType(t reflect.Type) funcType {
|
||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||
return invalidFunc
|
||||
}
|
||||
ni, no := t.NumIn(), t.NumOut()
|
||||
switch {
|
||||
case ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType:
|
||||
return equalFunc // or valueFilterFunc
|
||||
case ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType:
|
||||
return equalIfaceFunc
|
||||
case ni == 1 && no == 1:
|
||||
return transformFunc
|
||||
default:
|
||||
return invalidFunc
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ package cmp_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
@ -16,53 +18,17 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
pb "github.com/google/go-cmp/cmp/internal/testprotos"
|
||||
ts "github.com/google/go-cmp/cmp/internal/teststructs"
|
||||
)
|
||||
|
||||
var now = time.Now()
|
||||
var boolType = reflect.TypeOf(true)
|
||||
var mutexType = reflect.TypeOf(sync.Mutex{})
|
||||
|
||||
func intPtr(n int) *int { return &n }
|
||||
|
||||
func equalRegexp(x, y *regexp.Regexp) bool {
|
||||
if x == nil || y == nil {
|
||||
return x == nil && y == nil
|
||||
}
|
||||
return x.String() == y.String()
|
||||
}
|
||||
|
||||
func IgnoreUnexported(typs ...interface{}) cmp.Option {
|
||||
m := make(map[reflect.Type]bool)
|
||||
for _, typ := range typs {
|
||||
t := reflect.TypeOf(typ)
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid struct type: %T", typ))
|
||||
}
|
||||
m[t] = true
|
||||
}
|
||||
return cmp.FilterPath(func(p cmp.Path) bool {
|
||||
if len(p) < 2 {
|
||||
return false
|
||||
}
|
||||
sf, ok := p[len(p)-1].(cmp.StructField)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return m[p[len(p)-2].Type()] && !isExported(sf.Name())
|
||||
}, cmp.Ignore())
|
||||
}
|
||||
|
||||
func isExported(id string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(id)
|
||||
return unicode.IsUpper(r)
|
||||
}
|
||||
|
||||
type test struct {
|
||||
label string // Test description
|
||||
x, y interface{} // Input values to compare
|
||||
|
@ -83,7 +49,8 @@ func TestDiff(t *testing.T) {
|
|||
tests = append(tests, project4Tests()...)
|
||||
|
||||
for _, tt := range tests {
|
||||
tRun(t, tt.label, func(t *testing.T) {
|
||||
tt := tt
|
||||
tRunParallel(t, tt.label, func(t *testing.T) {
|
||||
var gotDiff, gotPanic string
|
||||
func() {
|
||||
defer func() {
|
||||
|
@ -101,8 +68,8 @@ func TestDiff(t *testing.T) {
|
|||
if gotPanic != "" {
|
||||
t.Fatalf("unexpected panic message: %s", gotPanic)
|
||||
}
|
||||
if strings.TrimSpace(gotDiff) != strings.TrimSpace(tt.wantDiff) {
|
||||
t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s", gotDiff, tt.wantDiff)
|
||||
if got, want := strings.TrimSpace(gotDiff), strings.TrimSpace(tt.wantDiff); got != want {
|
||||
t.Fatalf("difference message:\ngot:\n%s\n\nwant:\n%s", got, want)
|
||||
}
|
||||
} else {
|
||||
if !strings.Contains(gotPanic, tt.wantPanic) {
|
||||
|
@ -117,10 +84,9 @@ func comparerTests() []test {
|
|||
const label = "Comparer"
|
||||
|
||||
return []test{{
|
||||
label: label,
|
||||
x: 1,
|
||||
y: 1,
|
||||
wantDiff: "",
|
||||
label: label,
|
||||
x: 1,
|
||||
y: 1,
|
||||
}, {
|
||||
label: label,
|
||||
x: 1,
|
||||
|
@ -147,7 +113,7 @@ func comparerTests() []test {
|
|||
cmp.Comparer(func(x, y int) bool { return true }),
|
||||
cmp.Transformer("", func(x int) float64 { return float64(x) }),
|
||||
},
|
||||
wantPanic: "ambiguous set of options",
|
||||
wantPanic: "ambiguous set of applicable options",
|
||||
}, {
|
||||
label: label,
|
||||
x: 1,
|
||||
|
@ -164,10 +130,9 @@ func comparerTests() []test {
|
|||
opts: []cmp.Option{struct{ cmp.Option }{}},
|
||||
wantPanic: "unknown option",
|
||||
}, {
|
||||
label: label,
|
||||
x: struct{ A, B, C int }{1, 2, 3},
|
||||
y: struct{ A, B, C int }{1, 2, 3},
|
||||
wantDiff: "",
|
||||
label: label,
|
||||
x: struct{ A, B, C int }{1, 2, 3},
|
||||
y: struct{ A, B, C int }{1, 2, 3},
|
||||
}, {
|
||||
label: label,
|
||||
x: struct{ A, B, C int }{1, 2, 3},
|
||||
|
@ -244,17 +209,29 @@ func comparerTests() []test {
|
|||
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||
wantPanic: "cannot handle unexported field",
|
||||
}, {
|
||||
label: label,
|
||||
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||
opts: []cmp.Option{cmp.Comparer(equalRegexp)},
|
||||
wantDiff: "",
|
||||
label: label,
|
||||
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
|
||||
if x == nil || y == nil {
|
||||
return x == nil && y == nil
|
||||
}
|
||||
return x.String() == y.String()
|
||||
})},
|
||||
}, {
|
||||
label: label,
|
||||
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
|
||||
opts: []cmp.Option{cmp.Comparer(equalRegexp)},
|
||||
wantDiff: "{[]*regexp.Regexp}[1]:\n\t-: \"a*b*c*\"\n\t+: \"a*b*d*\"\n",
|
||||
label: label,
|
||||
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
|
||||
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
|
||||
if x == nil || y == nil {
|
||||
return x == nil && y == nil
|
||||
}
|
||||
return x.String() == y.String()
|
||||
})},
|
||||
wantDiff: `
|
||||
{[]*regexp.Regexp}[1]:
|
||||
-: "a*b*c*"
|
||||
+: "a*b*d*"`,
|
||||
}, {
|
||||
label: label,
|
||||
x: func() ***int {
|
||||
|
@ -305,6 +282,22 @@ func comparerTests() []test {
|
|||
root:
|
||||
-: "hello"
|
||||
+: "hello2"`,
|
||||
}, {
|
||||
label: label,
|
||||
x: md5.Sum([]byte{'a'}),
|
||||
y: md5.Sum([]byte{'b'}),
|
||||
wantDiff: `
|
||||
{[16]uint8}:
|
||||
-: [16]uint8{0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61}
|
||||
+: [16]uint8{0x92, 0xeb, 0x5f, 0xfe, 0xe6, 0xae, 0x2f, 0xec, 0x3a, 0xd7, 0x1c, 0x77, 0x75, 0x31, 0x57, 0x8f}`,
|
||||
}, {
|
||||
label: label,
|
||||
x: new(fmt.Stringer),
|
||||
y: nil,
|
||||
wantDiff: `
|
||||
:
|
||||
-: &<nil>
|
||||
+: <non-existent>`,
|
||||
}, {
|
||||
label: label,
|
||||
x: make([]int, 1000),
|
||||
|
@ -325,6 +318,41 @@ root:
|
|||
}, cmp.Ignore()),
|
||||
},
|
||||
wantPanic: "non-deterministic or non-symmetric function detected",
|
||||
}, {
|
||||
label: label,
|
||||
x: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
|
||||
y: []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
|
||||
opts: []cmp.Option{
|
||||
cmp.Comparer(func(x, y int) bool {
|
||||
return x < y
|
||||
}),
|
||||
},
|
||||
wantPanic: "non-deterministic or non-symmetric function detected",
|
||||
}, {
|
||||
label: label,
|
||||
x: make([]string, 1000),
|
||||
y: make([]string, 1000),
|
||||
opts: []cmp.Option{
|
||||
cmp.Transformer("", func(x string) int {
|
||||
return rand.Int()
|
||||
}),
|
||||
},
|
||||
wantPanic: "non-deterministic function detected",
|
||||
}, {
|
||||
// Make sure the dynamic checks don't raise a false positive for
|
||||
// non-reflexive comparisons.
|
||||
label: label,
|
||||
x: make([]int, 10),
|
||||
y: make([]int, 10),
|
||||
opts: []cmp.Option{
|
||||
cmp.Transformer("", func(x int) float64 {
|
||||
return math.NaN()
|
||||
}),
|
||||
},
|
||||
wantDiff: `
|
||||
{[]int}:
|
||||
-: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
+: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}`,
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -352,7 +380,7 @@ func transformerTests() []test {
|
|||
cmp.Transformer("", func(in int) int { return in / 2 }),
|
||||
cmp.Transformer("", func(in int) int { return in }),
|
||||
},
|
||||
wantPanic: "ambiguous set of options",
|
||||
wantPanic: "ambiguous set of applicable options",
|
||||
}, {
|
||||
label: label,
|
||||
x: []int{0, -5, 0, -1},
|
||||
|
@ -383,7 +411,7 @@ func transformerTests() []test {
|
|||
if in == 0 {
|
||||
return "string"
|
||||
}
|
||||
return in
|
||||
return float64(in)
|
||||
}),
|
||||
},
|
||||
wantDiff: `
|
||||
|
@ -496,7 +524,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructA{},
|
||||
y: ts.ParentStructA{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructA{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructA{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructA",
|
||||
|
@ -532,7 +560,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructB{},
|
||||
y: ts.ParentStructB{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructB{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructB{}),
|
||||
},
|
||||
wantPanic: "cannot handle unexported field",
|
||||
}, {
|
||||
|
@ -540,8 +568,8 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructB{},
|
||||
y: ts.ParentStructB{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructB{}),
|
||||
IgnoreUnexported(ts.PublicStruct{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructB{}),
|
||||
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructB",
|
||||
|
@ -582,7 +610,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructC{},
|
||||
y: ts.ParentStructC{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructC{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructC{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructC",
|
||||
|
@ -624,7 +652,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructD{},
|
||||
y: ts.ParentStructD{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructD{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructD{}),
|
||||
},
|
||||
wantPanic: "cannot handle unexported field",
|
||||
}, {
|
||||
|
@ -632,8 +660,8 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructD{},
|
||||
y: ts.ParentStructD{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructD{}),
|
||||
IgnoreUnexported(ts.PublicStruct{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructD{}),
|
||||
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructD",
|
||||
|
@ -675,7 +703,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructE{},
|
||||
y: ts.ParentStructE{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructE{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructE{}),
|
||||
},
|
||||
wantPanic: "cannot handle unexported field",
|
||||
}, {
|
||||
|
@ -683,8 +711,8 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructE{},
|
||||
y: ts.ParentStructE{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructE{}),
|
||||
IgnoreUnexported(ts.PublicStruct{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructE{}),
|
||||
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructE",
|
||||
|
@ -734,7 +762,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructF{},
|
||||
y: ts.ParentStructF{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructF{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructF{}),
|
||||
},
|
||||
wantPanic: "cannot handle unexported field",
|
||||
}, {
|
||||
|
@ -742,8 +770,8 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructF{},
|
||||
y: ts.ParentStructF{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructF{}),
|
||||
IgnoreUnexported(ts.PublicStruct{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructF{}),
|
||||
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructF",
|
||||
|
@ -804,7 +832,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructG{},
|
||||
y: ts.ParentStructG{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructG{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructG{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructG",
|
||||
|
@ -849,7 +877,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructH{},
|
||||
y: ts.ParentStructH{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructH{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructH{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructH",
|
||||
|
@ -890,14 +918,14 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructI{},
|
||||
y: ts.ParentStructI{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructI{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructI",
|
||||
x: createStructI(0),
|
||||
y: createStructI(0),
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructI{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
|
||||
},
|
||||
wantPanic: "cannot handle unexported field",
|
||||
}, {
|
||||
|
@ -905,7 +933,7 @@ func embeddedTests() []test {
|
|||
x: createStructI(0),
|
||||
y: createStructI(0),
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructI",
|
||||
|
@ -952,7 +980,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructJ{},
|
||||
y: ts.ParentStructJ{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructJ{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
|
||||
},
|
||||
wantPanic: "cannot handle unexported field",
|
||||
}, {
|
||||
|
@ -960,7 +988,7 @@ func embeddedTests() []test {
|
|||
x: ts.ParentStructJ{},
|
||||
y: ts.ParentStructJ{},
|
||||
opts: []cmp.Option{
|
||||
IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
|
||||
cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
|
||||
},
|
||||
}, {
|
||||
label: label + "ParentStructJ",
|
||||
|
@ -1029,7 +1057,7 @@ func methodTests() []test {
|
|||
if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
|
||||
tf := m.Func.Type()
|
||||
return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
|
||||
tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == boolType
|
||||
tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
|
||||
}
|
||||
return false
|
||||
}, cmp.Transformer("Ref", func(x interface{}) interface{} {
|
||||
|
@ -1319,7 +1347,7 @@ func methodTests() []test {
|
|||
func project1Tests() []test {
|
||||
const label = "Project1"
|
||||
|
||||
ignoreUnexported := IgnoreUnexported(
|
||||
ignoreUnexported := cmpopts.IgnoreUnexported(
|
||||
ts.EagleImmutable{},
|
||||
ts.DreamerImmutable{},
|
||||
ts.SlapImmutable{},
|
||||
|
@ -1392,8 +1420,7 @@ func project1Tests() []test {
|
|||
y: ts.Eagle{Slaps: []ts.Slap{{
|
||||
Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
|
||||
}}},
|
||||
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
|
||||
wantDiff: "",
|
||||
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
|
||||
}, {
|
||||
label: label,
|
||||
x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
|
||||
|
@ -1430,15 +1457,15 @@ func project1Tests() []test {
|
|||
-: "southbay2"
|
||||
+: "southbay"
|
||||
*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State:
|
||||
-: 6
|
||||
+: 5
|
||||
-: testprotos.Goat_States(6)
|
||||
+: testprotos.Goat_States(5)
|
||||
{teststructs.Eagle}.Slaps[0].Immutable.MildSlap:
|
||||
-: false
|
||||
+: true
|
||||
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1]:
|
||||
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1->?]:
|
||||
-: "bar"
|
||||
+: <non-existent>
|
||||
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2]:
|
||||
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2->?]:
|
||||
-: "baz"
|
||||
+: <non-existent>`,
|
||||
}}
|
||||
|
@ -1524,14 +1551,11 @@ func project2Tests() []test {
|
|||
}(),
|
||||
opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
|
||||
wantDiff: `
|
||||
{teststructs.GermBatch}.DirtyGerms[18][0]:
|
||||
{teststructs.GermBatch}.DirtyGerms[18][0->?]:
|
||||
-: "germ2"
|
||||
+: "germ3"
|
||||
{teststructs.GermBatch}.DirtyGerms[18][1]:
|
||||
-: "germ3"
|
||||
+: "germ4"
|
||||
{teststructs.GermBatch}.DirtyGerms[18][2]:
|
||||
-: "germ4"
|
||||
+: <non-existent>
|
||||
{teststructs.GermBatch}.DirtyGerms[18][?->2]:
|
||||
-: <non-existent>
|
||||
+: "germ2"`,
|
||||
}, {
|
||||
label: label,
|
||||
|
@ -1562,7 +1586,7 @@ func project2Tests() []test {
|
|||
{teststructs.GermBatch}.DirtyGerms[17]:
|
||||
-: <non-existent>
|
||||
+: []*testprotos.Germ{"germ1"}
|
||||
{teststructs.GermBatch}.DirtyGerms[18][2]:
|
||||
{teststructs.GermBatch}.DirtyGerms[18][2->?]:
|
||||
-: "germ4"
|
||||
+: <non-existent>
|
||||
{teststructs.GermBatch}.DishMap[1]:
|
||||
|
@ -1579,9 +1603,7 @@ func project3Tests() []test {
|
|||
|
||||
allowVisibility := cmp.AllowUnexported(ts.Dirt{})
|
||||
|
||||
ignoreLocker := cmp.FilterPath(func(p cmp.Path) bool {
|
||||
return len(p) > 0 && p[len(p)-1].Type() == mutexType
|
||||
}, cmp.Ignore())
|
||||
ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
|
||||
|
||||
transformProtos := cmp.Transformer("", func(x pb.Dirt) *pb.Dirt {
|
||||
return &x
|
||||
|
@ -1647,8 +1669,8 @@ func project3Tests() []test {
|
|||
-: &teststructs.MockTable{state: []string{"a", "c"}}
|
||||
+: &teststructs.MockTable{state: []string{"a", "b", "c"}}
|
||||
{teststructs.Dirt}.Discord:
|
||||
-: 554
|
||||
+: 500
|
||||
-: teststructs.DiscordState(554)
|
||||
+: teststructs.DiscordState(500)
|
||||
λ({teststructs.Dirt}.Proto):
|
||||
-: "blah"
|
||||
+: "proto"
|
||||
|
@ -1736,14 +1758,8 @@ func project4Tests() []test {
|
|||
}(),
|
||||
opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
|
||||
wantDiff: `
|
||||
{teststructs.Cartel}.Headquarter.subDivisions[0]:
|
||||
{teststructs.Cartel}.Headquarter.subDivisions[0->?]:
|
||||
-: "alpha"
|
||||
+: "bravo"
|
||||
{teststructs.Cartel}.Headquarter.subDivisions[1]:
|
||||
-: "bravo"
|
||||
+: "charlie"
|
||||
{teststructs.Cartel}.Headquarter.subDivisions[2]:
|
||||
-: "charlie"
|
||||
+: <non-existent>
|
||||
{teststructs.Cartel}.Headquarter.publicMessage[2]:
|
||||
-: 0x03
|
||||
|
@ -1752,23 +1768,27 @@ func project4Tests() []test {
|
|||
-: 0x04
|
||||
+: 0x03
|
||||
{teststructs.Cartel}.poisons[0].poisonType:
|
||||
-: 1
|
||||
+: 5
|
||||
{teststructs.Cartel}.poisons[1]:
|
||||
-: &teststructs.Poison{poisonType: 2, manufactuer: "acme2"}
|
||||
-: testprotos.PoisonType(1)
|
||||
+: testprotos.PoisonType(5)
|
||||
{teststructs.Cartel}.poisons[1->?]:
|
||||
-: &teststructs.Poison{poisonType: testprotos.PoisonType(2), manufactuer: "acme2"}
|
||||
+: <non-existent>`,
|
||||
}}
|
||||
}
|
||||
|
||||
// TODO: Delete this hack when we drop Go1.6 support.
|
||||
func tRun(t *testing.T, name string, f func(t *testing.T)) {
|
||||
func tRunParallel(t *testing.T, name string, f func(t *testing.T)) {
|
||||
type runner interface {
|
||||
Run(string, func(t *testing.T)) bool
|
||||
}
|
||||
var ti interface{} = t
|
||||
if r, ok := ti.(runner); ok {
|
||||
r.Run(name, f)
|
||||
r.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f(t)
|
||||
})
|
||||
} else {
|
||||
// Cannot run sub-tests in parallel in Go1.6.
|
||||
t.Logf("Test: %s", name)
|
||||
f(t)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,110 @@ import (
|
|||
// fundamental options and filters and not in terms of what cool things you can
|
||||
// do with them since that overlaps with cmp/cmpopts.
|
||||
|
||||
// Use Diff for printing out human-readable errors for test cases comparing
|
||||
// nested or structured data.
|
||||
func ExampleDiff_testing() {
|
||||
// Code under test:
|
||||
type ShipManifest struct {
|
||||
Name string
|
||||
Crew map[string]string
|
||||
Androids int
|
||||
Stolen bool
|
||||
}
|
||||
|
||||
// AddCrew tries to add the given crewmember to the manifest.
|
||||
AddCrew := func(m *ShipManifest, name, title string) {
|
||||
if m.Crew == nil {
|
||||
m.Crew = make(map[string]string)
|
||||
}
|
||||
m.Crew[title] = name
|
||||
}
|
||||
|
||||
// Test function:
|
||||
tests := []struct {
|
||||
desc string
|
||||
before *ShipManifest
|
||||
name, title string
|
||||
after *ShipManifest
|
||||
}{
|
||||
{
|
||||
desc: "add to empty",
|
||||
before: &ShipManifest{},
|
||||
name: "Zaphod Beeblebrox",
|
||||
title: "Galactic President",
|
||||
after: &ShipManifest{
|
||||
Crew: map[string]string{
|
||||
"Zaphod Beeblebrox": "Galactic President",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "add another",
|
||||
before: &ShipManifest{
|
||||
Crew: map[string]string{
|
||||
"Zaphod Beeblebrox": "Galactic President",
|
||||
},
|
||||
},
|
||||
name: "Trillian",
|
||||
title: "Human",
|
||||
after: &ShipManifest{
|
||||
Crew: map[string]string{
|
||||
"Zaphod Beeblebrox": "Galactic President",
|
||||
"Trillian": "Human",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "overwrite",
|
||||
before: &ShipManifest{
|
||||
Crew: map[string]string{
|
||||
"Zaphod Beeblebrox": "Galactic President",
|
||||
},
|
||||
},
|
||||
name: "Zaphod Beeblebrox",
|
||||
title: "Just this guy, you know?",
|
||||
after: &ShipManifest{
|
||||
Crew: map[string]string{
|
||||
"Zaphod Beeblebrox": "Just this guy, you know?",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var t fakeT
|
||||
for _, test := range tests {
|
||||
AddCrew(test.before, test.name, test.title)
|
||||
if diff := cmp.Diff(test.before, test.after); diff != "" {
|
||||
t.Errorf("%s: after AddCrew, manifest differs: (-got +want)\n%s", test.desc, diff)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// add to empty: after AddCrew, manifest differs: (-got +want)
|
||||
// {*cmp_test.ShipManifest}.Crew["Galactic President"]:
|
||||
// -: "Zaphod Beeblebrox"
|
||||
// +: <non-existent>
|
||||
// {*cmp_test.ShipManifest}.Crew["Zaphod Beeblebrox"]:
|
||||
// -: <non-existent>
|
||||
// +: "Galactic President"
|
||||
//
|
||||
// add another: after AddCrew, manifest differs: (-got +want)
|
||||
// {*cmp_test.ShipManifest}.Crew["Human"]:
|
||||
// -: "Trillian"
|
||||
// +: <non-existent>
|
||||
// {*cmp_test.ShipManifest}.Crew["Trillian"]:
|
||||
// -: <non-existent>
|
||||
// +: "Human"
|
||||
//
|
||||
// overwrite: after AddCrew, manifest differs: (-got +want)
|
||||
// {*cmp_test.ShipManifest}.Crew["Just this guy, you know?"]:
|
||||
// -: "Zaphod Beeblebrox"
|
||||
// +: <non-existent>
|
||||
// {*cmp_test.ShipManifest}.Crew["Zaphod Beeblebrox"]:
|
||||
// -: "Galactic President"
|
||||
// +: "Just this guy, you know?"
|
||||
}
|
||||
|
||||
// Approximate equality for floats can be handled by defining a custom
|
||||
// comparer on floats that determines two values to be equal if they are within
|
||||
// some range of each other.
|
||||
|
@ -264,3 +368,7 @@ func ExampleOption_transformComplex() {
|
|||
// false
|
||||
// false
|
||||
}
|
||||
|
||||
type fakeT struct{}
|
||||
|
||||
func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) }
|
||||
|
|
17
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
17
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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.md file.
|
||||
|
||||
// +build !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() {}
|
122
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
122
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
|||
// 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.md file.
|
||||
|
||||
// +build debug
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The algorithm can be seen running in real-time by enabling debugging:
|
||||
// go test -tags=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,
|
||||
// seperated 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)
|
||||
}
|
|
@ -0,0 +1,373 @@
|
|||
// 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.md 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
|
||||
|
||||
// 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.
|
||||
// NSame is the number of sub-elements that are equal.
|
||||
// NDiff is the number of sub-elements that are not equal.
|
||||
type Result struct{ NSame, NDiff int }
|
||||
|
||||
// Equal indicates whether the symbols are equal. Two symbols are equal
|
||||
// if and only if NDiff == 0. If Equal, then they are also Similar.
|
||||
func (r Result) Equal() bool { return r.NDiff == 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 NSame to NDiff to determine similarity may change.
|
||||
func (r Result) Similar() bool {
|
||||
// Use NSame+1 to offset NSame so that binary comparisons are similar.
|
||||
return r.NSame+1 >= r.NDiff
|
||||
}
|
||||
|
||||
// Difference reports whether two lists of lengths nx and ny are equal
|
||||
// given the definition of equality provided as f.
|
||||
//
|
||||
// This function may return a edit-script, which is a sequence of operations
|
||||
// needed to convert one list into the other. If non-nil, 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) (eq bool, es EditScript) {
|
||||
es = searchGraph(nx, ny, f)
|
||||
st := es.stats()
|
||||
eq = len(es) == st.NI
|
||||
if !eq && st.NI < (nx+ny)/4 {
|
||||
return eq, nil // Edit-script more distracting than helpful
|
||||
}
|
||||
return eq, es
|
||||
}
|
||||
|
||||
func searchGraph(nx, ny int, f EqualFunc) 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)
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Running the tests with the "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)
|
||||
for {
|
||||
// Forward search from the beginning.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
break
|
||||
}
|
||||
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++
|
||||
}
|
||||
|
||||
// Reverse search from the end.
|
||||
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||
break
|
||||
}
|
||||
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--
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -0,0 +1,467 @@
|
|||
// 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.md file.
|
||||
|
||||
package diff
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func TestDifference(t *testing.T) {
|
||||
tests := []struct {
|
||||
// Before passing x and y to Difference, we strip all spaces so that
|
||||
// they can be used by the test author to indicate a missing symbol
|
||||
// in one of the lists.
|
||||
x, y string
|
||||
want string
|
||||
}{{
|
||||
x: "",
|
||||
y: "",
|
||||
want: "",
|
||||
}, {
|
||||
x: "#",
|
||||
y: "#",
|
||||
want: ".",
|
||||
}, {
|
||||
x: "##",
|
||||
y: "# ",
|
||||
want: ".X",
|
||||
}, {
|
||||
x: "a#",
|
||||
y: "A ",
|
||||
want: "MX",
|
||||
}, {
|
||||
x: "#a",
|
||||
y: " A",
|
||||
want: "XM",
|
||||
}, {
|
||||
x: "# ",
|
||||
y: "##",
|
||||
want: ".Y",
|
||||
}, {
|
||||
x: " #",
|
||||
y: "@#",
|
||||
want: "Y.",
|
||||
}, {
|
||||
x: "@#",
|
||||
y: " #",
|
||||
want: "X.",
|
||||
}, {
|
||||
x: "##########0123456789",
|
||||
y: " 0123456789",
|
||||
want: "XXXXXXXXXX..........",
|
||||
}, {
|
||||
x: " 0123456789",
|
||||
y: "##########0123456789",
|
||||
want: "YYYYYYYYYY..........",
|
||||
}, {
|
||||
x: "#####0123456789#####",
|
||||
y: " 0123456789 ",
|
||||
want: "XXXXX..........XXXXX",
|
||||
}, {
|
||||
x: " 0123456789 ",
|
||||
y: "#####0123456789#####",
|
||||
want: "YYYYY..........YYYYY",
|
||||
}, {
|
||||
x: "01234##########56789",
|
||||
y: "01234 56789",
|
||||
want: ".....XXXXXXXXXX.....",
|
||||
}, {
|
||||
x: "01234 56789",
|
||||
y: "01234##########56789",
|
||||
want: ".....YYYYYYYYYY.....",
|
||||
}, {
|
||||
x: "0123456789##########",
|
||||
y: "0123456789 ",
|
||||
want: "..........XXXXXXXXXX",
|
||||
}, {
|
||||
x: "0123456789 ",
|
||||
y: "0123456789##########",
|
||||
want: "..........YYYYYYYYYY",
|
||||
}, {
|
||||
x: "abcdefghij0123456789",
|
||||
y: "ABCDEFGHIJ0123456789",
|
||||
want: "MMMMMMMMMM..........",
|
||||
}, {
|
||||
x: "ABCDEFGHIJ0123456789",
|
||||
y: "abcdefghij0123456789",
|
||||
want: "MMMMMMMMMM..........",
|
||||
}, {
|
||||
x: "01234abcdefghij56789",
|
||||
y: "01234ABCDEFGHIJ56789",
|
||||
want: ".....MMMMMMMMMM.....",
|
||||
}, {
|
||||
x: "01234ABCDEFGHIJ56789",
|
||||
y: "01234abcdefghij56789",
|
||||
want: ".....MMMMMMMMMM.....",
|
||||
}, {
|
||||
x: "0123456789abcdefghij",
|
||||
y: "0123456789ABCDEFGHIJ",
|
||||
want: "..........MMMMMMMMMM",
|
||||
}, {
|
||||
x: "0123456789ABCDEFGHIJ",
|
||||
y: "0123456789abcdefghij",
|
||||
want: "..........MMMMMMMMMM",
|
||||
}, {
|
||||
x: "ABCDEFGHIJ0123456789 ",
|
||||
y: " 0123456789abcdefghij",
|
||||
want: "XXXXXXXXXX..........YYYYYYYYYY",
|
||||
}, {
|
||||
x: " 0123456789abcdefghij",
|
||||
y: "ABCDEFGHIJ0123456789 ",
|
||||
want: "YYYYYYYYYY..........XXXXXXXXXX",
|
||||
}, {
|
||||
x: "ABCDE0123456789 FGHIJ",
|
||||
y: " 0123456789abcdefghij",
|
||||
want: "XXXXX..........YYYYYMMMMM",
|
||||
}, {
|
||||
x: " 0123456789abcdefghij",
|
||||
y: "ABCDE0123456789 FGHIJ",
|
||||
want: "YYYYY..........XXXXXMMMMM",
|
||||
}, {
|
||||
x: "ABCDE01234F G H I J 56789 ",
|
||||
y: " 01234 a b c d e56789fghij",
|
||||
want: "XXXXX.....XYXYXYXYXY.....YYYYY",
|
||||
}, {
|
||||
x: " 01234a b c d e 56789fghij",
|
||||
y: "ABCDE01234 F G H I J56789 ",
|
||||
want: "YYYYY.....XYXYXYXYXY.....XXXXX",
|
||||
}, {
|
||||
x: "FGHIJ01234ABCDE56789 ",
|
||||
y: " 01234abcde56789fghij",
|
||||
want: "XXXXX.....MMMMM.....YYYYY",
|
||||
}, {
|
||||
x: " 01234abcde56789fghij",
|
||||
y: "FGHIJ01234ABCDE56789 ",
|
||||
want: "YYYYY.....MMMMM.....XXXXX",
|
||||
}, {
|
||||
x: "ABCAB BA ",
|
||||
y: " C BABAC",
|
||||
want: "XX.X.Y..Y",
|
||||
}, {
|
||||
x: "# #### ###",
|
||||
y: "#y####yy###",
|
||||
want: ".Y....YY...",
|
||||
}, {
|
||||
x: "# #### # ##x#x",
|
||||
y: "#y####y y## # ",
|
||||
want: ".Y....YXY..X.X",
|
||||
}, {
|
||||
x: "###z#z###### x #",
|
||||
y: "#y##Z#Z###### yy#",
|
||||
want: ".Y..M.M......XYY.",
|
||||
}, {
|
||||
x: "0 12z3x 456789 x x 0",
|
||||
y: "0y12Z3 y456789y y y0",
|
||||
want: ".Y..M.XY......YXYXY.",
|
||||
}, {
|
||||
x: "0 2 4 6 8 ..................abXXcdEXF.ghXi",
|
||||
y: " 1 3 5 7 9..................AB CDE F.GH I",
|
||||
want: "XYXYXYXYXY..................MMXXMM.X..MMXM",
|
||||
}, {
|
||||
x: "I HG.F EDC BA..................9 7 5 3 1 ",
|
||||
y: "iXhg.FXEdcXXba.................. 8 6 4 2 0",
|
||||
want: "MYMM..Y.MMYYMM..................XYXYXYXYXY",
|
||||
}, {
|
||||
x: "x1234",
|
||||
y: " 1234",
|
||||
want: "X....",
|
||||
}, {
|
||||
x: "x123x4",
|
||||
y: " 123 4",
|
||||
want: "X...X.",
|
||||
}, {
|
||||
x: "x1234x56",
|
||||
y: " 1234 ",
|
||||
want: "X....XXX",
|
||||
}, {
|
||||
x: "x1234xxx56",
|
||||
y: " 1234 56",
|
||||
want: "X....XXX..",
|
||||
}, {
|
||||
x: ".1234...ab",
|
||||
y: " 1234 AB",
|
||||
want: "X....XXXMM",
|
||||
}, {
|
||||
x: "x1234xxab.",
|
||||
y: " 1234 AB ",
|
||||
want: "X....XXMMX",
|
||||
}, {
|
||||
x: " 0123456789",
|
||||
y: "9012345678 ",
|
||||
want: "Y.........X",
|
||||
}, {
|
||||
x: " 0123456789",
|
||||
y: "8901234567 ",
|
||||
want: "YY........XX",
|
||||
}, {
|
||||
x: " 0123456789",
|
||||
y: "7890123456 ",
|
||||
want: "YYY.......XXX",
|
||||
}, {
|
||||
x: " 0123456789",
|
||||
y: "6789012345 ",
|
||||
want: "YYYY......XXXX",
|
||||
}, {
|
||||
x: "0123456789 ",
|
||||
y: " 5678901234",
|
||||
want: "XXXXX.....YYYYY",
|
||||
}, {
|
||||
x: "0123456789 ",
|
||||
y: " 4567890123",
|
||||
want: "XXXX......YYYY",
|
||||
}, {
|
||||
x: "0123456789 ",
|
||||
y: " 3456789012",
|
||||
want: "XXX.......YYY",
|
||||
}, {
|
||||
x: "0123456789 ",
|
||||
y: " 2345678901",
|
||||
want: "XX........YY",
|
||||
}, {
|
||||
x: "0123456789 ",
|
||||
y: " 1234567890",
|
||||
want: "X.........Y",
|
||||
}, {
|
||||
x: "0123456789",
|
||||
y: "9876543210",
|
||||
}, {
|
||||
x: "0123456789",
|
||||
y: "6725819034",
|
||||
}, {
|
||||
x: "FBQMOIGTLN72X90E4SP651HKRJUDA83CVZW",
|
||||
y: "5WHXO10R9IVKZLCTAJ8P3NSEQM472G6UBDF",
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
tRun(t, "", func(t *testing.T) {
|
||||
x := strings.Replace(tt.x, " ", "", -1)
|
||||
y := strings.Replace(tt.y, " ", "", -1)
|
||||
es := testStrings(t, x, y)
|
||||
if got := es.String(); got != tt.want {
|
||||
t.Errorf("Difference(%s, %s):\ngot %s\nwant %s", x, y, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifferenceFuzz(t *testing.T) {
|
||||
tests := []struct{ px, py, pm float32 }{
|
||||
{px: 0.0, py: 0.0, pm: 0.1},
|
||||
{px: 0.0, py: 0.1, pm: 0.0},
|
||||
{px: 0.1, py: 0.0, pm: 0.0},
|
||||
{px: 0.0, py: 0.1, pm: 0.1},
|
||||
{px: 0.1, py: 0.0, pm: 0.1},
|
||||
{px: 0.2, py: 0.2, pm: 0.2},
|
||||
{px: 0.3, py: 0.1, pm: 0.2},
|
||||
{px: 0.1, py: 0.3, pm: 0.2},
|
||||
{px: 0.2, py: 0.2, pm: 0.2},
|
||||
{px: 0.3, py: 0.3, pm: 0.3},
|
||||
{px: 0.1, py: 0.1, pm: 0.5},
|
||||
{px: 0.4, py: 0.1, pm: 0.5},
|
||||
{px: 0.3, py: 0.2, pm: 0.5},
|
||||
{px: 0.2, py: 0.3, pm: 0.5},
|
||||
{px: 0.1, py: 0.4, pm: 0.5},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
tRun(t, fmt.Sprintf("P%d", i), func(t *testing.T) {
|
||||
// Sweep from 1B to 1KiB.
|
||||
for n := 1; n <= 1024; n <<= 1 {
|
||||
tRun(t, fmt.Sprintf("N%d", n), func(t *testing.T) {
|
||||
for j := 0; j < 10; j++ {
|
||||
x, y := generateStrings(n, tt.px, tt.py, tt.pm, int64(j))
|
||||
testStrings(t, x, y)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkDifference(b *testing.B, n int) {
|
||||
// TODO: Use testing.B.Run when we drop Go1.6 support.
|
||||
x, y := generateStrings(n, 0.05, 0.05, 0.10, 0)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(x) + len(y)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
Difference(len(x), len(y), func(ix, iy int) Result {
|
||||
return compareByte(x[ix], y[iy])
|
||||
})
|
||||
}
|
||||
}
|
||||
func BenchmarkDifference1K(b *testing.B) { benchmarkDifference(b, 1<<10) }
|
||||
func BenchmarkDifference4K(b *testing.B) { benchmarkDifference(b, 1<<12) }
|
||||
func BenchmarkDifference16K(b *testing.B) { benchmarkDifference(b, 1<<14) }
|
||||
func BenchmarkDifference64K(b *testing.B) { benchmarkDifference(b, 1<<16) }
|
||||
func BenchmarkDifference256K(b *testing.B) { benchmarkDifference(b, 1<<18) }
|
||||
func BenchmarkDifference1M(b *testing.B) { benchmarkDifference(b, 1<<20) }
|
||||
|
||||
func generateStrings(n int, px, py, pm float32, seed int64) (string, string) {
|
||||
if px+py+pm > 1.0 {
|
||||
panic("invalid probabilities")
|
||||
}
|
||||
py += px
|
||||
pm += py
|
||||
|
||||
b := make([]byte, n)
|
||||
r := rand.New(rand.NewSource(seed))
|
||||
r.Read(b)
|
||||
|
||||
var x, y []byte
|
||||
for len(b) > 0 {
|
||||
switch p := r.Float32(); {
|
||||
case p < px: // UniqueX
|
||||
x = append(x, b[0])
|
||||
case p < py: // UniqueY
|
||||
y = append(y, b[0])
|
||||
case p < pm: // Modified
|
||||
x = append(x, 'A'+(b[0]%26))
|
||||
y = append(y, 'a'+(b[0]%26))
|
||||
default: // Identity
|
||||
x = append(x, b[0])
|
||||
y = append(y, b[0])
|
||||
}
|
||||
b = b[1:]
|
||||
}
|
||||
return string(x), string(y)
|
||||
}
|
||||
|
||||
func testStrings(t *testing.T, x, y string) EditScript {
|
||||
wantEq := x == y
|
||||
eq, es := Difference(len(x), len(y), func(ix, iy int) Result {
|
||||
return compareByte(x[ix], y[iy])
|
||||
})
|
||||
if eq != wantEq {
|
||||
t.Errorf("equality mismatch: got %v, want %v", eq, wantEq)
|
||||
}
|
||||
if es != nil {
|
||||
if es.LenX() != len(x) {
|
||||
t.Errorf("es.LenX = %d, want %d", es.LenX(), len(x))
|
||||
}
|
||||
if es.LenY() != len(y) {
|
||||
t.Errorf("es.LenY = %d, want %d", es.LenY(), len(y))
|
||||
}
|
||||
if got := (es.Dist() == 0); got != wantEq {
|
||||
t.Errorf("violation of equality invariant: got %v, want %v", got, wantEq)
|
||||
}
|
||||
if !validateScript(x, y, es) {
|
||||
t.Errorf("invalid edit script: %v", es)
|
||||
}
|
||||
}
|
||||
return es
|
||||
}
|
||||
|
||||
func validateScript(x, y string, es EditScript) bool {
|
||||
var bx, by []byte
|
||||
for _, e := range es {
|
||||
switch e {
|
||||
case Identity:
|
||||
if !compareByte(x[len(bx)], y[len(by)]).Equal() {
|
||||
return false
|
||||
}
|
||||
bx = append(bx, x[len(bx)])
|
||||
by = append(by, y[len(by)])
|
||||
case UniqueX:
|
||||
bx = append(bx, x[len(bx)])
|
||||
case UniqueY:
|
||||
by = append(by, y[len(by)])
|
||||
case Modified:
|
||||
if !compareByte(x[len(bx)], y[len(by)]).Similar() {
|
||||
return false
|
||||
}
|
||||
bx = append(bx, x[len(bx)])
|
||||
by = append(by, y[len(by)])
|
||||
}
|
||||
}
|
||||
return string(bx) == x && string(by) == y
|
||||
}
|
||||
|
||||
// compareByte returns a Result where the result is Equal if x == y,
|
||||
// similar if x and y differ only in casing, and different otherwise.
|
||||
func compareByte(x, y byte) (r Result) {
|
||||
switch {
|
||||
case x == y:
|
||||
return equalResult // Identity
|
||||
case unicode.ToUpper(rune(x)) == unicode.ToUpper(rune(y)):
|
||||
return similarResult // Modified
|
||||
default:
|
||||
return differentResult // UniqueX or UniqueY
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
equalResult = Result{NDiff: 0}
|
||||
similarResult = Result{NDiff: 1}
|
||||
differentResult = Result{NDiff: 2}
|
||||
)
|
||||
|
||||
func TestResult(t *testing.T) {
|
||||
tests := []struct {
|
||||
result Result
|
||||
wantEqual bool
|
||||
wantSimilar bool
|
||||
}{
|
||||
// equalResult is equal since NDiff == 0, by definition of Equal method.
|
||||
{equalResult, true, true},
|
||||
// similarResult is similar since it is a binary result where only one
|
||||
// element was compared (i.e., Either NSame==1 or NDiff==1).
|
||||
{similarResult, false, true},
|
||||
// differentResult is different since there are enough differences that
|
||||
// it isn't even considered similar.
|
||||
{differentResult, false, false},
|
||||
|
||||
// Zero value is always equal.
|
||||
{Result{NSame: 0, NDiff: 0}, true, true},
|
||||
|
||||
// Binary comparisons (where NSame+NDiff == 1) are always similar.
|
||||
{Result{NSame: 1, NDiff: 0}, true, true},
|
||||
{Result{NSame: 0, NDiff: 1}, false, true},
|
||||
|
||||
// More complex ratios. The exact ratio for similarity may change,
|
||||
// and may require updates to these test cases.
|
||||
{Result{NSame: 1, NDiff: 1}, false, true},
|
||||
{Result{NSame: 1, NDiff: 2}, false, true},
|
||||
{Result{NSame: 1, NDiff: 3}, false, false},
|
||||
{Result{NSame: 2, NDiff: 1}, false, true},
|
||||
{Result{NSame: 2, NDiff: 2}, false, true},
|
||||
{Result{NSame: 2, NDiff: 3}, false, true},
|
||||
{Result{NSame: 3, NDiff: 1}, false, true},
|
||||
{Result{NSame: 3, NDiff: 2}, false, true},
|
||||
{Result{NSame: 3, NDiff: 3}, false, true},
|
||||
{Result{NSame: 1000, NDiff: 0}, true, true},
|
||||
{Result{NSame: 1000, NDiff: 1}, false, true},
|
||||
{Result{NSame: 1000, NDiff: 2}, false, true},
|
||||
{Result{NSame: 0, NDiff: 1000}, false, false},
|
||||
{Result{NSame: 1, NDiff: 1000}, false, false},
|
||||
{Result{NSame: 2, NDiff: 1000}, false, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := tt.result.Equal(); got != tt.wantEqual {
|
||||
t.Errorf("%#v.Equal() = %v, want %v", tt.result, got, tt.wantEqual)
|
||||
}
|
||||
if got := tt.result.Similar(); got != tt.wantSimilar {
|
||||
t.Errorf("%#v.Similar() = %v, want %v", tt.result, got, tt.wantSimilar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Delete this hack when we drop Go1.6 support.
|
||||
func tRun(t *testing.T, name string, f func(t *testing.T)) {
|
||||
type runner interface {
|
||||
Run(string, func(t *testing.T)) bool
|
||||
}
|
||||
var ti interface{} = t
|
||||
if r, ok := ti.(runner); ok {
|
||||
r.Run(name, f)
|
||||
} else {
|
||||
t.Logf("Test: %s", name)
|
||||
f(t)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// 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.md file.
|
||||
|
||||
// Package function identifies function types.
|
||||
package function
|
||||
|
||||
import "reflect"
|
||||
|
||||
type funcType int
|
||||
|
||||
const (
|
||||
_ funcType = iota
|
||||
|
||||
ttbFunc // func(T, T) 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
|
||||
)
|
||||
|
||||
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 ttbFunc: // func(T, T) bool
|
||||
if ni == 2 && no == 1 && t.In(0) == t.In(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
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
// 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.md file.
|
||||
|
||||
// Package value provides functionality for reflect.Value types.
|
||||
package value
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// formatFakePointers controls whether to substitute pointer addresses with nil.
|
||||
// This is used for deterministic testing.
|
||||
var formatFakePointers = false
|
||||
|
||||
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
|
||||
// Format formats the value v as a string.
|
||||
//
|
||||
// This is similar to fmt.Sprintf("%+v", v) except this:
|
||||
// * Prints the type unless it can be elided
|
||||
// * Avoids printing struct fields that are zero
|
||||
// * Prints a nil-slice as being nil, not empty
|
||||
// * Prints map entries in deterministic order
|
||||
func Format(v reflect.Value, useStringer bool) string {
|
||||
return formatAny(v, formatConfig{useStringer, true, true, !formatFakePointers}, nil)
|
||||
}
|
||||
|
||||
type formatConfig struct {
|
||||
useStringer bool // Should the String method be used if available?
|
||||
printType bool // Should we print the type before the value?
|
||||
followPointers bool // Should we recursively follow pointers?
|
||||
realPointers bool // Should we print the real address of pointers?
|
||||
}
|
||||
|
||||
func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string {
|
||||
// TODO: Should this be a multi-line printout in certain situations?
|
||||
|
||||
if !v.IsValid() {
|
||||
return "<non-existent>"
|
||||
}
|
||||
if conf.useStringer && v.Type().Implements(stringerIface) && v.CanInterface() {
|
||||
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String())
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return formatPrimitive(v.Type(), v.Bool(), conf)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return formatPrimitive(v.Type(), v.Int(), conf)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
|
||||
// Unnamed uints are usually bytes or words, so use hexadecimal.
|
||||
return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
|
||||
}
|
||||
return formatPrimitive(v.Type(), v.Uint(), conf)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return formatPrimitive(v.Type(), v.Float(), conf)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return formatPrimitive(v.Type(), v.Complex(), conf)
|
||||
case reflect.String:
|
||||
return formatPrimitive(v.Type(), fmt.Sprintf("%q", v), conf)
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
return formatPointer(v, conf)
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("(%v)(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] || !conf.followPointers {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
return "&" + formatAny(v.Elem(), conf, visited)
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
return formatAny(v.Elem(), conf, visited)
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
s := formatAny(v.Index(i), subConf, visited)
|
||||
ss = append(ss, s)
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||
for _, k := range SortKeys(v.MapKeys()) {
|
||||
sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited)
|
||||
sv := formatAny(v.MapIndex(k), subConf, visited)
|
||||
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
case reflect.Struct:
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = true
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
vv := v.Field(i)
|
||||
if isZero(vv) {
|
||||
continue // Elide zero value fields
|
||||
}
|
||||
name := v.Type().Field(i).Name
|
||||
subConf.useStringer = conf.useStringer && isExported(name)
|
||||
s := formatAny(vv, subConf, visited)
|
||||
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func formatPrimitive(t reflect.Type, v interface{}, conf formatConfig) string {
|
||||
if conf.printType && t.PkgPath() != "" {
|
||||
return fmt.Sprintf("%v(%v)", t, v)
|
||||
}
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
func formatPointer(v reflect.Value, conf formatConfig) string {
|
||||
p := v.Pointer()
|
||||
if !conf.realPointers {
|
||||
p = 0 // For deterministic printing purposes
|
||||
}
|
||||
s := formatHex(uint64(p))
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// insertPointer insert p into m, allocating m if necessary.
|
||||
func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
|
||||
if m == nil {
|
||||
m = make(map[uintptr]bool)
|
||||
}
|
||||
m[p] = true
|
||||
return m
|
||||
}
|
||||
|
||||
// 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 v.Float() == 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return 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
|
||||
}
|
||||
|
||||
// isExported reports whether the identifier is exported.
|
||||
func isExported(id string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(id)
|
||||
return unicode.IsUpper(r)
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
// 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.md file.
|
||||
|
||||
package value
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
type key struct {
|
||||
a int
|
||||
b string
|
||||
c chan bool
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in interface{}
|
||||
want string
|
||||
}{{
|
||||
in: []int{},
|
||||
want: "[]int{}",
|
||||
}, {
|
||||
in: []int(nil),
|
||||
want: "[]int(nil)",
|
||||
}, {
|
||||
in: []int{1, 2, 3, 4, 5},
|
||||
want: "[]int{1, 2, 3, 4, 5}",
|
||||
}, {
|
||||
in: []interface{}{1, true, "hello", struct{ A, B int }{1, 2}},
|
||||
want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}",
|
||||
}, {
|
||||
in: []struct{ A, B int }{{1, 2}, {0, 4}, {}},
|
||||
want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}",
|
||||
}, {
|
||||
in: map[*int]string{new(int): "hello"},
|
||||
want: "map[*int]string{0x00: \"hello\"}",
|
||||
}, {
|
||||
in: map[key]string{{}: "hello"},
|
||||
want: "map[value.key]string{{}: \"hello\"}",
|
||||
}, {
|
||||
in: map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"},
|
||||
want: "map[value.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}",
|
||||
}, {
|
||||
in: map[io.Reader]string{new(bytes.Reader): "hello"},
|
||||
want: "map[io.Reader]string{0x00: \"hello\"}",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
var a = []interface{}{nil}
|
||||
a[0] = a
|
||||
return a
|
||||
}(),
|
||||
want: "[]interface {}{([]interface {})(0x00)}",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
type A *A
|
||||
var a A
|
||||
a = &a
|
||||
return a
|
||||
}(),
|
||||
want: "&(value.A)(0x00)",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
type A map[*A]A
|
||||
a := make(A)
|
||||
a[&a] = a
|
||||
return a
|
||||
}(),
|
||||
want: "value.A{0x00: 0x00}",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
var a [2]interface{}
|
||||
a[0] = &a
|
||||
return a
|
||||
}(),
|
||||
want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}",
|
||||
}}
|
||||
|
||||
formatFakePointers = true
|
||||
defer func() { formatFakePointers = false }()
|
||||
for i, tt := range tests {
|
||||
got := Format(reflect.ValueOf(tt.in), true)
|
||||
if got != tt.want {
|
||||
t.Errorf("test %d, Format():\ngot %q\nwant %q", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
// 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.md 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.Sort(valueSorter(vs))
|
||||
|
||||
// Deduplicate keys (fails for NaNs).
|
||||
vs2 := vs[:1]
|
||||
for _, v := range vs[1:] {
|
||||
if v.Interface() != vs2[len(vs2)-1].Interface() {
|
||||
vs2 = append(vs2, v)
|
||||
}
|
||||
}
|
||||
return vs2
|
||||
}
|
||||
|
||||
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
|
||||
type valueSorter []reflect.Value
|
||||
|
||||
func (vs valueSorter) Len() int { return len(vs) }
|
||||
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
|
||||
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||
|
||||
// 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:
|
||||
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()))
|
||||
}
|
||||
}
|
|
@ -2,93 +2,17 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE.md file.
|
||||
|
||||
package cmp
|
||||
package value_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
func TestFormatAny(t *testing.T) {
|
||||
type key struct {
|
||||
a int
|
||||
b string
|
||||
c chan bool
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in interface{}
|
||||
want string
|
||||
}{{
|
||||
in: []int{},
|
||||
want: "[]int{}",
|
||||
}, {
|
||||
in: []int(nil),
|
||||
want: "[]int(nil)",
|
||||
}, {
|
||||
in: []int{1, 2, 3, 4, 5},
|
||||
want: "[]int{1, 2, 3, 4, 5}",
|
||||
}, {
|
||||
in: []interface{}{1, true, "hello", struct{ A, B int }{1, 2}},
|
||||
want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}",
|
||||
}, {
|
||||
in: []struct{ A, B int }{{1, 2}, {0, 4}, {}},
|
||||
want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}",
|
||||
}, {
|
||||
in: map[*int]string{new(int): "hello"},
|
||||
want: "map[*int]string{0x00: \"hello\"}",
|
||||
}, {
|
||||
in: map[key]string{{}: "hello"},
|
||||
want: "map[cmp.key]string{{}: \"hello\"}",
|
||||
}, {
|
||||
in: map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"},
|
||||
want: "map[cmp.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}",
|
||||
}, {
|
||||
in: map[io.Reader]string{new(bytes.Reader): "hello"},
|
||||
want: "map[io.Reader]string{0x00: \"hello\"}",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
var a = []interface{}{nil}
|
||||
a[0] = a
|
||||
return a
|
||||
}(),
|
||||
want: "[]interface {}{([]interface {})(0x00)}",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
type A *A
|
||||
var a A
|
||||
a = &a
|
||||
return a
|
||||
}(),
|
||||
want: "&(cmp.A)(0x00)",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
type A map[*A]A
|
||||
a := make(A)
|
||||
a[&a] = a
|
||||
return a
|
||||
}(),
|
||||
want: "cmp.A{0x00: 0x00}",
|
||||
}, {
|
||||
in: func() interface{} {
|
||||
var a [2]interface{}
|
||||
a[0] = &a
|
||||
return a
|
||||
}(),
|
||||
want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}",
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
got := formatAny(reflect.ValueOf(tt.in), formatConfig{true, true, true, false}, nil)
|
||||
if got != tt.want {
|
||||
t.Errorf("test %d, pretty print:\ngot %q\nwant %q", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortKeys(t *testing.T) {
|
||||
type (
|
||||
MyString string
|
||||
|
@ -101,14 +25,14 @@ func TestSortKeys(t *testing.T) {
|
|||
EmptyStruct struct{}
|
||||
)
|
||||
|
||||
opts := []Option{
|
||||
Comparer(func(x, y float64) bool {
|
||||
opts := []cmp.Option{
|
||||
cmp.Comparer(func(x, y float64) bool {
|
||||
if math.IsNaN(x) && math.IsNaN(y) {
|
||||
return true
|
||||
}
|
||||
return x == y
|
||||
}),
|
||||
Comparer(func(x, y complex128) bool {
|
||||
cmp.Comparer(func(x, y complex128) bool {
|
||||
rx, ix, ry, iy := real(x), imag(x), real(y), imag(y)
|
||||
if math.IsNaN(rx) && math.IsNaN(ry) {
|
||||
rx, ry = 0, 0
|
||||
|
@ -118,11 +42,11 @@ func TestSortKeys(t *testing.T) {
|
|||
}
|
||||
return rx == ry && ix == iy
|
||||
}),
|
||||
Comparer(func(x, y chan bool) bool { return true }),
|
||||
Comparer(func(x, y chan int) bool { return true }),
|
||||
Comparer(func(x, y chan float64) bool { return true }),
|
||||
Comparer(func(x, y chan interface{}) bool { return true }),
|
||||
Comparer(func(x, y *int) bool { return true }),
|
||||
cmp.Comparer(func(x, y chan bool) bool { return true }),
|
||||
cmp.Comparer(func(x, y chan int) bool { return true }),
|
||||
cmp.Comparer(func(x, y chan float64) bool { return true }),
|
||||
cmp.Comparer(func(x, y chan interface{}) bool { return true }),
|
||||
cmp.Comparer(func(x, y *int) bool { return true }),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
@ -188,7 +112,8 @@ func TestSortKeys(t *testing.T) {
|
|||
[2]int{2, 3}, [2]int{2, 4}, [2]int{4, 0}, MyArray([2]int{2, 4}),
|
||||
make(chan bool), make(chan bool), make(chan int), make(chan interface{}),
|
||||
new(int), new(int),
|
||||
MyString("abc"), MyString("abcd"), MyString("abcde"), "abc", "abcd", "abcde", "bar", "foo",
|
||||
"abc", "abcd", "abcde", "bar", "foo",
|
||||
MyString("abc"), MyString("abcd"), MyString("abcde"),
|
||||
EmptyStruct{},
|
||||
MyStruct{"alpha", [2]int{3, 3}, make(chan float64)},
|
||||
MyStruct{"bravo", [2]int{2, 3}, make(chan float64)},
|
||||
|
@ -217,11 +142,11 @@ func TestSortKeys(t *testing.T) {
|
|||
for i, tt := range tests {
|
||||
keys := append(reflect.ValueOf(tt.in).MapKeys(), reflect.ValueOf(tt.in).MapKeys()...)
|
||||
var got []interface{}
|
||||
for _, k := range sortKeys(keys) {
|
||||
for _, k := range value.SortKeys(keys) {
|
||||
got = append(got, k.Interface())
|
||||
}
|
||||
if !Equal(got, tt.want, opts...) {
|
||||
t.Errorf("test %d, output mismatch:\ngot %#v\nwant %#v", i, got, tt.want)
|
||||
if d := cmp.Diff(got, tt.want, opts...); d != "" {
|
||||
t.Errorf("test %d, Sort() mismatch (-got +want):\n%s", i, d)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,8 @@ import (
|
|||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp/internal/function"
|
||||
)
|
||||
|
||||
// Option configures for specific behavior of Equal and Diff. In particular,
|
||||
|
@ -21,11 +23,38 @@ import (
|
|||
// The cmp/cmpopts package provides helper functions for creating options that
|
||||
// may be used with Equal and Diff.
|
||||
type Option interface {
|
||||
// Prevent Option from being equivalent to interface{}, which provides
|
||||
// a small type checking benefit by preventing Equal(opt, x, y).
|
||||
option()
|
||||
// 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, vx, vy reflect.Value, t reflect.Type) applicableOption
|
||||
}
|
||||
|
||||
// applicableOption represents the following types:
|
||||
// Fundamental: ignore | invalid | *comparer | *transformer
|
||||
// Grouping: Options
|
||||
type applicableOption interface {
|
||||
Option
|
||||
|
||||
// apply executes the option and reports whether the option was applied.
|
||||
// Each option may mutate s.
|
||||
apply(s *state, vx, vy reflect.Value) bool
|
||||
}
|
||||
|
||||
// coreOption represents the following types:
|
||||
// Fundamental: ignore | invalid | *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,
|
||||
|
@ -35,79 +64,44 @@ type Option interface {
|
|||
// on all individual options held within.
|
||||
type Options []Option
|
||||
|
||||
func (Options) option() {}
|
||||
|
||||
type (
|
||||
pathFilter func(Path) bool
|
||||
valueFilter struct {
|
||||
in reflect.Type // T
|
||||
fnc reflect.Value // func(T, T) bool
|
||||
}
|
||||
)
|
||||
|
||||
type option struct {
|
||||
typeFilter reflect.Type
|
||||
pathFilters []pathFilter
|
||||
valueFilters []valueFilter
|
||||
|
||||
// op is the operation to perform. If nil, then this acts as an ignore.
|
||||
op interface{} // nil | *transformer | *comparer
|
||||
}
|
||||
|
||||
func (option) option() {}
|
||||
|
||||
func (o option) String() string {
|
||||
// TODO: Add information about the caller?
|
||||
// TODO: Maintain the order that filters were added?
|
||||
|
||||
var ss []string
|
||||
switch op := o.op.(type) {
|
||||
case *transformer:
|
||||
fn := getFuncName(op.fnc.Pointer())
|
||||
ss = append(ss, fmt.Sprintf("Transformer(%s, %s)", op.name, fn))
|
||||
case *comparer:
|
||||
fn := getFuncName(op.fnc.Pointer())
|
||||
ss = append(ss, fmt.Sprintf("Comparer(%s)", fn))
|
||||
default:
|
||||
ss = append(ss, "Ignore()")
|
||||
}
|
||||
|
||||
for _, f := range o.pathFilters {
|
||||
fn := getFuncName(reflect.ValueOf(f).Pointer())
|
||||
ss = append(ss, fmt.Sprintf("FilterPath(%s)", fn))
|
||||
}
|
||||
for _, f := range o.valueFilters {
|
||||
fn := getFuncName(f.fnc.Pointer())
|
||||
ss = append(ss, fmt.Sprintf("FilterValues(%s)", fn))
|
||||
}
|
||||
return strings.Join(ss, "\n\t")
|
||||
}
|
||||
|
||||
// getFuncName returns a short function name from the pointer.
|
||||
// The string parsing logic works up until Go1.9.
|
||||
func getFuncName(p uintptr) string {
|
||||
fnc := runtime.FuncForPC(p)
|
||||
if fnc == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
|
||||
if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
|
||||
// Strip the package name from method name.
|
||||
name = strings.TrimSuffix(name, ")-fm")
|
||||
name = strings.TrimSuffix(name, ")·fm")
|
||||
if i := strings.LastIndexByte(name, '('); i >= 0 {
|
||||
methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
|
||||
if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
|
||||
methodName = methodName[j+1:] // E.g., "myfunc"
|
||||
func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) {
|
||||
for _, opt := range opts {
|
||||
switch opt := opt.filter(s, vx, vy, t); opt.(type) {
|
||||
case ignore:
|
||||
return ignore{} // Only ignore can short-circuit evaluation
|
||||
case invalid:
|
||||
out = invalid{} // Takes precedence over comparer or transformer
|
||||
case *comparer, *transformer, Options:
|
||||
switch out.(type) {
|
||||
case nil:
|
||||
out = opt
|
||||
case invalid:
|
||||
// Keep invalid
|
||||
case *comparer, *transformer, Options:
|
||||
out = Options{out, opt} // Conflicting comparers or transformers
|
||||
}
|
||||
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
|
||||
}
|
||||
}
|
||||
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
||||
// Strip the package name.
|
||||
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
|
||||
return out
|
||||
}
|
||||
|
||||
func (opts Options) apply(s *state, _, _ reflect.Value) bool {
|
||||
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))
|
||||
}
|
||||
return name
|
||||
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
|
||||
|
@ -119,20 +113,28 @@ func FilterPath(f func(Path) bool, opt Option) Option {
|
|||
if f == nil {
|
||||
panic("invalid path filter function")
|
||||
}
|
||||
switch opt := opt.(type) {
|
||||
case Options:
|
||||
var opts []Option
|
||||
for _, o := range opt {
|
||||
opts = append(opts, FilterPath(f, o)) // Append to slice copy
|
||||
}
|
||||
return Options(opts)
|
||||
case option:
|
||||
n := len(opt.pathFilters)
|
||||
opt.pathFilters = append(opt.pathFilters[:n:n], f) // Append to copy
|
||||
return opt
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option type: %T", opt))
|
||||
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, vx, vy reflect.Value, t reflect.Type) applicableOption {
|
||||
if f.fnc(s.curPath) {
|
||||
return f.opt.filter(s, vx, vy, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f pathFilter) String() string {
|
||||
fn := getFuncName(reflect.ValueOf(f.fnc).Pointer())
|
||||
return fmt.Sprintf("FilterPath(%s, %v)", fn, f.opt)
|
||||
}
|
||||
|
||||
// FilterValues returns a new Option where opt is only evaluated if filter f,
|
||||
|
@ -150,31 +152,61 @@ func FilterPath(f func(Path) bool, opt Option) Option {
|
|||
// a previously filtered Option.
|
||||
func FilterValues(f interface{}, opt Option) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if functionType(v.Type()) != valueFilterFunc || v.IsNil() {
|
||||
if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid values filter function: %T", f))
|
||||
}
|
||||
switch opt := opt.(type) {
|
||||
case Options:
|
||||
var opts []Option
|
||||
for _, o := range opt {
|
||||
opts = append(opts, FilterValues(f, o)) // Append to slice copy
|
||||
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 Options(opts)
|
||||
case option:
|
||||
n := len(opt.valueFilters)
|
||||
vf := valueFilter{v.Type().In(0), v}
|
||||
opt.valueFilters = append(opt.valueFilters[:n:n], vf) // Append to copy
|
||||
return opt
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown option type: %T", opt))
|
||||
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, vx, vy reflect.Value, t reflect.Type) applicableOption {
|
||||
if !vx.IsValid() || !vy.IsValid() {
|
||||
return invalid{}
|
||||
}
|
||||
if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) {
|
||||
return f.opt.filter(s, vx, vy, t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f valuesFilter) String() string {
|
||||
fn := getFuncName(f.fnc.Pointer())
|
||||
return fmt.Sprintf("FilterValues(%s, %v)", fn, 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 option{}
|
||||
func Ignore() Option { return ignore{} }
|
||||
|
||||
type ignore struct{ core }
|
||||
|
||||
func (ignore) isFiltered() bool { return false }
|
||||
func (ignore) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return ignore{} }
|
||||
func (ignore) apply(_ *state, _, _ reflect.Value) bool { return true }
|
||||
func (ignore) String() string { return "Ignore()" }
|
||||
|
||||
// invalid is a sentinel Option type to indicate that some options could not
|
||||
// be evaluated due to unexported fields.
|
||||
type invalid struct{ core }
|
||||
|
||||
func (invalid) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption { return invalid{} }
|
||||
func (invalid) apply(s *state, _, _ reflect.Value) bool {
|
||||
const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
|
||||
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
|
||||
}
|
||||
|
||||
// Transformer returns an Option that applies a transformation function that
|
||||
|
@ -191,7 +223,7 @@ func Ignore() Option {
|
|||
// transformation PathStep. If empty, an arbitrary name is used.
|
||||
func Transformer(name string, f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if functionType(v.Type()) != transformFunc || v.IsNil() {
|
||||
if !function.IsType(v.Type(), function.Transformer) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid transformer function: %T", f))
|
||||
}
|
||||
if name == "" {
|
||||
|
@ -200,18 +232,45 @@ func Transformer(name string, f interface{}) Option {
|
|||
if !isValid(name) {
|
||||
panic(fmt.Sprintf("invalid name: %q", name))
|
||||
}
|
||||
opt := option{op: &transformer{name, reflect.ValueOf(f)}}
|
||||
tr := &transformer{name: name, fnc: reflect.ValueOf(f)}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
opt.typeFilter = ti
|
||||
tr.typ = ti
|
||||
}
|
||||
return opt
|
||||
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(_ *state, _, _ reflect.Value, t reflect.Type) applicableOption {
|
||||
if tr.typ == nil || t.AssignableTo(tr.typ) {
|
||||
return tr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *transformer) apply(s *state, vx, vy reflect.Value) bool {
|
||||
// Update path before calling the Transformer so that dynamic checks
|
||||
// will use the updated path.
|
||||
s.curPath.push(&transform{pathStep{tr.fnc.Type().Out(0)}, tr})
|
||||
defer s.curPath.pop()
|
||||
|
||||
vx = s.callTRFunc(tr.fnc, vx)
|
||||
vy = s.callTRFunc(tr.fnc, vy)
|
||||
s.compareAny(vx, vy)
|
||||
return true
|
||||
}
|
||||
|
||||
func (tr transformer) String() string {
|
||||
return fmt.Sprintf("Transformer(%s, %s)", tr.name, getFuncName(tr.fnc.Pointer()))
|
||||
}
|
||||
|
||||
// Comparer returns an Option that determines whether two values are equal
|
||||
// to each other.
|
||||
//
|
||||
|
@ -226,20 +285,41 @@ type transformer struct {
|
|||
// • Pure: equal(x, y) does not modify x or y
|
||||
func Comparer(f interface{}) Option {
|
||||
v := reflect.ValueOf(f)
|
||||
if functionType(v.Type()) != equalFunc || v.IsNil() {
|
||||
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
|
||||
panic(fmt.Sprintf("invalid comparer function: %T", f))
|
||||
}
|
||||
opt := option{op: &comparer{v}}
|
||||
cm := &comparer{fnc: v}
|
||||
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||
opt.typeFilter = ti
|
||||
cm.typ = ti
|
||||
}
|
||||
return opt
|
||||
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, _, _ reflect.Value, t reflect.Type) applicableOption {
|
||||
if cm.typ == nil || t.AssignableTo(cm.typ) {
|
||||
return cm
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cm *comparer) apply(s *state, vx, vy reflect.Value) bool {
|
||||
eq := s.callTTBFunc(cm.fnc, vx, vy)
|
||||
s.report(eq, vx, vy)
|
||||
return true
|
||||
}
|
||||
|
||||
func (cm comparer) String() string {
|
||||
return fmt.Sprintf("Comparer(%s)", getFuncName(cm.fnc.Pointer()))
|
||||
}
|
||||
|
||||
// AllowUnexported returns an Option that forcibly allows operations on
|
||||
// unexported fields in certain structs, which are specified by passing in a
|
||||
// value of each struct type.
|
||||
|
@ -283,12 +363,19 @@ func AllowUnexported(types ...interface{}) Option {
|
|||
|
||||
type visibleStructs map[reflect.Type]bool
|
||||
|
||||
func (visibleStructs) option() {}
|
||||
func (visibleStructs) filter(_ *state, _, _ reflect.Value, _ reflect.Type) applicableOption {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// reporter is an Option that configures how differences are reported.
|
||||
//
|
||||
// TODO: Not exported yet, see concerns in defaultReporter.Report.
|
||||
type reporter interface {
|
||||
// TODO: Not exported yet.
|
||||
//
|
||||
// Perhaps add PushStep and PopStep and change Report to only accept
|
||||
// a PathStep instead of the full-path? Adding a PushStep and PopStep makes
|
||||
// it clear that we are traversing the value tree in a depth-first-search
|
||||
// manner, which has an effect on how values are printed.
|
||||
|
||||
Option
|
||||
|
||||
// Report is called for every comparison made and will be provided with
|
||||
|
@ -297,8 +384,63 @@ type reporter interface {
|
|||
// invalid reflect.Value if one of the values is non-existent;
|
||||
// which is possible with maps and slices.
|
||||
Report(x, y reflect.Value, eq bool, p Path)
|
||||
|
||||
// TODO: Perhaps add PushStep and PopStep and change Report to only accept
|
||||
// a PathStep instead of the full-path? This change allows us to provide
|
||||
// better output closer to what pretty.Compare is able to achieve.
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// getFuncName returns a short function name from the pointer.
|
||||
// The string parsing logic works up until Go1.9.
|
||||
func getFuncName(p uintptr) string {
|
||||
fnc := runtime.FuncForPC(p)
|
||||
if fnc == nil {
|
||||
return "<unknown>"
|
||||
}
|
||||
name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
|
||||
if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
|
||||
// Strip the package name from method name.
|
||||
name = strings.TrimSuffix(name, ")-fm")
|
||||
name = strings.TrimSuffix(name, ")·fm")
|
||||
if i := strings.LastIndexByte(name, '('); i >= 0 {
|
||||
methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
|
||||
if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
|
||||
methodName = methodName[j+1:] // E.g., "myfunc"
|
||||
}
|
||||
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
|
||||
}
|
||||
}
|
||||
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
||||
// Strip the package name.
|
||||
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ func TestOptionPanic(t *testing.T) {
|
|||
label: "FilterPath",
|
||||
fnc: FilterPath,
|
||||
args: []interface{}{func(Path) bool { return true }, &defaultReporter{}},
|
||||
wantPanic: "unknown option type",
|
||||
wantPanic: "invalid option type",
|
||||
}, {
|
||||
label: "FilterPath",
|
||||
fnc: FilterPath,
|
||||
|
@ -139,7 +139,7 @@ func TestOptionPanic(t *testing.T) {
|
|||
label: "FilterPath",
|
||||
fnc: FilterPath,
|
||||
args: []interface{}{func(Path) bool { return true }, Options{Ignore(), &defaultReporter{}}},
|
||||
wantPanic: "unknown option type",
|
||||
wantPanic: "invalid option type",
|
||||
}, {
|
||||
label: "FilterValues",
|
||||
fnc: FilterValues,
|
||||
|
@ -172,7 +172,7 @@ func TestOptionPanic(t *testing.T) {
|
|||
label: "FilterValues",
|
||||
fnc: FilterValues,
|
||||
args: []interface{}{func(int, int) bool { return true }, &defaultReporter{}},
|
||||
wantPanic: "unknown option type",
|
||||
wantPanic: "invalid option type",
|
||||
}, {
|
||||
label: "FilterValues",
|
||||
fnc: FilterValues,
|
||||
|
@ -181,7 +181,7 @@ func TestOptionPanic(t *testing.T) {
|
|||
label: "FilterValues",
|
||||
fnc: FilterValues,
|
||||
args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), &defaultReporter{}}},
|
||||
wantPanic: "unknown option type",
|
||||
wantPanic: "invalid option type",
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -36,7 +36,19 @@ type (
|
|||
// SliceIndex is an index operation on a slice or array at some index Key.
|
||||
SliceIndex interface {
|
||||
PathStep
|
||||
Key() int
|
||||
Key() int // May return -1 if in a split state
|
||||
|
||||
// SplitKeys returns 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.
|
||||
SplitKeys() (x int, y int)
|
||||
|
||||
isSliceIndex()
|
||||
}
|
||||
// MapIndex is an index operation on a map at some index Key.
|
||||
|
@ -163,7 +175,7 @@ type (
|
|||
|
||||
sliceIndex struct {
|
||||
pathStep
|
||||
key int
|
||||
xkey, ykey int
|
||||
}
|
||||
mapIndex struct {
|
||||
pathStep
|
||||
|
@ -205,19 +217,39 @@ func (ps pathStep) String() string {
|
|||
return fmt.Sprintf("{%s}", s)
|
||||
}
|
||||
|
||||
func (si sliceIndex) String() string { return fmt.Sprintf("[%d]", si.key) }
|
||||
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)
|
||||
}
|
||||
}
|
||||
func (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
|
||||
func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
|
||||
func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) }
|
||||
func (in indirect) String() string { return "*" }
|
||||
func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
|
||||
|
||||
func (si sliceIndex) Key() int { return si.key }
|
||||
func (mi mapIndex) Key() reflect.Value { return mi.key }
|
||||
func (sf structField) Name() string { return sf.name }
|
||||
func (sf structField) Index() int { return sf.idx }
|
||||
func (tf transform) Name() string { return tf.trans.name }
|
||||
func (tf transform) Func() reflect.Value { return tf.trans.fnc }
|
||||
func (si sliceIndex) Key() int {
|
||||
if si.xkey != si.ykey {
|
||||
return -1
|
||||
}
|
||||
return si.xkey
|
||||
}
|
||||
func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey }
|
||||
func (mi mapIndex) Key() reflect.Value { return mi.key }
|
||||
func (sf structField) Name() string { return sf.name }
|
||||
func (sf structField) Index() int { return sf.idx }
|
||||
func (tf transform) Name() string { return tf.trans.name }
|
||||
func (tf transform) Func() reflect.Value { return tf.trans.fnc }
|
||||
|
||||
func (pathStep) isPathStep() {}
|
||||
func (sliceIndex) isSliceIndex() {}
|
||||
|
|
|
@ -6,14 +6,11 @@ package cmp
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO: Can we leave the interface for a reporter here in the cmp package
|
||||
// and somehow extract the implementation of defaultReporter into cmp/report?
|
||||
"github.com/google/go-cmp/cmp/internal/value"
|
||||
)
|
||||
|
||||
type defaultReporter struct {
|
||||
Option
|
||||
|
@ -26,45 +23,19 @@ type defaultReporter struct {
|
|||
var _ reporter = (*defaultReporter)(nil)
|
||||
|
||||
func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
|
||||
// TODO: Is there a way to nicely print added/modified/removed elements
|
||||
// from a slice? This will most certainly require support from the
|
||||
// equality logic, but what would be the right API for this?
|
||||
//
|
||||
// The current API is equivalent to a Hamming distance for measuring the
|
||||
// difference between two sequences of symbols. That is, the only operation
|
||||
// we can represent is substitution. The new API would need to handle a
|
||||
// Levenshtein distance, such that insertions, deletions, and substitutions
|
||||
// are permitted. Furthermore, this will require an algorithm for computing
|
||||
// the edit distance. Unfortunately, the time complexity for a minimal
|
||||
// edit distance algorithm is not much better than O(n^2).
|
||||
// There are approximations for the algorithm that can run much faster.
|
||||
// See literature on computing Levenshtein distance.
|
||||
//
|
||||
// Passing in a pair of x and y is actually good for representing insertion
|
||||
// and deletion by the fact that x or y may be an invalid value. However,
|
||||
// we may need to pass in two paths px and py, to indicate the paths
|
||||
// relative to x and y. Alternative, since we only perform the Levenshtein
|
||||
// distance on slices, maybe we alter the SliceIndex type to record
|
||||
// two different indexes.
|
||||
|
||||
// TODO: Perhaps we should coalesce differences on primitive kinds
|
||||
// together if the number of differences exceeds some ratio.
|
||||
// For example, comparing two SHA256s leads to many byte differences.
|
||||
|
||||
if eq {
|
||||
// TODO: Maybe print some equal results for context?
|
||||
return // Ignore equal results
|
||||
}
|
||||
const maxBytes = 4096
|
||||
const maxLines = 256
|
||||
r.ndiffs++
|
||||
if r.nbytes < maxBytes && r.nlines < maxLines {
|
||||
sx := prettyPrint(x, true)
|
||||
sy := prettyPrint(y, true)
|
||||
sx := value.Format(x, true)
|
||||
sy := value.Format(y, true)
|
||||
if sx == sy {
|
||||
// Use of Stringer is not helpful, so rely on more exact formatting.
|
||||
sx = prettyPrint(x, false)
|
||||
sy = prettyPrint(y, false)
|
||||
// Stringer is not helpful, so rely on more exact formatting.
|
||||
sx = value.Format(x, false)
|
||||
sy = value.Format(y, false)
|
||||
}
|
||||
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
|
||||
r.diffs = append(r.diffs, s)
|
||||
|
@ -80,327 +51,3 @@ func (r *defaultReporter) String() string {
|
|||
}
|
||||
return fmt.Sprintf("%s... %d more differences ...", s, len(r.diffs)-r.ndiffs)
|
||||
}
|
||||
|
||||
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
|
||||
func prettyPrint(v reflect.Value, useStringer bool) string {
|
||||
return formatAny(v, formatConfig{useStringer, true, true, true}, nil)
|
||||
}
|
||||
|
||||
type formatConfig struct {
|
||||
useStringer bool // Should the String method be used if available?
|
||||
printType bool // Should we print the type before the value?
|
||||
followPointers bool // Should we recursively follow pointers?
|
||||
realPointers bool // Should we print the real address of pointers?
|
||||
}
|
||||
|
||||
// formatAny prints the value v in a pretty formatted manner.
|
||||
// This is similar to fmt.Sprintf("%+v", v) except this:
|
||||
// * Prints the type unless it can be elided.
|
||||
// * Avoids printing struct fields that are zero.
|
||||
// * Prints a nil-slice as being nil, not empty.
|
||||
// * Prints map entries in deterministic order.
|
||||
func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string {
|
||||
// TODO: Should this be a multi-line printout in certain situations?
|
||||
|
||||
if !v.IsValid() {
|
||||
return "<non-existent>"
|
||||
}
|
||||
if conf.useStringer && v.Type().Implements(stringerIface) {
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String())
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return fmt.Sprint(v.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return fmt.Sprint(v.Int())
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
|
||||
return formatHex(v.Uint()) // Unnamed uints are usually bytes or words
|
||||
}
|
||||
return fmt.Sprint(v.Uint()) // Named uints are usually enumerations
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return fmt.Sprint(v.Float())
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return fmt.Sprint(v.Complex())
|
||||
case reflect.String:
|
||||
return fmt.Sprintf("%q", v)
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
return formatPointer(v, conf)
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("(%v)(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] || !conf.followPointers {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
return "&" + formatAny(v.Elem(), conf, visited)
|
||||
case reflect.Interface:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
return formatAny(v.Elem(), conf, visited)
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
s := formatAny(v.Index(i), subConf, visited)
|
||||
ss = append(ss, s)
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
case reflect.Map:
|
||||
if v.IsNil() {
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("%v(nil)", v.Type())
|
||||
}
|
||||
return "<nil>"
|
||||
}
|
||||
if visited[v.Pointer()] {
|
||||
return formatPointer(v, conf)
|
||||
}
|
||||
visited = insertPointer(visited, v.Pointer())
|
||||
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||
for _, k := range sortKeys(v.MapKeys()) {
|
||||
sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited)
|
||||
sv := formatAny(v.MapIndex(k), subConf, visited)
|
||||
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
case reflect.Struct:
|
||||
var ss []string
|
||||
subConf := conf
|
||||
subConf.printType = true
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
vv := v.Field(i)
|
||||
if isZero(vv) {
|
||||
continue // Elide zero value fields
|
||||
}
|
||||
name := v.Type().Field(i).Name
|
||||
subConf.useStringer = conf.useStringer && isExported(name)
|
||||
s := formatAny(vv, subConf, visited)
|
||||
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
|
||||
}
|
||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||
if conf.printType {
|
||||
return v.Type().String() + s
|
||||
}
|
||||
return s
|
||||
default:
|
||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
func formatPointer(v reflect.Value, conf formatConfig) string {
|
||||
p := v.Pointer()
|
||||
if !conf.realPointers {
|
||||
p = 0 // For deterministic printing purposes
|
||||
}
|
||||
s := formatHex(uint64(p))
|
||||
if conf.printType {
|
||||
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// insertPointer insert p into m, allocating m if necessary.
|
||||
func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
|
||||
if m == nil {
|
||||
m = make(map[uintptr]bool)
|
||||
}
|
||||
m[p] = true
|
||||
return m
|
||||
}
|
||||
|
||||
// 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 v.Float() == 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return 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
|
||||
}
|
||||
|
||||
// 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:
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
// sortKey sorts a list of map keys, deduplicating keys if necessary.
|
||||
func sortKeys(vs []reflect.Value) []reflect.Value {
|
||||
if len(vs) == 0 {
|
||||
return vs
|
||||
}
|
||||
|
||||
// Sort the map keys.
|
||||
sort.Sort(valueSorter(vs))
|
||||
|
||||
// Deduplicate keys (fails for NaNs).
|
||||
vs2 := vs[:1]
|
||||
for _, v := range vs[1:] {
|
||||
if v.Interface() != vs2[len(vs2)-1].Interface() {
|
||||
vs2 = append(vs2, v)
|
||||
}
|
||||
}
|
||||
return vs2
|
||||
}
|
||||
|
||||
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
|
||||
type valueSorter []reflect.Value
|
||||
|
||||
func (vs valueSorter) Len() int { return len(vs) }
|
||||
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
|
||||
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||
|
|
|
@ -217,11 +217,6 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
|
|||
continue
|
||||
}
|
||||
|
||||
if sv.Type() == timeType {
|
||||
values.Add(name, valueString(sv, opts))
|
||||
continue
|
||||
}
|
||||
|
||||
for sv.Kind() == reflect.Ptr {
|
||||
if sv.IsNil() {
|
||||
break
|
||||
|
@ -229,6 +224,11 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
|
|||
sv = sv.Elem()
|
||||
}
|
||||
|
||||
if sv.Type() == timeType {
|
||||
values.Add(name, valueString(sv, opts))
|
||||
continue
|
||||
}
|
||||
|
||||
if sv.Kind() == reflect.Struct {
|
||||
reflectValue(values, sv, name)
|
||||
continue
|
||||
|
|
|
@ -25,6 +25,7 @@ type SubNested struct {
|
|||
func TestValues_types(t *testing.T) {
|
||||
str := "string"
|
||||
strPtr := &str
|
||||
timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)
|
||||
|
||||
tests := []struct {
|
||||
in interface{}
|
||||
|
@ -53,11 +54,17 @@ func TestValues_types(t *testing.T) {
|
|||
A *string
|
||||
B *int
|
||||
C **string
|
||||
}{A: strPtr, C: &strPtr},
|
||||
D *time.Time
|
||||
}{
|
||||
A: strPtr,
|
||||
C: &strPtr,
|
||||
D: &timeVal,
|
||||
},
|
||||
url.Values{
|
||||
"A": {str},
|
||||
"B": {""},
|
||||
"C": {str},
|
||||
"D": {"2000-01-01T12:34:56Z"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||
license. Its contents can be found at:
|
||||
http://creativecommons.org/publicdomain/zero/1.0
|
|
@ -1,2 +0,0 @@
|
|||
all:
|
||||
make -C testdata
|
|
@ -0,0 +1 @@
|
|||
/releases
|
|
@ -0,0 +1,6 @@
|
|||
Pierre Baillet <pierre@baillet.name> Pierre Baillet <oct@fotonauts.com>
|
||||
Jordan Liggitt <jordan@liggitt.net> Jordan Liggitt <jliggitt@redhat.com>
|
||||
Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
||||
Jim Teeuwen <jimteeuwen@gmail.com>
|
||||
Craig Landry <craigtlandry@gmail.com>
|
||||
Kenta Oda <oda@headjapan.com>
|
|
@ -0,0 +1,13 @@
|
|||
go_import_path: github.com/kevinburke/go-bindata
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
# go vet --all doesn't work with 1.8 or 1.7
|
||||
- 1.9.x
|
||||
- master
|
||||
|
||||
script:
|
||||
- make ci
|
||||
|
||||
sudo: false
|
|
@ -0,0 +1,37 @@
|
|||
Aaron Schlesinger <aschlesinger@paypal.com>
|
||||
Alan Shreve <alan@inconshreveable.com>
|
||||
Anand Gaitonde and Michael Maximilien <agaitonde@pivotallabs.com>
|
||||
Andrew Bates <abates@omeganetserv.com>
|
||||
Beyang Liu <beyang@sourcegraph.com>
|
||||
Brandon Mulcahy <brandon@jangler.info>
|
||||
Craig Landry <craigtlandry@gmail.com>
|
||||
Dimitri Sokolyuk <demon@dim13.org>
|
||||
Dmitri Shuralyov <shurcooL@gmail.com>
|
||||
Elazar Leibovich <elazarl@gmail.com>
|
||||
Elias Naur <elias.naur@gmail.com>
|
||||
Emad Elsaid <blazeeboy@users.noreply.github.com>
|
||||
Ian Kent <email@iankent.co.uk>
|
||||
Jens Breitbart <jbreitbart@gmail.com>
|
||||
Jessica Forrester <jforrest@redhat.com>
|
||||
Jim Teeuwen <jimteeuwen@gmail.com>
|
||||
Jordan Liggitt <jordan@liggitt.net>
|
||||
Joseph Hager <ajhager@gmail.com>
|
||||
Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
||||
Kenta Oda <oda@headjapan.com>
|
||||
Kevin Burke <kev@inburke.com>
|
||||
Matt Dee <mdee@hioscar.com>
|
||||
Maxim Ignatenko <imax@cesanta.com>
|
||||
Paweł Błaszczyk <blaszczykpb@gmail.com>
|
||||
Pierre Baillet <pierre@baillet.name>
|
||||
Ruben Vermeersch <ruben@rocketeer.be>
|
||||
Ryosuke IWANAGA <riywo.jp@gmail.com>
|
||||
SATO taichi <ryushi@gmail.com>
|
||||
Sebastian Thiel <byronimo@gmail.com>
|
||||
Serhan Şen <mail@serhan-sen.de>
|
||||
Stephen Searles <stephen.searles@gmail.com>
|
||||
Tamir Duberstein <tamird@gmail.com>
|
||||
Tobias Schmidt <ts@soundcloud.com>
|
||||
Tobias Schottdorf <tobias.schottdorf@gmail.com>
|
||||
Yasuhiro Matsumoto <mattn.jp@gmail.com>
|
||||
ikawaha <ikawaha@users.noreply.github.com>
|
||||
opennota <opennota@gmail.com>
|
|
@ -4,7 +4,6 @@ So you wish to contribute to this project? Fantastic!
|
|||
Here are a few guidelines to help you do this in a
|
||||
streamlined fashion.
|
||||
|
||||
|
||||
## Bug reports
|
||||
|
||||
When supplying a bug report, please consider the following guidelines.
|
||||
|
@ -16,7 +15,7 @@ to reiterate them.
|
|||
just enough of it to clearly define the issue. Not everyone is a native
|
||||
English speaker. And while most can handle themselves pretty well,
|
||||
it helps to stay away from more esoteric vocabulary.
|
||||
|
||||
|
||||
Be patient with non-native English speakers. If their bug reports
|
||||
or comments are hard to understand, just ask for clarification.
|
||||
Do not start guessing at their meaning, as this may just lead to
|
||||
|
@ -31,7 +30,7 @@ to reiterate them.
|
|||
If need be, create a whole new code project on your local machine,
|
||||
which specifically tries to create the problem you are running into;
|
||||
nothing more, nothing less.
|
||||
|
||||
|
||||
Include this program in the bug report. It often suffices to paste
|
||||
the code in a [Gist](https://gist.github.com) or on the
|
||||
[Go playground](http://play.golang.org).
|
||||
|
@ -39,7 +38,6 @@ to reiterate them.
|
|||
undertaken to solve the problem. This can save us a great deal of
|
||||
wasted time, trying out solutions you have already covered.
|
||||
|
||||
|
||||
## Pull requests
|
||||
|
||||
Bug reports are great. Supplying fixes to bugs is even better.
|
||||
|
@ -50,25 +48,26 @@ good to keep in mind:
|
|||
committing it. Code has to be readable by many different
|
||||
people. And the only way this will be as painless as possible,
|
||||
is if we all stick to the same code style.
|
||||
|
||||
|
||||
Some of our projects may have automated build-servers hooked up
|
||||
to commit hooks. These will vet any submitted code and determine
|
||||
if it meets a set of properties. One of which is code formatting.
|
||||
These servers will outright deny a submission which has not been
|
||||
run through `go fmt`, even if the code itself is correct.
|
||||
|
||||
|
||||
We try to maintain a zero-tolerance policy on this matter,
|
||||
because consistently formatted code makes life a great deal
|
||||
easier for everyone involved.
|
||||
|
||||
* Commit log messages: When committing changes, do so often and
|
||||
clearly -- Even if you have changed only 1 character in a code
|
||||
comment. This means that commit log messages should clearly state
|
||||
exactly what the change does and why. If it fixes a known issue,
|
||||
then mention the issue number in the commit log. E.g.:
|
||||
|
||||
|
||||
> Fixes return value for `foo/boo.Baz()` to be consistent with
|
||||
> the rest of the API. This addresses issue #32
|
||||
|
||||
|
||||
Do not pile a lot of unrelated changes into a single commit.
|
||||
Pick and choose only those changes for a single commit, which are
|
||||
directly related. We would much rather see a hundred commits
|
||||
|
@ -76,4 +75,12 @@ good to keep in mind:
|
|||
than have these style changes embedded in those real fixes.
|
||||
It creates a lot of noise when trying to review code.
|
||||
|
||||
## New releases
|
||||
|
||||
1) Manually update version.go with the new version number.
|
||||
|
||||
2) Commit the change. The commit message should be the version number.
|
||||
|
||||
3) Add a git tag
|
||||
|
||||
4) Run GITHUB_TOKEN=mytoken make release version=my-git-tag
|
|
@ -0,0 +1,34 @@
|
|||
This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||
license. Its contents can be found at:
|
||||
|
||||
https://creativecommons.org/publicdomain/zero/1.0
|
||||
|
||||
safefile.go and safefile_test.go are imported from github.com/dchest/safefile
|
||||
and contain local modifications. The license from that project is included here:
|
||||
|
||||
Copyright (c) 2013 Dmitry Chestnykh <dmitry@codingrobots.com>
|
||||
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.
|
||||
|
||||
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.
|
|
@ -0,0 +1,64 @@
|
|||
DIFFER := $(GOPATH)/bin/differ
|
||||
MEGACHECK := $(GOPATH)/bin/megacheck
|
||||
RELEASE := $(GOPATH)/bin/github-release
|
||||
WRITE_MAILMAP := $(GOPATH)/bin/write_mailmap
|
||||
|
||||
all:
|
||||
$(MAKE) -C testdata
|
||||
|
||||
$(DIFFER):
|
||||
go get -u github.com/kevinburke/differ
|
||||
|
||||
diff-testdata: | $(DIFFER)
|
||||
$(DIFFER) $(MAKE) -C testdata
|
||||
$(DIFFER) go fmt ./testdata/out/...
|
||||
|
||||
$(MEGACHECK):
|
||||
go get honnef.co/go/tools/cmd/megacheck
|
||||
|
||||
lint: | $(MEGACHECK)
|
||||
go vet ./...
|
||||
$(MEGACHECK) ./...
|
||||
|
||||
go-test:
|
||||
go test ./...
|
||||
|
||||
go-race-test:
|
||||
go test -race ./...
|
||||
|
||||
test: go-test
|
||||
$(MAKE) -C testdata
|
||||
|
||||
race-test: lint go-race-test
|
||||
$(MAKE) -C testdata
|
||||
|
||||
$(WRITE_MAILMAP):
|
||||
go get -u github.com/kevinburke/write_mailmap
|
||||
|
||||
force: ;
|
||||
|
||||
AUTHORS.txt: force | $(WRITE_MAILMAP)
|
||||
$(WRITE_MAILMAP) > AUTHORS.txt
|
||||
|
||||
authors: AUTHORS.txt
|
||||
|
||||
ci: go-race-test diff-testdata
|
||||
|
||||
release: | $(RELEASE) race-test diff-testdata
|
||||
ifndef version
|
||||
@echo "Please provide a version"
|
||||
exit 1
|
||||
endif
|
||||
ifndef GITHUB_TOKEN
|
||||
@echo "Please set GITHUB_TOKEN in the environment"
|
||||
exit 1
|
||||
endif
|
||||
mkdir -p releases/$(version)
|
||||
GOOS=linux GOARCH=amd64 go build -o releases/$(version)/go-bindata-linux-amd64 ./go-bindata
|
||||
GOOS=darwin GOARCH=amd64 go build -o releases/$(version)/go-bindata-darwin-amd64 ./go-bindata
|
||||
GOOS=windows GOARCH=amd64 go build -o releases/$(version)/go-bindata-windows-amd64 ./go-bindata
|
||||
# these commands are not idempotent so ignore failures if an upload repeats
|
||||
$(RELEASE) release --user kevinburke --repo go-bindata --tag $(version) || true
|
||||
$(RELEASE) upload --user kevinburke --repo go-bindata --tag $(version) --name go-bindata-linux-amd64 --file releases/$(version)/go-bindata-linux-amd64 || true
|
||||
$(RELEASE) upload --user kevinburke --repo go-bindata --tag $(version) --name go-bindata-darwin-amd64 --file releases/$(version)/go-bindata-darwin-amd64 || true
|
||||
$(RELEASE) upload --user kevinburke --repo go-bindata --tag $(version) --name go-bindata-windows-amd64 --file releases/$(version)/go-bindata-windows-amd64 || true
|
|
@ -1,6 +1,23 @@
|
|||
## bindata
|
||||
|
||||
This package converts any file into managable Go source code. Useful for
|
||||
*This fork is maintained by Kevin Burke. Don't expect many new features, though
|
||||
I will take a look at PR's. Changes made include:*
|
||||
|
||||
- Atomic writes; generated file cannot be read while partially complete.
|
||||
|
||||
- Generated code is run through go fmt.
|
||||
|
||||
- SHA256 hashes are computed for all files and stored in the binary. You can
|
||||
use this to detect in-memory corruption and to provide easy cache-busting
|
||||
mechanisms.
|
||||
|
||||
- Added AssetString and MustAssetString functions.
|
||||
|
||||
- ByName is not public
|
||||
|
||||
- Some errors in file writes are now checked.
|
||||
|
||||
This package converts any file into manageable Go source code. Useful for
|
||||
embedding binary data into a go program. The file data is optionally gzip
|
||||
compressed before being converted to a raw byte slice.
|
||||
|
||||
|
@ -13,8 +30,7 @@ output being generated.
|
|||
|
||||
To install the library and command line program, use the following:
|
||||
|
||||
go get -u github.com/jteeuwen/go-bindata/...
|
||||
|
||||
go get -u github.com/kevinburke/go-bindata/...
|
||||
|
||||
### Usage
|
||||
|
||||
|
@ -42,7 +58,7 @@ Multiple input directories can be specified if necessary.
|
|||
$ go-bindata dir1/... /path/to/dir2/... dir3
|
||||
|
||||
|
||||
The following paragraphs detail some of the command line options which can be
|
||||
The following paragraphs detail some of the command line options which can be
|
||||
supplied to `go-bindata`. Refer to the `testdata/out` directory for various
|
||||
output examples from the assets in `testdata/in`. Each example uses different
|
||||
command line options.
|
||||
|
@ -90,7 +106,7 @@ It will now embed the latest version of the assets.
|
|||
|
||||
Using the `-nomemcopy` flag, will alter the way the output file is generated.
|
||||
It will employ a hack that allows us to read the file data directly from
|
||||
the compiled program's `.rodata` section. This ensures that when we call
|
||||
the compiled program's `.rodata` section. This ensures that when we
|
||||
call our generated function, we omit unnecessary memcopies.
|
||||
|
||||
The downside of this, is that it requires dependencies on the `reflect` and
|
||||
|
@ -171,7 +187,6 @@ Running with the `-prefix` flag, we get:
|
|||
|
||||
_bindata["templates/foo.html"] = templates_foo_html
|
||||
|
||||
|
||||
### Build tags
|
||||
|
||||
With the optional `-tags` flag, you can specify any go build tags that
|
||||
|
@ -184,6 +199,5 @@ and must follow the build tags syntax specified by the go tool.
|
|||
|
||||
### Related projects
|
||||
|
||||
[go-bindata-assetfs](https://github.com/elazarl/go-bindata-assetfs#readme) -
|
||||
[go-bindata-assetfs](https://github.com/elazarl/go-bindata-assetfs#readme) -
|
||||
implements `http.FileSystem` interface. Allows you to serve assets with `net/http`.
|
||||
|
|
@ -15,6 +15,7 @@ var (
|
|||
space = []byte{' '}
|
||||
)
|
||||
|
||||
// ByteWriter writes the hex-encoded version of input bytes to its Writer.
|
||||
type ByteWriter struct {
|
||||
io.Writer
|
||||
c int
|
||||
|
@ -39,6 +40,5 @@ func (w *ByteWriter) Write(p []byte) (n int, err error) {
|
|||
}
|
||||
|
||||
n++
|
||||
|
||||
return
|
||||
}
|
|
@ -5,8 +5,9 @@
|
|||
package bindata
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -22,8 +23,7 @@ func Translate(c *Config) error {
|
|||
var toc []Asset
|
||||
|
||||
// Ensure our configuration has sane values.
|
||||
err := c.validate()
|
||||
if err != nil {
|
||||
if err := c.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -31,29 +31,18 @@ func Translate(c *Config) error {
|
|||
var visitedPaths = make(map[string]bool)
|
||||
// Locate all the assets.
|
||||
for _, input := range c.Input {
|
||||
err = findFiles(input.Path, c.Prefix, input.Recursive, &toc, c.Ignore, knownFuncs, visitedPaths)
|
||||
if err != nil {
|
||||
if err := findFiles(input.Path, c.Prefix, input.Recursive, &toc, c.Ignore, knownFuncs, visitedPaths); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create output file.
|
||||
fd, err := os.Create(c.Output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer fd.Close()
|
||||
|
||||
// Create a buffered writer for better performance.
|
||||
bfd := bufio.NewWriter(fd)
|
||||
defer bfd.Flush()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
// Write the header. This makes e.g. Github ignore diffs in generated files.
|
||||
if _, err = fmt.Fprint(bfd, "// Code generated by go-bindata.\n"); err != nil {
|
||||
if _, err := fmt.Fprint(buf, "// Code generated by go-bindata. DO NOT EDIT.\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = fmt.Fprint(bfd, "// sources:\n"); err != nil {
|
||||
if _, err := fmt.Fprint(buf, "// sources:\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -64,57 +53,71 @@ func Translate(c *Config) error {
|
|||
|
||||
for _, asset := range toc {
|
||||
relative, _ := filepath.Rel(wd, asset.Path)
|
||||
if _, err = fmt.Fprintf(bfd, "// %s\n", filepath.ToSlash(relative)); err != nil {
|
||||
if _, err = fmt.Fprintf(buf, "// %s\n", filepath.ToSlash(relative)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err = fmt.Fprint(bfd, "// DO NOT EDIT!\n\n"); err != nil {
|
||||
if _, err = fmt.Fprint(buf, "\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write build tags, if applicable.
|
||||
if len(c.Tags) > 0 {
|
||||
if _, err = fmt.Fprintf(bfd, "// +build %s\n\n", c.Tags); err != nil {
|
||||
if _, err := fmt.Fprintf(buf, "// +build %s\n\n", c.Tags); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Write package declaration.
|
||||
_, err = fmt.Fprintf(bfd, "package %s\n\n", c.Package)
|
||||
_, err = fmt.Fprintf(buf, "package %s\n\n", c.Package)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write assets.
|
||||
if c.Debug || c.Dev {
|
||||
err = writeDebug(bfd, c, toc)
|
||||
if os.Getenv("GO_BINDATA_TEST") == "true" {
|
||||
// If we don't do this, people running the tests on different
|
||||
// machines get different git diffs.
|
||||
for i := range toc {
|
||||
toc[i].Path = strings.Replace(toc[i].Path, wd, "/test", 1)
|
||||
}
|
||||
}
|
||||
err = writeDebug(buf, c, toc)
|
||||
} else {
|
||||
err = writeRelease(bfd, c, toc)
|
||||
err = writeRelease(buf, c, toc)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write table of contents
|
||||
if err := writeTOC(bfd, toc); err != nil {
|
||||
if err := writeTOC(buf, toc); err != nil {
|
||||
return err
|
||||
}
|
||||
// Write hierarchical tree of assets
|
||||
if err := writeTOCTree(bfd, toc); err != nil {
|
||||
if err := writeTOCTree(buf, toc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write restore procedure
|
||||
return writeRestore(bfd)
|
||||
if err := writeRestore(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
fmted, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return safefileWriteFile(c.Output, fmted, 0666)
|
||||
}
|
||||
|
||||
// Implement sort.Interface for []os.FileInfo based on Name()
|
||||
type ByName []os.FileInfo
|
||||
type byName []os.FileInfo
|
||||
|
||||
func (v ByName) Len() int { return len(v) }
|
||||
func (v ByName) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
||||
func (v ByName) Less(i, j int) bool { return v[i].Name() < v[j].Name() }
|
||||
func (v byName) Len() int { return len(v) }
|
||||
func (v byName) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
||||
func (v byName) Less(i, j int) bool { return v[i].Name() < v[j].Name() }
|
||||
|
||||
// findFiles recursively finds all the file paths in the given directory tree.
|
||||
// They are added to the given map as keys. Values will be safe function names
|
||||
|
@ -152,7 +155,7 @@ func findFiles(dir, prefix string, recursive bool, toc *[]Asset, ignore []*regex
|
|||
}
|
||||
|
||||
// Sort to make output stable between invocations
|
||||
sort.Sort(ByName(list))
|
||||
sort.Sort(byName(list))
|
||||
}
|
||||
|
||||
for _, file := range list {
|
||||
|
@ -212,7 +215,10 @@ func findFiles(dir, prefix string, recursive bool, toc *[]Asset, ignore []*regex
|
|||
}
|
||||
|
||||
asset.Func = safeFunctionName(asset.Name, knownFuncs)
|
||||
asset.Path, _ = filepath.Abs(asset.Path)
|
||||
asset.Path, err = filepath.Abs(asset.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*toc = append(*toc, asset)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||
// license. Its contents can be found at:
|
||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
||||
// https://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
package bindata
|
||||
|
||||
|
@ -30,6 +30,7 @@ func writeDebug(w io.Writer, c *Config, toc []Asset) error {
|
|||
// This targets debug builds.
|
||||
func writeDebugHeader(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, `import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -47,8 +48,9 @@ func bindataRead(path, name string) ([]byte, error) {
|
|||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
}
|
||||
|
||||
`)
|
|
@ -7,6 +7,7 @@ package bindata
|
|||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -65,42 +66,86 @@ func writeReleaseAsset(w io.Writer, c *Config, asset *Asset) error {
|
|||
|
||||
defer fd.Close()
|
||||
|
||||
h := sha256.New()
|
||||
tr := io.TeeReader(fd, h)
|
||||
if c.NoCompress {
|
||||
if c.NoMemCopy {
|
||||
err = uncompressed_nomemcopy(w, asset, fd)
|
||||
err = uncompressed_nomemcopy(w, asset, tr)
|
||||
} else {
|
||||
err = uncompressed_memcopy(w, asset, fd)
|
||||
err = uncompressed_memcopy(w, asset, tr)
|
||||
}
|
||||
} else {
|
||||
if c.NoMemCopy {
|
||||
err = compressed_nomemcopy(w, asset, fd)
|
||||
err = compressed_nomemcopy(w, asset, tr)
|
||||
} else {
|
||||
err = compressed_memcopy(w, asset, fd)
|
||||
err = compressed_memcopy(w, asset, tr)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return asset_release_common(w, c, asset)
|
||||
var digest [sha256.Size]byte
|
||||
copy(digest[:], h.Sum(nil))
|
||||
return asset_release_common(w, c, asset, digest)
|
||||
}
|
||||
|
||||
var (
|
||||
backquote = []byte("`")
|
||||
bom = []byte("\xEF\xBB\xBF")
|
||||
)
|
||||
|
||||
// sanitize prepares a valid UTF-8 string as a raw string constant.
|
||||
// Based on https://code.google.com/p/go/source/browse/godoc/static/makestatic.go?repo=tools
|
||||
func sanitize(b []byte) []byte {
|
||||
// Replace ` with `+"`"+`
|
||||
b = bytes.Replace(b, []byte("`"), []byte("`+\"`\"+`"), -1)
|
||||
var chunks [][]byte
|
||||
for i, b := range bytes.Split(b, backquote) {
|
||||
if i > 0 {
|
||||
chunks = append(chunks, backquote)
|
||||
}
|
||||
for j, c := range bytes.Split(b, bom) {
|
||||
if j > 0 {
|
||||
chunks = append(chunks, bom)
|
||||
}
|
||||
if len(c) > 0 {
|
||||
chunks = append(chunks, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace BOM with `+"\xEF\xBB\xBF"+`
|
||||
// (A BOM is valid UTF-8 but not permitted in Go source files.
|
||||
// I wouldn't bother handling this, but for some insane reason
|
||||
// jquery.js has a BOM somewhere in the middle.)
|
||||
return bytes.Replace(b, []byte("\xEF\xBB\xBF"), []byte("`+\"\\xEF\\xBB\\xBF\"+`"), -1)
|
||||
var buf bytes.Buffer
|
||||
sanitizeChunks(&buf, chunks)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func sanitizeChunks(buf *bytes.Buffer, chunks [][]byte) {
|
||||
n := len(chunks)
|
||||
if n >= 2 {
|
||||
buf.WriteString("(")
|
||||
sanitizeChunks(buf, chunks[:n/2])
|
||||
buf.WriteString(" + ")
|
||||
sanitizeChunks(buf, chunks[n/2:])
|
||||
buf.WriteString(")")
|
||||
return
|
||||
}
|
||||
b := chunks[0]
|
||||
if bytes.Equal(b, backquote) {
|
||||
buf.WriteString("\"`\"")
|
||||
return
|
||||
}
|
||||
if bytes.Equal(b, bom) {
|
||||
buf.WriteString(`"\xEF\xBB\xBF"`)
|
||||
return
|
||||
}
|
||||
buf.WriteString("`")
|
||||
buf.Write(b)
|
||||
buf.WriteString("`")
|
||||
}
|
||||
|
||||
func header_compressed_nomemcopy(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, `import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -118,13 +163,14 @@ func bindataRead(data, name string) ([]byte, error) {
|
|||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %%q: %%v", name, err)
|
||||
}
|
||||
|
||||
clErr := gz.Close()
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
return nil, clErr
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
|
@ -138,6 +184,7 @@ func header_compressed_memcopy(w io.Writer) error {
|
|||
_, err := fmt.Fprintf(w, `import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -173,6 +220,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
|||
|
||||
func header_uncompressed_nomemcopy(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, `import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -200,6 +248,7 @@ func bindataRead(data, name string) ([]byte, error) {
|
|||
|
||||
func header_uncompressed_memcopy(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, `import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -213,8 +262,9 @@ func header_uncompressed_memcopy(w io.Writer) error {
|
|||
|
||||
func header_release_common(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, `type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
digest [sha256.Size]byte
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
|
@ -336,7 +386,7 @@ func uncompressed_memcopy(w io.Writer, asset *Asset, r io.Reader) error {
|
|||
return err
|
||||
}
|
||||
if utf8.Valid(b) && !bytes.Contains(b, []byte{0}) {
|
||||
fmt.Fprintf(w, "`%s`", sanitize(b))
|
||||
w.Write(sanitize(b))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%+q", b)
|
||||
}
|
||||
|
@ -351,7 +401,7 @@ func %sBytes() ([]byte, error) {
|
|||
return err
|
||||
}
|
||||
|
||||
func asset_release_common(w io.Writer, c *Config, asset *Asset) error {
|
||||
func asset_release_common(w io.Writer, c *Config, asset *Asset, digest [sha256.Size]byte) error {
|
||||
fi, err := os.Stat(asset.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -378,10 +428,10 @@ func asset_release_common(w io.Writer, c *Config, asset *Asset) error {
|
|||
}
|
||||
|
||||
info := bindataFileInfo{name: %q, size: %d, mode: os.FileMode(%d), modTime: time.Unix(%d, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
a := &asset{bytes: bytes, info: info, digest: %#v}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
`, asset.Func, asset.Func, asset.Name, size, mode, modTime)
|
||||
`, asset.Func, asset.Func, asset.Name, size, mode, modTime, digest)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package bindata
|
||||
|
||||
import "testing"
|
||||
|
||||
var sanitizeTests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{`hello`, "`hello`"},
|
||||
{"hello\nworld", "`hello\nworld`"},
|
||||
{"`ello", "(\"`\" + `ello`)"},
|
||||
{"`a`e`i`o`u`", "(((\"`\" + `a`) + (\"`\" + (`e` + \"`\"))) + ((`i` + (\"`\" + `o`)) + (\"`\" + (`u` + \"`\"))))"},
|
||||
{"\xEF\xBB\xBF`s away!", "(\"\\xEF\\xBB\\xBF\" + (\"`\" + `s away!`))"},
|
||||
}
|
||||
|
||||
func TestSanitize(t *testing.T) {
|
||||
for _, tt := range sanitizeTests {
|
||||
out := []byte(sanitize([]byte(tt.in)))
|
||||
if string(out) != tt.out {
|
||||
t.Errorf("sanitize(%q):\nhave %q\nwant %q", tt.in, out, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
func writeRestore(w io.Writer) error {
|
||||
_, err := fmt.Fprintf(w, `
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
// RestoreAsset restores an asset under the given directory.
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
|
@ -29,14 +29,10 @@ func RestoreAsset(dir, name string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
// RestoreAssets restores an asset under the given directory recursively.
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
|
@ -54,10 +50,9 @@ func RestoreAssets(dir, name string) error {
|
|||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
`)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2013 Dmitry Chestnykh. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// Some modifications made: API's were made internal to the package. os.Rename
|
||||
// hack for old Windows/Plan 9 builds removed; if you are using those systems,
|
||||
// it is OK if you don't have atomic rewrites.
|
||||
|
||||
package bindata
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrAlreadyCommitted error is returned when calling Commit on a file that
|
||||
// has been already successfully committed.
|
||||
var errAlreadyCommitted = errors.New("file already committed")
|
||||
|
||||
type safefileFile struct {
|
||||
*os.File
|
||||
origName string
|
||||
closeFunc func(*safefileFile) error
|
||||
isClosed bool // if true, temporary file has been closed, but not renamed
|
||||
isCommitted bool // if true, the file has been successfully committed
|
||||
}
|
||||
|
||||
func makeTempName(origname, prefix string) (tempname string, err error) {
|
||||
origname = filepath.Clean(origname)
|
||||
if len(origname) == 0 || origname[len(origname)-1] == filepath.Separator {
|
||||
return "", os.ErrInvalid
|
||||
}
|
||||
// Generate 10 random bytes.
|
||||
// This gives 80 bits of entropy, good enough
|
||||
// for making temporary file name unpredictable.
|
||||
var rnd [10]byte
|
||||
if _, err := rand.Read(rnd[:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := prefix + "-" + strings.ToLower(base32.StdEncoding.EncodeToString(rnd[:])) + ".tmp"
|
||||
return filepath.Join(filepath.Dir(origname), name), nil
|
||||
}
|
||||
|
||||
// safefileCreate creates a temporary file in the same directory as filename,
|
||||
// which will be renamed to the given filename when calling Commit.
|
||||
func safefileCreate(filename string, perm os.FileMode) (*safefileFile, error) {
|
||||
for {
|
||||
tempname, err := makeTempName(filename, "sf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.OpenFile(tempname, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
|
||||
if err != nil {
|
||||
if os.IsExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &safefileFile{
|
||||
File: f,
|
||||
origName: filename,
|
||||
closeFunc: closeUncommitted,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// OrigName returns the original filename given to Create.
|
||||
func (f *safefileFile) OrigName() string {
|
||||
return f.origName
|
||||
}
|
||||
|
||||
// Close closes temporary file and removes it.
|
||||
// If the file has been committed, Close is no-op.
|
||||
func (f *safefileFile) Close() error {
|
||||
return f.closeFunc(f)
|
||||
}
|
||||
|
||||
func closeUncommitted(f *safefileFile) error {
|
||||
err0 := f.File.Close()
|
||||
err1 := os.Remove(f.Name())
|
||||
f.closeFunc = closeAgainError
|
||||
if err0 != nil {
|
||||
return err0
|
||||
}
|
||||
return err1
|
||||
}
|
||||
|
||||
func closeAfterFailedRename(f *safefileFile) error {
|
||||
// Remove temporary file.
|
||||
//
|
||||
// The note from Commit function applies here too, as we may be
|
||||
// removing a different file. However, since we rely on our temporary
|
||||
// names being unpredictable, this should not be a concern.
|
||||
f.closeFunc = closeAgainError
|
||||
return os.Remove(f.Name())
|
||||
}
|
||||
|
||||
func closeCommitted(f *safefileFile) error {
|
||||
// noop
|
||||
return nil
|
||||
}
|
||||
|
||||
func closeAgainError(f *safefileFile) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
// Commit safely commits data into the original file by syncing temporary
|
||||
// file to disk, closing it and renaming to the original file name.
|
||||
//
|
||||
// In case of success, the temporary file is closed and no longer exists
|
||||
// on disk. It is safe to call Close after Commit: the operation will do
|
||||
// nothing.
|
||||
//
|
||||
// In case of error, the temporary file is still opened and exists on disk;
|
||||
// it must be closed by callers by calling Close or by trying to commit again.
|
||||
|
||||
// Note that when trying to Commit again after a failed Commit when the file
|
||||
// has been closed, but not renamed to its original name (the new commit will
|
||||
// try again to rename it), safefile cannot guarantee that the temporary file
|
||||
// has not been changed, or that it is the same temporary file we were dealing
|
||||
// with. However, since the temporary name is unpredictable, it is unlikely
|
||||
// that this happened accidentally. If complete atomicity is needed, do not
|
||||
// Commit again after error, write the file again.
|
||||
func (f *safefileFile) Commit() error {
|
||||
if f.isCommitted {
|
||||
return errAlreadyCommitted
|
||||
}
|
||||
if !f.isClosed {
|
||||
// Sync to disk.
|
||||
err := f.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Close underlying os.File.
|
||||
err = f.File.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.isClosed = true
|
||||
}
|
||||
err := os.Rename(f.Name(), f.origName)
|
||||
if err != nil {
|
||||
f.closeFunc = closeAfterFailedRename
|
||||
return err
|
||||
}
|
||||
f.closeFunc = closeCommitted
|
||||
f.isCommitted = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteFile is a safe analog of ioutil.WriteFile.
|
||||
func safefileWriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
f, err := safefileCreate(filename, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
n, err := f.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
return err
|
||||
}
|
||||
return f.Commit()
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright 2013 Dmitry Chestnykh. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bindata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ensureFileContains(name, data string) error {
|
||||
b, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(b) != data {
|
||||
return fmt.Errorf("wrong data in file: expected %s, got %s", data, string(b))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tempFileName(count int) string {
|
||||
return filepath.Join(os.TempDir(), fmt.Sprintf("safefile-test-%d-%x", count, time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
var testData = "Hello, this is a test data"
|
||||
|
||||
func testInTempDir() error {
|
||||
name := tempFileName(0)
|
||||
defer os.Remove(name)
|
||||
f, err := safefileCreate(name, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if name != f.OrigName() {
|
||||
f.Close()
|
||||
return fmt.Errorf("name %q differs from OrigName: %q", name, f.OrigName())
|
||||
}
|
||||
_, err = io.WriteString(f, testData)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
err = f.Commit()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ensureFileContains(name, testData)
|
||||
}
|
||||
|
||||
func TestMakeTempName(t *testing.T) {
|
||||
// Make sure temp name is random.
|
||||
m := make(map[string]bool)
|
||||
for i := 0; i < 100; i++ {
|
||||
name, err := makeTempName("/tmp", "sf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if m[name] {
|
||||
t.Fatal("repeated file name")
|
||||
}
|
||||
m[name] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestFile(t *testing.T) {
|
||||
err := testInTempDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
name := tempFileName(1)
|
||||
err := safefileWriteFile(name, []byte(testData), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ensureFileContains(name, testData)
|
||||
if err != nil {
|
||||
os.Remove(name)
|
||||
t.Fatal(err)
|
||||
}
|
||||
os.Remove(name)
|
||||
}
|
||||
|
||||
func TestAbandon(t *testing.T) {
|
||||
name := tempFileName(2)
|
||||
f, err := safefileCreate(name, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Abandon failed: %s", err)
|
||||
}
|
||||
// Make sure temporary file doesn't exist.
|
||||
_, err = os.Stat(f.Name())
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleCommit(t *testing.T) {
|
||||
name := tempFileName(3)
|
||||
f, err := safefileCreate(name, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = f.Commit()
|
||||
if err != nil {
|
||||
os.Remove(name)
|
||||
t.Fatalf("First commit failed: %s", err)
|
||||
}
|
||||
err = f.Commit()
|
||||
if err != errAlreadyCommitted {
|
||||
os.Remove(name)
|
||||
t.Fatalf("Second commit didn't fail: %s", err)
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
os.Remove(name)
|
||||
t.Fatalf("Close failed: %s", err)
|
||||
}
|
||||
os.Remove(name)
|
||||
}
|
||||
|
||||
func TestOverwriting(t *testing.T) {
|
||||
name := tempFileName(4)
|
||||
defer os.Remove(name)
|
||||
|
||||
olddata := "This is old data"
|
||||
err := ioutil.WriteFile(name, []byte(olddata), 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newdata := "This is new data"
|
||||
err = safefileWriteFile(name, []byte(newdata), 0600)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = ensureFileContains(name, newdata)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -87,6 +87,7 @@ func (root *assetTree) WriteAsGoMap(w io.Writer) error {
|
|||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = `)
|
||||
root.writeGoMap(w, 0)
|
||||
return err
|
||||
|
@ -102,15 +103,15 @@ func writeTOCTree(w io.Writer, toc []Asset) error {
|
|||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(canonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
|
@ -148,6 +149,10 @@ func writeTOC(w io.Writer, toc []Asset) error {
|
|||
}
|
||||
|
||||
for i := range toc {
|
||||
if i != 0 {
|
||||
// Newlines between elements make gofmt happy.
|
||||
w.Write([]byte{'\n'})
|
||||
}
|
||||
err = writeTOCAsset(w, &toc[i])
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -163,8 +168,8 @@ func writeTOCHeader(w io.Writer) error {
|
|||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %%s can't read by error: %%v", name, err)
|
||||
|
@ -174,6 +179,12 @@ func Asset(name string) ([]byte, error) {
|
|||
return nil, fmt.Errorf("Asset %%s not found", name)
|
||||
}
|
||||
|
||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
||||
func AssetString(name string) (string, error) {
|
||||
data, err := Asset(name)
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
|
@ -185,12 +196,18 @@ func MustAsset(name string) []byte {
|
|||
return a
|
||||
}
|
||||
|
||||
// MustAssetString is like AssetString but panics when Asset would return an
|
||||
// error. It simplifies safe initialization of global variables.
|
||||
func MustAssetString(name string) string {
|
||||
return string(MustAsset(name))
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %%s can't read by error: %%v", name, err)
|
||||
|
@ -200,6 +217,33 @@ func AssetInfo(name string) (os.FileInfo, error) {
|
|||
return nil, fmt.Errorf("AssetInfo %%s not found", name)
|
||||
}
|
||||
|
||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
||||
// error if the asset could not be found or the digest could not be loaded.
|
||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[canonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %%s can't read by error: %%v", name, err)
|
||||
}
|
||||
return a.digest, nil
|
||||
}
|
||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %%s not found", name)
|
||||
}
|
||||
|
||||
// Digests returns a map of all known files and their checksums.
|
||||
func Digests() (map[string][sha256.Size]byte, error) {
|
||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
||||
for name := range _bindata {
|
||||
a, err := _bindata[name]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp[name] = a.digest
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
|
@ -1,11 +1,10 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- 1.7.1
|
||||
- tip
|
||||
|
||||
script:
|
||||
|
|
|
@ -15,7 +15,6 @@ func noErrors(at, depth int) error {
|
|||
}
|
||||
return noErrors(at+1, depth)
|
||||
}
|
||||
|
||||
func yesErrors(at, depth int) error {
|
||||
if at >= depth {
|
||||
return New("ye error")
|
||||
|
@ -23,11 +22,8 @@ func yesErrors(at, depth int) error {
|
|||
return yesErrors(at+1, depth)
|
||||
}
|
||||
|
||||
// GlobalE is an exported global to store the result of benchmark results,
|
||||
// preventing the compiler from optimising the benchmark functions away.
|
||||
var GlobalE error
|
||||
|
||||
func BenchmarkErrors(b *testing.B) {
|
||||
var toperr error
|
||||
type run struct {
|
||||
stack int
|
||||
std bool
|
||||
|
@ -57,7 +53,7 @@ func BenchmarkErrors(b *testing.B) {
|
|||
err = f(0, r.stack)
|
||||
}
|
||||
b.StopTimer()
|
||||
GlobalE = err
|
||||
toperr = err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ func TestWithMessage(t *testing.T) {
|
|||
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// errors.New, etc values are not expected to be compared by value
|
||||
|
|
|
@ -6,9 +6,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -174,10 +172,12 @@ func (srv *Server) ListenTLS(certFile, keyFile string) (net.Listener, error) {
|
|||
}
|
||||
|
||||
var err error
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if certFile != "" && keyFile != "" {
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Enable http2
|
||||
|
@ -299,7 +299,7 @@ func (srv *Server) Serve(listener net.Listener) error {
|
|||
interrupt := srv.interruptChan()
|
||||
// Set up the interrupt handler
|
||||
if !srv.NoSignalHandling {
|
||||
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
signalNotify(interrupt)
|
||||
}
|
||||
quitting := make(chan struct{})
|
||||
go srv.handleInterrupt(interrupt, quitting, listener)
|
||||
|
@ -336,8 +336,7 @@ func (srv *Server) Stop(timeout time.Duration) {
|
|||
defer srv.stopLock.Unlock()
|
||||
|
||||
srv.Timeout = timeout
|
||||
interrupt := srv.interruptChan()
|
||||
interrupt <- syscall.SIGINT
|
||||
sendSignalInt(srv.interruptChan())
|
||||
}
|
||||
|
||||
// StopChan gets the stop channel which will block until
|
||||
|
@ -367,6 +366,7 @@ func (srv *Server) manageConnections(add, idle, active, remove chan net.Conn, sh
|
|||
select {
|
||||
case conn := <-add:
|
||||
srv.connections[conn] = struct{}{}
|
||||
srv.idleConnections[conn] = struct{}{} // Newly-added connections are considered idle until they become active.
|
||||
case conn := <-idle:
|
||||
srv.idleConnections[conn] = struct{}{}
|
||||
case conn := <-active:
|
||||
|
@ -458,6 +458,8 @@ func (srv *Server) shutdown(shutdown chan chan struct{}, kill chan struct{}) {
|
|||
done := make(chan struct{})
|
||||
shutdown <- done
|
||||
|
||||
srv.stopLock.Lock()
|
||||
defer srv.stopLock.Unlock()
|
||||
if srv.Timeout > 0 {
|
||||
select {
|
||||
case <-done:
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
//+build !appengine
|
||||
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func signalNotify(interrupt chan<- os.Signal) {
|
||||
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
func sendSignalInt(interrupt chan<- os.Signal) {
|
||||
interrupt <- syscall.SIGINT
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//+build appengine
|
||||
|
||||
package graceful
|
||||
|
||||
import "os"
|
||||
|
||||
func signalNotify(interrupt chan<- os.Signal) {
|
||||
// Does not notify in the case of AppEngine.
|
||||
}
|
||||
|
||||
func sendSignalInt(interrupt chan<- os.Signal) {
|
||||
// Does not send in the case of AppEngine.
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
This repository holds supplemental Go packages for low-level interactions with the operating system.
|
||||
|
||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
|
@ -0,0 +1,18 @@
|
|||
# sys
|
||||
|
||||
This repository holds supplemental Go packages for low-level interactions with
|
||||
the operating system.
|
||||
|
||||
## Download/Install
|
||||
|
||||
The easiest way to install is to run `go get -u golang.org/x/sys`. You can
|
||||
also manually git clone the repository to `$GOPATH/src/golang.org/x/sys`.
|
||||
|
||||
## Report Issues / Send Patches
|
||||
|
||||
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||
this repository, see https://golang.org/doc/contribute.html.
|
||||
|
||||
The main issue tracker for the sys repository is located at
|
||||
https://github.com/golang/go/issues. Prefix your issue with "x/sys:" in the
|
||||
subject line, so it is easy to find.
|
|
@ -1 +1,2 @@
|
|||
_obj/
|
||||
unix.test
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
# Building `sys/unix`
|
||||
|
||||
The sys/unix package provides access to the raw system call interface of the
|
||||
underlying operating system. See: https://godoc.org/golang.org/x/sys/unix
|
||||
|
||||
Porting Go to a new architecture/OS combination or adding syscalls, types, or
|
||||
constants to an existing architecture/OS pair requires some manual effort;
|
||||
however, there are tools that automate much of the process.
|
||||
|
||||
## Build Systems
|
||||
|
||||
There are currently two ways we generate the necessary files. We are currently
|
||||
migrating the build system to use containers so the builds are reproducible.
|
||||
This is being done on an OS-by-OS basis. Please update this documentation as
|
||||
components of the build system change.
|
||||
|
||||
### Old Build System (currently for `GOOS != "Linux" || GOARCH == "sparc64"`)
|
||||
|
||||
The old build system generates the Go files based on the C header files
|
||||
present on your system. This means that files
|
||||
for a given GOOS/GOARCH pair must be generated on a system with that OS and
|
||||
architecture. This also means that the generated code can differ from system
|
||||
to system, based on differences in the header files.
|
||||
|
||||
To avoid this, if you are using the old build system, only generate the Go
|
||||
files on an installation with unmodified header files. It is also important to
|
||||
keep track of which version of the OS the files were generated from (ex.
|
||||
Darwin 14 vs Darwin 15). This makes it easier to track the progress of changes
|
||||
and have each OS upgrade correspond to a single change.
|
||||
|
||||
To build the files for your current OS and architecture, make sure GOOS and
|
||||
GOARCH are set correctly and run `mkall.sh`. This will generate the files for
|
||||
your specific system. Running `mkall.sh -n` shows the commands that will be run.
|
||||
|
||||
Requirements: bash, perl, go
|
||||
|
||||
### New Build System (currently for `GOOS == "Linux" && GOARCH != "sparc64"`)
|
||||
|
||||
The new build system uses a Docker container to generate the go files directly
|
||||
from source checkouts of the kernel and various system libraries. This means
|
||||
that on any platform that supports Docker, all the files using the new build
|
||||
system can be generated at once, and generated files will not change based on
|
||||
what the person running the scripts has installed on their computer.
|
||||
|
||||
The OS specific files for the new build system are located in the `${GOOS}`
|
||||
directory, and the build is coordinated by the `${GOOS}/mkall.go` program. When
|
||||
the kernel or system library updates, modify the Dockerfile at
|
||||
`${GOOS}/Dockerfile` to checkout the new release of the source.
|
||||
|
||||
To build all the files under the new build system, you must be on an amd64/Linux
|
||||
system and have your GOOS and GOARCH set accordingly. Running `mkall.sh` will
|
||||
then generate all of the files for all of the GOOS/GOARCH pairs in the new build
|
||||
system. Running `mkall.sh -n` shows the commands that will be run.
|
||||
|
||||
Requirements: bash, perl, go, docker
|
||||
|
||||
## Component files
|
||||
|
||||
This section describes the various files used in the code generation process.
|
||||
It also contains instructions on how to modify these files to add a new
|
||||
architecture/OS or to add additional syscalls, types, or constants. Note that
|
||||
if you are using the new build system, the scripts cannot be called normally.
|
||||
They must be called from within the docker container.
|
||||
|
||||
### asm files
|
||||
|
||||
The hand-written assembly file at `asm_${GOOS}_${GOARCH}.s` implements system
|
||||
call dispatch. There are three entry points:
|
||||
```
|
||||
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
|
||||
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||
```
|
||||
The first and second are the standard ones; they differ only in how many
|
||||
arguments can be passed to the kernel. The third is for low-level use by the
|
||||
ForkExec wrapper. Unlike the first two, it does not call into the scheduler to
|
||||
let it know that a system call is running.
|
||||
|
||||
When porting Go to an new architecture/OS, this file must be implemented for
|
||||
each GOOS/GOARCH pair.
|
||||
|
||||
### mksysnum
|
||||
|
||||
Mksysnum is a script located at `${GOOS}/mksysnum.pl` (or `mksysnum_${GOOS}.pl`
|
||||
for the old system). This script takes in a list of header files containing the
|
||||
syscall number declarations and parses them to produce the corresponding list of
|
||||
Go numeric constants. See `zsysnum_${GOOS}_${GOARCH}.go` for the generated
|
||||
constants.
|
||||
|
||||
Adding new syscall numbers is mostly done by running the build on a sufficiently
|
||||
new installation of the target OS (or updating the source checkouts for the
|
||||
new build system). However, depending on the OS, you make need to update the
|
||||
parsing in mksysnum.
|
||||
|
||||
### mksyscall.pl
|
||||
|
||||
The `syscall.go`, `syscall_${GOOS}.go`, `syscall_${GOOS}_${GOARCH}.go` are
|
||||
hand-written Go files which implement system calls (for unix, the specific OS,
|
||||
or the specific OS/Architecture pair respectively) that need special handling
|
||||
and list `//sys` comments giving prototypes for ones that can be generated.
|
||||
|
||||
The mksyscall.pl script takes the `//sys` and `//sysnb` comments and converts
|
||||
them into syscalls. This requires the name of the prototype in the comment to
|
||||
match a syscall number in the `zsysnum_${GOOS}_${GOARCH}.go` file. The function
|
||||
prototype can be exported (capitalized) or not.
|
||||
|
||||
Adding a new syscall often just requires adding a new `//sys` function prototype
|
||||
with the desired arguments and a capitalized name so it is exported. However, if
|
||||
you want the interface to the syscall to be different, often one will make an
|
||||
unexported `//sys` prototype, an then write a custom wrapper in
|
||||
`syscall_${GOOS}.go`.
|
||||
|
||||
### types files
|
||||
|
||||
For each OS, there is a hand-written Go file at `${GOOS}/types.go` (or
|
||||
`types_${GOOS}.go` on the old system). This file includes standard C headers and
|
||||
creates Go type aliases to the corresponding C types. The file is then fed
|
||||
through godef to get the Go compatible definitions. Finally, the generated code
|
||||
is fed though mkpost.go to format the code correctly and remove any hidden or
|
||||
private identifiers. This cleaned-up code is written to
|
||||
`ztypes_${GOOS}_${GOARCH}.go`.
|
||||
|
||||
The hardest part about preparing this file is figuring out which headers to
|
||||
include and which symbols need to be `#define`d to get the actual data
|
||||
structures that pass through to the kernel system calls. Some C libraries
|
||||
preset alternate versions for binary compatibility and translate them on the
|
||||
way in and out of system calls, but there is almost always a `#define` that can
|
||||
get the real ones.
|
||||
See `types_darwin.go` and `linux/types.go` for examples.
|
||||
|
||||
To add a new type, add in the necessary include statement at the top of the
|
||||
file (if it is not already there) and add in a type alias line. Note that if
|
||||
your type is significantly different on different architectures, you may need
|
||||
some `#if/#elif` macros in your include statements.
|
||||
|
||||
### mkerrors.sh
|
||||
|
||||
This script is used to generate the system's various constants. This doesn't
|
||||
just include the error numbers and error strings, but also the signal numbers
|
||||
an a wide variety of miscellaneous constants. The constants come from the list
|
||||
of include files in the `includes_${uname}` variable. A regex then picks out
|
||||
the desired `#define` statements, and generates the corresponding Go constants.
|
||||
The error numbers and strings are generated from `#include <errno.h>`, and the
|
||||
signal numbers and strings are generated from `#include <signal.h>`. All of
|
||||
these constants are written to `zerrors_${GOOS}_${GOARCH}.go` via a C program,
|
||||
`_errors.c`, which prints out all the constants.
|
||||
|
||||
To add a constant, add the header that includes it to the appropriate variable.
|
||||
Then, edit the regex (if necessary) to match the desired constant. Avoid making
|
||||
the regex too broad to avoid matching unintended constants.
|
||||
|
||||
|
||||
## Generated files
|
||||
|
||||
### `zerror_${GOOS}_${GOARCH}.go`
|
||||
|
||||
A file containing all of the system's generated error numbers, error strings,
|
||||
signal numbers, and constants. Generated by `mkerrors.sh` (see above).
|
||||
|
||||
### `zsyscall_${GOOS}_${GOARCH}.go`
|
||||
|
||||
A file containing all the generated syscalls for a specific GOOS and GOARCH.
|
||||
Generated by `mksyscall.pl` (see above).
|
||||
|
||||
### `zsysnum_${GOOS}_${GOARCH}.go`
|
||||
|
||||
A list of numeric constants for all the syscall number of the specific GOOS
|
||||
and GOARCH. Generated by mksysnum (see above).
|
||||
|
||||
### `ztypes_${GOOS}_${GOARCH}.go`
|
||||
|
||||
A file containing Go types for passing into (or returning from) syscalls.
|
||||
Generated by godefs and the types file (see above).
|
|
@ -0,0 +1,124 @@
|
|||
// 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.
|
||||
|
||||
// CPU affinity functions
|
||||
|
||||
package unix
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS
|
||||
|
||||
// CPUSet represents a CPU affinity mask.
|
||||
type CPUSet [cpuSetSize]cpuMask
|
||||
|
||||
func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
|
||||
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set)))
|
||||
if e != 0 {
|
||||
return errnoErr(e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
|
||||
// If pid is 0 the calling thread is used.
|
||||
func SchedGetaffinity(pid int, set *CPUSet) error {
|
||||
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set)
|
||||
}
|
||||
|
||||
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
|
||||
// If pid is 0 the calling thread is used.
|
||||
func SchedSetaffinity(pid int, set *CPUSet) error {
|
||||
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set)
|
||||
}
|
||||
|
||||
// Zero clears the set s, so that it contains no CPUs.
|
||||
func (s *CPUSet) Zero() {
|
||||
for i := range s {
|
||||
s[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func cpuBitsIndex(cpu int) int {
|
||||
return cpu / _NCPUBITS
|
||||
}
|
||||
|
||||
func cpuBitsMask(cpu int) cpuMask {
|
||||
return cpuMask(1 << (uint(cpu) % _NCPUBITS))
|
||||
}
|
||||
|
||||
// Set adds cpu to the set s.
|
||||
func (s *CPUSet) Set(cpu int) {
|
||||
i := cpuBitsIndex(cpu)
|
||||
if i < len(s) {
|
||||
s[i] |= cpuBitsMask(cpu)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes cpu from the set s.
|
||||
func (s *CPUSet) Clear(cpu int) {
|
||||
i := cpuBitsIndex(cpu)
|
||||
if i < len(s) {
|
||||
s[i] &^= cpuBitsMask(cpu)
|
||||
}
|
||||
}
|
||||
|
||||
// IsSet reports whether cpu is in the set s.
|
||||
func (s *CPUSet) IsSet(cpu int) bool {
|
||||
i := cpuBitsIndex(cpu)
|
||||
if i < len(s) {
|
||||
return s[i]&cpuBitsMask(cpu) != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Count returns the number of CPUs in the set s.
|
||||
func (s *CPUSet) Count() int {
|
||||
c := 0
|
||||
for _, b := range s {
|
||||
c += onesCount64(uint64(b))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// onesCount64 is a copy of Go 1.9's math/bits.OnesCount64.
|
||||
// Once this package can require Go 1.9, we can delete this
|
||||
// and update the caller to use bits.OnesCount64.
|
||||
func onesCount64(x uint64) int {
|
||||
const m0 = 0x5555555555555555 // 01010101 ...
|
||||
const m1 = 0x3333333333333333 // 00110011 ...
|
||||
const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ...
|
||||
const m3 = 0x00ff00ff00ff00ff // etc.
|
||||
const m4 = 0x0000ffff0000ffff
|
||||
|
||||
// Implementation: Parallel summing of adjacent bits.
|
||||
// See "Hacker's Delight", Chap. 5: Counting Bits.
|
||||
// The following pattern shows the general approach:
|
||||
//
|
||||
// x = x>>1&(m0&m) + x&(m0&m)
|
||||
// x = x>>2&(m1&m) + x&(m1&m)
|
||||
// x = x>>4&(m2&m) + x&(m2&m)
|
||||
// x = x>>8&(m3&m) + x&(m3&m)
|
||||
// x = x>>16&(m4&m) + x&(m4&m)
|
||||
// x = x>>32&(m5&m) + x&(m5&m)
|
||||
// return int(x)
|
||||
//
|
||||
// Masking (& operations) can be left away when there's no
|
||||
// danger that a field's sum will carry over into the next
|
||||
// field: Since the result cannot be > 64, 8 bits is enough
|
||||
// and we can ignore the masks for the shifts by 8 and up.
|
||||
// Per "Hacker's Delight", the first line can be simplified
|
||||
// more, but it saves at best one instruction, so we leave
|
||||
// it alone for clarity.
|
||||
const m = 1<<64 - 1
|
||||
x = x>>1&(m0&m) + x&(m0&m)
|
||||
x = x>>2&(m1&m) + x&(m1&m)
|
||||
x = (x>>4 + x) & (m2 & m)
|
||||
x += x >> 8
|
||||
x += x >> 16
|
||||
x += x >> 32
|
||||
return int(x) & (1<<7 - 1)
|
||||
}
|
|
@ -10,21 +10,51 @@
|
|||
// System calls for 386, Linux
|
||||
//
|
||||
|
||||
// See ../runtime/sys_linux_386.s for the reason why we always use int 0x80
|
||||
// instead of the glibc-specific "CALL 0x10(GS)".
|
||||
#define INVOKE_SYSCALL INT $0x80
|
||||
|
||||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-24
|
||||
CALL runtime·entersyscall(SB)
|
||||
MOVL trap+0(FP), AX // syscall entry
|
||||
MOVL a1+4(FP), BX
|
||||
MOVL a2+8(FP), CX
|
||||
MOVL a3+12(FP), DX
|
||||
MOVL $0, SI
|
||||
MOVL $0, DI
|
||||
INVOKE_SYSCALL
|
||||
MOVL AX, r1+16(FP)
|
||||
MOVL DX, r2+20(FP)
|
||||
CALL runtime·exitsyscall(SB)
|
||||
RET
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||
JMP syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-24
|
||||
MOVL trap+0(FP), AX // syscall entry
|
||||
MOVL a1+4(FP), BX
|
||||
MOVL a2+8(FP), CX
|
||||
MOVL a3+12(FP), DX
|
||||
MOVL $0, SI
|
||||
MOVL $0, DI
|
||||
INVOKE_SYSCALL
|
||||
MOVL AX, r1+16(FP)
|
||||
MOVL DX, r2+20(FP)
|
||||
RET
|
||||
|
||||
TEXT ·socketcall(SB),NOSPLIT,$0-36
|
||||
JMP syscall·socketcall(SB)
|
||||
|
||||
|
|
|
@ -13,17 +13,45 @@
|
|||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
|
||||
CALL runtime·entersyscall(SB)
|
||||
MOVQ a1+8(FP), DI
|
||||
MOVQ a2+16(FP), SI
|
||||
MOVQ a3+24(FP), DX
|
||||
MOVQ $0, R10
|
||||
MOVQ $0, R8
|
||||
MOVQ $0, R9
|
||||
MOVQ trap+0(FP), AX // syscall entry
|
||||
SYSCALL
|
||||
MOVQ AX, r1+32(FP)
|
||||
MOVQ DX, r2+40(FP)
|
||||
CALL runtime·exitsyscall(SB)
|
||||
RET
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||
MOVQ a1+8(FP), DI
|
||||
MOVQ a2+16(FP), SI
|
||||
MOVQ a3+24(FP), DX
|
||||
MOVQ $0, R10
|
||||
MOVQ $0, R8
|
||||
MOVQ $0, R9
|
||||
MOVQ trap+0(FP), AX // syscall entry
|
||||
SYSCALL
|
||||
MOVQ AX, r1+32(FP)
|
||||
MOVQ DX, r2+40(FP)
|
||||
RET
|
||||
|
||||
TEXT ·gettimeofday(SB),NOSPLIT,$0-16
|
||||
JMP syscall·gettimeofday(SB)
|
||||
|
|
|
@ -13,17 +13,44 @@
|
|||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||
B syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||
B syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-24
|
||||
BL runtime·entersyscall(SB)
|
||||
MOVW trap+0(FP), R7
|
||||
MOVW a1+4(FP), R0
|
||||
MOVW a2+8(FP), R1
|
||||
MOVW a3+12(FP), R2
|
||||
MOVW $0, R3
|
||||
MOVW $0, R4
|
||||
MOVW $0, R5
|
||||
SWI $0
|
||||
MOVW R0, r1+16(FP)
|
||||
MOVW $0, R0
|
||||
MOVW R0, r2+20(FP)
|
||||
BL runtime·exitsyscall(SB)
|
||||
RET
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||
B syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||
B syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·seek(SB),NOSPLIT,$0-32
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-24
|
||||
MOVW trap+0(FP), R7 // syscall entry
|
||||
MOVW a1+4(FP), R0
|
||||
MOVW a2+8(FP), R1
|
||||
MOVW a3+12(FP), R2
|
||||
SWI $0
|
||||
MOVW R0, r1+16(FP)
|
||||
MOVW $0, R0
|
||||
MOVW R0, r2+20(FP)
|
||||
RET
|
||||
|
||||
TEXT ·seek(SB),NOSPLIT,$0-28
|
||||
B syscall·seek(SB)
|
||||
|
|
|
@ -11,14 +11,42 @@
|
|||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
B syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||
B syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
|
||||
BL runtime·entersyscall(SB)
|
||||
MOVD a1+8(FP), R0
|
||||
MOVD a2+16(FP), R1
|
||||
MOVD a3+24(FP), R2
|
||||
MOVD $0, R3
|
||||
MOVD $0, R4
|
||||
MOVD $0, R5
|
||||
MOVD trap+0(FP), R8 // syscall entry
|
||||
SVC
|
||||
MOVD R0, r1+32(FP) // r1
|
||||
MOVD R1, r2+40(FP) // r2
|
||||
BL runtime·exitsyscall(SB)
|
||||
RET
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
B syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
B syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||
MOVD a1+8(FP), R0
|
||||
MOVD a2+16(FP), R1
|
||||
MOVD a3+24(FP), R2
|
||||
MOVD $0, R3
|
||||
MOVD $0, R4
|
||||
MOVD $0, R5
|
||||
MOVD trap+0(FP), R8 // syscall entry
|
||||
SVC
|
||||
MOVD R0, r1+32(FP)
|
||||
MOVD R1, r2+40(FP)
|
||||
RET
|
||||
|
|
|
@ -15,14 +15,42 @@
|
|||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
|
||||
JAL runtime·entersyscall(SB)
|
||||
MOVV a1+8(FP), R4
|
||||
MOVV a2+16(FP), R5
|
||||
MOVV a3+24(FP), R6
|
||||
MOVV R0, R7
|
||||
MOVV R0, R8
|
||||
MOVV R0, R9
|
||||
MOVV trap+0(FP), R2 // syscall entry
|
||||
SYSCALL
|
||||
MOVV R2, r1+32(FP)
|
||||
MOVV R3, r2+40(FP)
|
||||
JAL runtime·exitsyscall(SB)
|
||||
RET
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·RawSyscall6(SB)
|
||||
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||
MOVV a1+8(FP), R4
|
||||
MOVV a2+16(FP), R5
|
||||
MOVV a3+24(FP), R6
|
||||
MOVV R0, R7
|
||||
MOVV R0, R8
|
||||
MOVV R0, R9
|
||||
MOVV trap+0(FP), R2 // syscall entry
|
||||
SYSCALL
|
||||
MOVV R2, r1+32(FP)
|
||||
MOVV R3, r2+40(FP)
|
||||
RET
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue