Merge branch 'master' into single-stat-polish

pull/2788/head
Alex Paxton 2018-02-08 15:41:26 -08:00 committed by GitHub
commit 4a82b57134
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
310 changed files with 52896 additions and 28123 deletions

View File

@ -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

112
Gopkg.lock generated
View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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,

View File

@ -3,7 +3,7 @@ machine:
services:
- docker
environment:
DOCKER_TAG: chronograf-20180206
DOCKER_TAG: chronograf-20180207
dependencies:
override:

View File

@ -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

View File

@ -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 ; \

View File

@ -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.

View File

@ -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>

View File

@ -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

View File

@ -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) => {

View File

@ -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': {

View File

@ -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',

View File

@ -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">

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -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

View File

@ -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 = () => {

View File

@ -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})

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -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 = () => {

View File

@ -1,6 +0,0 @@
#*
*.[568]
*.a
*~
[568].out
_*

View File

@ -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).

View File

@ -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

View File

@ -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"
}

View File

@ -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) {

View File

@ -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.

18
vendor/github.com/golang/protobuf/.travis.yml generated vendored Normal file
View File

@ -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

View File

@ -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; };

View File

@ -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 (

151
vendor/github.com/golang/protobuf/proto/discard.go generated vendored Normal file
View File

@ -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)
}
}
}
}

View File

@ -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

View File

@ -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 (

View File

@ -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.

View File

@ -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:

View File

@ -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.

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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...) }

View 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() {}

View 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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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()))
}
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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() {}

View File

@ -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] }

View File

@ -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

View File

@ -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"},
},
},
{

View File

@ -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

View File

@ -1,2 +0,0 @@
all:
make -C testdata

1
vendor/github.com/kevinburke/go-bindata/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
/releases

6
vendor/github.com/kevinburke/go-bindata/.mailmap generated vendored Normal file
View File

@ -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>

13
vendor/github.com/kevinburke/go-bindata/.travis.yml generated vendored Normal file
View File

@ -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

37
vendor/github.com/kevinburke/go-bindata/AUTHORS.txt generated vendored Normal file
View File

@ -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>

View File

@ -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

34
vendor/github.com/kevinburke/go-bindata/LICENSE generated vendored Normal file
View File

@ -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.

64
vendor/github.com/kevinburke/go-bindata/Makefile generated vendored Normal file
View File

@ -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

View File

@ -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`.

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}
`)

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

172
vendor/github.com/kevinburke/go-bindata/safefile.go generated vendored Normal file
View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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))

View File

@ -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:

View File

@ -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
})
}
}

View File

@ -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

View File

@ -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:

17
vendor/github.com/tylerb/graceful/signal.go generated vendored Normal file
View File

@ -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
}

13
vendor/github.com/tylerb/graceful/signal_appengine.go generated vendored Normal file
View File

@ -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.
}

3
vendor/golang.org/x/sys/README generated vendored
View File

@ -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.

18
vendor/golang.org/x/sys/README.md generated vendored Normal file
View File

@ -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.

View File

@ -1 +1,2 @@
_obj/
unix.test

173
vendor/golang.org/x/sys/unix/README.md generated vendored Normal file
View File

@ -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).

124
vendor/golang.org/x/sys/unix/affinity_linux.go generated vendored Normal file
View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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