linkerd2/cli/cmd/test_helper.go

220 lines
4.5 KiB
Go

package cmd
import (
"fmt"
"reflect"
"strings"
"sigs.k8s.io/yaml"
)
type (
manifest = map[string]interface{}
diff struct {
path []string
a interface{}
b interface{}
}
)
func splitManifests(manifest string) []string {
manifests := strings.Split(manifest, "\n---\n")
filtered := []string{}
for _, m := range manifests {
if !isManifestEmpty(m) {
filtered = append(filtered, m)
}
}
return filtered
}
func isManifestEmpty(manifest string) bool {
lines := strings.Split(manifest, "\n")
for _, line := range lines {
if line == "" || strings.HasPrefix(line, "#") || line == "---" {
continue
}
return false
}
return true
}
func manifestKey(m manifest) string {
kind := m["kind"].(string)
meta := m["metadata"].(map[string]interface{})
name := meta["name"].(string)
return fmt.Sprintf("%s/%s", kind, name)
}
func (d diff) String() string {
expected, _ := yaml.Marshal(d.a)
actual, _ := yaml.Marshal(d.b)
return fmt.Sprintf("Diff at [%s]:\nExpected:\n%s\nActual:\n%s", d.path, string(expected), string(actual))
}
func parseManifestList(in string) map[string]manifest {
manifestList := splitManifests(in)
manifestMap := map[string]manifest{}
for _, m := range manifestList {
manifest := manifest{}
yaml.Unmarshal([]byte(m), &manifest)
manifestMap[manifestKey(manifest)] = manifest
}
return manifestMap
}
func diffManifest(a manifest, b manifest, path []string) []diff {
diffs := []diff{}
for k, v := range a {
bv, bvExists := b[k]
switch val := v.(type) {
case manifest:
if !bvExists {
diffs = append(diffs, diff{
path: extend(path, k),
a: val,
b: nil,
})
} else {
bvm, ok := bv.(manifest)
if !ok {
diffs = append(diffs, diff{
path: extend(path, k),
a: val,
b: bv,
})
} else {
diffs = append(diffs, diffManifest(val, bvm, extend(path, k))...)
}
}
case []interface{}:
bva, ok := bv.([]interface{})
if !ok {
diffs = append(diffs, diff{
path: extend(path, k),
a: val,
b: bv,
})
} else if len(val) != len(bva) {
diffs = append(diffs, diff{
path: extend(path, k),
a: val,
b: bva,
})
} else {
diffs = append(diffs, diffArray(val, bva, extend(path, k))...)
}
default:
if !bvExists {
diffs = append(diffs, diff{
path: extend(path, k),
a: val,
b: nil,
})
} else {
if !reflect.DeepEqual(val, bv) {
diffs = append(diffs, diff{
path: extend(path, k),
a: val,
b: bv,
})
}
}
}
}
for k, v := range b {
_, avExists := a[k]
if !avExists {
diffs = append(diffs, diff{
path: extend(path, k),
a: nil,
b: v,
})
}
}
return diffs
}
func diffArray(a, b []interface{}, path []string) []diff {
diffs := []diff{}
for i, v := range a {
switch aVal := v.(type) {
case manifest:
bm, ok := b[i].(manifest)
if !ok {
diffs = append(diffs, diff{
path: extend(path, fmt.Sprintf("%d", i)),
a: aVal,
b: b[i],
})
} else {
diffs = append(diffs, diffManifest(aVal, bm, extend(path, fmt.Sprintf("%d", i)))...)
}
case []interface{}:
ba, ok := b[i].([]interface{})
if !ok {
diffs = append(diffs, diff{
path: extend(path, fmt.Sprintf("%d", i)),
a: aVal,
b: b[i],
})
} else if len(aVal) != len(ba) {
diffs = append(diffs, diff{
path: extend(path, fmt.Sprintf("%d", i)),
a: aVal,
b: b[i],
})
} else {
diffs = append(diffs, diffArray(aVal, ba, extend(path, fmt.Sprintf("%d", i)))...)
}
default:
if !reflect.DeepEqual(v, b[i]) {
diffs = append(diffs, diff{
path: extend(path, fmt.Sprintf("%d", i)),
a: v,
b: b[i],
})
}
}
}
return diffs
}
func diffManifestLists(a map[string]manifest, b map[string]manifest) map[string][]diff {
diffs := map[string][]diff{}
for k, am := range a {
bm, ok := b[k]
if !ok {
diffs[k] = []diff{{
a: am,
b: nil,
path: []string{},
}}
} else {
diffs[k] = diffManifest(am, bm, []string{})
}
}
for k, bm := range b {
_, ok := a[k]
if !ok {
diffs[k] = []diff{{
a: nil,
b: bm,
path: []string{},
}}
}
}
return diffs
}
// extend returns a new slice which is a copy of slice with next appended to it.
// The advantage of using extend instead of append is that modifying the
// returned slice will not modify the original.
func extend(slice []string, next string) []string {
new := make([]string, len(slice)+1)
copy(new, slice)
new[len(slice)] = next
return new
}