Merge branch 'master' into single-stat-polish
commit
4a82b57134
|
@ -5,6 +5,7 @@
|
||||||
1. [#2708](https://github.com/influxdata/chronograf/pull/2708): Link to specified kapacitor config panel from rule builder alert handlers
|
1. [#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. [#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. [#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. [#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
|
1. [#2777](https://github.com/influxdata/chronograf/pull/2777): Allow user to delete themselves
|
||||||
|
|
||||||
|
@ -17,8 +18,10 @@
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
1. [#2684](https://github.com/influxdata/chronograf/pull/2684): Fix TICKscript Sensu alerts when no group by tags selected
|
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. [#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. [#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. [#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]
|
## v1.4.0.1 [2017-1-9]
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/dustin/go-humanize"
|
name = "github.com/dustin/go-humanize"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "259d2a102b871d17f30e3cd9881a642961a1e486"
|
revision = "bb3d318650d48840a39aa21a027c6630e198e626"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/elazarl/go-bindata-assetfs"
|
name = "github.com/elazarl/go-bindata-assetfs"
|
||||||
|
@ -39,18 +39,53 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/gogo/protobuf"
|
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"
|
revision = "6abcf94fd4c97dcb423fdafd42fe9f96ca7e421b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/golang/protobuf"
|
name = "github.com/golang/protobuf"
|
||||||
packages = ["proto"]
|
packages = ["proto"]
|
||||||
revision = "8ee79997227bf9b34611aee7946ae64735e6fd93"
|
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/google/go-cmp"
|
name = "github.com/google/go-cmp"
|
||||||
packages = ["cmp","cmp/cmpopts"]
|
packages = [
|
||||||
revision = "79b2d888f100ec053545168aa94bcfb322e8bfc8"
|
"cmp",
|
||||||
|
"cmp/cmpopts",
|
||||||
|
"cmp/internal/diff",
|
||||||
|
"cmp/internal/function",
|
||||||
|
"cmp/internal/value"
|
||||||
|
]
|
||||||
|
revision = "8099a9787ce5dc5984ed879a3bda47dc730a8e97"
|
||||||
|
version = "v0.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/google/go-github"
|
name = "github.com/google/go-github"
|
||||||
|
@ -58,19 +93,35 @@
|
||||||
revision = "1bc362c7737e51014af7299e016444b654095ad9"
|
revision = "1bc362c7737e51014af7299e016444b654095ad9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
name = "github.com/google/go-querystring"
|
name = "github.com/google/go-querystring"
|
||||||
packages = ["query"]
|
packages = ["query"]
|
||||||
revision = "9235644dd9e52eeae6fa48efd539fdc351a0af53"
|
revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/influxdata/influxdb"
|
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"
|
revision = "cd9363b52cac452113b95554d98a6be51beda24e"
|
||||||
version = "v1.1.5"
|
version = "v1.1.5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/influxdata/kapacitor"
|
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"
|
revision = "6de30070b39afde111fea5e041281126fe8aae31"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
@ -84,15 +135,15 @@
|
||||||
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
|
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/jteeuwen/go-bindata"
|
name = "github.com/kevinburke/go-bindata"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "a0ff2567cfb70903282db057e799fd826784d41d"
|
revision = "46eb4c183bfc1ebb527d9d19bcded39476302eb8"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
|
||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "ff09b135c25aae272398c51a07235b90a75aa4f0"
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/satori/go.uuid"
|
name = "github.com/satori/go.uuid"
|
||||||
|
@ -107,39 +158,60 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/tylerb/graceful"
|
name = "github.com/tylerb/graceful"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "50a48b6e73fcc75b45e22c05b79629a67c79e938"
|
revision = "4654dfbb6ad53cb5e27f37d99b02e16c1872fbbb"
|
||||||
version = "v1.2.13"
|
version = "v1.2.15"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = ["context","context/ctxhttp"]
|
packages = [
|
||||||
|
"context",
|
||||||
|
"context/ctxhttp"
|
||||||
|
]
|
||||||
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
|
revision = "749a502dd1eaf3e5bfd4f8956748c502357c0bbe"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "golang.org/x/oauth2"
|
name = "golang.org/x/oauth2"
|
||||||
packages = [".","github","heroku","internal"]
|
packages = [
|
||||||
|
".",
|
||||||
|
"github",
|
||||||
|
"heroku",
|
||||||
|
"internal"
|
||||||
|
]
|
||||||
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
|
revision = "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["unix"]
|
packages = ["unix"]
|
||||||
revision = "f3918c30c5c2cb527c0b071a27c35120a6c0719a"
|
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "google.golang.org/api"
|
name = "google.golang.org/api"
|
||||||
packages = ["gensupport","googleapi","googleapi/internal/uritemplates","oauth2/v2"]
|
packages = [
|
||||||
|
"gensupport",
|
||||||
|
"googleapi",
|
||||||
|
"googleapi/internal/uritemplates",
|
||||||
|
"oauth2/v2"
|
||||||
|
]
|
||||||
revision = "bc20c61134e1d25265dd60049f5735381e79b631"
|
revision = "bc20c61134e1d25265dd60049f5735381e79b631"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "google.golang.org/appengine"
|
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"
|
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||||
version = "v1.0.0"
|
version = "v1.0.0"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "a5bd1aa82919723ff8ec5dd9d520329862de8181ca9dba75c6acb3a34df5f1a4"
|
inputs-digest = "11df631364d11bc05c8f71af1aa735360b5a40a793d32d47d1f1d8c694a55f6f"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
required = ["github.com/jteeuwen/go-bindata","github.com/gogo/protobuf/proto","github.com/gogo/protobuf/jsonpb","github.com/gogo/protobuf/protoc-gen-gogo","github.com/gogo/protobuf/gogoproto"]
|
required = ["github.com/kevinburke/go-bindata","github.com/gogo/protobuf/proto","github.com/gogo/protobuf/jsonpb","github.com/gogo/protobuf/protoc-gen-gogo","github.com/gogo/protobuf/gogoproto"]
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/NYTimes/gziphandler"
|
name = "github.com/NYTimes/gziphandler"
|
||||||
|
@ -41,8 +41,8 @@ required = ["github.com/jteeuwen/go-bindata","github.com/gogo/protobuf/proto","g
|
||||||
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
|
revision = "4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/jteeuwen/go-bindata"
|
name = "github.com/kevinburke/go-bindata"
|
||||||
revision = "a0ff2567cfb70903282db057e799fd826784d41d"
|
revision = "46eb4c183bfc1ebb527d9d19bcded39476302eb8"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/satori/go.uuid"
|
name = "github.com/satori/go.uuid"
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
VERSION ?= $(shell git describe --always --tags)
|
VERSION ?= $(shell git describe --always --tags)
|
||||||
COMMIT ?= $(shell git rev-parse --short=8 HEAD)
|
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)
|
YARN := $(shell command -v yarn 2> /dev/null)
|
||||||
|
|
||||||
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go' -not -path "./vendor/*" )
|
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go' -not -path "./vendor/*" )
|
||||||
|
@ -73,7 +73,7 @@ dep: .jsdep .godep
|
||||||
.godep:
|
.godep:
|
||||||
ifndef GOBINDATA
|
ifndef GOBINDATA
|
||||||
@echo "Installing go-bindata"
|
@echo "Installing go-bindata"
|
||||||
go get -u github.com/jteeuwen/go-bindata/...
|
go get -u github.com/kevinburke/go-bindata/...
|
||||||
endif
|
endif
|
||||||
@touch .godep
|
@touch .godep
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,97 @@ func (m *Source) String() string { return proto.CompactTextString(m)
|
||||||
func (*Source) ProtoMessage() {}
|
func (*Source) ProtoMessage() {}
|
||||||
func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
|
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 {
|
type Dashboard struct {
|
||||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,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) ProtoMessage() {}
|
||||||
func (*Dashboard) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} }
|
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 {
|
func (m *Dashboard) GetCells() []*DashboardCell {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Cells
|
return m.Cells
|
||||||
|
@ -97,6 +202,13 @@ func (m *Dashboard) GetTemplates() []*Template {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Dashboard) GetOrganization() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Organization
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type DashboardCell struct {
|
type DashboardCell struct {
|
||||||
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
||||||
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,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) ProtoMessage() {}
|
||||||
func (*DashboardCell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{2} }
|
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 {
|
func (m *DashboardCell) GetQueries() []*Query {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Queries
|
return m.Queries
|
||||||
|
@ -123,6 +263,27 @@ func (m *DashboardCell) GetQueries() []*Query {
|
||||||
return nil
|
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 {
|
func (m *DashboardCell) GetAxes() map[string]*Axis {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Axes
|
return m.Axes
|
||||||
|
@ -157,6 +318,41 @@ func (m *Color) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Color) ProtoMessage() {}
|
func (*Color) ProtoMessage() {}
|
||||||
func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} }
|
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 Legend struct {
|
||||||
Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"`
|
Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"`
|
||||||
Orientation string `protobuf:"bytes,2,opt,name=Orientation,proto3" json:"Orientation,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) ProtoMessage() {}
|
||||||
func (*Legend) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} }
|
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 {
|
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"`
|
Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"`
|
||||||
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,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"`
|
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) ProtoMessage() {}
|
||||||
func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} }
|
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 {
|
type Template struct {
|
||||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
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"`
|
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) ProtoMessage() {}
|
||||||
func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} }
|
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 {
|
func (m *Template) GetValues() []*TemplateValue {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Values
|
return m.Values
|
||||||
|
@ -203,6 +476,20 @@ func (m *Template) GetValues() []*TemplateValue {
|
||||||
return nil
|
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 {
|
func (m *Template) GetQuery() *TemplateQuery {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Query
|
return m.Query
|
||||||
|
@ -221,6 +508,27 @@ func (m *TemplateValue) String() string { return proto.CompactTextStr
|
||||||
func (*TemplateValue) ProtoMessage() {}
|
func (*TemplateValue) ProtoMessage() {}
|
||||||
func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} }
|
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 {
|
type TemplateQuery struct {
|
||||||
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
|
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
|
||||||
Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,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) ProtoMessage() {}
|
||||||
func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} }
|
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 {
|
type Server struct {
|
||||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,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) ProtoMessage() {}
|
||||||
func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} }
|
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 {
|
type Layout struct {
|
||||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,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) ProtoMessage() {}
|
||||||
func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} }
|
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 {
|
func (m *Layout) GetCells() []*Cell {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Cells
|
return m.Cells
|
||||||
|
@ -271,6 +698,13 @@ func (m *Layout) GetCells() []*Cell {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Layout) GetAutoflow() bool {
|
||||||
|
if m != nil {
|
||||||
|
return m.Autoflow
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type Cell struct {
|
type Cell struct {
|
||||||
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"`
|
||||||
Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,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"`
|
Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"`
|
||||||
I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,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"`
|
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"`
|
Ylabels []string `protobuf:"bytes,9,rep,name=ylabels" json:"ylabels,omitempty"`
|
||||||
Type string `protobuf:"bytes,10,opt,name=type,proto3" json:"type,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"`
|
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) ProtoMessage() {}
|
||||||
func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} }
|
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 {
|
func (m *Cell) GetQueries() []*Query {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Queries
|
return m.Queries
|
||||||
|
@ -297,6 +759,41 @@ func (m *Cell) GetQueries() []*Query {
|
||||||
return nil
|
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 {
|
func (m *Cell) GetAxes() map[string]*Axis {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Axes
|
return m.Axes
|
||||||
|
@ -321,6 +818,48 @@ func (m *Query) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Query) ProtoMessage() {}
|
func (*Query) ProtoMessage() {}
|
||||||
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} }
|
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 {
|
func (m *Query) GetRange() *Range {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Range
|
return m.Range
|
||||||
|
@ -328,6 +867,13 @@ func (m *Query) GetRange() *Range {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Query) GetSource() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Source
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Query) GetShifts() []*TimeShift {
|
func (m *Query) GetShifts() []*TimeShift {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Shifts
|
return m.Shifts
|
||||||
|
@ -346,6 +892,27 @@ func (m *TimeShift) String() string { return proto.CompactTextString(
|
||||||
func (*TimeShift) ProtoMessage() {}
|
func (*TimeShift) ProtoMessage() {}
|
||||||
func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} }
|
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 {
|
type Range struct {
|
||||||
Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"`
|
Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"`
|
||||||
Lower int64 `protobuf:"varint,2,opt,name=Lower,proto3" json:"Lower,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) ProtoMessage() {}
|
||||||
func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} }
|
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 {
|
type AlertRule struct {
|
||||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
JSON string `protobuf:"bytes,2,opt,name=JSON,proto3" json:"JSON,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) ProtoMessage() {}
|
||||||
func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} }
|
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 {
|
type User struct {
|
||||||
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,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) ProtoMessage() {}
|
||||||
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} }
|
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 {
|
func (m *User) GetRoles() []*Role {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Roles
|
return m.Roles
|
||||||
|
@ -389,6 +1026,13 @@ func (m *User) GetRoles() []*Role {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *User) GetSuperAdmin() bool {
|
||||||
|
if m != nil {
|
||||||
|
return m.SuperAdmin
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type Role struct {
|
type Role struct {
|
||||||
Organization string `protobuf:"bytes,1,opt,name=Organization,proto3" json:"Organization,omitempty"`
|
Organization string `protobuf:"bytes,1,opt,name=Organization,proto3" json:"Organization,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,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) ProtoMessage() {}
|
||||||
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} }
|
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 {
|
type Organization struct {
|
||||||
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||||
Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,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) ProtoMessage() {}
|
||||||
func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} }
|
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 {
|
type Config struct {
|
||||||
Auth *AuthConfig `protobuf:"bytes,1,opt,name=Auth" json:"Auth,omitempty"`
|
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) ProtoMessage() {}
|
||||||
func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} }
|
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 {
|
type BuildInfo struct {
|
||||||
Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"`
|
Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"`
|
||||||
Commit string `protobuf:"bytes,2,opt,name=Commit,proto3" json:"Commit,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) ProtoMessage() {}
|
||||||
func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} }
|
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() {
|
func init() {
|
||||||
proto.RegisterType((*Source)(nil), "internal.Source")
|
proto.RegisterType((*Source)(nil), "internal.Source")
|
||||||
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
|
proto.RegisterType((*Dashboard)(nil), "internal.Dashboard")
|
||||||
|
@ -475,7 +1182,7 @@ func init() { proto.RegisterFile("internal.proto", fileDescriptorInternal) }
|
||||||
|
|
||||||
var fileDescriptorInternal = []byte{
|
var fileDescriptorInternal = []byte{
|
||||||
// 1379 bytes of a gzipped FileDescriptorProto
|
// 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,
|
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,
|
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,
|
0x1d, 0x4f, 0xa8, 0xda, 0x38, 0x93, 0xc4, 0xaa, 0x63, 0x9b, 0xb5, 0x7d, 0x17, 0xf3, 0x61, 0x90,
|
||||||
|
|
|
@ -3,7 +3,7 @@ machine:
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
environment:
|
environment:
|
||||||
DOCKER_TAG: chronograf-20180206
|
DOCKER_TAG: chronograf-20180207
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
override:
|
override:
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
VERSION ?= $(shell git describe --always --tags)
|
VERSION ?= $(shell git describe --always --tags)
|
||||||
COMMIT ?= $(shell git rev-parse --short=8 HEAD)
|
COMMIT ?= $(shell git rev-parse --short=8 HEAD)
|
||||||
GDM := $(shell command -v gdm 2> /dev/null)
|
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)
|
YARN := $(shell command -v yarn 2> /dev/null)
|
||||||
|
|
||||||
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go')
|
SOURCES := $(shell find . -name '*.go' ! -name '*_gen.go')
|
||||||
|
@ -63,7 +63,7 @@ ifndef GDM
|
||||||
endif
|
endif
|
||||||
ifndef GOBINDATA
|
ifndef GOBINDATA
|
||||||
@echo "Installing go-bindata"
|
@echo "Installing go-bindata"
|
||||||
go get -u github.com/jteeuwen/go-bindata/...
|
go get -u github.com/kevinburke/go-bindata/...
|
||||||
endif
|
endif
|
||||||
gdm restore
|
gdm restore
|
||||||
@touch .godep
|
@touch .godep
|
||||||
|
|
|
@ -37,7 +37,7 @@ RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
||||||
|
|
||||||
# Install go
|
# Install go
|
||||||
ENV GOPATH /root/go
|
ENV GOPATH /root/go
|
||||||
ENV GO_VERSION 1.9.3
|
ENV GO_VERSION 1.9.4
|
||||||
ENV GO_ARCH amd64
|
ENV GO_ARCH amd64
|
||||||
RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \
|
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 ; \
|
tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \
|
||||||
|
|
|
@ -10,3 +10,6 @@ After updating the Dockerfile_build run
|
||||||
|
|
||||||
and push to quay with:
|
and push to quay with:
|
||||||
`docker push quay.io/influxdb/builder:chronograf-$(date "+%Y%m%d")`
|
`docker push quay.io/influxdb/builder:chronograf-$(date "+%Y%m%d")`
|
||||||
|
|
||||||
|
### Update circle
|
||||||
|
Update DOCKER_TAG in circle.yml to the new container.
|
||||||
|
|
|
@ -128,14 +128,6 @@ class AllUsersTable extends Component {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{isCreatingUser
|
|
||||||
? <AllUsersTableRowNew
|
|
||||||
organizations={organizations}
|
|
||||||
onBlur={this.handleBlurCreateUserRow}
|
|
||||||
onCreateUser={onCreateUser}
|
|
||||||
notify={notify}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
{users.length
|
{users.length
|
||||||
? users.map(user =>
|
? users.map(user =>
|
||||||
<AllUsersTableRow
|
<AllUsersTableRow
|
||||||
|
@ -156,6 +148,14 @@ class AllUsersTable extends Component {
|
||||||
<p>No Users to display</p>
|
<p>No Users to display</p>
|
||||||
</th>
|
</th>
|
||||||
</tr>}
|
</tr>}
|
||||||
|
{isCreatingUser
|
||||||
|
? <AllUsersTableRowNew
|
||||||
|
organizations={organizations}
|
||||||
|
onBlur={this.handleBlurCreateUserRow}
|
||||||
|
onCreateUser={onCreateUser}
|
||||||
|
notify={notify}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,8 @@ const {
|
||||||
colActions,
|
colActions,
|
||||||
} = ALL_USERS_TABLE
|
} = ALL_USERS_TABLE
|
||||||
|
|
||||||
const nullOrganization = {id: null, name: 'None'}
|
const nullOrganization = {id: undefined, name: 'None'}
|
||||||
|
const nullRole = {name: '*', organization: undefined}
|
||||||
|
|
||||||
class AllUsersTableRowNew extends Component {
|
class AllUsersTableRowNew extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -21,11 +22,9 @@ class AllUsersTableRowNew extends Component {
|
||||||
name: '',
|
name: '',
|
||||||
provider: '',
|
provider: '',
|
||||||
scheme: 'oauth2',
|
scheme: 'oauth2',
|
||||||
roles: [
|
role: {
|
||||||
{
|
...nullRole,
|
||||||
...nullOrganization,
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,16 +34,17 @@ class AllUsersTableRowNew extends Component {
|
||||||
|
|
||||||
handleConfirmCreateUser = () => {
|
handleConfirmCreateUser = () => {
|
||||||
const {onBlur, onCreateUser} = this.props
|
const {onBlur, onCreateUser} = this.props
|
||||||
const {name, provider, scheme, roles, superAdmin} = this.state
|
const {name, provider, scheme, role, superAdmin} = this.state
|
||||||
|
|
||||||
const newUser = {
|
const newUser = {
|
||||||
name,
|
name,
|
||||||
provider,
|
provider,
|
||||||
scheme,
|
scheme,
|
||||||
superAdmin,
|
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)
|
onCreateUser(newUser)
|
||||||
onBlur()
|
onBlur()
|
||||||
}
|
}
|
||||||
|
@ -54,17 +54,18 @@ class AllUsersTableRowNew extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectOrganization = newOrganization => {
|
handleSelectOrganization = newOrganization => {
|
||||||
const newRoles = [
|
// if "None" was selected for organization, create a "null role" from the predefined null role
|
||||||
newOrganization.id === null
|
// 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
|
name: '*', // '*' causes the server to determine the current defaultRole of the selected organization
|
||||||
},
|
}
|
||||||
]
|
this.setState({role: newRole})
|
||||||
this.setState({roles: newRoles})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown = e => {
|
handleKeyDown = e => {
|
||||||
|
@ -88,7 +89,7 @@ class AllUsersTableRowNew extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {organizations, onBlur} = this.props
|
const {organizations, onBlur} = this.props
|
||||||
const {name, provider, scheme, roles} = this.state
|
const {name, provider, scheme, role} = this.state
|
||||||
|
|
||||||
const dropdownOrganizationsItems = [
|
const dropdownOrganizationsItems = [
|
||||||
{...nullOrganization},
|
{...nullOrganization},
|
||||||
|
@ -98,7 +99,7 @@ class AllUsersTableRowNew extends Component {
|
||||||
text: o.name,
|
text: o.name,
|
||||||
}))
|
}))
|
||||||
const selectedRole = dropdownOrganizationsItems.find(
|
const selectedRole = dropdownOrganizationsItems.find(
|
||||||
o => roles[0].id === o.id
|
o => role.organization === o.id
|
||||||
)
|
)
|
||||||
|
|
||||||
const preventCreate = !name || !provider
|
const preventCreate = !name || !provider
|
||||||
|
|
|
@ -24,7 +24,7 @@ class AllUsersPage extends Component {
|
||||||
|
|
||||||
handleCreateUser = user => {
|
handleCreateUser = user => {
|
||||||
const {links, actionsAdmin: {createUserAsync}} = this.props
|
const {links, actionsAdmin: {createUserAsync}} = this.props
|
||||||
createUserAsync(links.users, user)
|
createUserAsync(links.allUsers, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateUserRoles = (user, roles, successMessage) => {
|
handleUpdateUserRoles = (user, roles, successMessage) => {
|
||||||
|
|
|
@ -20,7 +20,7 @@ const adminChronograf = (state = initialState, action) => {
|
||||||
|
|
||||||
case 'CHRONOGRAF_ADD_USER': {
|
case 'CHRONOGRAF_ADD_USER': {
|
||||||
const {user} = action.payload
|
const {user} = action.payload
|
||||||
return {...state, users: [user, ...state.users]}
|
return {...state, users: [...state.users, user]}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'CHRONOGRAF_UPDATE_USER': {
|
case 'CHRONOGRAF_UPDATE_USER': {
|
||||||
|
|
|
@ -84,11 +84,14 @@ class AlertTabs extends Component {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: `Alert configuration for ${section} successfully saved.`,
|
text: `Alert configuration for ${section} successfully saved.`,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
return true
|
||||||
|
} catch ({data: {error}}) {
|
||||||
|
const errorMsg = _.join(_.drop(_.split(error, ': '), 2), ': ')
|
||||||
this.props.addFlashMessage({
|
this.props.addFlashMessage({
|
||||||
type: 'error',
|
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()
|
e.preventDefault()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await testAlertOutput(this.props.kapacitor, section)
|
const {data} = await testAlertOutput(this.props.kapacitor, section)
|
||||||
this.props.addFlashMessage({
|
if (data.success) {
|
||||||
type: 'success',
|
this.props.addFlashMessage({
|
||||||
text: `Successfully triggered an alert to ${section}. If the alert does not reach its destination, please check your configuration settings.`,
|
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) {
|
} catch (error) {
|
||||||
this.props.addFlashMessage({
|
this.props.addFlashMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
|
|
|
@ -25,7 +25,7 @@ class KapacitorRule extends Component {
|
||||||
this.setState({timeRange})
|
this.setState({timeRange})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCreate = link => {
|
handleCreate = pathname => {
|
||||||
const {
|
const {
|
||||||
addFlashMessage,
|
addFlashMessage,
|
||||||
queryConfigs,
|
queryConfigs,
|
||||||
|
@ -42,7 +42,7 @@ class KapacitorRule extends Component {
|
||||||
|
|
||||||
createRule(kapacitor, newRule)
|
createRule(kapacitor, newRule)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
router.push(link || `/sources/${source.id}/alert-rules`)
|
router.push(pathname || `/sources/${source.id}/alert-rules`)
|
||||||
addFlashMessage({type: 'success', text: 'Rule successfully created'})
|
addFlashMessage({type: 'success', text: 'Rule successfully created'})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@ -53,7 +53,7 @@ class KapacitorRule extends Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEdit = link => {
|
handleEdit = pathname => {
|
||||||
const {addFlashMessage, queryConfigs, rule, router, source} = this.props
|
const {addFlashMessage, queryConfigs, rule, router, source} = this.props
|
||||||
const updatedRule = Object.assign({}, rule, {
|
const updatedRule = Object.assign({}, rule, {
|
||||||
query: queryConfigs[rule.queryID],
|
query: queryConfigs[rule.queryID],
|
||||||
|
@ -61,7 +61,7 @@ class KapacitorRule extends Component {
|
||||||
|
|
||||||
editRule(updatedRule)
|
editRule(updatedRule)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
router.push(link || `/sources/${source.id}/alert-rules`)
|
router.push(pathname || `/sources/${source.id}/alert-rules`)
|
||||||
addFlashMessage({
|
addFlashMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
text: `${rule.name} successfully saved!`,
|
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 => () => {
|
handleSaveToConfig = configName => () => {
|
||||||
const {rule, configLink, router} = this.props
|
const {rule, configLink, router} = this.props
|
||||||
|
const pathname = `${configLink}#${configName}`
|
||||||
if (this.validationError()) {
|
if (this.validationError()) {
|
||||||
router.push({
|
router.push({
|
||||||
pathname: `${configLink}#${configName}`,
|
pathname,
|
||||||
})
|
})
|
||||||
} else if (rule.id === DEFAULT_RULE_ID) {
|
return
|
||||||
this.handleCreate(configLink)
|
}
|
||||||
|
if (rule.id === DEFAULT_RULE_ID) {
|
||||||
|
this.handleCreate(pathname)
|
||||||
} else {
|
} else {
|
||||||
this.handleEdit(configLink)
|
this.handleEdit(pathname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,13 +171,12 @@ class KapacitorRule extends Component {
|
||||||
} = this.props
|
} = this.props
|
||||||
const {chooseTrigger, updateRuleValues} = ruleActions
|
const {chooseTrigger, updateRuleValues} = ruleActions
|
||||||
const {timeRange} = this.state
|
const {timeRange} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<RuleHeader
|
<RuleHeader
|
||||||
source={source}
|
source={source}
|
||||||
onSave={
|
onSave={this.handleSave}
|
||||||
rule.id === DEFAULT_RULE_ID ? this.handleCreate : this.handleEdit
|
|
||||||
}
|
|
||||||
validationError={this.validationError()}
|
validationError={this.validationError()}
|
||||||
/>
|
/>
|
||||||
<FancyScrollbar className="page-contents fancy-scroll--kapacitor">
|
<FancyScrollbar className="page-contents fancy-scroll--kapacitor">
|
||||||
|
|
|
@ -10,7 +10,7 @@ class AlertaConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -20,8 +20,10 @@ class AlertaConfig extends Component {
|
||||||
url: this.url.value,
|
url: this.url.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ class HipchatConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -21,8 +21,10 @@ class HipchatConfig extends Component {
|
||||||
token: this.token.value,
|
token: this.token.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -16,7 +16,7 @@ class OpsGenieConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -25,8 +25,10 @@ class OpsGenieConfig extends Component {
|
||||||
recipients: this.state.currentRecipients,
|
recipients: this.state.currentRecipients,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -9,7 +9,7 @@ class PagerDutyConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -17,8 +17,10 @@ class PagerDutyConfig extends Component {
|
||||||
url: this.url.value,
|
url: this.url.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -13,7 +13,7 @@ class PushoverConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -22,8 +22,10 @@ class PushoverConfig extends Component {
|
||||||
'user-key': this.userKey.value,
|
'user-key': this.userKey.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -8,19 +8,21 @@ class SMTPConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
host: this.host.value,
|
host: this.host.value,
|
||||||
port: this.port.value,
|
port: this.port.value,
|
||||||
from: this.from.value,
|
from: this.from.value,
|
||||||
|
to: this.to.value ? [this.to.value] : [],
|
||||||
username: this.username.value,
|
username: this.username.value,
|
||||||
password: this.password.value,
|
password: this.password.value,
|
||||||
}
|
}
|
||||||
|
const success = await this.props.onSave(properties)
|
||||||
this.props.onSave(properties)
|
if (success) {
|
||||||
this.setState({testEnabled: true})
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
@ -28,7 +30,7 @@ class SMTPConfig extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {host, port, from, username, password} = this.props.config.options
|
const {host, port, from, username, password, to} = this.props.config.options
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
@ -56,7 +58,7 @@ class SMTPConfig extends Component {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group col-xs-12">
|
<div className="form-group col-xs-6">
|
||||||
<label htmlFor="smtp-from">From Email</label>
|
<label htmlFor="smtp-from">From Email</label>
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
|
@ -69,6 +71,19 @@ class SMTPConfig extends Component {
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div className="form-group col-xs-12 col-md-6">
|
||||||
<label htmlFor="smtp-user">User</label>
|
<label htmlFor="smtp-user">User</label>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -8,7 +8,7 @@ class SensuConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -16,8 +16,10 @@ class SensuConfig extends Component {
|
||||||
addr: this.addr.value,
|
addr: this.addr.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -10,14 +10,16 @@ class SlackConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const properties = {
|
const properties = {
|
||||||
url: this.url.value,
|
url: this.url.value,
|
||||||
channel: this.channel.value,
|
channel: this.channel.value,
|
||||||
}
|
}
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
this.setState({testEnabled: false})
|
this.setState({testEnabled: false})
|
||||||
|
|
|
@ -10,7 +10,7 @@ class TalkConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -18,8 +18,10 @@ class TalkConfig extends Component {
|
||||||
author_name: this.author.value,
|
author_name: this.author.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ class TelegramConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
let parseMode
|
let parseMode
|
||||||
|
@ -31,8 +31,10 @@ class TelegramConfig extends Component {
|
||||||
token: this.token.value,
|
token: this.token.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ class VictorOpsConfig extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = e => {
|
handleSubmit = async e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const properties = {
|
const properties = {
|
||||||
|
@ -19,8 +19,10 @@ class VictorOpsConfig extends Component {
|
||||||
url: this.url.value,
|
url: this.url.value,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onSave(properties)
|
const success = await this.props.onSave(properties)
|
||||||
this.setState({testEnabled: true})
|
if (success) {
|
||||||
|
this.setState({testEnabled: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disableTest = () => {
|
disableTest = () => {
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
#*
|
|
||||||
*.[568]
|
|
||||||
*.a
|
|
||||||
*~
|
|
||||||
[568].out
|
|
||||||
_*
|
|
|
@ -1,13 +1,16 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.3.3
|
- 1.3.x
|
||||||
- 1.5.4
|
- 1.5.x
|
||||||
- 1.6.2
|
- 1.6.x
|
||||||
- tip
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- master
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- go: tip
|
- go: master
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
install:
|
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).
|
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||||
|
|
|
@ -88,5 +88,37 @@ Example:
|
||||||
humanize.SI(0.00000000223, "M") // 2.23 nM
|
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
|
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||||
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
func Comma(v int64) string {
|
func Comma(v int64) string {
|
||||||
sign := ""
|
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 {
|
if v == math.MinInt64 {
|
||||||
return "-9,223,372,036,854,775,808"
|
return "-9,223,372,036,854,775,808"
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnit
|
||||||
}
|
}
|
||||||
|
|
||||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||||
return magnitudes[i].D >= diff
|
return magnitudes[i].D > diff
|
||||||
})
|
})
|
||||||
|
|
||||||
if n >= len(magnitudes) {
|
if n >= len(magnitudes) {
|
||||||
|
|
|
@ -37,6 +37,17 @@ func TestPast(t *testing.T) {
|
||||||
}.validate(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) {
|
func TestFuture(t *testing.T) {
|
||||||
// Add a little time so that these things properly line up in
|
// Add a little time so that these things properly line up in
|
||||||
// the future.
|
// the future.
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.6.x
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -v -d -t github.com/golang/protobuf/...
|
||||||
|
- curl -L https://github.com/google/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip -o /tmp/protoc.zip
|
||||||
|
- unzip /tmp/protoc.zip -d $HOME/protoc
|
||||||
|
|
||||||
|
env:
|
||||||
|
- PATH=$HOME/protoc/bin:$PATH
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make all test
|
|
@ -1,5 +1,8 @@
|
||||||
# Go support for Protocol Buffers
|
# 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.
|
Google's data interchange format.
|
||||||
Copyright 2010 The Go Authors.
|
Copyright 2010 The Go Authors.
|
||||||
https://github.com/golang/protobuf
|
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
|
for details or, if you are using gccgo, follow the instructions at
|
||||||
https://golang.org/doc/install/gccgo
|
https://golang.org/doc/install/gccgo
|
||||||
- Grab the code from the repository and install the proto package.
|
- 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,
|
The compiler plugin, protoc-gen-go, will be installed in $GOBIN,
|
||||||
defaulting to $GOPATH/bin. It must be in your $PATH for the protocol
|
defaulting to $GOPATH/bin. It must be in your $PATH for the protocol
|
||||||
compiler, protoc, to find it.
|
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:
|
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||||
|
|
||||||
- Non-repeated fields of non-message type are values instead of pointers.
|
- 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.
|
- Enum types do not get an Enum method.
|
||||||
|
|
||||||
Consider file test.proto, containing
|
Consider file test.proto, containing
|
||||||
|
|
||||||
```proto
|
```proto
|
||||||
|
syntax = "proto2";
|
||||||
package example;
|
package example;
|
||||||
|
|
||||||
enum FOO { X = 17; };
|
enum FOO { X = 17; };
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
package proto_test
|
package proto_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiscardUnknown recursively discards all unknown fields from this message
|
||||||
|
// and all embedded messages.
|
||||||
|
//
|
||||||
|
// When unmarshaling a message with unrecognized fields, the tags and values
|
||||||
|
// of such fields are preserved in the Message. This allows a later call to
|
||||||
|
// marshal to be able to produce a message that continues to have those
|
||||||
|
// unrecognized fields. To avoid this, DiscardUnknown is used to
|
||||||
|
// explicitly clear the unknown fields after unmarshaling.
|
||||||
|
//
|
||||||
|
// For proto2 messages, the unknown fields of message extensions are only
|
||||||
|
// discarded from messages that have been accessed via GetExtension.
|
||||||
|
func DiscardUnknown(m Message) {
|
||||||
|
discardLegacy(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func discardLegacy(m Message) {
|
||||||
|
v := reflect.ValueOf(m)
|
||||||
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
if v.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := v.Type()
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
if strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vf := v.Field(i)
|
||||||
|
tf := f.Type
|
||||||
|
|
||||||
|
// Unwrap tf to get its most basic type.
|
||||||
|
var isPointer, isSlice bool
|
||||||
|
if tf.Kind() == reflect.Slice && tf.Elem().Kind() != reflect.Uint8 {
|
||||||
|
isSlice = true
|
||||||
|
tf = tf.Elem()
|
||||||
|
}
|
||||||
|
if tf.Kind() == reflect.Ptr {
|
||||||
|
isPointer = true
|
||||||
|
tf = tf.Elem()
|
||||||
|
}
|
||||||
|
if isPointer && isSlice && tf.Kind() != reflect.Struct {
|
||||||
|
panic(fmt.Sprintf("%T.%s cannot be a slice of pointers to primitive types", m, f.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tf.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
switch {
|
||||||
|
case !isPointer:
|
||||||
|
panic(fmt.Sprintf("%T.%s cannot be a direct struct value", m, f.Name))
|
||||||
|
case isSlice: // E.g., []*pb.T
|
||||||
|
for j := 0; j < vf.Len(); j++ {
|
||||||
|
discardLegacy(vf.Index(j).Interface().(Message))
|
||||||
|
}
|
||||||
|
default: // E.g., *pb.T
|
||||||
|
discardLegacy(vf.Interface().(Message))
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
switch {
|
||||||
|
case isPointer || isSlice:
|
||||||
|
panic(fmt.Sprintf("%T.%s cannot be a pointer to a map or a slice of map values", m, f.Name))
|
||||||
|
default: // E.g., map[K]V
|
||||||
|
tv := vf.Type().Elem()
|
||||||
|
if tv.Kind() == reflect.Ptr && tv.Implements(protoMessageType) { // Proto struct (e.g., *T)
|
||||||
|
for _, key := range vf.MapKeys() {
|
||||||
|
val := vf.MapIndex(key)
|
||||||
|
discardLegacy(val.Interface().(Message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Interface:
|
||||||
|
// Must be oneof field.
|
||||||
|
switch {
|
||||||
|
case isPointer || isSlice:
|
||||||
|
panic(fmt.Sprintf("%T.%s cannot be a pointer to a interface or a slice of interface values", m, f.Name))
|
||||||
|
default: // E.g., test_proto.isCommunique_Union interface
|
||||||
|
if !vf.IsNil() && f.Tag.Get("protobuf_oneof") != "" {
|
||||||
|
vf = vf.Elem() // E.g., *test_proto.Communique_Msg
|
||||||
|
if !vf.IsNil() {
|
||||||
|
vf = vf.Elem() // E.g., test_proto.Communique_Msg
|
||||||
|
vf = vf.Field(0) // E.g., Proto struct (e.g., *T) or primitive value
|
||||||
|
if vf.Kind() == reflect.Ptr {
|
||||||
|
discardLegacy(vf.Interface().(Message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vf := v.FieldByName("XXX_unrecognized"); vf.IsValid() {
|
||||||
|
if vf.Type() != reflect.TypeOf([]byte{}) {
|
||||||
|
panic("expected XXX_unrecognized to be of type []byte")
|
||||||
|
}
|
||||||
|
vf.Set(reflect.ValueOf([]byte(nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// For proto2 messages, only discard unknown fields in message extensions
|
||||||
|
// that have been accessed via GetExtension.
|
||||||
|
if em, ok := extendable(m); ok {
|
||||||
|
// Ignore lock since discardLegacy is not concurrency safe.
|
||||||
|
emm, _ := em.extensionsRead()
|
||||||
|
for _, mx := range emm {
|
||||||
|
if m, ok := mx.value.(Message); ok {
|
||||||
|
discardLegacy(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -174,11 +174,11 @@ func sizeFixed32(x uint64) int {
|
||||||
// This is the format used for the sint64 protocol buffer type.
|
// This is the format used for the sint64 protocol buffer type.
|
||||||
func (p *Buffer) EncodeZigzag64(x uint64) error {
|
func (p *Buffer) EncodeZigzag64(x uint64) error {
|
||||||
// use signed number to get arithmetic right shift.
|
// 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 {
|
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
|
// EncodeZigzag32 writes a zigzag-encoded 32-bit integer
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
|
||||||
package proto_test
|
package proto_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -73,7 +73,6 @@ for a protocol buffer variable v:
|
||||||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||||
|
|
||||||
- Non-repeated fields of non-message type are values instead of pointers.
|
- 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.
|
- Enum types do not get an Enum method.
|
||||||
|
|
||||||
The simplest way to describe this is to see an example.
|
The simplest way to describe this is to see an example.
|
||||||
|
|
|
@ -865,7 +865,7 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
||||||
return p.readStruct(fv, terminator)
|
return p.readStruct(fv, terminator)
|
||||||
case reflect.Uint32:
|
case reflect.Uint32:
|
||||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||||
fv.SetUint(uint64(x))
|
fv.SetUint(x)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case reflect.Uint64:
|
case reflect.Uint64:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
This package is intended to be a more powerful and safer alternative to
|
This package is intended to be a more powerful and safer alternative to
|
||||||
`reflect.DeepEqual` for comparing whether two values are semantically equal.
|
`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,
|
* When the default behavior of equality does not suit the needs of the test,
|
||||||
custom equality functions can override the equality operation.
|
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,
|
* If no custom equality functions are used and no `Equal` method is defined,
|
||||||
equality is determined by recursively comparing the primitive kinds on both
|
equality is determined by recursively comparing the primitive kinds on both
|
||||||
values, much like `reflect.DeepEqual`. Unlike `reflect.DeepEqual`, unexported
|
values, much like `reflect.DeepEqual`. Unlike `reflect.DeepEqual`, unexported
|
||||||
fields are not compared; they result in panics unless suppressed by using
|
fields are not compared by default; they result in panics unless suppressed
|
||||||
an `Ignore` option.
|
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.
|
This is not an official Google product.
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/internal/function"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SortSlices returns a Transformer option that sorts all []V.
|
// SortSlices returns a Transformer option that sorts all []V.
|
||||||
|
@ -26,7 +27,7 @@ import (
|
||||||
// SortSlices can be used in conjuction with EquateEmpty.
|
// SortSlices can be used in conjuction with EquateEmpty.
|
||||||
func SortSlices(less interface{}) cmp.Option {
|
func SortSlices(less interface{}) cmp.Option {
|
||||||
vf := reflect.ValueOf(less)
|
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))
|
panic(fmt.Sprintf("invalid less function: %T", less))
|
||||||
}
|
}
|
||||||
ss := sliceSorter{vf.Type().In(0), vf}
|
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.
|
// SortMaps can be used in conjuction with EquateEmpty.
|
||||||
func SortMaps(less interface{}) cmp.Option {
|
func SortMaps(less interface{}) cmp.Option {
|
||||||
vf := reflect.ValueOf(less)
|
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))
|
panic(fmt.Sprintf("invalid less function: %T", less))
|
||||||
}
|
}
|
||||||
ms := mapSorter{vf.Type().In(0), vf}
|
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()
|
return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
|
||||||
}
|
}
|
||||||
|
|
||||||
var boolType = reflect.TypeOf(true)
|
|
||||||
|
|
||||||
// isTTBoolFunc reports whether f is of the form: func(T, T) bool.
|
|
||||||
func isTTBoolFunc(t reflect.Type) bool {
|
|
||||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return t.NumIn() == 2 && t.NumOut() == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType
|
|
||||||
}
|
|
||||||
|
|
|
@ -29,6 +29,10 @@ package cmp
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"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
|
// 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.
|
// 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
|
// Equal reports whether x and y are equal by recursively applying the
|
||||||
// following rules in the given order to x and y and all of their sub-values:
|
// following rules in the given order to x and y and all of their sub-values:
|
||||||
//
|
//
|
||||||
|
@ -48,11 +54,10 @@ import (
|
||||||
// If at least one Ignore exists in S, then the comparison is ignored.
|
// If at least one Ignore exists in S, then the comparison is ignored.
|
||||||
// If the number of Transformer and Comparer options in S is greater than one,
|
// If the number of Transformer and Comparer options in S is greater than one,
|
||||||
// then Equal panics because it is ambiguous which option to use.
|
// then Equal panics because it is ambiguous which option to use.
|
||||||
// If S contains a single Transformer, then apply that transformer on the
|
// If S contains a single Transformer, then use that to transform the current
|
||||||
// current values and recursively call Equal on the transformed output values.
|
// values and recursively call Equal on the output values.
|
||||||
// If S contains a single Comparer, then use that Comparer to determine whether
|
// If S contains a single Comparer, then use that to compare the current values.
|
||||||
// the current values are equal or not.
|
// Otherwise, evaluation proceeds to the next rule.
|
||||||
// Otherwise, S is empty and evaluation proceeds to the next rule.
|
|
||||||
//
|
//
|
||||||
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
|
// • If the values have an Equal method of the form "(T) Equal(T) bool" or
|
||||||
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
|
// "(T) Equal(I) bool" where T is assignable to I, then use the result of
|
||||||
|
@ -79,7 +84,7 @@ import (
|
||||||
func Equal(x, y interface{}, opts ...Option) bool {
|
func Equal(x, y interface{}, opts ...Option) bool {
|
||||||
s := newState(opts)
|
s := newState(opts)
|
||||||
s.compareAny(reflect.ValueOf(x), reflect.ValueOf(y))
|
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.
|
// 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.
|
// Do not depend on this output being stable.
|
||||||
func Diff(x, y interface{}, opts ...Option) string {
|
func Diff(x, y interface{}, opts ...Option) string {
|
||||||
r := new(defaultReporter)
|
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...)
|
eq := Equal(x, y, opts...)
|
||||||
d := r.String()
|
d := r.String()
|
||||||
if (d == "") != eq {
|
if (d == "") != eq {
|
||||||
|
@ -101,48 +106,44 @@ func Diff(x, y interface{}, opts ...Option) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type state struct {
|
type state struct {
|
||||||
eq bool // Current result of comparison
|
// These fields represent the "comparison state".
|
||||||
curPath Path // The current path in the value tree
|
// 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
|
// dynChecker triggers pseudo-random checks for option correctness.
|
||||||
// user provided func(T, T) bool functions are symmetric and deterministic.
|
// It is safe for statelessCompare to mutate this value.
|
||||||
//
|
dynChecker dynChecker
|
||||||
// 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 }
|
|
||||||
|
|
||||||
// These fields, once set by processOption, will not change.
|
// These fields, once set by processOption, will not change.
|
||||||
exporters map[reflect.Type]bool // Set of structs with unexported field visibility
|
exporters map[reflect.Type]bool // Set of structs with unexported field visibility
|
||||||
optsIgn []option // List of all ignore options without value filters
|
opts Options // List of all fundamental and filter options
|
||||||
opts []option // List of all other options
|
|
||||||
reporter reporter // Optional reporter used for difference formatting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newState(opts []Option) *state {
|
func newState(opts []Option) *state {
|
||||||
s := &state{eq: true}
|
s := new(state)
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
s.processOption(opt)
|
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
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) processOption(opt Option) {
|
func (s *state) processOption(opt Option) {
|
||||||
switch opt := opt.(type) {
|
switch opt := opt.(type) {
|
||||||
|
case nil:
|
||||||
case Options:
|
case Options:
|
||||||
for _, o := range opt {
|
for _, o := range opt {
|
||||||
s.processOption(o)
|
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:
|
case visibleStructs:
|
||||||
if s.exporters == nil {
|
if s.exporters == nil {
|
||||||
s.exporters = make(map[reflect.Type]bool)
|
s.exporters = make(map[reflect.Type]bool)
|
||||||
|
@ -150,15 +151,6 @@ func (s *state) processOption(opt Option) {
|
||||||
for t := range opt {
|
for t := range opt {
|
||||||
s.exporters[t] = true
|
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:
|
case reporter:
|
||||||
if s.reporter != nil {
|
if s.reporter != nil {
|
||||||
panic("difference reporter already registered")
|
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) {
|
func (s *state) compareAny(vx, vy reflect.Value) {
|
||||||
// TODO: Support cyclic data structures.
|
// TODO: Support cyclic data structures.
|
||||||
|
|
||||||
|
@ -184,10 +194,12 @@ func (s *state) compareAny(vx, vy reflect.Value) {
|
||||||
t := vx.Type()
|
t := vx.Type()
|
||||||
if len(s.curPath) == 0 {
|
if len(s.curPath) == 0 {
|
||||||
s.curPath.push(&pathStep{typ: t})
|
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.
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,143 +276,144 @@ func (s *state) compareAny(vx, vy reflect.Value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryOptions iterates through all of the options and evaluates whether any
|
func (s *state) tryExporting(vx, vy reflect.Value) (reflect.Value, reflect.Value) {
|
||||||
// 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.
|
|
||||||
if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
|
if sf, ok := s.curPath[len(s.curPath)-1].(*structField); ok && sf.unexported {
|
||||||
if !sf.force {
|
if sf.force {
|
||||||
const help = "consider using AllowUnexported or cmpopts.IgnoreUnexported"
|
// Use unsafe pointer arithmetic to get read-write access to an
|
||||||
panic(fmt.Sprintf("cannot handle unexported field: %#v\n%s", s.curPath, help))
|
// 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
|
func (s *state) tryOptions(vx, vy reflect.Value, t reflect.Type) bool {
|
||||||
// unexported field in the struct.
|
// If there were no FilterValues, we will not detect invalid inputs,
|
||||||
*vx = unsafeRetrieveField(sf.pvx, sf.field)
|
// so manually check for them and append invalid if necessary.
|
||||||
*vy = unsafeRetrieveField(sf.pvy, sf.field)
|
// 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.
|
// Evaluate all filters and apply the remaining options.
|
||||||
optIdx := -1 // Index of Option to apply
|
if opt := opts.filter(s, vx, vy, t); opt != nil {
|
||||||
for i, opt := range s.opts {
|
return opt.apply(s, vx, vy)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
return false
|
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 {
|
func (s *state) tryMethod(vx, vy reflect.Value, t reflect.Type) bool {
|
||||||
// Check if this type even has an Equal method.
|
// Check if this type even has an Equal method.
|
||||||
m, ok := t.MethodByName("Equal")
|
m, ok := t.MethodByName("Equal")
|
||||||
ft := functionType(m.Type)
|
if !ok || !function.IsType(m.Type, function.EqualAssignable) {
|
||||||
if !ok || (ft != equalFunc && ft != equalIfaceFunc) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
eq := s.callFunc(m.Func, vx, vy)
|
eq := s.callTTBFunc(m.Func, vx, vy)
|
||||||
s.report(eq, vx, vy)
|
s.report(eq, vx, vy)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *state) callFunc(f, x, y reflect.Value) bool {
|
func (s *state) callTRFunc(f, v reflect.Value) reflect.Value {
|
||||||
got := f.Call([]reflect.Value{x, y})[0].Bool()
|
if !s.dynChecker.Next() {
|
||||||
if s.dsCheck.curr == s.dsCheck.next {
|
return f.Call([]reflect.Value{v})[0]
|
||||||
// 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++
|
|
||||||
}
|
}
|
||||||
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) {
|
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)
|
s.curPath.push(step)
|
||||||
defer s.curPath.pop()
|
|
||||||
|
|
||||||
// Regardless of the lengths, we always try to compare the elements.
|
// Compute an edit-script for slices vx and vy.
|
||||||
// If one slice is longer, we will report the elements of the longer
|
eq, es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result {
|
||||||
// slice as different (relative to an invalid reflect.Value).
|
step.xkey, step.ykey = ix, iy
|
||||||
nmin := vx.Len()
|
return s.statelessCompare(vx.Index(ix), vy.Index(iy))
|
||||||
if nmin > vy.Len() {
|
})
|
||||||
nmin = vy.Len()
|
|
||||||
|
// 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
|
// Replay the edit-script.
|
||||||
s.compareAny(vx.Index(i), vy.Index(i))
|
var ix, iy int
|
||||||
}
|
for _, e := range es {
|
||||||
for i := nmin; i < vx.Len(); i++ {
|
switch e {
|
||||||
step.key = i
|
case diff.UniqueX:
|
||||||
s.report(false, vx.Index(i), reflect.Value{})
|
step.xkey, step.ykey = ix, -1
|
||||||
}
|
s.report(false, vx.Index(ix), nothing)
|
||||||
for i := nmin; i < vy.Len(); i++ {
|
ix++
|
||||||
step.key = i
|
case diff.UniqueY:
|
||||||
s.report(false, reflect.Value{}, vy.Index(i))
|
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) {
|
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()}}
|
step := &mapIndex{pathStep: pathStep{t.Elem()}}
|
||||||
s.curPath.push(step)
|
s.curPath.push(step)
|
||||||
defer s.curPath.pop()
|
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
|
step.key = k
|
||||||
vvx := vx.MapIndex(k)
|
vvx := vx.MapIndex(k)
|
||||||
vvy := vy.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():
|
case vvx.IsValid() && vvy.IsValid():
|
||||||
s.compareAny(vvx, vvy)
|
s.compareAny(vvx, vvy)
|
||||||
case vvx.IsValid() && !vvy.IsValid():
|
case vvx.IsValid() && !vvy.IsValid():
|
||||||
s.report(false, vvx, reflect.Value{})
|
s.report(false, vvx, nothing)
|
||||||
case !vvx.IsValid() && vvy.IsValid():
|
case !vvx.IsValid() && vvy.IsValid():
|
||||||
s.report(false, reflect.Value{}, vvy)
|
s.report(false, nothing, vvy)
|
||||||
default:
|
default:
|
||||||
// It is possible for both vvx and vvy to be invalid if the
|
// 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
|
// 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.
|
// report records the result of a single comparison.
|
||||||
// It also calls Report if any reporter is registered.
|
// It also calls Report if any reporter is registered.
|
||||||
func (s *state) report(eq bool, vx, vy reflect.Value) {
|
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 {
|
if s.reporter != nil {
|
||||||
s.reporter.Report(vx, vy, eq, s.curPath)
|
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.
|
// makeAddressable returns a value that is always addressable.
|
||||||
// It returns the input verbatim if it is already addressable,
|
// It returns the input verbatim if it is already addressable,
|
||||||
// otherwise it creates a new value and returns an addressable copy.
|
// 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)
|
vc.Set(v)
|
||||||
return vc
|
return vc
|
||||||
}
|
}
|
||||||
|
|
||||||
type funcType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
invalidFunc funcType = iota
|
|
||||||
equalFunc // func(T, T) bool
|
|
||||||
equalIfaceFunc // func(T, I) bool
|
|
||||||
transformFunc // func(T) R
|
|
||||||
valueFilterFunc = equalFunc // func(T, T) bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var boolType = reflect.TypeOf(true)
|
|
||||||
|
|
||||||
// functionType identifies which type of function signature this is.
|
|
||||||
func functionType(t reflect.Type) funcType {
|
|
||||||
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
|
||||||
return invalidFunc
|
|
||||||
}
|
|
||||||
ni, no := t.NumIn(), t.NumOut()
|
|
||||||
switch {
|
|
||||||
case ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType:
|
|
||||||
return equalFunc // or valueFilterFunc
|
|
||||||
case ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType:
|
|
||||||
return equalIfaceFunc
|
|
||||||
case ni == 1 && no == 1:
|
|
||||||
return transformFunc
|
|
||||||
default:
|
|
||||||
return invalidFunc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,8 +6,10 @@ package cmp_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -16,53 +18,17 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
pb "github.com/google/go-cmp/cmp/internal/testprotos"
|
pb "github.com/google/go-cmp/cmp/internal/testprotos"
|
||||||
ts "github.com/google/go-cmp/cmp/internal/teststructs"
|
ts "github.com/google/go-cmp/cmp/internal/teststructs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var now = time.Now()
|
var now = time.Now()
|
||||||
var boolType = reflect.TypeOf(true)
|
|
||||||
var mutexType = reflect.TypeOf(sync.Mutex{})
|
|
||||||
|
|
||||||
func intPtr(n int) *int { return &n }
|
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 {
|
type test struct {
|
||||||
label string // Test description
|
label string // Test description
|
||||||
x, y interface{} // Input values to compare
|
x, y interface{} // Input values to compare
|
||||||
|
@ -83,7 +49,8 @@ func TestDiff(t *testing.T) {
|
||||||
tests = append(tests, project4Tests()...)
|
tests = append(tests, project4Tests()...)
|
||||||
|
|
||||||
for _, tt := range tests {
|
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
|
var gotDiff, gotPanic string
|
||||||
func() {
|
func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -101,8 +68,8 @@ func TestDiff(t *testing.T) {
|
||||||
if gotPanic != "" {
|
if gotPanic != "" {
|
||||||
t.Fatalf("unexpected panic message: %s", gotPanic)
|
t.Fatalf("unexpected panic message: %s", gotPanic)
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(gotDiff) != strings.TrimSpace(tt.wantDiff) {
|
if got, want := strings.TrimSpace(gotDiff), strings.TrimSpace(tt.wantDiff); got != want {
|
||||||
t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s", gotDiff, tt.wantDiff)
|
t.Fatalf("difference message:\ngot:\n%s\n\nwant:\n%s", got, want)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !strings.Contains(gotPanic, tt.wantPanic) {
|
if !strings.Contains(gotPanic, tt.wantPanic) {
|
||||||
|
@ -117,10 +84,9 @@ func comparerTests() []test {
|
||||||
const label = "Comparer"
|
const label = "Comparer"
|
||||||
|
|
||||||
return []test{{
|
return []test{{
|
||||||
label: label,
|
label: label,
|
||||||
x: 1,
|
x: 1,
|
||||||
y: 1,
|
y: 1,
|
||||||
wantDiff: "",
|
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: 1,
|
x: 1,
|
||||||
|
@ -147,7 +113,7 @@ func comparerTests() []test {
|
||||||
cmp.Comparer(func(x, y int) bool { return true }),
|
cmp.Comparer(func(x, y int) bool { return true }),
|
||||||
cmp.Transformer("", func(x int) float64 { return float64(x) }),
|
cmp.Transformer("", func(x int) float64 { return float64(x) }),
|
||||||
},
|
},
|
||||||
wantPanic: "ambiguous set of options",
|
wantPanic: "ambiguous set of applicable options",
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: 1,
|
x: 1,
|
||||||
|
@ -164,10 +130,9 @@ func comparerTests() []test {
|
||||||
opts: []cmp.Option{struct{ cmp.Option }{}},
|
opts: []cmp.Option{struct{ cmp.Option }{}},
|
||||||
wantPanic: "unknown option",
|
wantPanic: "unknown option",
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: struct{ A, B, C int }{1, 2, 3},
|
x: struct{ A, B, C int }{1, 2, 3},
|
||||||
y: struct{ A, B, C int }{1, 2, 3},
|
y: struct{ A, B, C int }{1, 2, 3},
|
||||||
wantDiff: "",
|
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: struct{ A, B, C int }{1, 2, 3},
|
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*")},
|
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||||
wantPanic: "cannot handle unexported field",
|
wantPanic: "cannot handle unexported field",
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||||
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||||
opts: []cmp.Option{cmp.Comparer(equalRegexp)},
|
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
|
||||||
wantDiff: "",
|
if x == nil || y == nil {
|
||||||
|
return x == nil && y == nil
|
||||||
|
}
|
||||||
|
return x.String() == y.String()
|
||||||
|
})},
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
x: []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
|
||||||
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
|
y: []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
|
||||||
opts: []cmp.Option{cmp.Comparer(equalRegexp)},
|
opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
|
||||||
wantDiff: "{[]*regexp.Regexp}[1]:\n\t-: \"a*b*c*\"\n\t+: \"a*b*d*\"\n",
|
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,
|
label: label,
|
||||||
x: func() ***int {
|
x: func() ***int {
|
||||||
|
@ -305,6 +282,22 @@ func comparerTests() []test {
|
||||||
root:
|
root:
|
||||||
-: "hello"
|
-: "hello"
|
||||||
+: "hello2"`,
|
+: "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,
|
label: label,
|
||||||
x: make([]int, 1000),
|
x: make([]int, 1000),
|
||||||
|
@ -325,6 +318,41 @@ root:
|
||||||
}, cmp.Ignore()),
|
}, cmp.Ignore()),
|
||||||
},
|
},
|
||||||
wantPanic: "non-deterministic or non-symmetric function detected",
|
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 / 2 }),
|
||||||
cmp.Transformer("", func(in int) int { return in }),
|
cmp.Transformer("", func(in int) int { return in }),
|
||||||
},
|
},
|
||||||
wantPanic: "ambiguous set of options",
|
wantPanic: "ambiguous set of applicable options",
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: []int{0, -5, 0, -1},
|
x: []int{0, -5, 0, -1},
|
||||||
|
@ -383,7 +411,7 @@ func transformerTests() []test {
|
||||||
if in == 0 {
|
if in == 0 {
|
||||||
return "string"
|
return "string"
|
||||||
}
|
}
|
||||||
return in
|
return float64(in)
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
wantDiff: `
|
wantDiff: `
|
||||||
|
@ -496,7 +524,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructA{},
|
x: ts.ParentStructA{},
|
||||||
y: ts.ParentStructA{},
|
y: ts.ParentStructA{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructA{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructA{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructA",
|
label: label + "ParentStructA",
|
||||||
|
@ -532,7 +560,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructB{},
|
x: ts.ParentStructB{},
|
||||||
y: ts.ParentStructB{},
|
y: ts.ParentStructB{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructB{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructB{}),
|
||||||
},
|
},
|
||||||
wantPanic: "cannot handle unexported field",
|
wantPanic: "cannot handle unexported field",
|
||||||
}, {
|
}, {
|
||||||
|
@ -540,8 +568,8 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructB{},
|
x: ts.ParentStructB{},
|
||||||
y: ts.ParentStructB{},
|
y: ts.ParentStructB{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructB{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructB{}),
|
||||||
IgnoreUnexported(ts.PublicStruct{}),
|
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructB",
|
label: label + "ParentStructB",
|
||||||
|
@ -582,7 +610,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructC{},
|
x: ts.ParentStructC{},
|
||||||
y: ts.ParentStructC{},
|
y: ts.ParentStructC{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructC{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructC{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructC",
|
label: label + "ParentStructC",
|
||||||
|
@ -624,7 +652,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructD{},
|
x: ts.ParentStructD{},
|
||||||
y: ts.ParentStructD{},
|
y: ts.ParentStructD{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructD{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructD{}),
|
||||||
},
|
},
|
||||||
wantPanic: "cannot handle unexported field",
|
wantPanic: "cannot handle unexported field",
|
||||||
}, {
|
}, {
|
||||||
|
@ -632,8 +660,8 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructD{},
|
x: ts.ParentStructD{},
|
||||||
y: ts.ParentStructD{},
|
y: ts.ParentStructD{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructD{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructD{}),
|
||||||
IgnoreUnexported(ts.PublicStruct{}),
|
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructD",
|
label: label + "ParentStructD",
|
||||||
|
@ -675,7 +703,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructE{},
|
x: ts.ParentStructE{},
|
||||||
y: ts.ParentStructE{},
|
y: ts.ParentStructE{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructE{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructE{}),
|
||||||
},
|
},
|
||||||
wantPanic: "cannot handle unexported field",
|
wantPanic: "cannot handle unexported field",
|
||||||
}, {
|
}, {
|
||||||
|
@ -683,8 +711,8 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructE{},
|
x: ts.ParentStructE{},
|
||||||
y: ts.ParentStructE{},
|
y: ts.ParentStructE{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructE{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructE{}),
|
||||||
IgnoreUnexported(ts.PublicStruct{}),
|
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructE",
|
label: label + "ParentStructE",
|
||||||
|
@ -734,7 +762,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructF{},
|
x: ts.ParentStructF{},
|
||||||
y: ts.ParentStructF{},
|
y: ts.ParentStructF{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructF{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructF{}),
|
||||||
},
|
},
|
||||||
wantPanic: "cannot handle unexported field",
|
wantPanic: "cannot handle unexported field",
|
||||||
}, {
|
}, {
|
||||||
|
@ -742,8 +770,8 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructF{},
|
x: ts.ParentStructF{},
|
||||||
y: ts.ParentStructF{},
|
y: ts.ParentStructF{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructF{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructF{}),
|
||||||
IgnoreUnexported(ts.PublicStruct{}),
|
cmpopts.IgnoreUnexported(ts.PublicStruct{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructF",
|
label: label + "ParentStructF",
|
||||||
|
@ -804,7 +832,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructG{},
|
x: ts.ParentStructG{},
|
||||||
y: ts.ParentStructG{},
|
y: ts.ParentStructG{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructG{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructG{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructG",
|
label: label + "ParentStructG",
|
||||||
|
@ -849,7 +877,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructH{},
|
x: ts.ParentStructH{},
|
||||||
y: ts.ParentStructH{},
|
y: ts.ParentStructH{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructH{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructH{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructH",
|
label: label + "ParentStructH",
|
||||||
|
@ -890,14 +918,14 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructI{},
|
x: ts.ParentStructI{},
|
||||||
y: ts.ParentStructI{},
|
y: ts.ParentStructI{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructI{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructI",
|
label: label + "ParentStructI",
|
||||||
x: createStructI(0),
|
x: createStructI(0),
|
||||||
y: createStructI(0),
|
y: createStructI(0),
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructI{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructI{}),
|
||||||
},
|
},
|
||||||
wantPanic: "cannot handle unexported field",
|
wantPanic: "cannot handle unexported field",
|
||||||
}, {
|
}, {
|
||||||
|
@ -905,7 +933,7 @@ func embeddedTests() []test {
|
||||||
x: createStructI(0),
|
x: createStructI(0),
|
||||||
y: createStructI(0),
|
y: createStructI(0),
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructI",
|
label: label + "ParentStructI",
|
||||||
|
@ -952,7 +980,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructJ{},
|
x: ts.ParentStructJ{},
|
||||||
y: ts.ParentStructJ{},
|
y: ts.ParentStructJ{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructJ{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
|
||||||
},
|
},
|
||||||
wantPanic: "cannot handle unexported field",
|
wantPanic: "cannot handle unexported field",
|
||||||
}, {
|
}, {
|
||||||
|
@ -960,7 +988,7 @@ func embeddedTests() []test {
|
||||||
x: ts.ParentStructJ{},
|
x: ts.ParentStructJ{},
|
||||||
y: ts.ParentStructJ{},
|
y: ts.ParentStructJ{},
|
||||||
opts: []cmp.Option{
|
opts: []cmp.Option{
|
||||||
IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
|
cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
label: label + "ParentStructJ",
|
label: label + "ParentStructJ",
|
||||||
|
@ -1029,7 +1057,7 @@ func methodTests() []test {
|
||||||
if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
|
if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
|
||||||
tf := m.Func.Type()
|
tf := m.Func.Type()
|
||||||
return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
|
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
|
return false
|
||||||
}, cmp.Transformer("Ref", func(x interface{}) interface{} {
|
}, cmp.Transformer("Ref", func(x interface{}) interface{} {
|
||||||
|
@ -1319,7 +1347,7 @@ func methodTests() []test {
|
||||||
func project1Tests() []test {
|
func project1Tests() []test {
|
||||||
const label = "Project1"
|
const label = "Project1"
|
||||||
|
|
||||||
ignoreUnexported := IgnoreUnexported(
|
ignoreUnexported := cmpopts.IgnoreUnexported(
|
||||||
ts.EagleImmutable{},
|
ts.EagleImmutable{},
|
||||||
ts.DreamerImmutable{},
|
ts.DreamerImmutable{},
|
||||||
ts.SlapImmutable{},
|
ts.SlapImmutable{},
|
||||||
|
@ -1392,8 +1420,7 @@ func project1Tests() []test {
|
||||||
y: ts.Eagle{Slaps: []ts.Slap{{
|
y: ts.Eagle{Slaps: []ts.Slap{{
|
||||||
Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
|
Args: &pb.MetaData{Stringer: pb.Stringer{"metadata"}},
|
||||||
}}},
|
}}},
|
||||||
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
|
opts: []cmp.Option{cmp.Comparer(pb.Equal)},
|
||||||
wantDiff: "",
|
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
|
x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
|
||||||
|
@ -1430,15 +1457,15 @@ func project1Tests() []test {
|
||||||
-: "southbay2"
|
-: "southbay2"
|
||||||
+: "southbay"
|
+: "southbay"
|
||||||
*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State:
|
*{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State:
|
||||||
-: 6
|
-: testprotos.Goat_States(6)
|
||||||
+: 5
|
+: testprotos.Goat_States(5)
|
||||||
{teststructs.Eagle}.Slaps[0].Immutable.MildSlap:
|
{teststructs.Eagle}.Slaps[0].Immutable.MildSlap:
|
||||||
-: false
|
-: false
|
||||||
+: true
|
+: true
|
||||||
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1]:
|
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1->?]:
|
||||||
-: "bar"
|
-: "bar"
|
||||||
+: <non-existent>
|
+: <non-existent>
|
||||||
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2]:
|
{teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2->?]:
|
||||||
-: "baz"
|
-: "baz"
|
||||||
+: <non-existent>`,
|
+: <non-existent>`,
|
||||||
}}
|
}}
|
||||||
|
@ -1524,14 +1551,11 @@ func project2Tests() []test {
|
||||||
}(),
|
}(),
|
||||||
opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
|
opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
|
||||||
wantDiff: `
|
wantDiff: `
|
||||||
{teststructs.GermBatch}.DirtyGerms[18][0]:
|
{teststructs.GermBatch}.DirtyGerms[18][0->?]:
|
||||||
-: "germ2"
|
-: "germ2"
|
||||||
+: "germ3"
|
+: <non-existent>
|
||||||
{teststructs.GermBatch}.DirtyGerms[18][1]:
|
{teststructs.GermBatch}.DirtyGerms[18][?->2]:
|
||||||
-: "germ3"
|
-: <non-existent>
|
||||||
+: "germ4"
|
|
||||||
{teststructs.GermBatch}.DirtyGerms[18][2]:
|
|
||||||
-: "germ4"
|
|
||||||
+: "germ2"`,
|
+: "germ2"`,
|
||||||
}, {
|
}, {
|
||||||
label: label,
|
label: label,
|
||||||
|
@ -1562,7 +1586,7 @@ func project2Tests() []test {
|
||||||
{teststructs.GermBatch}.DirtyGerms[17]:
|
{teststructs.GermBatch}.DirtyGerms[17]:
|
||||||
-: <non-existent>
|
-: <non-existent>
|
||||||
+: []*testprotos.Germ{"germ1"}
|
+: []*testprotos.Germ{"germ1"}
|
||||||
{teststructs.GermBatch}.DirtyGerms[18][2]:
|
{teststructs.GermBatch}.DirtyGerms[18][2->?]:
|
||||||
-: "germ4"
|
-: "germ4"
|
||||||
+: <non-existent>
|
+: <non-existent>
|
||||||
{teststructs.GermBatch}.DishMap[1]:
|
{teststructs.GermBatch}.DishMap[1]:
|
||||||
|
@ -1579,9 +1603,7 @@ func project3Tests() []test {
|
||||||
|
|
||||||
allowVisibility := cmp.AllowUnexported(ts.Dirt{})
|
allowVisibility := cmp.AllowUnexported(ts.Dirt{})
|
||||||
|
|
||||||
ignoreLocker := cmp.FilterPath(func(p cmp.Path) bool {
|
ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
|
||||||
return len(p) > 0 && p[len(p)-1].Type() == mutexType
|
|
||||||
}, cmp.Ignore())
|
|
||||||
|
|
||||||
transformProtos := cmp.Transformer("", func(x pb.Dirt) *pb.Dirt {
|
transformProtos := cmp.Transformer("", func(x pb.Dirt) *pb.Dirt {
|
||||||
return &x
|
return &x
|
||||||
|
@ -1647,8 +1669,8 @@ func project3Tests() []test {
|
||||||
-: &teststructs.MockTable{state: []string{"a", "c"}}
|
-: &teststructs.MockTable{state: []string{"a", "c"}}
|
||||||
+: &teststructs.MockTable{state: []string{"a", "b", "c"}}
|
+: &teststructs.MockTable{state: []string{"a", "b", "c"}}
|
||||||
{teststructs.Dirt}.Discord:
|
{teststructs.Dirt}.Discord:
|
||||||
-: 554
|
-: teststructs.DiscordState(554)
|
||||||
+: 500
|
+: teststructs.DiscordState(500)
|
||||||
λ({teststructs.Dirt}.Proto):
|
λ({teststructs.Dirt}.Proto):
|
||||||
-: "blah"
|
-: "blah"
|
||||||
+: "proto"
|
+: "proto"
|
||||||
|
@ -1736,14 +1758,8 @@ func project4Tests() []test {
|
||||||
}(),
|
}(),
|
||||||
opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
|
opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
|
||||||
wantDiff: `
|
wantDiff: `
|
||||||
{teststructs.Cartel}.Headquarter.subDivisions[0]:
|
{teststructs.Cartel}.Headquarter.subDivisions[0->?]:
|
||||||
-: "alpha"
|
-: "alpha"
|
||||||
+: "bravo"
|
|
||||||
{teststructs.Cartel}.Headquarter.subDivisions[1]:
|
|
||||||
-: "bravo"
|
|
||||||
+: "charlie"
|
|
||||||
{teststructs.Cartel}.Headquarter.subDivisions[2]:
|
|
||||||
-: "charlie"
|
|
||||||
+: <non-existent>
|
+: <non-existent>
|
||||||
{teststructs.Cartel}.Headquarter.publicMessage[2]:
|
{teststructs.Cartel}.Headquarter.publicMessage[2]:
|
||||||
-: 0x03
|
-: 0x03
|
||||||
|
@ -1752,23 +1768,27 @@ func project4Tests() []test {
|
||||||
-: 0x04
|
-: 0x04
|
||||||
+: 0x03
|
+: 0x03
|
||||||
{teststructs.Cartel}.poisons[0].poisonType:
|
{teststructs.Cartel}.poisons[0].poisonType:
|
||||||
-: 1
|
-: testprotos.PoisonType(1)
|
||||||
+: 5
|
+: testprotos.PoisonType(5)
|
||||||
{teststructs.Cartel}.poisons[1]:
|
{teststructs.Cartel}.poisons[1->?]:
|
||||||
-: &teststructs.Poison{poisonType: 2, manufactuer: "acme2"}
|
-: &teststructs.Poison{poisonType: testprotos.PoisonType(2), manufactuer: "acme2"}
|
||||||
+: <non-existent>`,
|
+: <non-existent>`,
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Delete this hack when we drop Go1.6 support.
|
// 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 {
|
type runner interface {
|
||||||
Run(string, func(t *testing.T)) bool
|
Run(string, func(t *testing.T)) bool
|
||||||
}
|
}
|
||||||
var ti interface{} = t
|
var ti interface{} = t
|
||||||
if r, ok := ti.(runner); ok {
|
if r, ok := ti.(runner); ok {
|
||||||
r.Run(name, f)
|
r.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f(t)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
|
// Cannot run sub-tests in parallel in Go1.6.
|
||||||
t.Logf("Test: %s", name)
|
t.Logf("Test: %s", name)
|
||||||
f(t)
|
f(t)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,110 @@ import (
|
||||||
// fundamental options and filters and not in terms of what cool things you can
|
// fundamental options and filters and not in terms of what cool things you can
|
||||||
// do with them since that overlaps with cmp/cmpopts.
|
// 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
|
// 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
|
// comparer on floats that determines two values to be equal if they are within
|
||||||
// some range of each other.
|
// some range of each other.
|
||||||
|
@ -264,3 +368,7 @@ func ExampleOption_transformComplex() {
|
||||||
// false
|
// false
|
||||||
// false
|
// false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeT struct{}
|
||||||
|
|
||||||
|
func (t fakeT) Errorf(format string, args ...interface{}) { fmt.Printf(format+"\n", args...) }
|
||||||
|
|
17
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
17
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// +build !debug
|
||||||
|
|
||||||
|
package diff
|
||||||
|
|
||||||
|
var debug debugger
|
||||||
|
|
||||||
|
type debugger struct{}
|
||||||
|
|
||||||
|
func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
func (debugger) Update() {}
|
||||||
|
func (debugger) Finish() {}
|
122
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
122
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// +build debug
|
||||||
|
|
||||||
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The algorithm can be seen running in real-time by enabling debugging:
|
||||||
|
// go test -tags=debug -v
|
||||||
|
//
|
||||||
|
// Example output:
|
||||||
|
// === RUN TestDifference/#34
|
||||||
|
// ┌───────────────────────────────┐
|
||||||
|
// │ \ · · · · · · · · · · · · · · │
|
||||||
|
// │ · # · · · · · · · · · · · · · │
|
||||||
|
// │ · \ · · · · · · · · · · · · · │
|
||||||
|
// │ · · \ · · · · · · · · · · · · │
|
||||||
|
// │ · · · X # · · · · · · · · · · │
|
||||||
|
// │ · · · # \ · · · · · · · · · · │
|
||||||
|
// │ · · · · · # # · · · · · · · · │
|
||||||
|
// │ · · · · · # \ · · · · · · · · │
|
||||||
|
// │ · · · · · · · \ · · · · · · · │
|
||||||
|
// │ · · · · · · · · \ · · · · · · │
|
||||||
|
// │ · · · · · · · · · \ · · · · · │
|
||||||
|
// │ · · · · · · · · · · \ · · # · │
|
||||||
|
// │ · · · · · · · · · · · \ # # · │
|
||||||
|
// │ · · · · · · · · · · · # # # · │
|
||||||
|
// │ · · · · · · · · · · # # # # · │
|
||||||
|
// │ · · · · · · · · · # # # # # · │
|
||||||
|
// │ · · · · · · · · · · · · · · \ │
|
||||||
|
// └───────────────────────────────┘
|
||||||
|
// [.Y..M.XY......YXYXY.|]
|
||||||
|
//
|
||||||
|
// The grid represents the edit-graph where the horizontal axis represents
|
||||||
|
// list X and the vertical axis represents list Y. The start of the two lists
|
||||||
|
// is the top-left, while the ends are the bottom-right. The '·' represents
|
||||||
|
// an unexplored node in the graph. The '\' indicates that the two symbols
|
||||||
|
// from list X and Y are equal. The 'X' indicates that two symbols are similar
|
||||||
|
// (but not exactly equal) to each other. The '#' indicates that the two symbols
|
||||||
|
// are different (and not similar). The algorithm traverses this graph trying to
|
||||||
|
// make the paths starting in the top-left and the bottom-right connect.
|
||||||
|
//
|
||||||
|
// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents
|
||||||
|
// the currently established path from the forward and reverse searches,
|
||||||
|
// seperated by a '|' character.
|
||||||
|
|
||||||
|
const (
|
||||||
|
updateDelay = 100 * time.Millisecond
|
||||||
|
finishDelay = 500 * time.Millisecond
|
||||||
|
ansiTerminal = true // ANSI escape codes used to move terminal cursor
|
||||||
|
)
|
||||||
|
|
||||||
|
var debug debugger
|
||||||
|
|
||||||
|
type debugger struct {
|
||||||
|
sync.Mutex
|
||||||
|
p1, p2 EditScript
|
||||||
|
fwdPath, revPath *EditScript
|
||||||
|
grid []byte
|
||||||
|
lines int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc {
|
||||||
|
dbg.Lock()
|
||||||
|
dbg.fwdPath, dbg.revPath = p1, p2
|
||||||
|
top := "┌─" + strings.Repeat("──", nx) + "┐\n"
|
||||||
|
row := "│ " + strings.Repeat("· ", nx) + "│\n"
|
||||||
|
btm := "└─" + strings.Repeat("──", nx) + "┘\n"
|
||||||
|
dbg.grid = []byte(top + strings.Repeat(row, ny) + btm)
|
||||||
|
dbg.lines = strings.Count(dbg.String(), "\n")
|
||||||
|
fmt.Print(dbg)
|
||||||
|
|
||||||
|
// Wrap the EqualFunc so that we can intercept each result.
|
||||||
|
return func(ix, iy int) (r Result) {
|
||||||
|
cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")]
|
||||||
|
for i := range cell {
|
||||||
|
cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot
|
||||||
|
}
|
||||||
|
switch r = f(ix, iy); {
|
||||||
|
case r.Equal():
|
||||||
|
cell[0] = '\\'
|
||||||
|
case r.Similar():
|
||||||
|
cell[0] = 'X'
|
||||||
|
default:
|
||||||
|
cell[0] = '#'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) Update() {
|
||||||
|
dbg.print(updateDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) Finish() {
|
||||||
|
dbg.print(finishDelay)
|
||||||
|
dbg.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) String() string {
|
||||||
|
dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0]
|
||||||
|
for i := len(*dbg.revPath) - 1; i >= 0; i-- {
|
||||||
|
dbg.p2 = append(dbg.p2, (*dbg.revPath)[i])
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbg *debugger) print(d time.Duration) {
|
||||||
|
if ansiTerminal {
|
||||||
|
fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor
|
||||||
|
}
|
||||||
|
fmt.Print(dbg)
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
|
@ -0,0 +1,373 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// Package diff implements an algorithm for producing edit-scripts.
|
||||||
|
// The edit-script is a sequence of operations needed to transform one list
|
||||||
|
// of symbols into another (or vice-versa). The edits allowed are insertions,
|
||||||
|
// deletions, and modifications. The summation of all edits is called the
|
||||||
|
// Levenshtein distance as this problem is well-known in computer science.
|
||||||
|
//
|
||||||
|
// This package prioritizes performance over accuracy. That is, the run time
|
||||||
|
// is more important than obtaining a minimal Levenshtein distance.
|
||||||
|
package diff
|
||||||
|
|
||||||
|
// EditType represents a single operation within an edit-script.
|
||||||
|
type EditType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Identity indicates that a symbol pair is identical in both list X and Y.
|
||||||
|
Identity EditType = iota
|
||||||
|
// UniqueX indicates that a symbol only exists in X and not Y.
|
||||||
|
UniqueX
|
||||||
|
// UniqueY indicates that a symbol only exists in Y and not X.
|
||||||
|
UniqueY
|
||||||
|
// Modified indicates that a symbol pair is a modification of each other.
|
||||||
|
Modified
|
||||||
|
)
|
||||||
|
|
||||||
|
// EditScript represents the series of differences between two lists.
|
||||||
|
type EditScript []EditType
|
||||||
|
|
||||||
|
// String returns a human-readable string representing the edit-script where
|
||||||
|
// Identity, UniqueX, UniqueY, and Modified are represented by the
|
||||||
|
// '.', 'X', 'Y', and 'M' characters, respectively.
|
||||||
|
func (es EditScript) String() string {
|
||||||
|
b := make([]byte, len(es))
|
||||||
|
for i, e := range es {
|
||||||
|
switch e {
|
||||||
|
case Identity:
|
||||||
|
b[i] = '.'
|
||||||
|
case UniqueX:
|
||||||
|
b[i] = 'X'
|
||||||
|
case UniqueY:
|
||||||
|
b[i] = 'Y'
|
||||||
|
case Modified:
|
||||||
|
b[i] = 'M'
|
||||||
|
default:
|
||||||
|
panic("invalid edit-type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stats returns a histogram of the number of each type of edit operation.
|
||||||
|
func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) {
|
||||||
|
for _, e := range es {
|
||||||
|
switch e {
|
||||||
|
case Identity:
|
||||||
|
s.NI++
|
||||||
|
case UniqueX:
|
||||||
|
s.NX++
|
||||||
|
case UniqueY:
|
||||||
|
s.NY++
|
||||||
|
case Modified:
|
||||||
|
s.NM++
|
||||||
|
default:
|
||||||
|
panic("invalid edit-type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if
|
||||||
|
// lists X and Y are equal.
|
||||||
|
func (es EditScript) Dist() int { return len(es) - es.stats().NI }
|
||||||
|
|
||||||
|
// LenX is the length of the X list.
|
||||||
|
func (es EditScript) LenX() int { return len(es) - es.stats().NY }
|
||||||
|
|
||||||
|
// LenY is the length of the Y list.
|
||||||
|
func (es EditScript) LenY() int { return len(es) - es.stats().NX }
|
||||||
|
|
||||||
|
// EqualFunc reports whether the symbols at indexes ix and iy are equal.
|
||||||
|
// When called by Difference, the index is guaranteed to be within nx and ny.
|
||||||
|
type EqualFunc func(ix int, iy int) Result
|
||||||
|
|
||||||
|
// Result is the result of comparison.
|
||||||
|
// NSame is the number of sub-elements that are equal.
|
||||||
|
// NDiff is the number of sub-elements that are not equal.
|
||||||
|
type Result struct{ NSame, NDiff int }
|
||||||
|
|
||||||
|
// Equal indicates whether the symbols are equal. Two symbols are equal
|
||||||
|
// if and only if NDiff == 0. If Equal, then they are also Similar.
|
||||||
|
func (r Result) Equal() bool { return r.NDiff == 0 }
|
||||||
|
|
||||||
|
// Similar indicates whether two symbols are similar and may be represented
|
||||||
|
// by using the Modified type. As a special case, we consider binary comparisons
|
||||||
|
// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar.
|
||||||
|
//
|
||||||
|
// The exact ratio of NSame to NDiff to determine similarity may change.
|
||||||
|
func (r Result) Similar() bool {
|
||||||
|
// Use NSame+1 to offset NSame so that binary comparisons are similar.
|
||||||
|
return r.NSame+1 >= r.NDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difference reports whether two lists of lengths nx and ny are equal
|
||||||
|
// given the definition of equality provided as f.
|
||||||
|
//
|
||||||
|
// This function may return a edit-script, which is a sequence of operations
|
||||||
|
// needed to convert one list into the other. If non-nil, the following
|
||||||
|
// invariants for the edit-script are maintained:
|
||||||
|
// • eq == (es.Dist()==0)
|
||||||
|
// • nx == es.LenX()
|
||||||
|
// • ny == es.LenY()
|
||||||
|
//
|
||||||
|
// This algorithm is not guaranteed to be an optimal solution (i.e., one that
|
||||||
|
// produces an edit-script with a minimal Levenshtein distance). This algorithm
|
||||||
|
// favors performance over optimality. The exact output is not guaranteed to
|
||||||
|
// be stable and may change over time.
|
||||||
|
func Difference(nx, ny int, f EqualFunc) (eq bool, es EditScript) {
|
||||||
|
es = searchGraph(nx, ny, f)
|
||||||
|
st := es.stats()
|
||||||
|
eq = len(es) == st.NI
|
||||||
|
if !eq && st.NI < (nx+ny)/4 {
|
||||||
|
return eq, nil // Edit-script more distracting than helpful
|
||||||
|
}
|
||||||
|
return eq, es
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchGraph(nx, ny int, f EqualFunc) EditScript {
|
||||||
|
// This algorithm is based on traversing what is known as an "edit-graph".
|
||||||
|
// See Figure 1 from "An O(ND) Difference Algorithm and Its Variations"
|
||||||
|
// by Eugene W. Myers. Since D can be as large as N itself, this is
|
||||||
|
// effectively O(N^2). Unlike the algorithm from that paper, we are not
|
||||||
|
// interested in the optimal path, but at least some "decent" path.
|
||||||
|
//
|
||||||
|
// For example, let X and Y be lists of symbols:
|
||||||
|
// X = [A B C A B B A]
|
||||||
|
// Y = [C B A B A C]
|
||||||
|
//
|
||||||
|
// The edit-graph can be drawn as the following:
|
||||||
|
// A B C A B B A
|
||||||
|
// ┌─────────────┐
|
||||||
|
// C │_|_|\|_|_|_|_│ 0
|
||||||
|
// B │_|\|_|_|\|\|_│ 1
|
||||||
|
// A │\|_|_|\|_|_|\│ 2
|
||||||
|
// B │_|\|_|_|\|\|_│ 3
|
||||||
|
// A │\|_|_|\|_|_|\│ 4
|
||||||
|
// C │ | |\| | | | │ 5
|
||||||
|
// └─────────────┘ 6
|
||||||
|
// 0 1 2 3 4 5 6 7
|
||||||
|
//
|
||||||
|
// List X is written along the horizontal axis, while list Y is written
|
||||||
|
// along the vertical axis. At any point on this grid, if the symbol in
|
||||||
|
// list X matches the corresponding symbol in list Y, then a '\' is drawn.
|
||||||
|
// The goal of any minimal edit-script algorithm is to find a path from the
|
||||||
|
// top-left corner to the bottom-right corner, while traveling through the
|
||||||
|
// fewest horizontal or vertical edges.
|
||||||
|
// A horizontal edge is equivalent to inserting a symbol from list X.
|
||||||
|
// A vertical edge is equivalent to inserting a symbol from list Y.
|
||||||
|
// A diagonal edge is equivalent to a matching symbol between both X and Y.
|
||||||
|
|
||||||
|
// Invariants:
|
||||||
|
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
|
||||||
|
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
|
||||||
|
//
|
||||||
|
// In general:
|
||||||
|
// • fwdFrontier.X < revFrontier.X
|
||||||
|
// • fwdFrontier.Y < revFrontier.Y
|
||||||
|
// Unless, it is time for the algorithm to terminate.
|
||||||
|
fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)}
|
||||||
|
revPath := path{-1, point{nx, ny}, make(EditScript, 0)}
|
||||||
|
fwdFrontier := fwdPath.point // Forward search frontier
|
||||||
|
revFrontier := revPath.point // Reverse search frontier
|
||||||
|
|
||||||
|
// Search budget bounds the cost of searching for better paths.
|
||||||
|
// The longest sequence of non-matching symbols that can be tolerated is
|
||||||
|
// approximately the square-root of the search budget.
|
||||||
|
searchBudget := 4 * (nx + ny) // O(n)
|
||||||
|
|
||||||
|
// The algorithm below is a greedy, meet-in-the-middle algorithm for
|
||||||
|
// computing sub-optimal edit-scripts between two lists.
|
||||||
|
//
|
||||||
|
// The algorithm is approximately as follows:
|
||||||
|
// • Searching for differences switches back-and-forth between
|
||||||
|
// a search that starts at the beginning (the top-left corner), and
|
||||||
|
// a search that starts at the end (the bottom-right corner). The goal of
|
||||||
|
// the search is connect with the search from the opposite corner.
|
||||||
|
// • As we search, we build a path in a greedy manner, where the first
|
||||||
|
// match seen is added to the path (this is sub-optimal, but provides a
|
||||||
|
// decent result in practice). When matches are found, we try the next pair
|
||||||
|
// of symbols in the lists and follow all matches as far as possible.
|
||||||
|
// • When searching for matches, we search along a diagonal going through
|
||||||
|
// through the "frontier" point. If no matches are found, we advance the
|
||||||
|
// frontier towards the opposite corner.
|
||||||
|
// • This algorithm terminates when either the X coordinates or the
|
||||||
|
// Y coordinates of the forward and reverse frontier points ever intersect.
|
||||||
|
//
|
||||||
|
// This algorithm is correct even if searching only in the forward direction
|
||||||
|
// or in the reverse direction. We do both because it is commonly observed
|
||||||
|
// that two lists commonly differ because elements were added to the front
|
||||||
|
// or end of the other list.
|
||||||
|
//
|
||||||
|
// Running the tests with the "debug" build tag prints a visualization of
|
||||||
|
// the algorithm running in real-time. This is educational for understanding
|
||||||
|
// how the algorithm works. See debug_enable.go.
|
||||||
|
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
|
||||||
|
for {
|
||||||
|
// Forward search from the beginning.
|
||||||
|
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||||
|
// Search in a diagonal pattern for a match.
|
||||||
|
z := zigzag(i)
|
||||||
|
p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
|
||||||
|
switch {
|
||||||
|
case p.X >= revPath.X || p.Y < fwdPath.Y:
|
||||||
|
stop1 = true // Hit top-right corner
|
||||||
|
case p.Y >= revPath.Y || p.X < fwdPath.X:
|
||||||
|
stop2 = true // Hit bottom-left corner
|
||||||
|
case f(p.X, p.Y).Equal():
|
||||||
|
// Match found, so connect the path to this point.
|
||||||
|
fwdPath.connect(p, f)
|
||||||
|
fwdPath.append(Identity)
|
||||||
|
// Follow sequence of matches as far as possible.
|
||||||
|
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||||
|
if !f(fwdPath.X, fwdPath.Y).Equal() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fwdPath.append(Identity)
|
||||||
|
}
|
||||||
|
fwdFrontier = fwdPath.point
|
||||||
|
stop1, stop2 = true, true
|
||||||
|
default:
|
||||||
|
searchBudget-- // Match not found
|
||||||
|
}
|
||||||
|
debug.Update()
|
||||||
|
}
|
||||||
|
// Advance the frontier towards reverse point.
|
||||||
|
if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y {
|
||||||
|
fwdFrontier.X++
|
||||||
|
} else {
|
||||||
|
fwdFrontier.Y++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse search from the end.
|
||||||
|
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
|
||||||
|
// Search in a diagonal pattern for a match.
|
||||||
|
z := zigzag(i)
|
||||||
|
p := point{revFrontier.X - z, revFrontier.Y + z}
|
||||||
|
switch {
|
||||||
|
case fwdPath.X >= p.X || revPath.Y < p.Y:
|
||||||
|
stop1 = true // Hit bottom-left corner
|
||||||
|
case fwdPath.Y >= p.Y || revPath.X < p.X:
|
||||||
|
stop2 = true // Hit top-right corner
|
||||||
|
case f(p.X-1, p.Y-1).Equal():
|
||||||
|
// Match found, so connect the path to this point.
|
||||||
|
revPath.connect(p, f)
|
||||||
|
revPath.append(Identity)
|
||||||
|
// Follow sequence of matches as far as possible.
|
||||||
|
for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y {
|
||||||
|
if !f(revPath.X-1, revPath.Y-1).Equal() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
revPath.append(Identity)
|
||||||
|
}
|
||||||
|
revFrontier = revPath.point
|
||||||
|
stop1, stop2 = true, true
|
||||||
|
default:
|
||||||
|
searchBudget-- // Match not found
|
||||||
|
}
|
||||||
|
debug.Update()
|
||||||
|
}
|
||||||
|
// Advance the frontier towards forward point.
|
||||||
|
if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y {
|
||||||
|
revFrontier.X--
|
||||||
|
} else {
|
||||||
|
revFrontier.Y--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the forward and reverse paths and then append the reverse path.
|
||||||
|
fwdPath.connect(revPath.point, f)
|
||||||
|
for i := len(revPath.es) - 1; i >= 0; i-- {
|
||||||
|
t := revPath.es[i]
|
||||||
|
revPath.es = revPath.es[:i]
|
||||||
|
fwdPath.append(t)
|
||||||
|
}
|
||||||
|
debug.Finish()
|
||||||
|
return fwdPath.es
|
||||||
|
}
|
||||||
|
|
||||||
|
type path struct {
|
||||||
|
dir int // +1 if forward, -1 if reverse
|
||||||
|
point // Leading point of the EditScript path
|
||||||
|
es EditScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types
|
||||||
|
// to the edit-script to connect p.point to dst.
|
||||||
|
func (p *path) connect(dst point, f EqualFunc) {
|
||||||
|
if p.dir > 0 {
|
||||||
|
// Connect in forward direction.
|
||||||
|
for dst.X > p.X && dst.Y > p.Y {
|
||||||
|
switch r := f(p.X, p.Y); {
|
||||||
|
case r.Equal():
|
||||||
|
p.append(Identity)
|
||||||
|
case r.Similar():
|
||||||
|
p.append(Modified)
|
||||||
|
case dst.X-p.X >= dst.Y-p.Y:
|
||||||
|
p.append(UniqueX)
|
||||||
|
default:
|
||||||
|
p.append(UniqueY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for dst.X > p.X {
|
||||||
|
p.append(UniqueX)
|
||||||
|
}
|
||||||
|
for dst.Y > p.Y {
|
||||||
|
p.append(UniqueY)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Connect in reverse direction.
|
||||||
|
for p.X > dst.X && p.Y > dst.Y {
|
||||||
|
switch r := f(p.X-1, p.Y-1); {
|
||||||
|
case r.Equal():
|
||||||
|
p.append(Identity)
|
||||||
|
case r.Similar():
|
||||||
|
p.append(Modified)
|
||||||
|
case p.Y-dst.Y >= p.X-dst.X:
|
||||||
|
p.append(UniqueY)
|
||||||
|
default:
|
||||||
|
p.append(UniqueX)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for p.X > dst.X {
|
||||||
|
p.append(UniqueX)
|
||||||
|
}
|
||||||
|
for p.Y > dst.Y {
|
||||||
|
p.append(UniqueY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *path) append(t EditType) {
|
||||||
|
p.es = append(p.es, t)
|
||||||
|
switch t {
|
||||||
|
case Identity, Modified:
|
||||||
|
p.add(p.dir, p.dir)
|
||||||
|
case UniqueX:
|
||||||
|
p.add(p.dir, 0)
|
||||||
|
case UniqueY:
|
||||||
|
p.add(0, p.dir)
|
||||||
|
}
|
||||||
|
debug.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
type point struct{ X, Y int }
|
||||||
|
|
||||||
|
func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy }
|
||||||
|
|
||||||
|
// zigzag maps a consecutive sequence of integers to a zig-zag sequence.
|
||||||
|
// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
|
||||||
|
func zigzag(x int) int {
|
||||||
|
if x&1 != 0 {
|
||||||
|
x = ^x
|
||||||
|
}
|
||||||
|
return x >> 1
|
||||||
|
}
|
|
@ -0,0 +1,467 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDifference(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
// Before passing x and y to Difference, we strip all spaces so that
|
||||||
|
// they can be used by the test author to indicate a missing symbol
|
||||||
|
// in one of the lists.
|
||||||
|
x, y string
|
||||||
|
want string
|
||||||
|
}{{
|
||||||
|
x: "",
|
||||||
|
y: "",
|
||||||
|
want: "",
|
||||||
|
}, {
|
||||||
|
x: "#",
|
||||||
|
y: "#",
|
||||||
|
want: ".",
|
||||||
|
}, {
|
||||||
|
x: "##",
|
||||||
|
y: "# ",
|
||||||
|
want: ".X",
|
||||||
|
}, {
|
||||||
|
x: "a#",
|
||||||
|
y: "A ",
|
||||||
|
want: "MX",
|
||||||
|
}, {
|
||||||
|
x: "#a",
|
||||||
|
y: " A",
|
||||||
|
want: "XM",
|
||||||
|
}, {
|
||||||
|
x: "# ",
|
||||||
|
y: "##",
|
||||||
|
want: ".Y",
|
||||||
|
}, {
|
||||||
|
x: " #",
|
||||||
|
y: "@#",
|
||||||
|
want: "Y.",
|
||||||
|
}, {
|
||||||
|
x: "@#",
|
||||||
|
y: " #",
|
||||||
|
want: "X.",
|
||||||
|
}, {
|
||||||
|
x: "##########0123456789",
|
||||||
|
y: " 0123456789",
|
||||||
|
want: "XXXXXXXXXX..........",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789",
|
||||||
|
y: "##########0123456789",
|
||||||
|
want: "YYYYYYYYYY..........",
|
||||||
|
}, {
|
||||||
|
x: "#####0123456789#####",
|
||||||
|
y: " 0123456789 ",
|
||||||
|
want: "XXXXX..........XXXXX",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789 ",
|
||||||
|
y: "#####0123456789#####",
|
||||||
|
want: "YYYYY..........YYYYY",
|
||||||
|
}, {
|
||||||
|
x: "01234##########56789",
|
||||||
|
y: "01234 56789",
|
||||||
|
want: ".....XXXXXXXXXX.....",
|
||||||
|
}, {
|
||||||
|
x: "01234 56789",
|
||||||
|
y: "01234##########56789",
|
||||||
|
want: ".....YYYYYYYYYY.....",
|
||||||
|
}, {
|
||||||
|
x: "0123456789##########",
|
||||||
|
y: "0123456789 ",
|
||||||
|
want: "..........XXXXXXXXXX",
|
||||||
|
}, {
|
||||||
|
x: "0123456789 ",
|
||||||
|
y: "0123456789##########",
|
||||||
|
want: "..........YYYYYYYYYY",
|
||||||
|
}, {
|
||||||
|
x: "abcdefghij0123456789",
|
||||||
|
y: "ABCDEFGHIJ0123456789",
|
||||||
|
want: "MMMMMMMMMM..........",
|
||||||
|
}, {
|
||||||
|
x: "ABCDEFGHIJ0123456789",
|
||||||
|
y: "abcdefghij0123456789",
|
||||||
|
want: "MMMMMMMMMM..........",
|
||||||
|
}, {
|
||||||
|
x: "01234abcdefghij56789",
|
||||||
|
y: "01234ABCDEFGHIJ56789",
|
||||||
|
want: ".....MMMMMMMMMM.....",
|
||||||
|
}, {
|
||||||
|
x: "01234ABCDEFGHIJ56789",
|
||||||
|
y: "01234abcdefghij56789",
|
||||||
|
want: ".....MMMMMMMMMM.....",
|
||||||
|
}, {
|
||||||
|
x: "0123456789abcdefghij",
|
||||||
|
y: "0123456789ABCDEFGHIJ",
|
||||||
|
want: "..........MMMMMMMMMM",
|
||||||
|
}, {
|
||||||
|
x: "0123456789ABCDEFGHIJ",
|
||||||
|
y: "0123456789abcdefghij",
|
||||||
|
want: "..........MMMMMMMMMM",
|
||||||
|
}, {
|
||||||
|
x: "ABCDEFGHIJ0123456789 ",
|
||||||
|
y: " 0123456789abcdefghij",
|
||||||
|
want: "XXXXXXXXXX..........YYYYYYYYYY",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789abcdefghij",
|
||||||
|
y: "ABCDEFGHIJ0123456789 ",
|
||||||
|
want: "YYYYYYYYYY..........XXXXXXXXXX",
|
||||||
|
}, {
|
||||||
|
x: "ABCDE0123456789 FGHIJ",
|
||||||
|
y: " 0123456789abcdefghij",
|
||||||
|
want: "XXXXX..........YYYYYMMMMM",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789abcdefghij",
|
||||||
|
y: "ABCDE0123456789 FGHIJ",
|
||||||
|
want: "YYYYY..........XXXXXMMMMM",
|
||||||
|
}, {
|
||||||
|
x: "ABCDE01234F G H I J 56789 ",
|
||||||
|
y: " 01234 a b c d e56789fghij",
|
||||||
|
want: "XXXXX.....XYXYXYXYXY.....YYYYY",
|
||||||
|
}, {
|
||||||
|
x: " 01234a b c d e 56789fghij",
|
||||||
|
y: "ABCDE01234 F G H I J56789 ",
|
||||||
|
want: "YYYYY.....XYXYXYXYXY.....XXXXX",
|
||||||
|
}, {
|
||||||
|
x: "FGHIJ01234ABCDE56789 ",
|
||||||
|
y: " 01234abcde56789fghij",
|
||||||
|
want: "XXXXX.....MMMMM.....YYYYY",
|
||||||
|
}, {
|
||||||
|
x: " 01234abcde56789fghij",
|
||||||
|
y: "FGHIJ01234ABCDE56789 ",
|
||||||
|
want: "YYYYY.....MMMMM.....XXXXX",
|
||||||
|
}, {
|
||||||
|
x: "ABCAB BA ",
|
||||||
|
y: " C BABAC",
|
||||||
|
want: "XX.X.Y..Y",
|
||||||
|
}, {
|
||||||
|
x: "# #### ###",
|
||||||
|
y: "#y####yy###",
|
||||||
|
want: ".Y....YY...",
|
||||||
|
}, {
|
||||||
|
x: "# #### # ##x#x",
|
||||||
|
y: "#y####y y## # ",
|
||||||
|
want: ".Y....YXY..X.X",
|
||||||
|
}, {
|
||||||
|
x: "###z#z###### x #",
|
||||||
|
y: "#y##Z#Z###### yy#",
|
||||||
|
want: ".Y..M.M......XYY.",
|
||||||
|
}, {
|
||||||
|
x: "0 12z3x 456789 x x 0",
|
||||||
|
y: "0y12Z3 y456789y y y0",
|
||||||
|
want: ".Y..M.XY......YXYXY.",
|
||||||
|
}, {
|
||||||
|
x: "0 2 4 6 8 ..................abXXcdEXF.ghXi",
|
||||||
|
y: " 1 3 5 7 9..................AB CDE F.GH I",
|
||||||
|
want: "XYXYXYXYXY..................MMXXMM.X..MMXM",
|
||||||
|
}, {
|
||||||
|
x: "I HG.F EDC BA..................9 7 5 3 1 ",
|
||||||
|
y: "iXhg.FXEdcXXba.................. 8 6 4 2 0",
|
||||||
|
want: "MYMM..Y.MMYYMM..................XYXYXYXYXY",
|
||||||
|
}, {
|
||||||
|
x: "x1234",
|
||||||
|
y: " 1234",
|
||||||
|
want: "X....",
|
||||||
|
}, {
|
||||||
|
x: "x123x4",
|
||||||
|
y: " 123 4",
|
||||||
|
want: "X...X.",
|
||||||
|
}, {
|
||||||
|
x: "x1234x56",
|
||||||
|
y: " 1234 ",
|
||||||
|
want: "X....XXX",
|
||||||
|
}, {
|
||||||
|
x: "x1234xxx56",
|
||||||
|
y: " 1234 56",
|
||||||
|
want: "X....XXX..",
|
||||||
|
}, {
|
||||||
|
x: ".1234...ab",
|
||||||
|
y: " 1234 AB",
|
||||||
|
want: "X....XXXMM",
|
||||||
|
}, {
|
||||||
|
x: "x1234xxab.",
|
||||||
|
y: " 1234 AB ",
|
||||||
|
want: "X....XXMMX",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789",
|
||||||
|
y: "9012345678 ",
|
||||||
|
want: "Y.........X",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789",
|
||||||
|
y: "8901234567 ",
|
||||||
|
want: "YY........XX",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789",
|
||||||
|
y: "7890123456 ",
|
||||||
|
want: "YYY.......XXX",
|
||||||
|
}, {
|
||||||
|
x: " 0123456789",
|
||||||
|
y: "6789012345 ",
|
||||||
|
want: "YYYY......XXXX",
|
||||||
|
}, {
|
||||||
|
x: "0123456789 ",
|
||||||
|
y: " 5678901234",
|
||||||
|
want: "XXXXX.....YYYYY",
|
||||||
|
}, {
|
||||||
|
x: "0123456789 ",
|
||||||
|
y: " 4567890123",
|
||||||
|
want: "XXXX......YYYY",
|
||||||
|
}, {
|
||||||
|
x: "0123456789 ",
|
||||||
|
y: " 3456789012",
|
||||||
|
want: "XXX.......YYY",
|
||||||
|
}, {
|
||||||
|
x: "0123456789 ",
|
||||||
|
y: " 2345678901",
|
||||||
|
want: "XX........YY",
|
||||||
|
}, {
|
||||||
|
x: "0123456789 ",
|
||||||
|
y: " 1234567890",
|
||||||
|
want: "X.........Y",
|
||||||
|
}, {
|
||||||
|
x: "0123456789",
|
||||||
|
y: "9876543210",
|
||||||
|
}, {
|
||||||
|
x: "0123456789",
|
||||||
|
y: "6725819034",
|
||||||
|
}, {
|
||||||
|
x: "FBQMOIGTLN72X90E4SP651HKRJUDA83CVZW",
|
||||||
|
y: "5WHXO10R9IVKZLCTAJ8P3NSEQM472G6UBDF",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tRun(t, "", func(t *testing.T) {
|
||||||
|
x := strings.Replace(tt.x, " ", "", -1)
|
||||||
|
y := strings.Replace(tt.y, " ", "", -1)
|
||||||
|
es := testStrings(t, x, y)
|
||||||
|
if got := es.String(); got != tt.want {
|
||||||
|
t.Errorf("Difference(%s, %s):\ngot %s\nwant %s", x, y, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDifferenceFuzz(t *testing.T) {
|
||||||
|
tests := []struct{ px, py, pm float32 }{
|
||||||
|
{px: 0.0, py: 0.0, pm: 0.1},
|
||||||
|
{px: 0.0, py: 0.1, pm: 0.0},
|
||||||
|
{px: 0.1, py: 0.0, pm: 0.0},
|
||||||
|
{px: 0.0, py: 0.1, pm: 0.1},
|
||||||
|
{px: 0.1, py: 0.0, pm: 0.1},
|
||||||
|
{px: 0.2, py: 0.2, pm: 0.2},
|
||||||
|
{px: 0.3, py: 0.1, pm: 0.2},
|
||||||
|
{px: 0.1, py: 0.3, pm: 0.2},
|
||||||
|
{px: 0.2, py: 0.2, pm: 0.2},
|
||||||
|
{px: 0.3, py: 0.3, pm: 0.3},
|
||||||
|
{px: 0.1, py: 0.1, pm: 0.5},
|
||||||
|
{px: 0.4, py: 0.1, pm: 0.5},
|
||||||
|
{px: 0.3, py: 0.2, pm: 0.5},
|
||||||
|
{px: 0.2, py: 0.3, pm: 0.5},
|
||||||
|
{px: 0.1, py: 0.4, pm: 0.5},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
tRun(t, fmt.Sprintf("P%d", i), func(t *testing.T) {
|
||||||
|
// Sweep from 1B to 1KiB.
|
||||||
|
for n := 1; n <= 1024; n <<= 1 {
|
||||||
|
tRun(t, fmt.Sprintf("N%d", n), func(t *testing.T) {
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
x, y := generateStrings(n, tt.px, tt.py, tt.pm, int64(j))
|
||||||
|
testStrings(t, x, y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkDifference(b *testing.B, n int) {
|
||||||
|
// TODO: Use testing.B.Run when we drop Go1.6 support.
|
||||||
|
x, y := generateStrings(n, 0.05, 0.05, 0.10, 0)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(x) + len(y)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Difference(len(x), len(y), func(ix, iy int) Result {
|
||||||
|
return compareByte(x[ix], y[iy])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func BenchmarkDifference1K(b *testing.B) { benchmarkDifference(b, 1<<10) }
|
||||||
|
func BenchmarkDifference4K(b *testing.B) { benchmarkDifference(b, 1<<12) }
|
||||||
|
func BenchmarkDifference16K(b *testing.B) { benchmarkDifference(b, 1<<14) }
|
||||||
|
func BenchmarkDifference64K(b *testing.B) { benchmarkDifference(b, 1<<16) }
|
||||||
|
func BenchmarkDifference256K(b *testing.B) { benchmarkDifference(b, 1<<18) }
|
||||||
|
func BenchmarkDifference1M(b *testing.B) { benchmarkDifference(b, 1<<20) }
|
||||||
|
|
||||||
|
func generateStrings(n int, px, py, pm float32, seed int64) (string, string) {
|
||||||
|
if px+py+pm > 1.0 {
|
||||||
|
panic("invalid probabilities")
|
||||||
|
}
|
||||||
|
py += px
|
||||||
|
pm += py
|
||||||
|
|
||||||
|
b := make([]byte, n)
|
||||||
|
r := rand.New(rand.NewSource(seed))
|
||||||
|
r.Read(b)
|
||||||
|
|
||||||
|
var x, y []byte
|
||||||
|
for len(b) > 0 {
|
||||||
|
switch p := r.Float32(); {
|
||||||
|
case p < px: // UniqueX
|
||||||
|
x = append(x, b[0])
|
||||||
|
case p < py: // UniqueY
|
||||||
|
y = append(y, b[0])
|
||||||
|
case p < pm: // Modified
|
||||||
|
x = append(x, 'A'+(b[0]%26))
|
||||||
|
y = append(y, 'a'+(b[0]%26))
|
||||||
|
default: // Identity
|
||||||
|
x = append(x, b[0])
|
||||||
|
y = append(y, b[0])
|
||||||
|
}
|
||||||
|
b = b[1:]
|
||||||
|
}
|
||||||
|
return string(x), string(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStrings(t *testing.T, x, y string) EditScript {
|
||||||
|
wantEq := x == y
|
||||||
|
eq, es := Difference(len(x), len(y), func(ix, iy int) Result {
|
||||||
|
return compareByte(x[ix], y[iy])
|
||||||
|
})
|
||||||
|
if eq != wantEq {
|
||||||
|
t.Errorf("equality mismatch: got %v, want %v", eq, wantEq)
|
||||||
|
}
|
||||||
|
if es != nil {
|
||||||
|
if es.LenX() != len(x) {
|
||||||
|
t.Errorf("es.LenX = %d, want %d", es.LenX(), len(x))
|
||||||
|
}
|
||||||
|
if es.LenY() != len(y) {
|
||||||
|
t.Errorf("es.LenY = %d, want %d", es.LenY(), len(y))
|
||||||
|
}
|
||||||
|
if got := (es.Dist() == 0); got != wantEq {
|
||||||
|
t.Errorf("violation of equality invariant: got %v, want %v", got, wantEq)
|
||||||
|
}
|
||||||
|
if !validateScript(x, y, es) {
|
||||||
|
t.Errorf("invalid edit script: %v", es)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateScript(x, y string, es EditScript) bool {
|
||||||
|
var bx, by []byte
|
||||||
|
for _, e := range es {
|
||||||
|
switch e {
|
||||||
|
case Identity:
|
||||||
|
if !compareByte(x[len(bx)], y[len(by)]).Equal() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bx = append(bx, x[len(bx)])
|
||||||
|
by = append(by, y[len(by)])
|
||||||
|
case UniqueX:
|
||||||
|
bx = append(bx, x[len(bx)])
|
||||||
|
case UniqueY:
|
||||||
|
by = append(by, y[len(by)])
|
||||||
|
case Modified:
|
||||||
|
if !compareByte(x[len(bx)], y[len(by)]).Similar() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
bx = append(bx, x[len(bx)])
|
||||||
|
by = append(by, y[len(by)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(bx) == x && string(by) == y
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareByte returns a Result where the result is Equal if x == y,
|
||||||
|
// similar if x and y differ only in casing, and different otherwise.
|
||||||
|
func compareByte(x, y byte) (r Result) {
|
||||||
|
switch {
|
||||||
|
case x == y:
|
||||||
|
return equalResult // Identity
|
||||||
|
case unicode.ToUpper(rune(x)) == unicode.ToUpper(rune(y)):
|
||||||
|
return similarResult // Modified
|
||||||
|
default:
|
||||||
|
return differentResult // UniqueX or UniqueY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
equalResult = Result{NDiff: 0}
|
||||||
|
similarResult = Result{NDiff: 1}
|
||||||
|
differentResult = Result{NDiff: 2}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResult(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
result Result
|
||||||
|
wantEqual bool
|
||||||
|
wantSimilar bool
|
||||||
|
}{
|
||||||
|
// equalResult is equal since NDiff == 0, by definition of Equal method.
|
||||||
|
{equalResult, true, true},
|
||||||
|
// similarResult is similar since it is a binary result where only one
|
||||||
|
// element was compared (i.e., Either NSame==1 or NDiff==1).
|
||||||
|
{similarResult, false, true},
|
||||||
|
// differentResult is different since there are enough differences that
|
||||||
|
// it isn't even considered similar.
|
||||||
|
{differentResult, false, false},
|
||||||
|
|
||||||
|
// Zero value is always equal.
|
||||||
|
{Result{NSame: 0, NDiff: 0}, true, true},
|
||||||
|
|
||||||
|
// Binary comparisons (where NSame+NDiff == 1) are always similar.
|
||||||
|
{Result{NSame: 1, NDiff: 0}, true, true},
|
||||||
|
{Result{NSame: 0, NDiff: 1}, false, true},
|
||||||
|
|
||||||
|
// More complex ratios. The exact ratio for similarity may change,
|
||||||
|
// and may require updates to these test cases.
|
||||||
|
{Result{NSame: 1, NDiff: 1}, false, true},
|
||||||
|
{Result{NSame: 1, NDiff: 2}, false, true},
|
||||||
|
{Result{NSame: 1, NDiff: 3}, false, false},
|
||||||
|
{Result{NSame: 2, NDiff: 1}, false, true},
|
||||||
|
{Result{NSame: 2, NDiff: 2}, false, true},
|
||||||
|
{Result{NSame: 2, NDiff: 3}, false, true},
|
||||||
|
{Result{NSame: 3, NDiff: 1}, false, true},
|
||||||
|
{Result{NSame: 3, NDiff: 2}, false, true},
|
||||||
|
{Result{NSame: 3, NDiff: 3}, false, true},
|
||||||
|
{Result{NSame: 1000, NDiff: 0}, true, true},
|
||||||
|
{Result{NSame: 1000, NDiff: 1}, false, true},
|
||||||
|
{Result{NSame: 1000, NDiff: 2}, false, true},
|
||||||
|
{Result{NSame: 0, NDiff: 1000}, false, false},
|
||||||
|
{Result{NSame: 1, NDiff: 1000}, false, false},
|
||||||
|
{Result{NSame: 2, NDiff: 1000}, false, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := tt.result.Equal(); got != tt.wantEqual {
|
||||||
|
t.Errorf("%#v.Equal() = %v, want %v", tt.result, got, tt.wantEqual)
|
||||||
|
}
|
||||||
|
if got := tt.result.Similar(); got != tt.wantSimilar {
|
||||||
|
t.Errorf("%#v.Similar() = %v, want %v", tt.result, got, tt.wantSimilar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Delete this hack when we drop Go1.6 support.
|
||||||
|
func tRun(t *testing.T, name string, f func(t *testing.T)) {
|
||||||
|
type runner interface {
|
||||||
|
Run(string, func(t *testing.T)) bool
|
||||||
|
}
|
||||||
|
var ti interface{} = t
|
||||||
|
if r, ok := ti.(runner); ok {
|
||||||
|
r.Run(name, f)
|
||||||
|
} else {
|
||||||
|
t.Logf("Test: %s", name)
|
||||||
|
f(t)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// Package function identifies function types.
|
||||||
|
package function
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
type funcType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ funcType = iota
|
||||||
|
|
||||||
|
ttbFunc // func(T, T) bool
|
||||||
|
tibFunc // func(T, I) bool
|
||||||
|
trFunc // func(T) R
|
||||||
|
|
||||||
|
Equal = ttbFunc // func(T, T) bool
|
||||||
|
EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool
|
||||||
|
Transformer = trFunc // func(T) R
|
||||||
|
ValueFilter = ttbFunc // func(T, T) bool
|
||||||
|
Less = ttbFunc // func(T, T) bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var boolType = reflect.TypeOf(true)
|
||||||
|
|
||||||
|
// IsType reports whether the reflect.Type is of the specified function type.
|
||||||
|
func IsType(t reflect.Type, ft funcType) bool {
|
||||||
|
if t == nil || t.Kind() != reflect.Func || t.IsVariadic() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ni, no := t.NumIn(), t.NumOut()
|
||||||
|
switch ft {
|
||||||
|
case ttbFunc: // func(T, T) bool
|
||||||
|
if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case tibFunc: // func(T, I) bool
|
||||||
|
if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case trFunc: // func(T) R
|
||||||
|
if ni == 1 && no == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// Package value provides functionality for reflect.Value types.
|
||||||
|
package value
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// formatFakePointers controls whether to substitute pointer addresses with nil.
|
||||||
|
// This is used for deterministic testing.
|
||||||
|
var formatFakePointers = false
|
||||||
|
|
||||||
|
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||||
|
|
||||||
|
// Format formats the value v as a string.
|
||||||
|
//
|
||||||
|
// This is similar to fmt.Sprintf("%+v", v) except this:
|
||||||
|
// * Prints the type unless it can be elided
|
||||||
|
// * Avoids printing struct fields that are zero
|
||||||
|
// * Prints a nil-slice as being nil, not empty
|
||||||
|
// * Prints map entries in deterministic order
|
||||||
|
func Format(v reflect.Value, useStringer bool) string {
|
||||||
|
return formatAny(v, formatConfig{useStringer, true, true, !formatFakePointers}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatConfig struct {
|
||||||
|
useStringer bool // Should the String method be used if available?
|
||||||
|
printType bool // Should we print the type before the value?
|
||||||
|
followPointers bool // Should we recursively follow pointers?
|
||||||
|
realPointers bool // Should we print the real address of pointers?
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string {
|
||||||
|
// TODO: Should this be a multi-line printout in certain situations?
|
||||||
|
|
||||||
|
if !v.IsValid() {
|
||||||
|
return "<non-existent>"
|
||||||
|
}
|
||||||
|
if conf.useStringer && v.Type().Implements(stringerIface) && v.CanInterface() {
|
||||||
|
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && v.IsNil() {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return formatPrimitive(v.Type(), v.Bool(), conf)
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return formatPrimitive(v.Type(), v.Int(), conf)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
|
||||||
|
// Unnamed uints are usually bytes or words, so use hexadecimal.
|
||||||
|
return formatPrimitive(v.Type(), formatHex(v.Uint()), conf)
|
||||||
|
}
|
||||||
|
return formatPrimitive(v.Type(), v.Uint(), conf)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return formatPrimitive(v.Type(), v.Float(), conf)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return formatPrimitive(v.Type(), v.Complex(), conf)
|
||||||
|
case reflect.String:
|
||||||
|
return formatPrimitive(v.Type(), fmt.Sprintf("%q", v), conf)
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
return formatPointer(v, conf)
|
||||||
|
case reflect.Ptr:
|
||||||
|
if v.IsNil() {
|
||||||
|
if conf.printType {
|
||||||
|
return fmt.Sprintf("(%v)(nil)", v.Type())
|
||||||
|
}
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
if visited[v.Pointer()] || !conf.followPointers {
|
||||||
|
return formatPointer(v, conf)
|
||||||
|
}
|
||||||
|
visited = insertPointer(visited, v.Pointer())
|
||||||
|
return "&" + formatAny(v.Elem(), conf, visited)
|
||||||
|
case reflect.Interface:
|
||||||
|
if v.IsNil() {
|
||||||
|
if conf.printType {
|
||||||
|
return fmt.Sprintf("%v(nil)", v.Type())
|
||||||
|
}
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return formatAny(v.Elem(), conf, visited)
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
if conf.printType {
|
||||||
|
return fmt.Sprintf("%v(nil)", v.Type())
|
||||||
|
}
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
if visited[v.Pointer()] {
|
||||||
|
return formatPointer(v, conf)
|
||||||
|
}
|
||||||
|
visited = insertPointer(visited, v.Pointer())
|
||||||
|
fallthrough
|
||||||
|
case reflect.Array:
|
||||||
|
var ss []string
|
||||||
|
subConf := conf
|
||||||
|
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
s := formatAny(v.Index(i), subConf, visited)
|
||||||
|
ss = append(ss, s)
|
||||||
|
}
|
||||||
|
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||||
|
if conf.printType {
|
||||||
|
return v.Type().String() + s
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
case reflect.Map:
|
||||||
|
if v.IsNil() {
|
||||||
|
if conf.printType {
|
||||||
|
return fmt.Sprintf("%v(nil)", v.Type())
|
||||||
|
}
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
if visited[v.Pointer()] {
|
||||||
|
return formatPointer(v, conf)
|
||||||
|
}
|
||||||
|
visited = insertPointer(visited, v.Pointer())
|
||||||
|
|
||||||
|
var ss []string
|
||||||
|
subConf := conf
|
||||||
|
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
||||||
|
for _, k := range SortKeys(v.MapKeys()) {
|
||||||
|
sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited)
|
||||||
|
sv := formatAny(v.MapIndex(k), subConf, visited)
|
||||||
|
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
|
||||||
|
}
|
||||||
|
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||||
|
if conf.printType {
|
||||||
|
return v.Type().String() + s
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
case reflect.Struct:
|
||||||
|
var ss []string
|
||||||
|
subConf := conf
|
||||||
|
subConf.printType = true
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
vv := v.Field(i)
|
||||||
|
if isZero(vv) {
|
||||||
|
continue // Elide zero value fields
|
||||||
|
}
|
||||||
|
name := v.Type().Field(i).Name
|
||||||
|
subConf.useStringer = conf.useStringer && isExported(name)
|
||||||
|
s := formatAny(vv, subConf, visited)
|
||||||
|
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
|
||||||
|
}
|
||||||
|
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
||||||
|
if conf.printType {
|
||||||
|
return v.Type().String() + s
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatPrimitive(t reflect.Type, v interface{}, conf formatConfig) string {
|
||||||
|
if conf.printType && t.PkgPath() != "" {
|
||||||
|
return fmt.Sprintf("%v(%v)", t, v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatPointer(v reflect.Value, conf formatConfig) string {
|
||||||
|
p := v.Pointer()
|
||||||
|
if !conf.realPointers {
|
||||||
|
p = 0 // For deterministic printing purposes
|
||||||
|
}
|
||||||
|
s := formatHex(uint64(p))
|
||||||
|
if conf.printType {
|
||||||
|
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatHex(u uint64) string {
|
||||||
|
var f string
|
||||||
|
switch {
|
||||||
|
case u <= 0xff:
|
||||||
|
f = "0x%02x"
|
||||||
|
case u <= 0xffff:
|
||||||
|
f = "0x%04x"
|
||||||
|
case u <= 0xffffff:
|
||||||
|
f = "0x%06x"
|
||||||
|
case u <= 0xffffffff:
|
||||||
|
f = "0x%08x"
|
||||||
|
case u <= 0xffffffffff:
|
||||||
|
f = "0x%010x"
|
||||||
|
case u <= 0xffffffffffff:
|
||||||
|
f = "0x%012x"
|
||||||
|
case u <= 0xffffffffffffff:
|
||||||
|
f = "0x%014x"
|
||||||
|
case u <= 0xffffffffffffffff:
|
||||||
|
f = "0x%016x"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(f, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertPointer insert p into m, allocating m if necessary.
|
||||||
|
func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[uintptr]bool)
|
||||||
|
}
|
||||||
|
m[p] = true
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// isZero reports whether v is the zero value.
|
||||||
|
// This does not rely on Interface and so can be used on unexported fields.
|
||||||
|
func isZero(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return v.Bool() == false
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return v.Complex() == 0
|
||||||
|
case reflect.String:
|
||||||
|
return v.String() == ""
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
return v.Pointer() == 0
|
||||||
|
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
||||||
|
return v.IsNil()
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if !isZero(v.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
if !isZero(v.Field(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExported reports whether the identifier is exported.
|
||||||
|
func isExported(id string) bool {
|
||||||
|
r, _ := utf8.DecodeRuneInString(id)
|
||||||
|
return unicode.IsUpper(r)
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
package value
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormat(t *testing.T) {
|
||||||
|
type key struct {
|
||||||
|
a int
|
||||||
|
b string
|
||||||
|
c chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
in interface{}
|
||||||
|
want string
|
||||||
|
}{{
|
||||||
|
in: []int{},
|
||||||
|
want: "[]int{}",
|
||||||
|
}, {
|
||||||
|
in: []int(nil),
|
||||||
|
want: "[]int(nil)",
|
||||||
|
}, {
|
||||||
|
in: []int{1, 2, 3, 4, 5},
|
||||||
|
want: "[]int{1, 2, 3, 4, 5}",
|
||||||
|
}, {
|
||||||
|
in: []interface{}{1, true, "hello", struct{ A, B int }{1, 2}},
|
||||||
|
want: "[]interface {}{1, true, \"hello\", struct { A int; B int }{A: 1, B: 2}}",
|
||||||
|
}, {
|
||||||
|
in: []struct{ A, B int }{{1, 2}, {0, 4}, {}},
|
||||||
|
want: "[]struct { A int; B int }{{A: 1, B: 2}, {B: 4}, {}}",
|
||||||
|
}, {
|
||||||
|
in: map[*int]string{new(int): "hello"},
|
||||||
|
want: "map[*int]string{0x00: \"hello\"}",
|
||||||
|
}, {
|
||||||
|
in: map[key]string{{}: "hello"},
|
||||||
|
want: "map[value.key]string{{}: \"hello\"}",
|
||||||
|
}, {
|
||||||
|
in: map[key]string{{a: 5, b: "key", c: make(chan bool)}: "hello"},
|
||||||
|
want: "map[value.key]string{{a: 5, b: \"key\", c: (chan bool)(0x00)}: \"hello\"}",
|
||||||
|
}, {
|
||||||
|
in: map[io.Reader]string{new(bytes.Reader): "hello"},
|
||||||
|
want: "map[io.Reader]string{0x00: \"hello\"}",
|
||||||
|
}, {
|
||||||
|
in: func() interface{} {
|
||||||
|
var a = []interface{}{nil}
|
||||||
|
a[0] = a
|
||||||
|
return a
|
||||||
|
}(),
|
||||||
|
want: "[]interface {}{([]interface {})(0x00)}",
|
||||||
|
}, {
|
||||||
|
in: func() interface{} {
|
||||||
|
type A *A
|
||||||
|
var a A
|
||||||
|
a = &a
|
||||||
|
return a
|
||||||
|
}(),
|
||||||
|
want: "&(value.A)(0x00)",
|
||||||
|
}, {
|
||||||
|
in: func() interface{} {
|
||||||
|
type A map[*A]A
|
||||||
|
a := make(A)
|
||||||
|
a[&a] = a
|
||||||
|
return a
|
||||||
|
}(),
|
||||||
|
want: "value.A{0x00: 0x00}",
|
||||||
|
}, {
|
||||||
|
in: func() interface{} {
|
||||||
|
var a [2]interface{}
|
||||||
|
a[0] = &a
|
||||||
|
return a
|
||||||
|
}(),
|
||||||
|
want: "[2]interface {}{&[2]interface {}{(*[2]interface {})(0x00), interface {}(nil)}, interface {}(nil)}",
|
||||||
|
}}
|
||||||
|
|
||||||
|
formatFakePointers = true
|
||||||
|
defer func() { formatFakePointers = false }()
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := Format(reflect.ValueOf(tt.in), true)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("test %d, Format():\ngot %q\nwant %q", i, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
// Copyright 2017, The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
package value
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SortKeys sorts a list of map keys, deduplicating keys if necessary.
|
||||||
|
// The type of each value must be comparable.
|
||||||
|
func SortKeys(vs []reflect.Value) []reflect.Value {
|
||||||
|
if len(vs) == 0 {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the map keys.
|
||||||
|
sort.Sort(valueSorter(vs))
|
||||||
|
|
||||||
|
// Deduplicate keys (fails for NaNs).
|
||||||
|
vs2 := vs[:1]
|
||||||
|
for _, v := range vs[1:] {
|
||||||
|
if v.Interface() != vs2[len(vs2)-1].Interface() {
|
||||||
|
vs2 = append(vs2, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs2
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
|
||||||
|
type valueSorter []reflect.Value
|
||||||
|
|
||||||
|
func (vs valueSorter) Len() int { return len(vs) }
|
||||||
|
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
|
||||||
|
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||||
|
|
||||||
|
// isLess is a generic function for sorting arbitrary map keys.
|
||||||
|
// The inputs must be of the same type and must be comparable.
|
||||||
|
func isLess(x, y reflect.Value) bool {
|
||||||
|
switch x.Type().Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !x.Bool() && y.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return x.Int() < y.Int()
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return x.Uint() < y.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
fx, fy := x.Float(), y.Float()
|
||||||
|
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
cx, cy := x.Complex(), y.Complex()
|
||||||
|
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
|
||||||
|
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
|
||||||
|
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
|
||||||
|
}
|
||||||
|
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
|
||||||
|
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
|
||||||
|
return x.Pointer() < y.Pointer()
|
||||||
|
case reflect.String:
|
||||||
|
return x.String() < y.String()
|
||||||
|
case reflect.Array:
|
||||||
|
for i := 0; i < x.Len(); i++ {
|
||||||
|
if isLess(x.Index(i), y.Index(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isLess(y.Index(i), x.Index(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Struct:
|
||||||
|
for i := 0; i < x.NumField(); i++ {
|
||||||
|
if isLess(x.Field(i), y.Field(i)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isLess(y.Field(i), x.Field(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case reflect.Interface:
|
||||||
|
vx, vy := x.Elem(), y.Elem()
|
||||||
|
if !vx.IsValid() || !vy.IsValid() {
|
||||||
|
return !vx.IsValid() && vy.IsValid()
|
||||||
|
}
|
||||||
|
tx, ty := vx.Type(), vy.Type()
|
||||||
|
if tx == ty {
|
||||||
|
return isLess(x.Elem(), y.Elem())
|
||||||
|
}
|
||||||
|
if tx.Kind() != ty.Kind() {
|
||||||
|
return vx.Kind() < vy.Kind()
|
||||||
|
}
|
||||||
|
if tx.String() != ty.String() {
|
||||||
|
return tx.String() < ty.String()
|
||||||
|
}
|
||||||
|
if tx.PkgPath() != ty.PkgPath() {
|
||||||
|
return tx.PkgPath() < ty.PkgPath()
|
||||||
|
}
|
||||||
|
// This can happen in rare situations, so we fallback to just comparing
|
||||||
|
// the unique pointer for a reflect.Type. This guarantees deterministic
|
||||||
|
// ordering within a program, but it is obviously not stable.
|
||||||
|
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
|
||||||
|
default:
|
||||||
|
// Must be Func, Map, or Slice; which are not comparable.
|
||||||
|
panic(fmt.Sprintf("%T is not comparable", x.Type()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,93 +2,17 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE.md file.
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
package cmp
|
package value_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"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) {
|
func TestSortKeys(t *testing.T) {
|
||||||
type (
|
type (
|
||||||
MyString string
|
MyString string
|
||||||
|
@ -101,14 +25,14 @@ func TestSortKeys(t *testing.T) {
|
||||||
EmptyStruct struct{}
|
EmptyStruct struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
opts := []Option{
|
opts := []cmp.Option{
|
||||||
Comparer(func(x, y float64) bool {
|
cmp.Comparer(func(x, y float64) bool {
|
||||||
if math.IsNaN(x) && math.IsNaN(y) {
|
if math.IsNaN(x) && math.IsNaN(y) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return x == y
|
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)
|
rx, ix, ry, iy := real(x), imag(x), real(y), imag(y)
|
||||||
if math.IsNaN(rx) && math.IsNaN(ry) {
|
if math.IsNaN(rx) && math.IsNaN(ry) {
|
||||||
rx, ry = 0, 0
|
rx, ry = 0, 0
|
||||||
|
@ -118,11 +42,11 @@ func TestSortKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
return rx == ry && ix == iy
|
return rx == ry && ix == iy
|
||||||
}),
|
}),
|
||||||
Comparer(func(x, y chan bool) bool { return true }),
|
cmp.Comparer(func(x, y chan bool) bool { return true }),
|
||||||
Comparer(func(x, y chan int) bool { return true }),
|
cmp.Comparer(func(x, y chan int) bool { return true }),
|
||||||
Comparer(func(x, y chan float64) bool { return true }),
|
cmp.Comparer(func(x, y chan float64) bool { return true }),
|
||||||
Comparer(func(x, y chan interface{}) bool { return true }),
|
cmp.Comparer(func(x, y chan interface{}) bool { return true }),
|
||||||
Comparer(func(x, y *int) bool { return true }),
|
cmp.Comparer(func(x, y *int) bool { return true }),
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
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}),
|
[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{}),
|
make(chan bool), make(chan bool), make(chan int), make(chan interface{}),
|
||||||
new(int), new(int),
|
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{},
|
EmptyStruct{},
|
||||||
MyStruct{"alpha", [2]int{3, 3}, make(chan float64)},
|
MyStruct{"alpha", [2]int{3, 3}, make(chan float64)},
|
||||||
MyStruct{"bravo", [2]int{2, 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 {
|
for i, tt := range tests {
|
||||||
keys := append(reflect.ValueOf(tt.in).MapKeys(), reflect.ValueOf(tt.in).MapKeys()...)
|
keys := append(reflect.ValueOf(tt.in).MapKeys(), reflect.ValueOf(tt.in).MapKeys()...)
|
||||||
var got []interface{}
|
var got []interface{}
|
||||||
for _, k := range sortKeys(keys) {
|
for _, k := range value.SortKeys(keys) {
|
||||||
got = append(got, k.Interface())
|
got = append(got, k.Interface())
|
||||||
}
|
}
|
||||||
if !Equal(got, tt.want, opts...) {
|
if d := cmp.Diff(got, tt.want, opts...); d != "" {
|
||||||
t.Errorf("test %d, output mismatch:\ngot %#v\nwant %#v", i, got, tt.want)
|
t.Errorf("test %d, Sort() mismatch (-got +want):\n%s", i, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/internal/function"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Option configures for specific behavior of Equal and Diff. In particular,
|
// 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
|
// The cmp/cmpopts package provides helper functions for creating options that
|
||||||
// may be used with Equal and Diff.
|
// may be used with Equal and Diff.
|
||||||
type Option interface {
|
type Option interface {
|
||||||
// Prevent Option from being equivalent to interface{}, which provides
|
// filter applies all filters and returns the option that remains.
|
||||||
// a small type checking benefit by preventing Equal(opt, x, y).
|
// Each option may only read s.curPath and call s.callTTBFunc.
|
||||||
option()
|
//
|
||||||
|
// 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.
|
// Options is a list of Option values that also satisfies the Option interface.
|
||||||
// Helper comparison packages may return an Options value when packing multiple
|
// Helper comparison packages may return an Options value when packing multiple
|
||||||
// Option values into a single Option. When this package processes an Options,
|
// 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.
|
// on all individual options held within.
|
||||||
type Options []Option
|
type Options []Option
|
||||||
|
|
||||||
func (Options) option() {}
|
func (opts Options) filter(s *state, vx, vy reflect.Value, t reflect.Type) (out applicableOption) {
|
||||||
|
for _, opt := range opts {
|
||||||
type (
|
switch opt := opt.filter(s, vx, vy, t); opt.(type) {
|
||||||
pathFilter func(Path) bool
|
case ignore:
|
||||||
valueFilter struct {
|
return ignore{} // Only ignore can short-circuit evaluation
|
||||||
in reflect.Type // T
|
case invalid:
|
||||||
fnc reflect.Value // func(T, T) bool
|
out = invalid{} // Takes precedence over comparer or transformer
|
||||||
}
|
case *comparer, *transformer, Options:
|
||||||
)
|
switch out.(type) {
|
||||||
|
case nil:
|
||||||
type option struct {
|
out = opt
|
||||||
typeFilter reflect.Type
|
case invalid:
|
||||||
pathFilters []pathFilter
|
// Keep invalid
|
||||||
valueFilters []valueFilter
|
case *comparer, *transformer, Options:
|
||||||
|
out = Options{out, opt} // Conflicting comparers or transformers
|
||||||
// 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"
|
|
||||||
}
|
}
|
||||||
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
return out
|
||||||
// Strip the package name.
|
}
|
||||||
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
|
|
||||||
|
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
|
// 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 {
|
if f == nil {
|
||||||
panic("invalid path filter function")
|
panic("invalid path filter function")
|
||||||
}
|
}
|
||||||
switch opt := opt.(type) {
|
if opt := normalizeOption(opt); opt != nil {
|
||||||
case Options:
|
return &pathFilter{fnc: f, opt: opt}
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
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,
|
// 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.
|
// a previously filtered Option.
|
||||||
func FilterValues(f interface{}, opt Option) Option {
|
func FilterValues(f interface{}, opt Option) Option {
|
||||||
v := reflect.ValueOf(f)
|
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))
|
panic(fmt.Sprintf("invalid values filter function: %T", f))
|
||||||
}
|
}
|
||||||
switch opt := opt.(type) {
|
if opt := normalizeOption(opt); opt != nil {
|
||||||
case Options:
|
vf := &valuesFilter{fnc: v, opt: opt}
|
||||||
var opts []Option
|
if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 {
|
||||||
for _, o := range opt {
|
vf.typ = ti
|
||||||
opts = append(opts, FilterValues(f, o)) // Append to slice copy
|
|
||||||
}
|
}
|
||||||
return Options(opts)
|
return vf
|
||||||
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 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.
|
// Ignore is an Option that causes all comparisons to be ignored.
|
||||||
// This value is intended to be combined with FilterPath or FilterValues.
|
// This value is intended to be combined with FilterPath or FilterValues.
|
||||||
// It is an error to pass an unfiltered Ignore option to Equal.
|
// It is an error to pass an unfiltered Ignore option to Equal.
|
||||||
func Ignore() Option {
|
func Ignore() Option { return ignore{} }
|
||||||
return option{}
|
|
||||||
|
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
|
// 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.
|
// transformation PathStep. If empty, an arbitrary name is used.
|
||||||
func Transformer(name string, f interface{}) Option {
|
func Transformer(name string, f interface{}) Option {
|
||||||
v := reflect.ValueOf(f)
|
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))
|
panic(fmt.Sprintf("invalid transformer function: %T", f))
|
||||||
}
|
}
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
@ -200,18 +232,45 @@ func Transformer(name string, f interface{}) Option {
|
||||||
if !isValid(name) {
|
if !isValid(name) {
|
||||||
panic(fmt.Sprintf("invalid name: %q", 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 {
|
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 {
|
type transformer struct {
|
||||||
|
core
|
||||||
name string
|
name string
|
||||||
|
typ reflect.Type // T
|
||||||
fnc reflect.Value // func(T) R
|
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
|
// Comparer returns an Option that determines whether two values are equal
|
||||||
// to each other.
|
// to each other.
|
||||||
//
|
//
|
||||||
|
@ -226,20 +285,41 @@ type transformer struct {
|
||||||
// • Pure: equal(x, y) does not modify x or y
|
// • Pure: equal(x, y) does not modify x or y
|
||||||
func Comparer(f interface{}) Option {
|
func Comparer(f interface{}) Option {
|
||||||
v := reflect.ValueOf(f)
|
v := reflect.ValueOf(f)
|
||||||
if functionType(v.Type()) != equalFunc || v.IsNil() {
|
if !function.IsType(v.Type(), function.Equal) || v.IsNil() {
|
||||||
panic(fmt.Sprintf("invalid comparer function: %T", f))
|
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 {
|
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 {
|
type comparer struct {
|
||||||
|
core
|
||||||
|
typ reflect.Type // T
|
||||||
fnc reflect.Value // func(T, T) bool
|
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
|
// AllowUnexported returns an Option that forcibly allows operations on
|
||||||
// unexported fields in certain structs, which are specified by passing in a
|
// unexported fields in certain structs, which are specified by passing in a
|
||||||
// value of each struct type.
|
// value of each struct type.
|
||||||
|
@ -283,12 +363,19 @@ func AllowUnexported(types ...interface{}) Option {
|
||||||
|
|
||||||
type visibleStructs map[reflect.Type]bool
|
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.
|
// reporter is an Option that configures how differences are reported.
|
||||||
//
|
|
||||||
// TODO: Not exported yet, see concerns in defaultReporter.Report.
|
|
||||||
type reporter interface {
|
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
|
Option
|
||||||
|
|
||||||
// Report is called for every comparison made and will be provided with
|
// 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;
|
// invalid reflect.Value if one of the values is non-existent;
|
||||||
// which is possible with maps and slices.
|
// which is possible with maps and slices.
|
||||||
Report(x, y reflect.Value, eq bool, p Path)
|
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
|
// normalizeOption normalizes the input options such that all Options groups
|
||||||
// better output closer to what pretty.Compare is able to achieve.
|
// are flattened and groups with a single element are reduced to that element.
|
||||||
|
// Only coreOptions and Options containing coreOptions are allowed.
|
||||||
|
func normalizeOption(src Option) Option {
|
||||||
|
switch opts := flattenOptions(nil, Options{src}); len(opts) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return opts[0]
|
||||||
|
default:
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flattenOptions copies all options in src to dst as a flat list.
|
||||||
|
// Only coreOptions and Options containing coreOptions are allowed.
|
||||||
|
func flattenOptions(dst, src Options) Options {
|
||||||
|
for _, opt := range src {
|
||||||
|
switch opt := opt.(type) {
|
||||||
|
case nil:
|
||||||
|
continue
|
||||||
|
case Options:
|
||||||
|
dst = flattenOptions(dst, opt)
|
||||||
|
case coreOption:
|
||||||
|
dst = append(dst, opt)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid option type: %T", opt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFuncName returns a short function name from the pointer.
|
||||||
|
// The string parsing logic works up until Go1.9.
|
||||||
|
func getFuncName(p uintptr) string {
|
||||||
|
fnc := runtime.FuncForPC(p)
|
||||||
|
if fnc == nil {
|
||||||
|
return "<unknown>"
|
||||||
|
}
|
||||||
|
name := fnc.Name() // E.g., "long/path/name/mypkg.(mytype).(long/path/name/mypkg.myfunc)-fm"
|
||||||
|
if strings.HasSuffix(name, ")-fm") || strings.HasSuffix(name, ")·fm") {
|
||||||
|
// Strip the package name from method name.
|
||||||
|
name = strings.TrimSuffix(name, ")-fm")
|
||||||
|
name = strings.TrimSuffix(name, ")·fm")
|
||||||
|
if i := strings.LastIndexByte(name, '('); i >= 0 {
|
||||||
|
methodName := name[i+1:] // E.g., "long/path/name/mypkg.myfunc"
|
||||||
|
if j := strings.LastIndexByte(methodName, '.'); j >= 0 {
|
||||||
|
methodName = methodName[j+1:] // E.g., "myfunc"
|
||||||
|
}
|
||||||
|
name = name[:i] + methodName // E.g., "long/path/name/mypkg.(mytype)." + "myfunc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i := strings.LastIndexByte(name, '/'); i >= 0 {
|
||||||
|
// Strip the package name.
|
||||||
|
name = name[i+1:] // E.g., "mypkg.(mytype).myfunc"
|
||||||
|
}
|
||||||
|
return name
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@ func TestOptionPanic(t *testing.T) {
|
||||||
label: "FilterPath",
|
label: "FilterPath",
|
||||||
fnc: FilterPath,
|
fnc: FilterPath,
|
||||||
args: []interface{}{func(Path) bool { return true }, &defaultReporter{}},
|
args: []interface{}{func(Path) bool { return true }, &defaultReporter{}},
|
||||||
wantPanic: "unknown option type",
|
wantPanic: "invalid option type",
|
||||||
}, {
|
}, {
|
||||||
label: "FilterPath",
|
label: "FilterPath",
|
||||||
fnc: FilterPath,
|
fnc: FilterPath,
|
||||||
|
@ -139,7 +139,7 @@ func TestOptionPanic(t *testing.T) {
|
||||||
label: "FilterPath",
|
label: "FilterPath",
|
||||||
fnc: FilterPath,
|
fnc: FilterPath,
|
||||||
args: []interface{}{func(Path) bool { return true }, Options{Ignore(), &defaultReporter{}}},
|
args: []interface{}{func(Path) bool { return true }, Options{Ignore(), &defaultReporter{}}},
|
||||||
wantPanic: "unknown option type",
|
wantPanic: "invalid option type",
|
||||||
}, {
|
}, {
|
||||||
label: "FilterValues",
|
label: "FilterValues",
|
||||||
fnc: FilterValues,
|
fnc: FilterValues,
|
||||||
|
@ -172,7 +172,7 @@ func TestOptionPanic(t *testing.T) {
|
||||||
label: "FilterValues",
|
label: "FilterValues",
|
||||||
fnc: FilterValues,
|
fnc: FilterValues,
|
||||||
args: []interface{}{func(int, int) bool { return true }, &defaultReporter{}},
|
args: []interface{}{func(int, int) bool { return true }, &defaultReporter{}},
|
||||||
wantPanic: "unknown option type",
|
wantPanic: "invalid option type",
|
||||||
}, {
|
}, {
|
||||||
label: "FilterValues",
|
label: "FilterValues",
|
||||||
fnc: FilterValues,
|
fnc: FilterValues,
|
||||||
|
@ -181,7 +181,7 @@ func TestOptionPanic(t *testing.T) {
|
||||||
label: "FilterValues",
|
label: "FilterValues",
|
||||||
fnc: FilterValues,
|
fnc: FilterValues,
|
||||||
args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), &defaultReporter{}}},
|
args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), &defaultReporter{}}},
|
||||||
wantPanic: "unknown option type",
|
wantPanic: "invalid option type",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -36,7 +36,19 @@ type (
|
||||||
// SliceIndex is an index operation on a slice or array at some index Key.
|
// SliceIndex is an index operation on a slice or array at some index Key.
|
||||||
SliceIndex interface {
|
SliceIndex interface {
|
||||||
PathStep
|
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()
|
isSliceIndex()
|
||||||
}
|
}
|
||||||
// MapIndex is an index operation on a map at some index Key.
|
// MapIndex is an index operation on a map at some index Key.
|
||||||
|
@ -163,7 +175,7 @@ type (
|
||||||
|
|
||||||
sliceIndex struct {
|
sliceIndex struct {
|
||||||
pathStep
|
pathStep
|
||||||
key int
|
xkey, ykey int
|
||||||
}
|
}
|
||||||
mapIndex struct {
|
mapIndex struct {
|
||||||
pathStep
|
pathStep
|
||||||
|
@ -205,19 +217,39 @@ func (ps pathStep) String() string {
|
||||||
return fmt.Sprintf("{%s}", s)
|
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 (mi mapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }
|
||||||
func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
|
func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
|
||||||
func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) }
|
func (sf structField) String() string { return fmt.Sprintf(".%s", sf.name) }
|
||||||
func (in indirect) String() string { return "*" }
|
func (in indirect) String() string { return "*" }
|
||||||
func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
|
func (tf transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }
|
||||||
|
|
||||||
func (si sliceIndex) Key() int { return si.key }
|
func (si sliceIndex) Key() int {
|
||||||
func (mi mapIndex) Key() reflect.Value { return mi.key }
|
if si.xkey != si.ykey {
|
||||||
func (sf structField) Name() string { return sf.name }
|
return -1
|
||||||
func (sf structField) Index() int { return sf.idx }
|
}
|
||||||
func (tf transform) Name() string { return tf.trans.name }
|
return si.xkey
|
||||||
func (tf transform) Func() reflect.Value { return tf.trans.fnc }
|
}
|
||||||
|
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 (pathStep) isPathStep() {}
|
||||||
func (sliceIndex) isSliceIndex() {}
|
func (sliceIndex) isSliceIndex() {}
|
||||||
|
|
|
@ -6,14 +6,11 @@ package cmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Can we leave the interface for a reporter here in the cmp package
|
"github.com/google/go-cmp/cmp/internal/value"
|
||||||
// and somehow extract the implementation of defaultReporter into cmp/report?
|
)
|
||||||
|
|
||||||
type defaultReporter struct {
|
type defaultReporter struct {
|
||||||
Option
|
Option
|
||||||
|
@ -26,45 +23,19 @@ type defaultReporter struct {
|
||||||
var _ reporter = (*defaultReporter)(nil)
|
var _ reporter = (*defaultReporter)(nil)
|
||||||
|
|
||||||
func (r *defaultReporter) Report(x, y reflect.Value, eq bool, p Path) {
|
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 {
|
if eq {
|
||||||
// TODO: Maybe print some equal results for context?
|
|
||||||
return // Ignore equal results
|
return // Ignore equal results
|
||||||
}
|
}
|
||||||
const maxBytes = 4096
|
const maxBytes = 4096
|
||||||
const maxLines = 256
|
const maxLines = 256
|
||||||
r.ndiffs++
|
r.ndiffs++
|
||||||
if r.nbytes < maxBytes && r.nlines < maxLines {
|
if r.nbytes < maxBytes && r.nlines < maxLines {
|
||||||
sx := prettyPrint(x, true)
|
sx := value.Format(x, true)
|
||||||
sy := prettyPrint(y, true)
|
sy := value.Format(y, true)
|
||||||
if sx == sy {
|
if sx == sy {
|
||||||
// Use of Stringer is not helpful, so rely on more exact formatting.
|
// Stringer is not helpful, so rely on more exact formatting.
|
||||||
sx = prettyPrint(x, false)
|
sx = value.Format(x, false)
|
||||||
sy = prettyPrint(y, false)
|
sy = value.Format(y, false)
|
||||||
}
|
}
|
||||||
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
|
s := fmt.Sprintf("%#v:\n\t-: %s\n\t+: %s\n", p, sx, sy)
|
||||||
r.diffs = append(r.diffs, s)
|
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)
|
return fmt.Sprintf("%s... %d more differences ...", s, len(r.diffs)-r.ndiffs)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringerIface = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
|
||||||
|
|
||||||
func prettyPrint(v reflect.Value, useStringer bool) string {
|
|
||||||
return formatAny(v, formatConfig{useStringer, true, true, true}, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
type formatConfig struct {
|
|
||||||
useStringer bool // Should the String method be used if available?
|
|
||||||
printType bool // Should we print the type before the value?
|
|
||||||
followPointers bool // Should we recursively follow pointers?
|
|
||||||
realPointers bool // Should we print the real address of pointers?
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatAny prints the value v in a pretty formatted manner.
|
|
||||||
// This is similar to fmt.Sprintf("%+v", v) except this:
|
|
||||||
// * Prints the type unless it can be elided.
|
|
||||||
// * Avoids printing struct fields that are zero.
|
|
||||||
// * Prints a nil-slice as being nil, not empty.
|
|
||||||
// * Prints map entries in deterministic order.
|
|
||||||
func formatAny(v reflect.Value, conf formatConfig, visited map[uintptr]bool) string {
|
|
||||||
// TODO: Should this be a multi-line printout in certain situations?
|
|
||||||
|
|
||||||
if !v.IsValid() {
|
|
||||||
return "<non-existent>"
|
|
||||||
}
|
|
||||||
if conf.useStringer && v.Type().Implements(stringerIface) {
|
|
||||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%q", v.Interface().(fmt.Stringer).String())
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return fmt.Sprint(v.Bool())
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return fmt.Sprint(v.Int())
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
if v.Type().PkgPath() == "" || v.Kind() == reflect.Uintptr {
|
|
||||||
return formatHex(v.Uint()) // Unnamed uints are usually bytes or words
|
|
||||||
}
|
|
||||||
return fmt.Sprint(v.Uint()) // Named uints are usually enumerations
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return fmt.Sprint(v.Float())
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
return fmt.Sprint(v.Complex())
|
|
||||||
case reflect.String:
|
|
||||||
return fmt.Sprintf("%q", v)
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
return formatPointer(v, conf)
|
|
||||||
case reflect.Ptr:
|
|
||||||
if v.IsNil() {
|
|
||||||
if conf.printType {
|
|
||||||
return fmt.Sprintf("(%v)(nil)", v.Type())
|
|
||||||
}
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
if visited[v.Pointer()] || !conf.followPointers {
|
|
||||||
return formatPointer(v, conf)
|
|
||||||
}
|
|
||||||
visited = insertPointer(visited, v.Pointer())
|
|
||||||
return "&" + formatAny(v.Elem(), conf, visited)
|
|
||||||
case reflect.Interface:
|
|
||||||
if v.IsNil() {
|
|
||||||
if conf.printType {
|
|
||||||
return fmt.Sprintf("%v(nil)", v.Type())
|
|
||||||
}
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
return formatAny(v.Elem(), conf, visited)
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
if conf.printType {
|
|
||||||
return fmt.Sprintf("%v(nil)", v.Type())
|
|
||||||
}
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
if visited[v.Pointer()] {
|
|
||||||
return formatPointer(v, conf)
|
|
||||||
}
|
|
||||||
visited = insertPointer(visited, v.Pointer())
|
|
||||||
fallthrough
|
|
||||||
case reflect.Array:
|
|
||||||
var ss []string
|
|
||||||
subConf := conf
|
|
||||||
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
s := formatAny(v.Index(i), subConf, visited)
|
|
||||||
ss = append(ss, s)
|
|
||||||
}
|
|
||||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
|
||||||
if conf.printType {
|
|
||||||
return v.Type().String() + s
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
case reflect.Map:
|
|
||||||
if v.IsNil() {
|
|
||||||
if conf.printType {
|
|
||||||
return fmt.Sprintf("%v(nil)", v.Type())
|
|
||||||
}
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
if visited[v.Pointer()] {
|
|
||||||
return formatPointer(v, conf)
|
|
||||||
}
|
|
||||||
visited = insertPointer(visited, v.Pointer())
|
|
||||||
|
|
||||||
var ss []string
|
|
||||||
subConf := conf
|
|
||||||
subConf.printType = v.Type().Elem().Kind() == reflect.Interface
|
|
||||||
for _, k := range sortKeys(v.MapKeys()) {
|
|
||||||
sk := formatAny(k, formatConfig{realPointers: conf.realPointers}, visited)
|
|
||||||
sv := formatAny(v.MapIndex(k), subConf, visited)
|
|
||||||
ss = append(ss, fmt.Sprintf("%s: %s", sk, sv))
|
|
||||||
}
|
|
||||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
|
||||||
if conf.printType {
|
|
||||||
return v.Type().String() + s
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
case reflect.Struct:
|
|
||||||
var ss []string
|
|
||||||
subConf := conf
|
|
||||||
subConf.printType = true
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
vv := v.Field(i)
|
|
||||||
if isZero(vv) {
|
|
||||||
continue // Elide zero value fields
|
|
||||||
}
|
|
||||||
name := v.Type().Field(i).Name
|
|
||||||
subConf.useStringer = conf.useStringer && isExported(name)
|
|
||||||
s := formatAny(vv, subConf, visited)
|
|
||||||
ss = append(ss, fmt.Sprintf("%s: %s", name, s))
|
|
||||||
}
|
|
||||||
s := fmt.Sprintf("{%s}", strings.Join(ss, ", "))
|
|
||||||
if conf.printType {
|
|
||||||
return v.Type().String() + s
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("%v kind not handled", v.Kind()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatPointer(v reflect.Value, conf formatConfig) string {
|
|
||||||
p := v.Pointer()
|
|
||||||
if !conf.realPointers {
|
|
||||||
p = 0 // For deterministic printing purposes
|
|
||||||
}
|
|
||||||
s := formatHex(uint64(p))
|
|
||||||
if conf.printType {
|
|
||||||
return fmt.Sprintf("(%v)(%s)", v.Type(), s)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatHex(u uint64) string {
|
|
||||||
var f string
|
|
||||||
switch {
|
|
||||||
case u <= 0xff:
|
|
||||||
f = "0x%02x"
|
|
||||||
case u <= 0xffff:
|
|
||||||
f = "0x%04x"
|
|
||||||
case u <= 0xffffff:
|
|
||||||
f = "0x%06x"
|
|
||||||
case u <= 0xffffffff:
|
|
||||||
f = "0x%08x"
|
|
||||||
case u <= 0xffffffffff:
|
|
||||||
f = "0x%010x"
|
|
||||||
case u <= 0xffffffffffff:
|
|
||||||
f = "0x%012x"
|
|
||||||
case u <= 0xffffffffffffff:
|
|
||||||
f = "0x%014x"
|
|
||||||
case u <= 0xffffffffffffffff:
|
|
||||||
f = "0x%016x"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(f, u)
|
|
||||||
}
|
|
||||||
|
|
||||||
// insertPointer insert p into m, allocating m if necessary.
|
|
||||||
func insertPointer(m map[uintptr]bool, p uintptr) map[uintptr]bool {
|
|
||||||
if m == nil {
|
|
||||||
m = make(map[uintptr]bool)
|
|
||||||
}
|
|
||||||
m[p] = true
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// isZero reports whether v is the zero value.
|
|
||||||
// This does not rely on Interface and so can be used on unexported fields.
|
|
||||||
func isZero(v reflect.Value) bool {
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return v.Bool() == false
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return v.Int() == 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return v.Uint() == 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return v.Float() == 0
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
return v.Complex() == 0
|
|
||||||
case reflect.String:
|
|
||||||
return v.String() == ""
|
|
||||||
case reflect.UnsafePointer:
|
|
||||||
return v.Pointer() == 0
|
|
||||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
|
||||||
return v.IsNil()
|
|
||||||
case reflect.Array:
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
if !isZero(v.Index(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
if !isZero(v.Field(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// isLess is a generic function for sorting arbitrary map keys.
|
|
||||||
// The inputs must be of the same type and must be comparable.
|
|
||||||
func isLess(x, y reflect.Value) bool {
|
|
||||||
switch x.Type().Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return !x.Bool() && y.Bool()
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return x.Int() < y.Int()
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return x.Uint() < y.Uint()
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
fx, fy := x.Float(), y.Float()
|
|
||||||
return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy)
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
cx, cy := x.Complex(), y.Complex()
|
|
||||||
rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy)
|
|
||||||
if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) {
|
|
||||||
return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy)
|
|
||||||
}
|
|
||||||
return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry)
|
|
||||||
case reflect.Ptr, reflect.UnsafePointer, reflect.Chan:
|
|
||||||
return x.Pointer() < y.Pointer()
|
|
||||||
case reflect.String:
|
|
||||||
return x.String() < y.String()
|
|
||||||
case reflect.Array:
|
|
||||||
for i := 0; i < x.Len(); i++ {
|
|
||||||
if isLess(x.Index(i), y.Index(i)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if isLess(y.Index(i), x.Index(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < x.NumField(); i++ {
|
|
||||||
if isLess(x.Field(i), y.Field(i)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if isLess(y.Field(i), x.Field(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case reflect.Interface:
|
|
||||||
vx, vy := x.Elem(), y.Elem()
|
|
||||||
if !vx.IsValid() || !vy.IsValid() {
|
|
||||||
return !vx.IsValid() && vy.IsValid()
|
|
||||||
}
|
|
||||||
tx, ty := vx.Type(), vy.Type()
|
|
||||||
if tx == ty {
|
|
||||||
return isLess(x.Elem(), y.Elem())
|
|
||||||
}
|
|
||||||
if tx.Kind() != ty.Kind() {
|
|
||||||
return vx.Kind() < vy.Kind()
|
|
||||||
}
|
|
||||||
if tx.String() != ty.String() {
|
|
||||||
return tx.String() < ty.String()
|
|
||||||
}
|
|
||||||
if tx.PkgPath() != ty.PkgPath() {
|
|
||||||
return tx.PkgPath() < ty.PkgPath()
|
|
||||||
}
|
|
||||||
// This can happen in rare situations, so we fallback to just comparing
|
|
||||||
// the unique pointer for a reflect.Type. This guarantees deterministic
|
|
||||||
// ordering within a program, but it is obviously not stable.
|
|
||||||
return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer()
|
|
||||||
default:
|
|
||||||
// Must be Func, Map, or Slice; which are not comparable.
|
|
||||||
panic(fmt.Sprintf("%T is not comparable", x.Type()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortKey sorts a list of map keys, deduplicating keys if necessary.
|
|
||||||
func sortKeys(vs []reflect.Value) []reflect.Value {
|
|
||||||
if len(vs) == 0 {
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the map keys.
|
|
||||||
sort.Sort(valueSorter(vs))
|
|
||||||
|
|
||||||
// Deduplicate keys (fails for NaNs).
|
|
||||||
vs2 := vs[:1]
|
|
||||||
for _, v := range vs[1:] {
|
|
||||||
if v.Interface() != vs2[len(vs2)-1].Interface() {
|
|
||||||
vs2 = append(vs2, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vs2
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use sort.Slice once Google AppEngine is on Go1.8 or above.
|
|
||||||
type valueSorter []reflect.Value
|
|
||||||
|
|
||||||
func (vs valueSorter) Len() int { return len(vs) }
|
|
||||||
func (vs valueSorter) Less(i, j int) bool { return isLess(vs[i], vs[j]) }
|
|
||||||
func (vs valueSorter) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
|
||||||
|
|
|
@ -217,11 +217,6 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if sv.Type() == timeType {
|
|
||||||
values.Add(name, valueString(sv, opts))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for sv.Kind() == reflect.Ptr {
|
for sv.Kind() == reflect.Ptr {
|
||||||
if sv.IsNil() {
|
if sv.IsNil() {
|
||||||
break
|
break
|
||||||
|
@ -229,6 +224,11 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
|
||||||
sv = sv.Elem()
|
sv = sv.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sv.Type() == timeType {
|
||||||
|
values.Add(name, valueString(sv, opts))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if sv.Kind() == reflect.Struct {
|
if sv.Kind() == reflect.Struct {
|
||||||
reflectValue(values, sv, name)
|
reflectValue(values, sv, name)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -25,6 +25,7 @@ type SubNested struct {
|
||||||
func TestValues_types(t *testing.T) {
|
func TestValues_types(t *testing.T) {
|
||||||
str := "string"
|
str := "string"
|
||||||
strPtr := &str
|
strPtr := &str
|
||||||
|
timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
in interface{}
|
in interface{}
|
||||||
|
@ -53,11 +54,17 @@ func TestValues_types(t *testing.T) {
|
||||||
A *string
|
A *string
|
||||||
B *int
|
B *int
|
||||||
C **string
|
C **string
|
||||||
}{A: strPtr, C: &strPtr},
|
D *time.Time
|
||||||
|
}{
|
||||||
|
A: strPtr,
|
||||||
|
C: &strPtr,
|
||||||
|
D: &timeVal,
|
||||||
|
},
|
||||||
url.Values{
|
url.Values{
|
||||||
"A": {str},
|
"A": {str},
|
||||||
"B": {""},
|
"B": {""},
|
||||||
"C": {str},
|
"C": {str},
|
||||||
|
"D": {"2000-01-01T12:34:56Z"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
|
||||||
license. Its contents can be found at:
|
|
||||||
http://creativecommons.org/publicdomain/zero/1.0
|
|
|
@ -1,2 +0,0 @@
|
||||||
all:
|
|
||||||
make -C testdata
|
|
|
@ -0,0 +1 @@
|
||||||
|
/releases
|
|
@ -0,0 +1,6 @@
|
||||||
|
Pierre Baillet <pierre@baillet.name> Pierre Baillet <oct@fotonauts.com>
|
||||||
|
Jordan Liggitt <jordan@liggitt.net> Jordan Liggitt <jliggitt@redhat.com>
|
||||||
|
Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
||||||
|
Jim Teeuwen <jimteeuwen@gmail.com>
|
||||||
|
Craig Landry <craigtlandry@gmail.com>
|
||||||
|
Kenta Oda <oda@headjapan.com>
|
|
@ -0,0 +1,13 @@
|
||||||
|
go_import_path: github.com/kevinburke/go-bindata
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
# go vet --all doesn't work with 1.8 or 1.7
|
||||||
|
- 1.9.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
script:
|
||||||
|
- make ci
|
||||||
|
|
||||||
|
sudo: false
|
|
@ -0,0 +1,37 @@
|
||||||
|
Aaron Schlesinger <aschlesinger@paypal.com>
|
||||||
|
Alan Shreve <alan@inconshreveable.com>
|
||||||
|
Anand Gaitonde and Michael Maximilien <agaitonde@pivotallabs.com>
|
||||||
|
Andrew Bates <abates@omeganetserv.com>
|
||||||
|
Beyang Liu <beyang@sourcegraph.com>
|
||||||
|
Brandon Mulcahy <brandon@jangler.info>
|
||||||
|
Craig Landry <craigtlandry@gmail.com>
|
||||||
|
Dimitri Sokolyuk <demon@dim13.org>
|
||||||
|
Dmitri Shuralyov <shurcooL@gmail.com>
|
||||||
|
Elazar Leibovich <elazarl@gmail.com>
|
||||||
|
Elias Naur <elias.naur@gmail.com>
|
||||||
|
Emad Elsaid <blazeeboy@users.noreply.github.com>
|
||||||
|
Ian Kent <email@iankent.co.uk>
|
||||||
|
Jens Breitbart <jbreitbart@gmail.com>
|
||||||
|
Jessica Forrester <jforrest@redhat.com>
|
||||||
|
Jim Teeuwen <jimteeuwen@gmail.com>
|
||||||
|
Jordan Liggitt <jordan@liggitt.net>
|
||||||
|
Joseph Hager <ajhager@gmail.com>
|
||||||
|
Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
||||||
|
Kenta Oda <oda@headjapan.com>
|
||||||
|
Kevin Burke <kev@inburke.com>
|
||||||
|
Matt Dee <mdee@hioscar.com>
|
||||||
|
Maxim Ignatenko <imax@cesanta.com>
|
||||||
|
Paweł Błaszczyk <blaszczykpb@gmail.com>
|
||||||
|
Pierre Baillet <pierre@baillet.name>
|
||||||
|
Ruben Vermeersch <ruben@rocketeer.be>
|
||||||
|
Ryosuke IWANAGA <riywo.jp@gmail.com>
|
||||||
|
SATO taichi <ryushi@gmail.com>
|
||||||
|
Sebastian Thiel <byronimo@gmail.com>
|
||||||
|
Serhan Şen <mail@serhan-sen.de>
|
||||||
|
Stephen Searles <stephen.searles@gmail.com>
|
||||||
|
Tamir Duberstein <tamird@gmail.com>
|
||||||
|
Tobias Schmidt <ts@soundcloud.com>
|
||||||
|
Tobias Schottdorf <tobias.schottdorf@gmail.com>
|
||||||
|
Yasuhiro Matsumoto <mattn.jp@gmail.com>
|
||||||
|
ikawaha <ikawaha@users.noreply.github.com>
|
||||||
|
opennota <opennota@gmail.com>
|
|
@ -4,7 +4,6 @@ So you wish to contribute to this project? Fantastic!
|
||||||
Here are a few guidelines to help you do this in a
|
Here are a few guidelines to help you do this in a
|
||||||
streamlined fashion.
|
streamlined fashion.
|
||||||
|
|
||||||
|
|
||||||
## Bug reports
|
## Bug reports
|
||||||
|
|
||||||
When supplying a bug report, please consider the following guidelines.
|
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
|
just enough of it to clearly define the issue. Not everyone is a native
|
||||||
English speaker. And while most can handle themselves pretty well,
|
English speaker. And while most can handle themselves pretty well,
|
||||||
it helps to stay away from more esoteric vocabulary.
|
it helps to stay away from more esoteric vocabulary.
|
||||||
|
|
||||||
Be patient with non-native English speakers. If their bug reports
|
Be patient with non-native English speakers. If their bug reports
|
||||||
or comments are hard to understand, just ask for clarification.
|
or comments are hard to understand, just ask for clarification.
|
||||||
Do not start guessing at their meaning, as this may just lead to
|
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,
|
If need be, create a whole new code project on your local machine,
|
||||||
which specifically tries to create the problem you are running into;
|
which specifically tries to create the problem you are running into;
|
||||||
nothing more, nothing less.
|
nothing more, nothing less.
|
||||||
|
|
||||||
Include this program in the bug report. It often suffices to paste
|
Include this program in the bug report. It often suffices to paste
|
||||||
the code in a [Gist](https://gist.github.com) or on the
|
the code in a [Gist](https://gist.github.com) or on the
|
||||||
[Go playground](http://play.golang.org).
|
[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
|
undertaken to solve the problem. This can save us a great deal of
|
||||||
wasted time, trying out solutions you have already covered.
|
wasted time, trying out solutions you have already covered.
|
||||||
|
|
||||||
|
|
||||||
## Pull requests
|
## Pull requests
|
||||||
|
|
||||||
Bug reports are great. Supplying fixes to bugs is even better.
|
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
|
committing it. Code has to be readable by many different
|
||||||
people. And the only way this will be as painless as possible,
|
people. And the only way this will be as painless as possible,
|
||||||
is if we all stick to the same code style.
|
is if we all stick to the same code style.
|
||||||
|
|
||||||
Some of our projects may have automated build-servers hooked up
|
Some of our projects may have automated build-servers hooked up
|
||||||
to commit hooks. These will vet any submitted code and determine
|
to commit hooks. These will vet any submitted code and determine
|
||||||
if it meets a set of properties. One of which is code formatting.
|
if it meets a set of properties. One of which is code formatting.
|
||||||
These servers will outright deny a submission which has not been
|
These servers will outright deny a submission which has not been
|
||||||
run through `go fmt`, even if the code itself is correct.
|
run through `go fmt`, even if the code itself is correct.
|
||||||
|
|
||||||
We try to maintain a zero-tolerance policy on this matter,
|
We try to maintain a zero-tolerance policy on this matter,
|
||||||
because consistently formatted code makes life a great deal
|
because consistently formatted code makes life a great deal
|
||||||
easier for everyone involved.
|
easier for everyone involved.
|
||||||
|
|
||||||
* Commit log messages: When committing changes, do so often and
|
* Commit log messages: When committing changes, do so often and
|
||||||
clearly -- Even if you have changed only 1 character in a code
|
clearly -- Even if you have changed only 1 character in a code
|
||||||
comment. This means that commit log messages should clearly state
|
comment. This means that commit log messages should clearly state
|
||||||
exactly what the change does and why. If it fixes a known issue,
|
exactly what the change does and why. If it fixes a known issue,
|
||||||
then mention the issue number in the commit log. E.g.:
|
then mention the issue number in the commit log. E.g.:
|
||||||
|
|
||||||
> Fixes return value for `foo/boo.Baz()` to be consistent with
|
> Fixes return value for `foo/boo.Baz()` to be consistent with
|
||||||
> the rest of the API. This addresses issue #32
|
> the rest of the API. This addresses issue #32
|
||||||
|
|
||||||
Do not pile a lot of unrelated changes into a single commit.
|
Do not pile a lot of unrelated changes into a single commit.
|
||||||
Pick and choose only those changes for a single commit, which are
|
Pick and choose only those changes for a single commit, which are
|
||||||
directly related. We would much rather see a hundred commits
|
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.
|
than have these style changes embedded in those real fixes.
|
||||||
It creates a lot of noise when trying to review code.
|
It creates a lot of noise when trying to review code.
|
||||||
|
|
||||||
|
## New releases
|
||||||
|
|
||||||
|
1) Manually update version.go with the new version number.
|
||||||
|
|
||||||
|
2) Commit the change. The commit message should be the version number.
|
||||||
|
|
||||||
|
3) Add a git tag
|
||||||
|
|
||||||
|
4) Run GITHUB_TOKEN=mytoken make release version=my-git-tag
|
|
@ -0,0 +1,34 @@
|
||||||
|
This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||||
|
license. Its contents can be found at:
|
||||||
|
|
||||||
|
https://creativecommons.org/publicdomain/zero/1.0
|
||||||
|
|
||||||
|
safefile.go and safefile_test.go are imported from github.com/dchest/safefile
|
||||||
|
and contain local modifications. The license from that project is included here:
|
||||||
|
|
||||||
|
Copyright (c) 2013 Dmitry Chestnykh <dmitry@codingrobots.com>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials
|
||||||
|
provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,64 @@
|
||||||
|
DIFFER := $(GOPATH)/bin/differ
|
||||||
|
MEGACHECK := $(GOPATH)/bin/megacheck
|
||||||
|
RELEASE := $(GOPATH)/bin/github-release
|
||||||
|
WRITE_MAILMAP := $(GOPATH)/bin/write_mailmap
|
||||||
|
|
||||||
|
all:
|
||||||
|
$(MAKE) -C testdata
|
||||||
|
|
||||||
|
$(DIFFER):
|
||||||
|
go get -u github.com/kevinburke/differ
|
||||||
|
|
||||||
|
diff-testdata: | $(DIFFER)
|
||||||
|
$(DIFFER) $(MAKE) -C testdata
|
||||||
|
$(DIFFER) go fmt ./testdata/out/...
|
||||||
|
|
||||||
|
$(MEGACHECK):
|
||||||
|
go get honnef.co/go/tools/cmd/megacheck
|
||||||
|
|
||||||
|
lint: | $(MEGACHECK)
|
||||||
|
go vet ./...
|
||||||
|
$(MEGACHECK) ./...
|
||||||
|
|
||||||
|
go-test:
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
go-race-test:
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
test: go-test
|
||||||
|
$(MAKE) -C testdata
|
||||||
|
|
||||||
|
race-test: lint go-race-test
|
||||||
|
$(MAKE) -C testdata
|
||||||
|
|
||||||
|
$(WRITE_MAILMAP):
|
||||||
|
go get -u github.com/kevinburke/write_mailmap
|
||||||
|
|
||||||
|
force: ;
|
||||||
|
|
||||||
|
AUTHORS.txt: force | $(WRITE_MAILMAP)
|
||||||
|
$(WRITE_MAILMAP) > AUTHORS.txt
|
||||||
|
|
||||||
|
authors: AUTHORS.txt
|
||||||
|
|
||||||
|
ci: go-race-test diff-testdata
|
||||||
|
|
||||||
|
release: | $(RELEASE) race-test diff-testdata
|
||||||
|
ifndef version
|
||||||
|
@echo "Please provide a version"
|
||||||
|
exit 1
|
||||||
|
endif
|
||||||
|
ifndef GITHUB_TOKEN
|
||||||
|
@echo "Please set GITHUB_TOKEN in the environment"
|
||||||
|
exit 1
|
||||||
|
endif
|
||||||
|
mkdir -p releases/$(version)
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o releases/$(version)/go-bindata-linux-amd64 ./go-bindata
|
||||||
|
GOOS=darwin GOARCH=amd64 go build -o releases/$(version)/go-bindata-darwin-amd64 ./go-bindata
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o releases/$(version)/go-bindata-windows-amd64 ./go-bindata
|
||||||
|
# these commands are not idempotent so ignore failures if an upload repeats
|
||||||
|
$(RELEASE) release --user kevinburke --repo go-bindata --tag $(version) || true
|
||||||
|
$(RELEASE) upload --user kevinburke --repo go-bindata --tag $(version) --name go-bindata-linux-amd64 --file releases/$(version)/go-bindata-linux-amd64 || true
|
||||||
|
$(RELEASE) upload --user kevinburke --repo go-bindata --tag $(version) --name go-bindata-darwin-amd64 --file releases/$(version)/go-bindata-darwin-amd64 || true
|
||||||
|
$(RELEASE) upload --user kevinburke --repo go-bindata --tag $(version) --name go-bindata-windows-amd64 --file releases/$(version)/go-bindata-windows-amd64 || true
|
|
@ -1,6 +1,23 @@
|
||||||
## bindata
|
## 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
|
embedding binary data into a go program. The file data is optionally gzip
|
||||||
compressed before being converted to a raw byte slice.
|
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:
|
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
|
### Usage
|
||||||
|
|
||||||
|
@ -42,7 +58,7 @@ Multiple input directories can be specified if necessary.
|
||||||
$ go-bindata dir1/... /path/to/dir2/... dir3
|
$ 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
|
supplied to `go-bindata`. Refer to the `testdata/out` directory for various
|
||||||
output examples from the assets in `testdata/in`. Each example uses different
|
output examples from the assets in `testdata/in`. Each example uses different
|
||||||
command line options.
|
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.
|
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
|
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.
|
call our generated function, we omit unnecessary memcopies.
|
||||||
|
|
||||||
The downside of this, is that it requires dependencies on the `reflect` and
|
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
|
_bindata["templates/foo.html"] = templates_foo_html
|
||||||
|
|
||||||
|
|
||||||
### Build tags
|
### Build tags
|
||||||
|
|
||||||
With the optional `-tags` flag, you can specify any go build tags that
|
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
|
### 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`.
|
implements `http.FileSystem` interface. Allows you to serve assets with `net/http`.
|
||||||
|
|
|
@ -15,6 +15,7 @@ var (
|
||||||
space = []byte{' '}
|
space = []byte{' '}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ByteWriter writes the hex-encoded version of input bytes to its Writer.
|
||||||
type ByteWriter struct {
|
type ByteWriter struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
c int
|
c int
|
||||||
|
@ -39,6 +40,5 @@ func (w *ByteWriter) Write(p []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
n++
|
n++
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -5,8 +5,9 @@
|
||||||
package bindata
|
package bindata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/format"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -22,8 +23,7 @@ func Translate(c *Config) error {
|
||||||
var toc []Asset
|
var toc []Asset
|
||||||
|
|
||||||
// Ensure our configuration has sane values.
|
// Ensure our configuration has sane values.
|
||||||
err := c.validate()
|
if err := c.validate(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,29 +31,18 @@ func Translate(c *Config) error {
|
||||||
var visitedPaths = make(map[string]bool)
|
var visitedPaths = make(map[string]bool)
|
||||||
// Locate all the assets.
|
// Locate all the assets.
|
||||||
for _, input := range c.Input {
|
for _, input := range c.Input {
|
||||||
err = findFiles(input.Path, c.Prefix, input.Recursive, &toc, c.Ignore, knownFuncs, visitedPaths)
|
if err := findFiles(input.Path, c.Prefix, input.Recursive, &toc, c.Ignore, knownFuncs, visitedPaths); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create output file.
|
// Create output file.
|
||||||
fd, err := os.Create(c.Output)
|
buf := new(bytes.Buffer)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer fd.Close()
|
|
||||||
|
|
||||||
// Create a buffered writer for better performance.
|
|
||||||
bfd := bufio.NewWriter(fd)
|
|
||||||
defer bfd.Flush()
|
|
||||||
|
|
||||||
// Write the header. This makes e.g. Github ignore diffs in generated files.
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
if _, err = fmt.Fprint(bfd, "// sources:\n"); err != nil {
|
if _, err := fmt.Fprint(buf, "// sources:\n"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,57 +53,71 @@ func Translate(c *Config) error {
|
||||||
|
|
||||||
for _, asset := range toc {
|
for _, asset := range toc {
|
||||||
relative, _ := filepath.Rel(wd, asset.Path)
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err = fmt.Fprint(bfd, "// DO NOT EDIT!\n\n"); err != nil {
|
if _, err = fmt.Fprint(buf, "\n"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write build tags, if applicable.
|
// Write build tags, if applicable.
|
||||||
if len(c.Tags) > 0 {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write package declaration.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write assets.
|
// Write assets.
|
||||||
if c.Debug || c.Dev {
|
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 {
|
} else {
|
||||||
err = writeRelease(bfd, c, toc)
|
err = writeRelease(buf, c, toc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write table of contents
|
// Write table of contents
|
||||||
if err := writeTOC(bfd, toc); err != nil {
|
if err := writeTOC(buf, toc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Write hierarchical tree of assets
|
// Write hierarchical tree of assets
|
||||||
if err := writeTOCTree(bfd, toc); err != nil {
|
if err := writeTOCTree(buf, toc); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write restore procedure
|
// 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()
|
// 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) Len() int { return len(v) }
|
||||||
func (v ByName) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
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) Less(i, j int) bool { return v[i].Name() < v[j].Name() }
|
||||||
|
|
||||||
// findFiles recursively finds all the file paths in the given directory tree.
|
// 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
|
// 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 to make output stable between invocations
|
||||||
sort.Sort(ByName(list))
|
sort.Sort(byName(list))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range 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.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)
|
*toc = append(*toc, asset)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||||
// license. Its contents can be found at:
|
// license. Its contents can be found at:
|
||||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
// https://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
|
||||||
package bindata
|
package bindata
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ func writeDebug(w io.Writer, c *Config, toc []Asset) error {
|
||||||
// This targets debug builds.
|
// This targets debug builds.
|
||||||
func writeDebugHeader(w io.Writer) error {
|
func writeDebugHeader(w io.Writer) error {
|
||||||
_, err := fmt.Fprintf(w, `import (
|
_, err := fmt.Fprintf(w, `import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -47,8 +48,9 @@ func bindataRead(path, name string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type asset struct {
|
type asset struct {
|
||||||
bytes []byte
|
bytes []byte
|
||||||
info os.FileInfo
|
info os.FileInfo
|
||||||
|
digest [sha256.Size]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
`)
|
`)
|
|
@ -7,6 +7,7 @@ package bindata
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -65,42 +66,86 @@ func writeReleaseAsset(w io.Writer, c *Config, asset *Asset) error {
|
||||||
|
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
tr := io.TeeReader(fd, h)
|
||||||
if c.NoCompress {
|
if c.NoCompress {
|
||||||
if c.NoMemCopy {
|
if c.NoMemCopy {
|
||||||
err = uncompressed_nomemcopy(w, asset, fd)
|
err = uncompressed_nomemcopy(w, asset, tr)
|
||||||
} else {
|
} else {
|
||||||
err = uncompressed_memcopy(w, asset, fd)
|
err = uncompressed_memcopy(w, asset, tr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if c.NoMemCopy {
|
if c.NoMemCopy {
|
||||||
err = compressed_nomemcopy(w, asset, fd)
|
err = compressed_nomemcopy(w, asset, tr)
|
||||||
} else {
|
} else {
|
||||||
err = compressed_memcopy(w, asset, fd)
|
err = compressed_memcopy(w, asset, tr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// 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
|
// Based on https://code.google.com/p/go/source/browse/godoc/static/makestatic.go?repo=tools
|
||||||
func sanitize(b []byte) []byte {
|
func sanitize(b []byte) []byte {
|
||||||
// Replace ` with `+"`"+`
|
var chunks [][]byte
|
||||||
b = bytes.Replace(b, []byte("`"), []byte("`+\"`\"+`"), -1)
|
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"+`
|
var buf bytes.Buffer
|
||||||
// (A BOM is valid UTF-8 but not permitted in Go source files.
|
sanitizeChunks(&buf, chunks)
|
||||||
// I wouldn't bother handling this, but for some insane reason
|
return buf.Bytes()
|
||||||
// jquery.js has a BOM somewhere in the middle.)
|
}
|
||||||
return bytes.Replace(b, []byte("\xEF\xBB\xBF"), []byte("`+\"\\xEF\\xBB\\xBF\"+`"), -1)
|
|
||||||
|
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 {
|
func header_compressed_nomemcopy(w io.Writer) error {
|
||||||
_, err := fmt.Fprintf(w, `import (
|
_, err := fmt.Fprintf(w, `import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -118,13 +163,14 @@ func bindataRead(data, name string) ([]byte, error) {
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
_, err = io.Copy(&buf, gz)
|
_, err = io.Copy(&buf, gz)
|
||||||
clErr := gz.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Read %%q: %%v", name, err)
|
return nil, fmt.Errorf("Read %%q: %%v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clErr := gz.Close()
|
||||||
if clErr != nil {
|
if clErr != nil {
|
||||||
return nil, err
|
return nil, clErr
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
|
@ -138,6 +184,7 @@ func header_compressed_memcopy(w io.Writer) error {
|
||||||
_, err := fmt.Fprintf(w, `import (
|
_, err := fmt.Fprintf(w, `import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -173,6 +220,7 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
||||||
|
|
||||||
func header_uncompressed_nomemcopy(w io.Writer) error {
|
func header_uncompressed_nomemcopy(w io.Writer) error {
|
||||||
_, err := fmt.Fprintf(w, `import (
|
_, err := fmt.Fprintf(w, `import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -200,6 +248,7 @@ func bindataRead(data, name string) ([]byte, error) {
|
||||||
|
|
||||||
func header_uncompressed_memcopy(w io.Writer) error {
|
func header_uncompressed_memcopy(w io.Writer) error {
|
||||||
_, err := fmt.Fprintf(w, `import (
|
_, err := fmt.Fprintf(w, `import (
|
||||||
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -213,8 +262,9 @@ func header_uncompressed_memcopy(w io.Writer) error {
|
||||||
|
|
||||||
func header_release_common(w io.Writer) error {
|
func header_release_common(w io.Writer) error {
|
||||||
_, err := fmt.Fprintf(w, `type asset struct {
|
_, err := fmt.Fprintf(w, `type asset struct {
|
||||||
bytes []byte
|
bytes []byte
|
||||||
info os.FileInfo
|
info os.FileInfo
|
||||||
|
digest [sha256.Size]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type bindataFileInfo struct {
|
type bindataFileInfo struct {
|
||||||
|
@ -336,7 +386,7 @@ func uncompressed_memcopy(w io.Writer, asset *Asset, r io.Reader) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if utf8.Valid(b) && !bytes.Contains(b, []byte{0}) {
|
if utf8.Valid(b) && !bytes.Contains(b, []byte{0}) {
|
||||||
fmt.Fprintf(w, "`%s`", sanitize(b))
|
w.Write(sanitize(b))
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "%+q", b)
|
fmt.Fprintf(w, "%+q", b)
|
||||||
}
|
}
|
||||||
|
@ -351,7 +401,7 @@ func %sBytes() ([]byte, error) {
|
||||||
return err
|
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)
|
fi, err := os.Stat(asset.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)}
|
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
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
`, asset.Func, asset.Func, asset.Name, size, mode, modTime)
|
`, asset.Func, asset.Func, asset.Name, size, mode, modTime, digest)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package bindata
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var sanitizeTests = []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{`hello`, "`hello`"},
|
||||||
|
{"hello\nworld", "`hello\nworld`"},
|
||||||
|
{"`ello", "(\"`\" + `ello`)"},
|
||||||
|
{"`a`e`i`o`u`", "(((\"`\" + `a`) + (\"`\" + (`e` + \"`\"))) + ((`i` + (\"`\" + `o`)) + (\"`\" + (`u` + \"`\"))))"},
|
||||||
|
{"\xEF\xBB\xBF`s away!", "(\"\\xEF\\xBB\\xBF\" + (\"`\" + `s away!`))"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanitize(t *testing.T) {
|
||||||
|
for _, tt := range sanitizeTests {
|
||||||
|
out := []byte(sanitize([]byte(tt.in)))
|
||||||
|
if string(out) != tt.out {
|
||||||
|
t.Errorf("sanitize(%q):\nhave %q\nwant %q", tt.in, out, tt.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
func writeRestore(w io.Writer) error {
|
func writeRestore(w io.Writer) error {
|
||||||
_, err := fmt.Fprintf(w, `
|
_, 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 {
|
func RestoreAsset(dir, name string) error {
|
||||||
data, err := Asset(name)
|
data, err := Asset(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -29,14 +29,10 @@ func RestoreAsset(dir, name string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreAssets restores an asset under the given directory recursively
|
// RestoreAssets restores an asset under the given directory recursively.
|
||||||
func RestoreAssets(dir, name string) error {
|
func RestoreAssets(dir, name string) error {
|
||||||
children, err := AssetDir(name)
|
children, err := AssetDir(name)
|
||||||
// File
|
// File
|
||||||
|
@ -54,10 +50,9 @@ func RestoreAssets(dir, name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func _filePath(dir, name string) string {
|
func _filePath(dir, name string) string {
|
||||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
`)
|
`)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
// Copyright 2013 Dmitry Chestnykh. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
//
|
||||||
|
// Some modifications made: API's were made internal to the package. os.Rename
|
||||||
|
// hack for old Windows/Plan 9 builds removed; if you are using those systems,
|
||||||
|
// it is OK if you don't have atomic rewrites.
|
||||||
|
|
||||||
|
package bindata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base32"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrAlreadyCommitted error is returned when calling Commit on a file that
|
||||||
|
// has been already successfully committed.
|
||||||
|
var errAlreadyCommitted = errors.New("file already committed")
|
||||||
|
|
||||||
|
type safefileFile struct {
|
||||||
|
*os.File
|
||||||
|
origName string
|
||||||
|
closeFunc func(*safefileFile) error
|
||||||
|
isClosed bool // if true, temporary file has been closed, but not renamed
|
||||||
|
isCommitted bool // if true, the file has been successfully committed
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTempName(origname, prefix string) (tempname string, err error) {
|
||||||
|
origname = filepath.Clean(origname)
|
||||||
|
if len(origname) == 0 || origname[len(origname)-1] == filepath.Separator {
|
||||||
|
return "", os.ErrInvalid
|
||||||
|
}
|
||||||
|
// Generate 10 random bytes.
|
||||||
|
// This gives 80 bits of entropy, good enough
|
||||||
|
// for making temporary file name unpredictable.
|
||||||
|
var rnd [10]byte
|
||||||
|
if _, err := rand.Read(rnd[:]); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
name := prefix + "-" + strings.ToLower(base32.StdEncoding.EncodeToString(rnd[:])) + ".tmp"
|
||||||
|
return filepath.Join(filepath.Dir(origname), name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// safefileCreate creates a temporary file in the same directory as filename,
|
||||||
|
// which will be renamed to the given filename when calling Commit.
|
||||||
|
func safefileCreate(filename string, perm os.FileMode) (*safefileFile, error) {
|
||||||
|
for {
|
||||||
|
tempname, err := makeTempName(filename, "sf")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(tempname, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &safefileFile{
|
||||||
|
File: f,
|
||||||
|
origName: filename,
|
||||||
|
closeFunc: closeUncommitted,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrigName returns the original filename given to Create.
|
||||||
|
func (f *safefileFile) OrigName() string {
|
||||||
|
return f.origName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes temporary file and removes it.
|
||||||
|
// If the file has been committed, Close is no-op.
|
||||||
|
func (f *safefileFile) Close() error {
|
||||||
|
return f.closeFunc(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeUncommitted(f *safefileFile) error {
|
||||||
|
err0 := f.File.Close()
|
||||||
|
err1 := os.Remove(f.Name())
|
||||||
|
f.closeFunc = closeAgainError
|
||||||
|
if err0 != nil {
|
||||||
|
return err0
|
||||||
|
}
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeAfterFailedRename(f *safefileFile) error {
|
||||||
|
// Remove temporary file.
|
||||||
|
//
|
||||||
|
// The note from Commit function applies here too, as we may be
|
||||||
|
// removing a different file. However, since we rely on our temporary
|
||||||
|
// names being unpredictable, this should not be a concern.
|
||||||
|
f.closeFunc = closeAgainError
|
||||||
|
return os.Remove(f.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeCommitted(f *safefileFile) error {
|
||||||
|
// noop
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeAgainError(f *safefileFile) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit safely commits data into the original file by syncing temporary
|
||||||
|
// file to disk, closing it and renaming to the original file name.
|
||||||
|
//
|
||||||
|
// In case of success, the temporary file is closed and no longer exists
|
||||||
|
// on disk. It is safe to call Close after Commit: the operation will do
|
||||||
|
// nothing.
|
||||||
|
//
|
||||||
|
// In case of error, the temporary file is still opened and exists on disk;
|
||||||
|
// it must be closed by callers by calling Close or by trying to commit again.
|
||||||
|
|
||||||
|
// Note that when trying to Commit again after a failed Commit when the file
|
||||||
|
// has been closed, but not renamed to its original name (the new commit will
|
||||||
|
// try again to rename it), safefile cannot guarantee that the temporary file
|
||||||
|
// has not been changed, or that it is the same temporary file we were dealing
|
||||||
|
// with. However, since the temporary name is unpredictable, it is unlikely
|
||||||
|
// that this happened accidentally. If complete atomicity is needed, do not
|
||||||
|
// Commit again after error, write the file again.
|
||||||
|
func (f *safefileFile) Commit() error {
|
||||||
|
if f.isCommitted {
|
||||||
|
return errAlreadyCommitted
|
||||||
|
}
|
||||||
|
if !f.isClosed {
|
||||||
|
// Sync to disk.
|
||||||
|
err := f.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Close underlying os.File.
|
||||||
|
err = f.File.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.isClosed = true
|
||||||
|
}
|
||||||
|
err := os.Rename(f.Name(), f.origName)
|
||||||
|
if err != nil {
|
||||||
|
f.closeFunc = closeAfterFailedRename
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.closeFunc = closeCommitted
|
||||||
|
f.isCommitted = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile is a safe analog of ioutil.WriteFile.
|
||||||
|
func safefileWriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||||
|
f, err := safefileCreate(filename, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
n, err := f.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == nil && n < len(data) {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f.Commit()
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
// Copyright 2013 Dmitry Chestnykh. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bindata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ensureFileContains(name, data string) error {
|
||||||
|
b, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if string(b) != data {
|
||||||
|
return fmt.Errorf("wrong data in file: expected %s, got %s", data, string(b))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tempFileName(count int) string {
|
||||||
|
return filepath.Join(os.TempDir(), fmt.Sprintf("safefile-test-%d-%x", count, time.Now().UnixNano()))
|
||||||
|
}
|
||||||
|
|
||||||
|
var testData = "Hello, this is a test data"
|
||||||
|
|
||||||
|
func testInTempDir() error {
|
||||||
|
name := tempFileName(0)
|
||||||
|
defer os.Remove(name)
|
||||||
|
f, err := safefileCreate(name, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if name != f.OrigName() {
|
||||||
|
f.Close()
|
||||||
|
return fmt.Errorf("name %q differs from OrigName: %q", name, f.OrigName())
|
||||||
|
}
|
||||||
|
_, err = io.WriteString(f, testData)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = f.Commit()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ensureFileContains(name, testData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeTempName(t *testing.T) {
|
||||||
|
// Make sure temp name is random.
|
||||||
|
m := make(map[string]bool)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
name, err := makeTempName("/tmp", "sf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if m[name] {
|
||||||
|
t.Fatal("repeated file name")
|
||||||
|
}
|
||||||
|
m[name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFile(t *testing.T) {
|
||||||
|
err := testInTempDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteFile(t *testing.T) {
|
||||||
|
name := tempFileName(1)
|
||||||
|
err := safefileWriteFile(name, []byte(testData), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = ensureFileContains(name, testData)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(name)
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAbandon(t *testing.T) {
|
||||||
|
name := tempFileName(2)
|
||||||
|
f, err := safefileCreate(name, 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Abandon failed: %s", err)
|
||||||
|
}
|
||||||
|
// Make sure temporary file doesn't exist.
|
||||||
|
_, err = os.Stat(f.Name())
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoubleCommit(t *testing.T) {
|
||||||
|
name := tempFileName(3)
|
||||||
|
f, err := safefileCreate(name, 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = f.Commit()
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(name)
|
||||||
|
t.Fatalf("First commit failed: %s", err)
|
||||||
|
}
|
||||||
|
err = f.Commit()
|
||||||
|
if err != errAlreadyCommitted {
|
||||||
|
os.Remove(name)
|
||||||
|
t.Fatalf("Second commit didn't fail: %s", err)
|
||||||
|
}
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(name)
|
||||||
|
t.Fatalf("Close failed: %s", err)
|
||||||
|
}
|
||||||
|
os.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverwriting(t *testing.T) {
|
||||||
|
name := tempFileName(4)
|
||||||
|
defer os.Remove(name)
|
||||||
|
|
||||||
|
olddata := "This is old data"
|
||||||
|
err := ioutil.WriteFile(name, []byte(olddata), 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newdata := "This is new data"
|
||||||
|
err = safefileWriteFile(name, []byte(newdata), 0600)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ensureFileContains(name, newdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,6 +87,7 @@ func (root *assetTree) WriteAsGoMap(w io.Writer) error {
|
||||||
Func func() (*asset, error)
|
Func func() (*asset, error)
|
||||||
Children map[string]*bintree
|
Children map[string]*bintree
|
||||||
}
|
}
|
||||||
|
|
||||||
var _bintree = `)
|
var _bintree = `)
|
||||||
root.writeGoMap(w, 0)
|
root.writeGoMap(w, 0)
|
||||||
return err
|
return err
|
||||||
|
@ -102,15 +103,15 @@ func writeTOCTree(w io.Writer, toc []Asset) error {
|
||||||
// img/
|
// img/
|
||||||
// a.png
|
// a.png
|
||||||
// b.png
|
// b.png
|
||||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||||
// AssetDir("") will return []string{"data"}.
|
// AssetDir("") will return []string{"data"}.
|
||||||
func AssetDir(name string) ([]string, error) {
|
func AssetDir(name string) ([]string, error) {
|
||||||
node := _bintree
|
node := _bintree
|
||||||
if len(name) != 0 {
|
if len(name) != 0 {
|
||||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
pathList := strings.Split(cannonicalName, "/")
|
pathList := strings.Split(canonicalName, "/")
|
||||||
for _, p := range pathList {
|
for _, p := range pathList {
|
||||||
node = node.Children[p]
|
node = node.Children[p]
|
||||||
if node == nil {
|
if node == nil {
|
||||||
|
@ -148,6 +149,10 @@ func writeTOC(w io.Writer, toc []Asset) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range toc {
|
for i := range toc {
|
||||||
|
if i != 0 {
|
||||||
|
// Newlines between elements make gofmt happy.
|
||||||
|
w.Write([]byte{'\n'})
|
||||||
|
}
|
||||||
err = writeTOCAsset(w, &toc[i])
|
err = writeTOCAsset(w, &toc[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -163,8 +168,8 @@ func writeTOCHeader(w io.Writer) error {
|
||||||
// It returns an error if the asset could not be found or
|
// It returns an error if the asset could not be found or
|
||||||
// could not be loaded.
|
// could not be loaded.
|
||||||
func Asset(name string) ([]byte, error) {
|
func Asset(name string) ([]byte, error) {
|
||||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
if f, ok := _bindata[cannonicalName]; ok {
|
if f, ok := _bindata[canonicalName]; ok {
|
||||||
a, err := f()
|
a, err := f()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Asset %%s can't read by error: %%v", name, err)
|
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)
|
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.
|
// MustAsset is like Asset but panics when Asset would return an error.
|
||||||
// It simplifies safe initialization of global variables.
|
// It simplifies safe initialization of global variables.
|
||||||
func MustAsset(name string) []byte {
|
func MustAsset(name string) []byte {
|
||||||
|
@ -185,12 +196,18 @@ func MustAsset(name string) []byte {
|
||||||
return a
|
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.
|
// AssetInfo loads and returns the asset info for the given name.
|
||||||
// It returns an error if the asset could not be found or
|
// It returns an error if the asset could not be found or
|
||||||
// could not be loaded.
|
// could not be loaded.
|
||||||
func AssetInfo(name string) (os.FileInfo, error) {
|
func AssetInfo(name string) (os.FileInfo, error) {
|
||||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
canonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
if f, ok := _bindata[cannonicalName]; ok {
|
if f, ok := _bindata[canonicalName]; ok {
|
||||||
a, err := f()
|
a, err := f()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("AssetInfo %%s can't read by error: %%v", name, err)
|
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)
|
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.
|
// AssetNames returns the names of the assets.
|
||||||
func AssetNames() []string {
|
func AssetNames() []string {
|
||||||
names := make([]string, 0, len(_bindata))
|
names := make([]string, 0, len(_bindata))
|
|
@ -1,11 +1,10 @@
|
||||||
language: go
|
language: go
|
||||||
go_import_path: github.com/pkg/errors
|
go_import_path: github.com/pkg/errors
|
||||||
go:
|
go:
|
||||||
- 1.4.x
|
- 1.4.3
|
||||||
- 1.5.x
|
- 1.5.4
|
||||||
- 1.6.x
|
- 1.6.2
|
||||||
- 1.7.x
|
- 1.7.1
|
||||||
- 1.8.x
|
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
|
|
@ -15,7 +15,6 @@ func noErrors(at, depth int) error {
|
||||||
}
|
}
|
||||||
return noErrors(at+1, depth)
|
return noErrors(at+1, depth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func yesErrors(at, depth int) error {
|
func yesErrors(at, depth int) error {
|
||||||
if at >= depth {
|
if at >= depth {
|
||||||
return New("ye error")
|
return New("ye error")
|
||||||
|
@ -23,11 +22,8 @@ func yesErrors(at, depth int) error {
|
||||||
return yesErrors(at+1, depth)
|
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) {
|
func BenchmarkErrors(b *testing.B) {
|
||||||
|
var toperr error
|
||||||
type run struct {
|
type run struct {
|
||||||
stack int
|
stack int
|
||||||
std bool
|
std bool
|
||||||
|
@ -57,7 +53,7 @@ func BenchmarkErrors(b *testing.B) {
|
||||||
err = f(0, r.stack)
|
err = f(0, r.stack)
|
||||||
}
|
}
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
GlobalE = err
|
toperr = err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ func TestWithMessage(t *testing.T) {
|
||||||
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
|
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
|
// errors.New, etc values are not expected to be compared by value
|
||||||
|
|
|
@ -6,9 +6,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -174,10 +172,12 @@ func (srv *Server) ListenTLS(certFile, keyFile string) (net.Listener, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
config.Certificates = make([]tls.Certificate, 1)
|
if certFile != "" && keyFile != "" {
|
||||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
config.Certificates = make([]tls.Certificate, 1)
|
||||||
if err != nil {
|
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable http2
|
// Enable http2
|
||||||
|
@ -299,7 +299,7 @@ func (srv *Server) Serve(listener net.Listener) error {
|
||||||
interrupt := srv.interruptChan()
|
interrupt := srv.interruptChan()
|
||||||
// Set up the interrupt handler
|
// Set up the interrupt handler
|
||||||
if !srv.NoSignalHandling {
|
if !srv.NoSignalHandling {
|
||||||
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
signalNotify(interrupt)
|
||||||
}
|
}
|
||||||
quitting := make(chan struct{})
|
quitting := make(chan struct{})
|
||||||
go srv.handleInterrupt(interrupt, quitting, listener)
|
go srv.handleInterrupt(interrupt, quitting, listener)
|
||||||
|
@ -336,8 +336,7 @@ func (srv *Server) Stop(timeout time.Duration) {
|
||||||
defer srv.stopLock.Unlock()
|
defer srv.stopLock.Unlock()
|
||||||
|
|
||||||
srv.Timeout = timeout
|
srv.Timeout = timeout
|
||||||
interrupt := srv.interruptChan()
|
sendSignalInt(srv.interruptChan())
|
||||||
interrupt <- syscall.SIGINT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopChan gets the stop channel which will block until
|
// 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 {
|
select {
|
||||||
case conn := <-add:
|
case conn := <-add:
|
||||||
srv.connections[conn] = struct{}{}
|
srv.connections[conn] = struct{}{}
|
||||||
|
srv.idleConnections[conn] = struct{}{} // Newly-added connections are considered idle until they become active.
|
||||||
case conn := <-idle:
|
case conn := <-idle:
|
||||||
srv.idleConnections[conn] = struct{}{}
|
srv.idleConnections[conn] = struct{}{}
|
||||||
case conn := <-active:
|
case conn := <-active:
|
||||||
|
@ -458,6 +458,8 @@ func (srv *Server) shutdown(shutdown chan chan struct{}, kill chan struct{}) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
shutdown <- done
|
shutdown <- done
|
||||||
|
|
||||||
|
srv.stopLock.Lock()
|
||||||
|
defer srv.stopLock.Unlock()
|
||||||
if srv.Timeout > 0 {
|
if srv.Timeout > 0 {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
//+build !appengine
|
||||||
|
|
||||||
|
package graceful
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func signalNotify(interrupt chan<- os.Signal) {
|
||||||
|
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSignalInt(interrupt chan<- os.Signal) {
|
||||||
|
interrupt <- syscall.SIGINT
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
//+build appengine
|
||||||
|
|
||||||
|
package graceful
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func signalNotify(interrupt chan<- os.Signal) {
|
||||||
|
// Does not notify in the case of AppEngine.
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSignalInt(interrupt chan<- os.Signal) {
|
||||||
|
// Does not send in the case of AppEngine.
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
This repository holds supplemental Go packages for low-level interactions with the operating system.
|
|
||||||
|
|
||||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# sys
|
||||||
|
|
||||||
|
This repository holds supplemental Go packages for low-level interactions with
|
||||||
|
the operating system.
|
||||||
|
|
||||||
|
## Download/Install
|
||||||
|
|
||||||
|
The easiest way to install is to run `go get -u golang.org/x/sys`. You can
|
||||||
|
also manually git clone the repository to `$GOPATH/src/golang.org/x/sys`.
|
||||||
|
|
||||||
|
## Report Issues / Send Patches
|
||||||
|
|
||||||
|
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||||
|
this repository, see https://golang.org/doc/contribute.html.
|
||||||
|
|
||||||
|
The main issue tracker for the sys repository is located at
|
||||||
|
https://github.com/golang/go/issues. Prefix your issue with "x/sys:" in the
|
||||||
|
subject line, so it is easy to find.
|
|
@ -1 +1,2 @@
|
||||||
_obj/
|
_obj/
|
||||||
|
unix.test
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
# Building `sys/unix`
|
||||||
|
|
||||||
|
The sys/unix package provides access to the raw system call interface of the
|
||||||
|
underlying operating system. See: https://godoc.org/golang.org/x/sys/unix
|
||||||
|
|
||||||
|
Porting Go to a new architecture/OS combination or adding syscalls, types, or
|
||||||
|
constants to an existing architecture/OS pair requires some manual effort;
|
||||||
|
however, there are tools that automate much of the process.
|
||||||
|
|
||||||
|
## Build Systems
|
||||||
|
|
||||||
|
There are currently two ways we generate the necessary files. We are currently
|
||||||
|
migrating the build system to use containers so the builds are reproducible.
|
||||||
|
This is being done on an OS-by-OS basis. Please update this documentation as
|
||||||
|
components of the build system change.
|
||||||
|
|
||||||
|
### Old Build System (currently for `GOOS != "Linux" || GOARCH == "sparc64"`)
|
||||||
|
|
||||||
|
The old build system generates the Go files based on the C header files
|
||||||
|
present on your system. This means that files
|
||||||
|
for a given GOOS/GOARCH pair must be generated on a system with that OS and
|
||||||
|
architecture. This also means that the generated code can differ from system
|
||||||
|
to system, based on differences in the header files.
|
||||||
|
|
||||||
|
To avoid this, if you are using the old build system, only generate the Go
|
||||||
|
files on an installation with unmodified header files. It is also important to
|
||||||
|
keep track of which version of the OS the files were generated from (ex.
|
||||||
|
Darwin 14 vs Darwin 15). This makes it easier to track the progress of changes
|
||||||
|
and have each OS upgrade correspond to a single change.
|
||||||
|
|
||||||
|
To build the files for your current OS and architecture, make sure GOOS and
|
||||||
|
GOARCH are set correctly and run `mkall.sh`. This will generate the files for
|
||||||
|
your specific system. Running `mkall.sh -n` shows the commands that will be run.
|
||||||
|
|
||||||
|
Requirements: bash, perl, go
|
||||||
|
|
||||||
|
### New Build System (currently for `GOOS == "Linux" && GOARCH != "sparc64"`)
|
||||||
|
|
||||||
|
The new build system uses a Docker container to generate the go files directly
|
||||||
|
from source checkouts of the kernel and various system libraries. This means
|
||||||
|
that on any platform that supports Docker, all the files using the new build
|
||||||
|
system can be generated at once, and generated files will not change based on
|
||||||
|
what the person running the scripts has installed on their computer.
|
||||||
|
|
||||||
|
The OS specific files for the new build system are located in the `${GOOS}`
|
||||||
|
directory, and the build is coordinated by the `${GOOS}/mkall.go` program. When
|
||||||
|
the kernel or system library updates, modify the Dockerfile at
|
||||||
|
`${GOOS}/Dockerfile` to checkout the new release of the source.
|
||||||
|
|
||||||
|
To build all the files under the new build system, you must be on an amd64/Linux
|
||||||
|
system and have your GOOS and GOARCH set accordingly. Running `mkall.sh` will
|
||||||
|
then generate all of the files for all of the GOOS/GOARCH pairs in the new build
|
||||||
|
system. Running `mkall.sh -n` shows the commands that will be run.
|
||||||
|
|
||||||
|
Requirements: bash, perl, go, docker
|
||||||
|
|
||||||
|
## Component files
|
||||||
|
|
||||||
|
This section describes the various files used in the code generation process.
|
||||||
|
It also contains instructions on how to modify these files to add a new
|
||||||
|
architecture/OS or to add additional syscalls, types, or constants. Note that
|
||||||
|
if you are using the new build system, the scripts cannot be called normally.
|
||||||
|
They must be called from within the docker container.
|
||||||
|
|
||||||
|
### asm files
|
||||||
|
|
||||||
|
The hand-written assembly file at `asm_${GOOS}_${GOARCH}.s` implements system
|
||||||
|
call dispatch. There are three entry points:
|
||||||
|
```
|
||||||
|
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||||
|
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
|
||||||
|
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||||
|
```
|
||||||
|
The first and second are the standard ones; they differ only in how many
|
||||||
|
arguments can be passed to the kernel. The third is for low-level use by the
|
||||||
|
ForkExec wrapper. Unlike the first two, it does not call into the scheduler to
|
||||||
|
let it know that a system call is running.
|
||||||
|
|
||||||
|
When porting Go to an new architecture/OS, this file must be implemented for
|
||||||
|
each GOOS/GOARCH pair.
|
||||||
|
|
||||||
|
### mksysnum
|
||||||
|
|
||||||
|
Mksysnum is a script located at `${GOOS}/mksysnum.pl` (or `mksysnum_${GOOS}.pl`
|
||||||
|
for the old system). This script takes in a list of header files containing the
|
||||||
|
syscall number declarations and parses them to produce the corresponding list of
|
||||||
|
Go numeric constants. See `zsysnum_${GOOS}_${GOARCH}.go` for the generated
|
||||||
|
constants.
|
||||||
|
|
||||||
|
Adding new syscall numbers is mostly done by running the build on a sufficiently
|
||||||
|
new installation of the target OS (or updating the source checkouts for the
|
||||||
|
new build system). However, depending on the OS, you make need to update the
|
||||||
|
parsing in mksysnum.
|
||||||
|
|
||||||
|
### mksyscall.pl
|
||||||
|
|
||||||
|
The `syscall.go`, `syscall_${GOOS}.go`, `syscall_${GOOS}_${GOARCH}.go` are
|
||||||
|
hand-written Go files which implement system calls (for unix, the specific OS,
|
||||||
|
or the specific OS/Architecture pair respectively) that need special handling
|
||||||
|
and list `//sys` comments giving prototypes for ones that can be generated.
|
||||||
|
|
||||||
|
The mksyscall.pl script takes the `//sys` and `//sysnb` comments and converts
|
||||||
|
them into syscalls. This requires the name of the prototype in the comment to
|
||||||
|
match a syscall number in the `zsysnum_${GOOS}_${GOARCH}.go` file. The function
|
||||||
|
prototype can be exported (capitalized) or not.
|
||||||
|
|
||||||
|
Adding a new syscall often just requires adding a new `//sys` function prototype
|
||||||
|
with the desired arguments and a capitalized name so it is exported. However, if
|
||||||
|
you want the interface to the syscall to be different, often one will make an
|
||||||
|
unexported `//sys` prototype, an then write a custom wrapper in
|
||||||
|
`syscall_${GOOS}.go`.
|
||||||
|
|
||||||
|
### types files
|
||||||
|
|
||||||
|
For each OS, there is a hand-written Go file at `${GOOS}/types.go` (or
|
||||||
|
`types_${GOOS}.go` on the old system). This file includes standard C headers and
|
||||||
|
creates Go type aliases to the corresponding C types. The file is then fed
|
||||||
|
through godef to get the Go compatible definitions. Finally, the generated code
|
||||||
|
is fed though mkpost.go to format the code correctly and remove any hidden or
|
||||||
|
private identifiers. This cleaned-up code is written to
|
||||||
|
`ztypes_${GOOS}_${GOARCH}.go`.
|
||||||
|
|
||||||
|
The hardest part about preparing this file is figuring out which headers to
|
||||||
|
include and which symbols need to be `#define`d to get the actual data
|
||||||
|
structures that pass through to the kernel system calls. Some C libraries
|
||||||
|
preset alternate versions for binary compatibility and translate them on the
|
||||||
|
way in and out of system calls, but there is almost always a `#define` that can
|
||||||
|
get the real ones.
|
||||||
|
See `types_darwin.go` and `linux/types.go` for examples.
|
||||||
|
|
||||||
|
To add a new type, add in the necessary include statement at the top of the
|
||||||
|
file (if it is not already there) and add in a type alias line. Note that if
|
||||||
|
your type is significantly different on different architectures, you may need
|
||||||
|
some `#if/#elif` macros in your include statements.
|
||||||
|
|
||||||
|
### mkerrors.sh
|
||||||
|
|
||||||
|
This script is used to generate the system's various constants. This doesn't
|
||||||
|
just include the error numbers and error strings, but also the signal numbers
|
||||||
|
an a wide variety of miscellaneous constants. The constants come from the list
|
||||||
|
of include files in the `includes_${uname}` variable. A regex then picks out
|
||||||
|
the desired `#define` statements, and generates the corresponding Go constants.
|
||||||
|
The error numbers and strings are generated from `#include <errno.h>`, and the
|
||||||
|
signal numbers and strings are generated from `#include <signal.h>`. All of
|
||||||
|
these constants are written to `zerrors_${GOOS}_${GOARCH}.go` via a C program,
|
||||||
|
`_errors.c`, which prints out all the constants.
|
||||||
|
|
||||||
|
To add a constant, add the header that includes it to the appropriate variable.
|
||||||
|
Then, edit the regex (if necessary) to match the desired constant. Avoid making
|
||||||
|
the regex too broad to avoid matching unintended constants.
|
||||||
|
|
||||||
|
|
||||||
|
## Generated files
|
||||||
|
|
||||||
|
### `zerror_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A file containing all of the system's generated error numbers, error strings,
|
||||||
|
signal numbers, and constants. Generated by `mkerrors.sh` (see above).
|
||||||
|
|
||||||
|
### `zsyscall_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A file containing all the generated syscalls for a specific GOOS and GOARCH.
|
||||||
|
Generated by `mksyscall.pl` (see above).
|
||||||
|
|
||||||
|
### `zsysnum_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A list of numeric constants for all the syscall number of the specific GOOS
|
||||||
|
and GOARCH. Generated by mksysnum (see above).
|
||||||
|
|
||||||
|
### `ztypes_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A file containing Go types for passing into (or returning from) syscalls.
|
||||||
|
Generated by godefs and the types file (see above).
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// CPU affinity functions
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS
|
||||||
|
|
||||||
|
// CPUSet represents a CPU affinity mask.
|
||||||
|
type CPUSet [cpuSetSize]cpuMask
|
||||||
|
|
||||||
|
func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
|
||||||
|
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set)))
|
||||||
|
if e != 0 {
|
||||||
|
return errnoErr(e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
|
||||||
|
// If pid is 0 the calling thread is used.
|
||||||
|
func SchedGetaffinity(pid int, set *CPUSet) error {
|
||||||
|
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
|
||||||
|
// If pid is 0 the calling thread is used.
|
||||||
|
func SchedSetaffinity(pid int, set *CPUSet) error {
|
||||||
|
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero clears the set s, so that it contains no CPUs.
|
||||||
|
func (s *CPUSet) Zero() {
|
||||||
|
for i := range s {
|
||||||
|
s[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuBitsIndex(cpu int) int {
|
||||||
|
return cpu / _NCPUBITS
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuBitsMask(cpu int) cpuMask {
|
||||||
|
return cpuMask(1 << (uint(cpu) % _NCPUBITS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set adds cpu to the set s.
|
||||||
|
func (s *CPUSet) Set(cpu int) {
|
||||||
|
i := cpuBitsIndex(cpu)
|
||||||
|
if i < len(s) {
|
||||||
|
s[i] |= cpuBitsMask(cpu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes cpu from the set s.
|
||||||
|
func (s *CPUSet) Clear(cpu int) {
|
||||||
|
i := cpuBitsIndex(cpu)
|
||||||
|
if i < len(s) {
|
||||||
|
s[i] &^= cpuBitsMask(cpu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet reports whether cpu is in the set s.
|
||||||
|
func (s *CPUSet) IsSet(cpu int) bool {
|
||||||
|
i := cpuBitsIndex(cpu)
|
||||||
|
if i < len(s) {
|
||||||
|
return s[i]&cpuBitsMask(cpu) != 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of CPUs in the set s.
|
||||||
|
func (s *CPUSet) Count() int {
|
||||||
|
c := 0
|
||||||
|
for _, b := range s {
|
||||||
|
c += onesCount64(uint64(b))
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// onesCount64 is a copy of Go 1.9's math/bits.OnesCount64.
|
||||||
|
// Once this package can require Go 1.9, we can delete this
|
||||||
|
// and update the caller to use bits.OnesCount64.
|
||||||
|
func onesCount64(x uint64) int {
|
||||||
|
const m0 = 0x5555555555555555 // 01010101 ...
|
||||||
|
const m1 = 0x3333333333333333 // 00110011 ...
|
||||||
|
const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ...
|
||||||
|
const m3 = 0x00ff00ff00ff00ff // etc.
|
||||||
|
const m4 = 0x0000ffff0000ffff
|
||||||
|
|
||||||
|
// Implementation: Parallel summing of adjacent bits.
|
||||||
|
// See "Hacker's Delight", Chap. 5: Counting Bits.
|
||||||
|
// The following pattern shows the general approach:
|
||||||
|
//
|
||||||
|
// x = x>>1&(m0&m) + x&(m0&m)
|
||||||
|
// x = x>>2&(m1&m) + x&(m1&m)
|
||||||
|
// x = x>>4&(m2&m) + x&(m2&m)
|
||||||
|
// x = x>>8&(m3&m) + x&(m3&m)
|
||||||
|
// x = x>>16&(m4&m) + x&(m4&m)
|
||||||
|
// x = x>>32&(m5&m) + x&(m5&m)
|
||||||
|
// return int(x)
|
||||||
|
//
|
||||||
|
// Masking (& operations) can be left away when there's no
|
||||||
|
// danger that a field's sum will carry over into the next
|
||||||
|
// field: Since the result cannot be > 64, 8 bits is enough
|
||||||
|
// and we can ignore the masks for the shifts by 8 and up.
|
||||||
|
// Per "Hacker's Delight", the first line can be simplified
|
||||||
|
// more, but it saves at best one instruction, so we leave
|
||||||
|
// it alone for clarity.
|
||||||
|
const m = 1<<64 - 1
|
||||||
|
x = x>>1&(m0&m) + x&(m0&m)
|
||||||
|
x = x>>2&(m1&m) + x&(m1&m)
|
||||||
|
x = (x>>4 + x) & (m2 & m)
|
||||||
|
x += x >> 8
|
||||||
|
x += x >> 16
|
||||||
|
x += x >> 32
|
||||||
|
return int(x) & (1<<7 - 1)
|
||||||
|
}
|
|
@ -10,21 +10,51 @@
|
||||||
// System calls for 386, Linux
|
// 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.
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
// The runtime may know about them.
|
// The runtime may know about them.
|
||||||
|
|
||||||
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||||
JMP syscall·Syscall(SB)
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||||
JMP syscall·Syscall6(SB)
|
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
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
JMP syscall·RawSyscall(SB)
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
JMP syscall·RawSyscall6(SB)
|
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
|
TEXT ·socketcall(SB),NOSPLIT,$0-36
|
||||||
JMP syscall·socketcall(SB)
|
JMP syscall·socketcall(SB)
|
||||||
|
|
||||||
|
|
|
@ -13,17 +13,45 @@
|
||||||
// Just jump to package syscall's implementation for all these functions.
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
// The runtime may know about them.
|
// The runtime may know about them.
|
||||||
|
|
||||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
JMP syscall·Syscall(SB)
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
JMP syscall·Syscall6(SB)
|
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
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
JMP syscall·RawSyscall(SB)
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
JMP syscall·RawSyscall6(SB)
|
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
|
TEXT ·gettimeofday(SB),NOSPLIT,$0-16
|
||||||
JMP syscall·gettimeofday(SB)
|
JMP syscall·gettimeofday(SB)
|
||||||
|
|
|
@ -13,17 +13,44 @@
|
||||||
// Just jump to package syscall's implementation for all these functions.
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
// The runtime may know about them.
|
// The runtime may know about them.
|
||||||
|
|
||||||
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||||
B syscall·Syscall(SB)
|
B syscall·Syscall(SB)
|
||||||
|
|
||||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||||
B syscall·Syscall6(SB)
|
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
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
B syscall·RawSyscall(SB)
|
B syscall·RawSyscall(SB)
|
||||||
|
|
||||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
B syscall·RawSyscall6(SB)
|
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)
|
B syscall·seek(SB)
|
||||||
|
|
|
@ -11,14 +11,42 @@
|
||||||
// Just jump to package syscall's implementation for all these functions.
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
// The runtime may know about them.
|
// The runtime may know about them.
|
||||||
|
|
||||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
B syscall·Syscall(SB)
|
B syscall·Syscall(SB)
|
||||||
|
|
||||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
B syscall·Syscall6(SB)
|
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
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
B syscall·RawSyscall(SB)
|
B syscall·RawSyscall(SB)
|
||||||
|
|
||||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
B syscall·RawSyscall6(SB)
|
B syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
MOVD a1+8(FP), R0
|
||||||
|
MOVD a2+16(FP), R1
|
||||||
|
MOVD a3+24(FP), R2
|
||||||
|
MOVD $0, R3
|
||||||
|
MOVD $0, R4
|
||||||
|
MOVD $0, R5
|
||||||
|
MOVD trap+0(FP), R8 // syscall entry
|
||||||
|
SVC
|
||||||
|
MOVD R0, r1+32(FP)
|
||||||
|
MOVD R1, r2+40(FP)
|
||||||
|
RET
|
||||||
|
|
|
@ -15,14 +15,42 @@
|
||||||
// Just jump to package syscall's implementation for all these functions.
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
// The runtime may know about them.
|
// The runtime may know about them.
|
||||||
|
|
||||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
JMP syscall·Syscall(SB)
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
JMP syscall·Syscall6(SB)
|
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)
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
JMP syscall·RawSyscall6(SB)
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
MOVV a1+8(FP), R4
|
||||||
|
MOVV a2+16(FP), R5
|
||||||
|
MOVV a3+24(FP), R6
|
||||||
|
MOVV R0, R7
|
||||||
|
MOVV R0, R8
|
||||||
|
MOVV R0, R9
|
||||||
|
MOVV trap+0(FP), R2 // syscall entry
|
||||||
|
SYSCALL
|
||||||
|
MOVV R2, r1+32(FP)
|
||||||
|
MOVV R3, r2+40(FP)
|
||||||
|
RET
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue