Resultant acl (#4386)
This commit is contained in:
parent
4881a53eb0
commit
640b30ff7b
|
@ -1075,6 +1075,14 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
|
|||
HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-mounts"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["internal-ui-mounts"][1]),
|
||||
},
|
||||
&framework.Path{
|
||||
Pattern: "internal/ui/resultant-acl",
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
logical.ReadOperation: b.pathInternalUIResultantACL,
|
||||
},
|
||||
HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-resultant-acl"][0]),
|
||||
HelpDescription: strings.TrimSpace(sysHelp["internal-ui-resultant-acl"][1]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -3430,6 +3438,110 @@ func (b *SystemBackend) pathInternalUIMountsRead(ctx context.Context, req *logic
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
if req.ClientToken == "" {
|
||||
// 204 -- no ACL
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
acl, _, entity, err := b.Core.fetchACLTokenEntryAndEntity(req.ClientToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if entity != nil && entity.Disabled {
|
||||
return logical.ErrorResponse(logical.ErrEntityDisabled.Error()), nil
|
||||
}
|
||||
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"root": false,
|
||||
},
|
||||
}
|
||||
|
||||
if acl.root {
|
||||
resp.Data["root"] = true
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
exact := map[string]interface{}{}
|
||||
glob := map[string]interface{}{}
|
||||
|
||||
walkFn := func(pt map[string]interface{}, s string, v interface{}) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
perms := v.(*ACLPermissions)
|
||||
capabilities := []string{}
|
||||
|
||||
if perms.CapabilitiesBitmap&CreateCapabilityInt > 0 {
|
||||
capabilities = append(capabilities, CreateCapability)
|
||||
}
|
||||
if perms.CapabilitiesBitmap&DeleteCapabilityInt > 0 {
|
||||
capabilities = append(capabilities, DeleteCapability)
|
||||
}
|
||||
if perms.CapabilitiesBitmap&ListCapabilityInt > 0 {
|
||||
capabilities = append(capabilities, ListCapability)
|
||||
}
|
||||
if perms.CapabilitiesBitmap&ReadCapabilityInt > 0 {
|
||||
capabilities = append(capabilities, ReadCapability)
|
||||
}
|
||||
if perms.CapabilitiesBitmap&SudoCapabilityInt > 0 {
|
||||
capabilities = append(capabilities, SudoCapability)
|
||||
}
|
||||
if perms.CapabilitiesBitmap&UpdateCapabilityInt > 0 {
|
||||
capabilities = append(capabilities, UpdateCapability)
|
||||
}
|
||||
|
||||
// If "deny" is explicitly set or if the path has no capabilities at all,
|
||||
// set the path capabilities to "deny"
|
||||
if perms.CapabilitiesBitmap&DenyCapabilityInt > 0 || len(capabilities) == 0 {
|
||||
capabilities = []string{DenyCapability}
|
||||
}
|
||||
|
||||
res := map[string]interface{}{}
|
||||
if len(capabilities) > 0 {
|
||||
res["capabilities"] = capabilities
|
||||
}
|
||||
if perms.MinWrappingTTL != 0 {
|
||||
res["min_wrapping_ttl"] = int64(perms.MinWrappingTTL.Seconds())
|
||||
}
|
||||
if perms.MaxWrappingTTL != 0 {
|
||||
res["max_wrapping_ttl"] = int64(perms.MaxWrappingTTL.Seconds())
|
||||
}
|
||||
if len(perms.AllowedParameters) > 0 {
|
||||
res["allowed_parameters"] = perms.AllowedParameters
|
||||
}
|
||||
if len(perms.DeniedParameters) > 0 {
|
||||
res["denied_parameters"] = perms.DeniedParameters
|
||||
}
|
||||
if len(perms.RequiredParameters) > 0 {
|
||||
res["required_parameters"] = perms.RequiredParameters
|
||||
}
|
||||
|
||||
pt[s] = res
|
||||
}
|
||||
|
||||
exactWalkFn := func(s string, v interface{}) bool {
|
||||
walkFn(exact, s, v)
|
||||
return false
|
||||
}
|
||||
|
||||
globWalkFn := func(s string, v interface{}) bool {
|
||||
walkFn(glob, s, v)
|
||||
return false
|
||||
}
|
||||
|
||||
acl.exactRules.Walk(exactWalkFn)
|
||||
acl.globRules.Walk(globWalkFn)
|
||||
|
||||
resp.Data["exact_paths"] = exact
|
||||
resp.Data["glob_paths"] = glob
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func sanitizeMountPath(path string) string {
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
|
@ -4040,12 +4152,22 @@ This path responds to the following HTTP methods.
|
|||
},
|
||||
"listing_visibility": {
|
||||
"Determines the visibility of the mount in the UI-specific listing endpoint.",
|
||||
"",
|
||||
},
|
||||
"passthrough_request_headers": {
|
||||
"A list of headers to whitelist and pass from the request to the backend.",
|
||||
"",
|
||||
},
|
||||
"raw": {
|
||||
"Write, Read, and Delete data directly in the Storage backend.",
|
||||
"",
|
||||
},
|
||||
"internal-ui-mounts": {
|
||||
"Information about mounts returned according to their tuned visibility. Internal API; its location, inputs, and outputs may change.",
|
||||
"",
|
||||
},
|
||||
"internal-ui-resultant-acl": {
|
||||
"Information about a token's resultant ACL. Internal API; its location, inputs, and outputs may change.",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/builtin/plugin"
|
||||
"github.com/hashicorp/vault/helper/pluginutil"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
|
@ -604,3 +606,137 @@ func TestBackend_PluginMainCredentials(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemBackend_InternalUIResultantACL(t *testing.T) {
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
client := cluster.Cores[0].Client
|
||||
|
||||
resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{
|
||||
Policies: []string{"default"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("nil response")
|
||||
}
|
||||
if resp.Auth == nil {
|
||||
t.Fatal("nil auth")
|
||||
}
|
||||
if resp.Auth.ClientToken == "" {
|
||||
t.Fatal("empty client token")
|
||||
}
|
||||
|
||||
client.SetToken(resp.Auth.ClientToken)
|
||||
|
||||
resp, err = client.Logical().Read("sys/internal/ui/resultant-acl")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("nil response")
|
||||
}
|
||||
if resp.Data == nil {
|
||||
t.Fatal("nil data")
|
||||
}
|
||||
|
||||
exp := map[string]interface{}{
|
||||
"exact_paths": map[string]interface{}{
|
||||
"auth/token/lookup-self": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"read",
|
||||
},
|
||||
},
|
||||
"auth/token/renew-self": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"auth/token/revoke-self": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/capabilities-self": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/internal/ui/resultant-acl": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"read",
|
||||
},
|
||||
},
|
||||
"sys/leases/lookup": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/leases/renew": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/renew": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/tools/hash": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/tools/random": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/wrapping/lookup": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/wrapping/unwrap": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/wrapping/wrap": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
},
|
||||
"glob_paths": map[string]interface{}{
|
||||
"cubbyhole/": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"create",
|
||||
"delete",
|
||||
"list",
|
||||
"read",
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/tools/hash/": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
"sys/tools/random/": map[string]interface{}{
|
||||
"capabilities": []interface{}{
|
||||
"update",
|
||||
},
|
||||
},
|
||||
},
|
||||
"root": false,
|
||||
}
|
||||
|
||||
if diff := deep.Equal(resp.Data, exp); diff != nil {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,13 @@ path "sys/capabilities-self" {
|
|||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# Allow a token to look up its resultant ACL from all policies. This is useful
|
||||
# for UIs. It is an internal path because the format may change at any time
|
||||
# based on how the internal ACL features and capabilities change.
|
||||
path "sys/internal/ui/resultant-acl" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
# Allow a token to renew a lease via lease_id in the request body; old path for
|
||||
# old clients, new path for newer
|
||||
path "sys/renew" {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# go-test/deep Changelog
|
||||
|
||||
## v1.0.1 released 2018-01-28
|
||||
|
||||
* Fixed #12: Arrays are not properly compared (samlitowitz)
|
||||
|
||||
## v1.0.0 releaesd 2017-10-27
|
||||
|
||||
* First release
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright 2015-2017 Daniel Nichter
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,51 @@
|
|||
# Deep Variable Equality for Humans
|
||||
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/go-test/deep)](https://goreportcard.com/report/github.com/go-test/deep) [![Build Status](https://travis-ci.org/go-test/deep.svg?branch=master)](https://travis-ci.org/go-test/deep) [![Coverage Status](https://coveralls.io/repos/github/go-test/deep/badge.svg?branch=master)](https://coveralls.io/github/go-test/deep?branch=master) [![GoDoc](https://godoc.org/github.com/go-test/deep?status.svg)](https://godoc.org/github.com/go-test/deep)
|
||||
|
||||
This package provides a single function: `deep.Equal`. It's like [reflect.DeepEqual](http://golang.org/pkg/reflect/#DeepEqual) but much friendlier to humans (or any sentient being) for two reason:
|
||||
|
||||
* `deep.Equal` returns a list of differences
|
||||
* `deep.Equal` does not compare unexported fields (by default)
|
||||
|
||||
`reflect.DeepEqual` is good (like all things Golang!), but it's a game of [Hunt the Wumpus](https://en.wikipedia.org/wiki/Hunt_the_Wumpus). For large maps, slices, and structs, finding the difference is difficult.
|
||||
|
||||
`deep.Equal` doesn't play games with you, it lists the differences:
|
||||
|
||||
```go
|
||||
package main_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/go-test/deep"
|
||||
)
|
||||
|
||||
type T struct {
|
||||
Name string
|
||||
Numbers []float64
|
||||
}
|
||||
|
||||
func TestDeepEqual(t *testing.T) {
|
||||
// Can you spot the difference?
|
||||
t1 := T{
|
||||
Name: "Isabella",
|
||||
Numbers: []float64{1.13459, 2.29343, 3.010100010},
|
||||
}
|
||||
t2 := T{
|
||||
Name: "Isabella",
|
||||
Numbers: []float64{1.13459, 2.29843, 3.010100010},
|
||||
}
|
||||
|
||||
if diff := deep.Equal(t1, t2); diff != nil {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
$ go test
|
||||
--- FAIL: TestDeepEqual (0.00s)
|
||||
main_test.go:25: [Numbers.slice[1]: 2.29343 != 2.29843]
|
||||
```
|
||||
|
||||
The difference is in `Numbers.slice[1]`: the two values aren't equal using Go `==`.
|
|
@ -0,0 +1,352 @@
|
|||
// Package deep provides function deep.Equal which is like reflect.DeepEqual but
|
||||
// returns a list of differences. This is helpful when comparing complex types
|
||||
// like structures and maps.
|
||||
package deep
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// FloatPrecision is the number of decimal places to round float values
|
||||
// to when comparing.
|
||||
FloatPrecision = 10
|
||||
|
||||
// MaxDiff specifies the maximum number of differences to return.
|
||||
MaxDiff = 10
|
||||
|
||||
// MaxDepth specifies the maximum levels of a struct to recurse into.
|
||||
MaxDepth = 10
|
||||
|
||||
// LogErrors causes errors to be logged to STDERR when true.
|
||||
LogErrors = false
|
||||
|
||||
// CompareUnexportedFields causes unexported struct fields, like s in
|
||||
// T{s int}, to be comparsed when true.
|
||||
CompareUnexportedFields = false
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrMaxRecursion is logged when MaxDepth is reached.
|
||||
ErrMaxRecursion = errors.New("recursed to MaxDepth")
|
||||
|
||||
// ErrTypeMismatch is logged when Equal passed two different types of values.
|
||||
ErrTypeMismatch = errors.New("variables are different reflect.Type")
|
||||
|
||||
// ErrNotHandled is logged when a primitive Go kind is not handled.
|
||||
ErrNotHandled = errors.New("cannot compare the reflect.Kind")
|
||||
)
|
||||
|
||||
type cmp struct {
|
||||
diff []string
|
||||
buff []string
|
||||
floatFormat string
|
||||
}
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
// Equal compares variables a and b, recursing into their structure up to
|
||||
// MaxDepth levels deep, and returns a list of differences, or nil if there are
|
||||
// none. Some differences may not be found if an error is also returned.
|
||||
//
|
||||
// If a type has an Equal method, like time.Equal, it is called to check for
|
||||
// equality.
|
||||
func Equal(a, b interface{}) []string {
|
||||
aVal := reflect.ValueOf(a)
|
||||
bVal := reflect.ValueOf(b)
|
||||
c := &cmp{
|
||||
diff: []string{},
|
||||
buff: []string{},
|
||||
floatFormat: fmt.Sprintf("%%.%df", FloatPrecision),
|
||||
}
|
||||
if a == nil && b == nil {
|
||||
return nil
|
||||
} else if a == nil && b != nil {
|
||||
c.saveDiff(b, "<nil pointer>")
|
||||
} else if a != nil && b == nil {
|
||||
c.saveDiff(a, "<nil pointer>")
|
||||
}
|
||||
if len(c.diff) > 0 {
|
||||
return c.diff
|
||||
}
|
||||
|
||||
c.equals(aVal, bVal, 0)
|
||||
if len(c.diff) > 0 {
|
||||
return c.diff // diffs
|
||||
}
|
||||
return nil // no diffs
|
||||
}
|
||||
|
||||
func (c *cmp) equals(a, b reflect.Value, level int) {
|
||||
if level > MaxDepth {
|
||||
logError(ErrMaxRecursion)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if one value is nil, e.g. T{x: *X} and T.x is nil
|
||||
if !a.IsValid() || !b.IsValid() {
|
||||
if a.IsValid() && !b.IsValid() {
|
||||
c.saveDiff(a.Type(), "<nil pointer>")
|
||||
} else if !a.IsValid() && b.IsValid() {
|
||||
c.saveDiff("<nil pointer>", b.Type())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If differenet types, they can't be equal
|
||||
aType := a.Type()
|
||||
bType := b.Type()
|
||||
if aType != bType {
|
||||
c.saveDiff(aType, bType)
|
||||
logError(ErrTypeMismatch)
|
||||
return
|
||||
}
|
||||
|
||||
// Primitive https://golang.org/pkg/reflect/#Kind
|
||||
aKind := a.Kind()
|
||||
bKind := b.Kind()
|
||||
|
||||
// If both types implement the error interface, compare the error strings.
|
||||
// This must be done before dereferencing because the interface is on a
|
||||
// pointer receiver.
|
||||
if aType.Implements(errorType) && bType.Implements(errorType) {
|
||||
if a.Elem().IsValid() && b.Elem().IsValid() { // both err != nil
|
||||
aString := a.MethodByName("Error").Call(nil)[0].String()
|
||||
bString := b.MethodByName("Error").Call(nil)[0].String()
|
||||
if aString != bString {
|
||||
c.saveDiff(aString, bString)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Dereference pointers and interface{}
|
||||
if aElem, bElem := (aKind == reflect.Ptr || aKind == reflect.Interface),
|
||||
(bKind == reflect.Ptr || bKind == reflect.Interface); aElem || bElem {
|
||||
|
||||
if aElem {
|
||||
a = a.Elem()
|
||||
}
|
||||
|
||||
if bElem {
|
||||
b = b.Elem()
|
||||
}
|
||||
|
||||
c.equals(a, b, level+1)
|
||||
return
|
||||
}
|
||||
|
||||
// Types with an Equal(), like time.Time.
|
||||
eqFunc := a.MethodByName("Equal")
|
||||
if eqFunc.IsValid() {
|
||||
retVals := eqFunc.Call([]reflect.Value{b})
|
||||
if !retVals[0].Bool() {
|
||||
c.saveDiff(a, b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch aKind {
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Iterable kinds
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
case reflect.Struct:
|
||||
/*
|
||||
The variables are structs like:
|
||||
type T struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
Type = <pkg>.T, Kind = reflect.Struct
|
||||
|
||||
Iterate through the fields (FirstName, LastName), recurse into their values.
|
||||
*/
|
||||
for i := 0; i < a.NumField(); i++ {
|
||||
if aType.Field(i).PkgPath != "" && !CompareUnexportedFields {
|
||||
continue // skip unexported field, e.g. s in type T struct {s string}
|
||||
}
|
||||
|
||||
c.push(aType.Field(i).Name) // push field name to buff
|
||||
|
||||
// Get the Value for each field, e.g. FirstName has Type = string,
|
||||
// Kind = reflect.String.
|
||||
af := a.Field(i)
|
||||
bf := b.Field(i)
|
||||
|
||||
// Recurse to compare the field values
|
||||
c.equals(af, bf, level+1)
|
||||
|
||||
c.pop() // pop field name from buff
|
||||
|
||||
if len(c.diff) >= MaxDiff {
|
||||
break
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
/*
|
||||
The variables are maps like:
|
||||
map[string]int{
|
||||
"foo": 1,
|
||||
"bar": 2,
|
||||
}
|
||||
Type = map[string]int, Kind = reflect.Map
|
||||
|
||||
Or:
|
||||
type T map[string]int{}
|
||||
Type = <pkg>.T, Kind = reflect.Map
|
||||
|
||||
Iterate through the map keys (foo, bar), recurse into their values.
|
||||
*/
|
||||
|
||||
if a.IsNil() || b.IsNil() {
|
||||
if a.IsNil() && !b.IsNil() {
|
||||
c.saveDiff("<nil map>", b)
|
||||
} else if !a.IsNil() && b.IsNil() {
|
||||
c.saveDiff(a, "<nil map>")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if a.Pointer() == b.Pointer() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range a.MapKeys() {
|
||||
c.push(fmt.Sprintf("map[%s]", key))
|
||||
|
||||
aVal := a.MapIndex(key)
|
||||
bVal := b.MapIndex(key)
|
||||
if bVal.IsValid() {
|
||||
c.equals(aVal, bVal, level+1)
|
||||
} else {
|
||||
c.saveDiff(aVal, "<does not have key>")
|
||||
}
|
||||
|
||||
c.pop()
|
||||
|
||||
if len(c.diff) >= MaxDiff {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range b.MapKeys() {
|
||||
if aVal := a.MapIndex(key); aVal.IsValid() {
|
||||
continue
|
||||
}
|
||||
|
||||
c.push(fmt.Sprintf("map[%s]", key))
|
||||
c.saveDiff("<does not have key>", b.MapIndex(key))
|
||||
c.pop()
|
||||
if len(c.diff) >= MaxDiff {
|
||||
return
|
||||
}
|
||||
}
|
||||
case reflect.Array:
|
||||
n := a.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
c.push(fmt.Sprintf("array[%d]", i))
|
||||
c.equals(a.Index(i), b.Index(i), level+1)
|
||||
c.pop()
|
||||
if len(c.diff) >= MaxDiff {
|
||||
break
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
if a.IsNil() || b.IsNil() {
|
||||
if a.IsNil() && !b.IsNil() {
|
||||
c.saveDiff("<nil slice>", b)
|
||||
} else if !a.IsNil() && b.IsNil() {
|
||||
c.saveDiff(a, "<nil slice>")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if a.Pointer() == b.Pointer() {
|
||||
return
|
||||
}
|
||||
|
||||
aLen := a.Len()
|
||||
bLen := b.Len()
|
||||
n := aLen
|
||||
if bLen > aLen {
|
||||
n = bLen
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
c.push(fmt.Sprintf("slice[%d]", i))
|
||||
if i < aLen && i < bLen {
|
||||
c.equals(a.Index(i), b.Index(i), level+1)
|
||||
} else if i < aLen {
|
||||
c.saveDiff(a.Index(i), "<no value>")
|
||||
} else {
|
||||
c.saveDiff("<no value>", b.Index(i))
|
||||
}
|
||||
c.pop()
|
||||
if len(c.diff) >= MaxDiff {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Primitive kinds
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
// Avoid 0.04147685731961082 != 0.041476857319611
|
||||
// 6 decimal places is close enough
|
||||
aval := fmt.Sprintf(c.floatFormat, a.Float())
|
||||
bval := fmt.Sprintf(c.floatFormat, b.Float())
|
||||
if aval != bval {
|
||||
c.saveDiff(a.Float(), b.Float())
|
||||
}
|
||||
case reflect.Bool:
|
||||
if a.Bool() != b.Bool() {
|
||||
c.saveDiff(a.Bool(), b.Bool())
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if a.Int() != b.Int() {
|
||||
c.saveDiff(a.Int(), b.Int())
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if a.Uint() != b.Uint() {
|
||||
c.saveDiff(a.Uint(), b.Uint())
|
||||
}
|
||||
case reflect.String:
|
||||
if a.String() != b.String() {
|
||||
c.saveDiff(a.String(), b.String())
|
||||
}
|
||||
|
||||
default:
|
||||
logError(ErrNotHandled)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmp) push(name string) {
|
||||
c.buff = append(c.buff, name)
|
||||
}
|
||||
|
||||
func (c *cmp) pop() {
|
||||
if len(c.buff) > 0 {
|
||||
c.buff = c.buff[0 : len(c.buff)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmp) saveDiff(aval, bval interface{}) {
|
||||
if len(c.buff) > 0 {
|
||||
varName := strings.Join(c.buff, ".")
|
||||
c.diff = append(c.diff, fmt.Sprintf("%s: %v != %v", varName, aval, bval))
|
||||
} else {
|
||||
c.diff = append(c.diff, fmt.Sprintf("%v != %v", aval, bval))
|
||||
}
|
||||
}
|
||||
|
||||
func logError(err error) {
|
||||
if LogErrors {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
|
@ -924,6 +924,12 @@
|
|||
"revision": "bc14601d1bd56421dd60f561e6052c9ed77f9daf",
|
||||
"revisionTime": "2018-01-25T05:47:45Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "dni8Puy96l7QbQlJwLtrZvM5rxM=",
|
||||
"path": "github.com/go-test/deep",
|
||||
"revision": "6592d9cc0a499ad2d5f574fde80a2b5c5cc3b4f5",
|
||||
"revisionTime": "2018-01-28T22:55:04Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "989p8YX+h2GxOFWhUMvh1O/K3rc=",
|
||||
"path": "github.com/gocql/gocql",
|
||||
|
|
Loading…
Reference in New Issue