tests: Use the require/assert package and replace cmp.Diff

This commit is contained in:
Alexander Todorov 2020-04-10 08:27:58 -04:00 committed by Tom Gundersen
parent 3740e9bc6b
commit 869bb2afd7
9 changed files with 60 additions and 824 deletions

View file

@ -6,7 +6,8 @@ import (
"io/ioutil"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro"
@ -22,9 +23,8 @@ import (
func TestDistro_Manifest(t *testing.T) {
pipelinePath := "../../test/cases/"
fileInfos, err := ioutil.ReadDir(pipelinePath)
if err != nil {
t.Errorf("Could not read pipelines directory '%s': %v", pipelinePath, err)
}
assert.NoErrorf(t, err, "Could not read pipelines directory '%s'", pipelinePath)
for _, fileInfo := range fileInfos {
type repository struct {
BaseURL string `json:"baseurl,omitempty"`
@ -49,13 +49,11 @@ func TestDistro_Manifest(t *testing.T) {
Manifest *osbuild.Manifest `json:"manifest,omitempty"`
}
file, err := ioutil.ReadFile(pipelinePath + fileInfo.Name())
if err != nil {
t.Errorf("Could not read test-case '%s': %v", fileInfo.Name(), err)
}
assert.NoErrorf(t, err, "Could not read test-case '%s'", fileInfo.Name())
err = json.Unmarshal([]byte(file), &tt)
if err != nil {
t.Errorf("Could not parse test-case '%s': %v", fileInfo.Name(), err)
}
assert.NoErrorf(t, err, "Could not parse test-case '%s'", fileInfo.Name())
if tt.ComposeRequest == nil || tt.ComposeRequest.Blueprint == nil {
t.Logf("Skipping '%s'.", fileInfo.Name())
continue
@ -77,20 +75,14 @@ func TestDistro_Manifest(t *testing.T) {
t.Fatal(err)
}
d := distros.GetDistro(tt.ComposeRequest.Distro)
if d == nil {
t.Errorf("unknown distro: %v", tt.ComposeRequest.Distro)
return
}
require.NotNilf(t, d, "unknown distro: %v", tt.ComposeRequest.Distro)
arch, err := d.GetArch(tt.ComposeRequest.Arch)
if err != nil {
t.Errorf("unknown arch: %v", tt.ComposeRequest.Arch)
return
}
require.NoErrorf(t, err, "unknown arch: %v", tt.ComposeRequest.Arch)
imageType, err := arch.GetImageType(tt.ComposeRequest.ImageType)
if err != nil {
t.Errorf("unknown image type: %v", tt.ComposeRequest.ImageType)
return
}
require.NoError(t, err, "unknown image type: %v", tt.ComposeRequest.ImageType)
got, err := imageType.Manifest(tt.ComposeRequest.Blueprint.Customizations,
repos,
tt.RpmMD.Packages,
@ -101,9 +93,7 @@ func TestDistro_Manifest(t *testing.T) {
return
}
if tt.Manifest != nil {
if diff := cmp.Diff(got, tt.Manifest); diff != "" {
t.Errorf("d.Manifest() different from expected: %v", diff)
}
assert.Equalf(t, tt.Manifest, got, "d.Manifest() different from expected")
}
})
}
@ -120,11 +110,7 @@ func TestDistro_RegistryList(t *testing.T) {
}
distros, err := distro.NewRegistry(fedora30.New(), fedora31.New(), fedora32.New(), rhel81.New(), rhel82.New())
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
if diff := cmp.Diff(distros.List(), expected); diff != "" {
t.Errorf("unexpected list of distros: %v", diff)
}
require.Equalf(t, expected, distros.List(), "unexpected list of distros")
}

View file

@ -11,12 +11,13 @@ import (
"os"
"os/exec"
"path"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type API interface {
@ -105,66 +106,41 @@ func TestRoute(t *testing.T, api API, external bool, method, path, body string,
t.Skip("This test is for internal testing only")
}
if resp.StatusCode != expectedStatus {
t.Errorf("%s: expected status %v, but got %v", path, expectedStatus, resp.StatusCode)
}
assert.Equalf(t, expectedStatus, resp.StatusCode, "SendHTTP failed for path %s", path)
replyJSON, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("%s: could not read response body: %v", path, err)
return
}
require.NoErrorf(t, err, "%s: could not read response body", path)
if expectedJSON == "" {
if len(replyJSON) != 0 {
t.Errorf("%s: expected no response body, but got:\n%s", path, replyJSON)
}
return
require.Lenf(t, replyJSON, 0, "%s: expected no response body, but got:\n%s", path, replyJSON)
}
var reply, expected interface{}
err = json.Unmarshal(replyJSON, &reply)
if err != nil {
t.Errorf("%s: %v\n%s", path, err, string(replyJSON))
return
}
require.NoErrorf(t, err, "%s: json.Unmarshal failed for\n%s", path, string(replyJSON))
if expectedJSON == "*" {
return
}
err = json.Unmarshal([]byte(expectedJSON), &expected)
if err != nil {
t.Errorf("%s: expected JSON is invalid: %v", path, err)
return
}
require.NoErrorf(t, err, "%s: expected JSON is invalid", path)
dropFields(reply, ignoreFields...)
dropFields(expected, ignoreFields...)
if diff := cmp.Diff(expected, reply); diff != "" {
t.Errorf("%s: reply != expected:\n reply: %s\nexpected: %s\ndiff: %s", path, strings.TrimSpace(string(replyJSON)), expectedJSON, diff)
return
}
require.Equal(t, expected, reply)
}
func TestNonJsonRoute(t *testing.T, api API, external bool, method, path, body string, expectedStatus int, expectedResponse string) {
response := SendHTTP(api, external, method, path, body)
if response.StatusCode != expectedStatus {
t.Errorf("%s: expected status %v, but got %v", path, expectedStatus, response.StatusCode)
}
assert.Equalf(t, expectedStatus, response.StatusCode, "%s: status mismatch", path)
responseBodyBytes, err := ioutil.ReadAll(response.Body)
if err != nil {
t.Errorf("%s: could not read response body: %v", path, err)
}
require.NoErrorf(t, err, "%s: could not read response body", path)
responseBody := string(responseBodyBytes)
if responseBody != expectedResponse {
t.Errorf("%s: expected response \"%s\", but got \"%s\"", path, expectedResponse, responseBody)
}
require.Equalf(t, expectedResponse, responseBody, "%s: body mismatch", path)
}
func IgnoreDates() cmp.Option {

View file

@ -25,7 +25,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require"
)
func createWeldrAPI(fixtureGenerator rpmmd_mock.FixtureGenerator) (*API, *store.Store) {
@ -106,9 +106,7 @@ version = "2.4.*"`
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusOK {
t.Fatalf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusOK, r.StatusCode)
}
func TestBlueprintsEmptyToml(t *testing.T) {
@ -120,9 +118,7 @@ func TestBlueprintsEmptyToml(t *testing.T) {
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusBadRequest {
t.Fatalf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsInvalidToml(t *testing.T) {
@ -143,9 +139,7 @@ version = "2.4.*"`
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusBadRequest {
t.Fatalf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsWorkspaceJSON(t *testing.T) {
@ -185,9 +179,7 @@ version = "2.4.*"`
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusOK {
t.Fatalf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusOK, r.StatusCode)
}
func TestBlueprintsWorkspaceEmptyTOML(t *testing.T) {
@ -199,9 +191,7 @@ func TestBlueprintsWorkspaceEmptyTOML(t *testing.T) {
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusBadRequest {
t.Fatalf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsWorkspaceInvalidTOML(t *testing.T) {
@ -222,9 +212,7 @@ version = "2.4.*"`
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusBadRequest {
t.Fatalf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
func TestBlueprintsInfo(t *testing.T) {
@ -262,15 +250,11 @@ func TestBlueprintsInfoToml(t *testing.T) {
api.ServeHTTP(recorder, req)
resp := recorder.Result()
if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status %v", resp.StatusCode)
}
require.Equal(t, http.StatusOK, resp.StatusCode)
var got blueprint.Blueprint
_, err := toml.DecodeReader(resp.Body, &got)
if err != nil {
t.Fatalf("error decoding toml file: %v", err)
}
require.NoErrorf(t, err, "error decoding toml file")
expected := blueprint.Blueprint{
Name: "test1",
@ -282,9 +266,7 @@ func TestBlueprintsInfoToml(t *testing.T) {
Groups: []blueprint.Group{},
Modules: []blueprint.Package{},
}
if diff := cmp.Diff(got, expected); diff != "" {
t.Fatalf("received unexpected blueprint: %s", diff)
}
require.Equalf(t, expected, got, "received unexpected blueprint")
}
func TestNonExistentBlueprintsInfoToml(t *testing.T) {
@ -294,9 +276,7 @@ func TestNonExistentBlueprintsInfoToml(t *testing.T) {
api.ServeHTTP(recorder, req)
resp := recorder.Result()
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("unexpected status %v", resp.StatusCode)
}
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
func TestSetPkgEVRA(t *testing.T) {
@ -331,15 +311,9 @@ func TestSetPkgEVRA(t *testing.T) {
}
// Replace globs with dependencies
err := setPkgEVRA(deps, pkgs)
if err != nil {
t.Fatalf("setPkgEVRA failed: %s", err.Error())
}
if pkgs[0].Version != "1.33-2.fc30.x86_64" {
t.Fatalf("setPkgEVRA Unexpected pkg version")
}
if pkgs[1].Version != "2.9-1.fc30.x86_64" {
t.Fatalf("setPkgEVRA Unexpected pkg version")
}
require.NoErrorf(t, err, "setPkgEVRA failed")
require.Equalf(t, "1.33-2.fc30.x86_64", pkgs[0].Version, "setPkgEVRA Unexpected pkg version")
require.Equalf(t, "2.9-1.fc30.x86_64", pkgs[1].Version, "setPkgEVRA Unexpected pkg version")
// Test that a missing package in deps returns an error
pkgs = []blueprint.Package{
@ -347,9 +321,7 @@ func TestSetPkgEVRA(t *testing.T) {
{Name: "dep-package0", Version: "*"},
}
err = setPkgEVRA(deps, pkgs)
if err == nil || err.Error() != "dep-package0 missing from depsolve results" {
t.Fatalf("setPkgEVRA missing package failed to return error")
}
require.EqualErrorf(t, err, "dep-package0 missing from depsolve results", "setPkgEVRA missing package failed to return error")
}
func TestBlueprintsFreeze(t *testing.T) {
@ -437,9 +409,7 @@ func TestGetPkgNameGlob(t *testing.T) {
for _, c := range cases {
result := getPkgNameGlob(c.pkg)
if result != c.result {
t.Fatalf("getPkgNameGlob failed: %s != %s", result, c.result)
}
require.Equalf(t, c.result, result, "getPkgNameGlob failed for %s", c.pkg.Name)
}
}
@ -550,9 +520,7 @@ func TestCompose(t *testing.T) {
continue
}
if len(s.Composes) != 1 {
t.Fatalf("%s: bad compose count in store: %d", c.Path, len(s.Composes))
}
require.Equalf(t, 1, len(s.Composes), "%s: bad compose count in store", c.Path)
// I have no idea how to get the compose in better way
var composeStruct compose.Compose
@ -561,12 +529,9 @@ func TestCompose(t *testing.T) {
break
}
if composeStruct.ImageBuilds[0].Manifest == nil {
t.Fatalf("%s: the compose in the store did not contain a blueprint", c.Path)
} else {
// TODO: find some (reasonable) way to verify the contents of the pipeline
composeStruct.ImageBuilds[0].Manifest = nil
}
require.NotNilf(t, composeStruct.ImageBuilds[0].Manifest, "%s: the compose in the store did not contain a blueprint", c.Path)
// TODO: find some (reasonable) way to verify the contents of the pipeline
composeStruct.ImageBuilds[0].Manifest = nil
if diff := cmp.Diff(composeStruct, *c.ExpectedCompose, test.IgnoreDates(), test.IgnoreUuids(), test.Ignore("Targets.Options.Location")); diff != "" {
t.Errorf("%s: compose in store isn't the same as expected, diff:\n%s", c.Path, diff)
@ -602,11 +567,7 @@ func TestComposeDelete(t *testing.T) {
idsInStore = append(idsInStore, id.String())
}
diff := cmp.Diff(idsInStore, c.ExpectedIDsInStore, cmpopts.SortSlices(func(a, b string) bool { return a < b }))
if diff != "" {
t.Errorf("%s: composes in store are different, expected: %v, got: %v, diff:\n%s", c.Path, c.ExpectedIDsInStore, idsInStore, diff)
}
require.ElementsMatch(t, c.ExpectedIDsInStore, idsInStore, "%s: composes in store are different", c.Path)
}
}
@ -682,39 +643,21 @@ func TestComposeLogs(t *testing.T) {
api, _ := createWeldrAPI(rpmmd_mock.BaseFixture)
response := test.SendHTTP(api, false, "GET", c.Path, "")
if response.StatusCode != http.StatusOK {
t.Errorf("%s: expected status code: %d, but got: %d", c.Path, 200, response.StatusCode)
}
if response.Header.Get("content-disposition") != c.ExpectedContentDisposition {
t.Errorf("%s: expected content-disposition: %s, but got: %s", c.Path, c.ExpectedContentDisposition, response.Header.Get("content-disposition"))
}
if response.Header.Get("content-type") != c.ExpectedContentType {
t.Errorf("%s: expected content-type: %s, but got: %s", c.Path, c.ExpectedContentType, response.Header.Get("content-type"))
}
require.Equalf(t, http.StatusOK, response.StatusCode, "%s: unexpected status code", c.Path)
require.Equalf(t, c.ExpectedContentDisposition, response.Header.Get("content-disposition"), "%s: header mismatch", c.Path)
require.Equalf(t, c.ExpectedContentType, response.Header.Get("content-type"), "%s: header mismatch", c.Path)
tr := tar.NewReader(response.Body)
h, err := tr.Next()
if err != nil {
t.Errorf("untarring failed with error: %s", err.Error())
}
if h.Name != c.ExpectedFileName {
t.Errorf("%s: expected log content: %s, but got: %s", c.Path, c.ExpectedFileName, h.Name)
}
require.NoErrorf(t, err, "untarring failed with error")
require.Equalf(t, c.ExpectedFileName, h.Name, "%s: unexpected file name", c.Path)
var buffer bytes.Buffer
_, err = io.Copy(&buffer, tr)
if err != nil {
t.Errorf("cannot copy untar result: %v", err)
}
if buffer.String() != c.ExpectedFileContent {
t.Errorf("%s: expected log content: %s, but got: %s", c.Path, c.ExpectedFileContent, buffer.String())
}
require.NoErrorf(t, err, "cannot copy untar result")
require.Equalf(t, c.ExpectedFileContent, buffer.String(), "%s: unexpected log content", c.Path)
}
var failureCases = []struct {
@ -871,9 +814,7 @@ check_gpg = false
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusOK {
t.Fatalf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusOK, r.StatusCode)
test.SendHTTP(api, true, "DELETE", "/api/v0/projects/source/delete/fish", ``)
}
@ -897,9 +838,7 @@ check_gpg = false
api.ServeHTTP(recorder, req)
r := recorder.Result()
if r.StatusCode != http.StatusBadRequest {
t.Errorf("unexpected status %v", r.StatusCode)
}
require.Equal(t, http.StatusBadRequest, r.StatusCode)
}
}
@ -926,9 +865,7 @@ func TestSourcesInfoToml(t *testing.T) {
var sources map[string]store.SourceConfig
_, err := toml.DecodeReader(resp.Body, &sources)
if err != nil {
t.Fatalf("error decoding toml file: %v", err)
}
require.NoErrorf(t, err, "error decoding toml file")
expected := map[string]store.SourceConfig{
"fish": {
@ -938,9 +875,7 @@ func TestSourcesInfoToml(t *testing.T) {
},
}
if diff := cmp.Diff(sources, expected); diff != "" {
t.Fatalf("received unexpected source: %s", diff)
}
require.Equal(t, expected, sources)
}
func TestSourcesDelete(t *testing.T) {

View file

@ -1,89 +0,0 @@
// 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 cmpopts provides common options for the cmp package.
package cmpopts
import (
"math"
"reflect"
"github.com/google/go-cmp/cmp"
)
func equateAlways(_, _ interface{}) bool { return true }
// EquateEmpty returns a Comparer option that determines all maps and slices
// with a length of zero to be equal, regardless of whether they are nil.
//
// EquateEmpty can be used in conjunction with SortSlices and SortMaps.
func EquateEmpty() cmp.Option {
return cmp.FilterValues(isEmpty, cmp.Comparer(equateAlways))
}
func isEmpty(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
return (x != nil && y != nil && vx.Type() == vy.Type()) &&
(vx.Kind() == reflect.Slice || vx.Kind() == reflect.Map) &&
(vx.Len() == 0 && vy.Len() == 0)
}
// EquateApprox returns a Comparer option that determines float32 or float64
// values to be equal if they are within a relative fraction or absolute margin.
// This option is not used when either x or y is NaN or infinite.
//
// The fraction determines that the difference of two values must be within the
// smaller fraction of the two values, while the margin determines that the two
// values must be within some absolute margin.
// To express only a fraction or only a margin, use 0 for the other parameter.
// The fraction and margin must be non-negative.
//
// The mathematical expression used is equivalent to:
// |x-y| ≤ max(fraction*min(|x|, |y|), margin)
//
// EquateApprox can be used in conjunction with EquateNaNs.
func EquateApprox(fraction, margin float64) cmp.Option {
if margin < 0 || fraction < 0 || math.IsNaN(margin) || math.IsNaN(fraction) {
panic("margin or fraction must be a non-negative number")
}
a := approximator{fraction, margin}
return cmp.Options{
cmp.FilterValues(areRealF64s, cmp.Comparer(a.compareF64)),
cmp.FilterValues(areRealF32s, cmp.Comparer(a.compareF32)),
}
}
type approximator struct{ frac, marg float64 }
func areRealF64s(x, y float64) bool {
return !math.IsNaN(x) && !math.IsNaN(y) && !math.IsInf(x, 0) && !math.IsInf(y, 0)
}
func areRealF32s(x, y float32) bool {
return areRealF64s(float64(x), float64(y))
}
func (a approximator) compareF64(x, y float64) bool {
relMarg := a.frac * math.Min(math.Abs(x), math.Abs(y))
return math.Abs(x-y) <= math.Max(a.marg, relMarg)
}
func (a approximator) compareF32(x, y float32) bool {
return a.compareF64(float64(x), float64(y))
}
// EquateNaNs returns a Comparer option that determines float32 and float64
// NaN values to be equal.
//
// EquateNaNs can be used in conjunction with EquateApprox.
func EquateNaNs() cmp.Option {
return cmp.Options{
cmp.FilterValues(areNaNsF64s, cmp.Comparer(equateAlways)),
cmp.FilterValues(areNaNsF32s, cmp.Comparer(equateAlways)),
}
}
func areNaNsF64s(x, y float64) bool {
return math.IsNaN(x) && math.IsNaN(y)
}
func areNaNsF32s(x, y float32) bool {
return areNaNsF64s(float64(x), float64(y))
}

View file

@ -1,207 +0,0 @@
// 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 cmpopts
import (
"fmt"
"reflect"
"unicode"
"unicode/utf8"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/internal/function"
)
// IgnoreFields returns an Option that ignores exported fields of the
// given names on a single struct type.
// The struct type is specified by passing in a value of that type.
//
// The name may be a dot-delimited string (e.g., "Foo.Bar") to ignore a
// specific sub-field that is embedded or nested within the parent struct.
//
// This does not handle unexported fields; use IgnoreUnexported instead.
func IgnoreFields(typ interface{}, names ...string) cmp.Option {
sf := newStructFilter(typ, names...)
return cmp.FilterPath(sf.filter, cmp.Ignore())
}
// IgnoreTypes returns an Option that ignores all values assignable to
// certain types, which are specified by passing in a value of each type.
func IgnoreTypes(typs ...interface{}) cmp.Option {
tf := newTypeFilter(typs...)
return cmp.FilterPath(tf.filter, cmp.Ignore())
}
type typeFilter []reflect.Type
func newTypeFilter(typs ...interface{}) (tf typeFilter) {
for _, typ := range typs {
t := reflect.TypeOf(typ)
if t == nil {
// This occurs if someone tries to pass in sync.Locker(nil)
panic("cannot determine type; consider using IgnoreInterfaces")
}
tf = append(tf, t)
}
return tf
}
func (tf typeFilter) filter(p cmp.Path) bool {
if len(p) < 1 {
return false
}
t := p.Last().Type()
for _, ti := range tf {
if t.AssignableTo(ti) {
return true
}
}
return false
}
// IgnoreInterfaces returns an Option that ignores all values or references of
// values assignable to certain interface types. These interfaces are specified
// by passing in an anonymous struct with the interface types embedded in it.
// For example, to ignore sync.Locker, pass in struct{sync.Locker}{}.
func IgnoreInterfaces(ifaces interface{}) cmp.Option {
tf := newIfaceFilter(ifaces)
return cmp.FilterPath(tf.filter, cmp.Ignore())
}
type ifaceFilter []reflect.Type
func newIfaceFilter(ifaces interface{}) (tf ifaceFilter) {
t := reflect.TypeOf(ifaces)
if ifaces == nil || t.Name() != "" || t.Kind() != reflect.Struct {
panic("input must be an anonymous struct")
}
for i := 0; i < t.NumField(); i++ {
fi := t.Field(i)
switch {
case !fi.Anonymous:
panic("struct cannot have named fields")
case fi.Type.Kind() != reflect.Interface:
panic("embedded field must be an interface type")
case fi.Type.NumMethod() == 0:
// This matches everything; why would you ever want this?
panic("cannot ignore empty interface")
default:
tf = append(tf, fi.Type)
}
}
return tf
}
func (tf ifaceFilter) filter(p cmp.Path) bool {
if len(p) < 1 {
return false
}
t := p.Last().Type()
for _, ti := range tf {
if t.AssignableTo(ti) {
return true
}
if t.Kind() != reflect.Ptr && reflect.PtrTo(t).AssignableTo(ti) {
return true
}
}
return false
}
// IgnoreUnexported returns an Option that only ignores the immediate unexported
// fields of a struct, including anonymous fields of unexported types.
// In particular, unexported fields within the struct's exported fields
// of struct types, including anonymous fields, will not be ignored unless the
// type of the field itself is also passed to IgnoreUnexported.
//
// Avoid ignoring unexported fields of a type which you do not control (i.e. a
// type from another repository), as changes to the implementation of such types
// may change how the comparison behaves. Prefer a custom Comparer instead.
func IgnoreUnexported(typs ...interface{}) cmp.Option {
ux := newUnexportedFilter(typs...)
return cmp.FilterPath(ux.filter, cmp.Ignore())
}
type unexportedFilter struct{ m map[reflect.Type]bool }
func newUnexportedFilter(typs ...interface{}) unexportedFilter {
ux := unexportedFilter{m: make(map[reflect.Type]bool)}
for _, typ := range typs {
t := reflect.TypeOf(typ)
if t == nil || t.Kind() != reflect.Struct {
panic(fmt.Sprintf("invalid struct type: %T", typ))
}
ux.m[t] = true
}
return ux
}
func (xf unexportedFilter) filter(p cmp.Path) bool {
sf, ok := p.Index(-1).(cmp.StructField)
if !ok {
return false
}
return xf.m[p.Index(-2).Type()] && !isExported(sf.Name())
}
// isExported reports whether the identifier is exported.
func isExported(id string) bool {
r, _ := utf8.DecodeRuneInString(id)
return unicode.IsUpper(r)
}
// IgnoreSliceElements returns an Option that ignores elements of []V.
// The discard function must be of the form "func(T) bool" which is used to
// ignore slice elements of type V, where V is assignable to T.
// Elements are ignored if the function reports true.
func IgnoreSliceElements(discardFunc interface{}) cmp.Option {
vf := reflect.ValueOf(discardFunc)
if !function.IsType(vf.Type(), function.ValuePredicate) || vf.IsNil() {
panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
}
return cmp.FilterPath(func(p cmp.Path) bool {
si, ok := p.Index(-1).(cmp.SliceIndex)
if !ok {
return false
}
if !si.Type().AssignableTo(vf.Type().In(0)) {
return false
}
vx, vy := si.Values()
if vx.IsValid() && vf.Call([]reflect.Value{vx})[0].Bool() {
return true
}
if vy.IsValid() && vf.Call([]reflect.Value{vy})[0].Bool() {
return true
}
return false
}, cmp.Ignore())
}
// IgnoreMapEntries returns an Option that ignores entries of map[K]V.
// The discard function must be of the form "func(T, R) bool" which is used to
// ignore map entries of type K and V, where K and V are assignable to T and R.
// Entries are ignored if the function reports true.
func IgnoreMapEntries(discardFunc interface{}) cmp.Option {
vf := reflect.ValueOf(discardFunc)
if !function.IsType(vf.Type(), function.KeyValuePredicate) || vf.IsNil() {
panic(fmt.Sprintf("invalid discard function: %T", discardFunc))
}
return cmp.FilterPath(func(p cmp.Path) bool {
mi, ok := p.Index(-1).(cmp.MapIndex)
if !ok {
return false
}
if !mi.Key().Type().AssignableTo(vf.Type().In(0)) || !mi.Type().AssignableTo(vf.Type().In(1)) {
return false
}
k := mi.Key()
vx, vy := mi.Values()
if vx.IsValid() && vf.Call([]reflect.Value{k, vx})[0].Bool() {
return true
}
if vy.IsValid() && vf.Call([]reflect.Value{k, vy})[0].Bool() {
return true
}
return false
}, cmp.Ignore())
}

View file

@ -1,147 +0,0 @@
// 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 cmpopts
import (
"fmt"
"reflect"
"sort"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/internal/function"
)
// SortSlices returns a Transformer option that sorts all []V.
// The less function must be of the form "func(T, T) bool" which is used to
// sort any slice with element type V that is assignable to T.
//
// The less function must be:
// • Deterministic: less(x, y) == less(x, y)
// • Irreflexive: !less(x, x)
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
//
// The less function does not have to be "total". That is, if !less(x, y) and
// !less(y, x) for two elements x and y, their relative order is maintained.
//
// SortSlices can be used in conjunction with EquateEmpty.
func SortSlices(lessFunc interface{}) cmp.Option {
vf := reflect.ValueOf(lessFunc)
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
panic(fmt.Sprintf("invalid less function: %T", lessFunc))
}
ss := sliceSorter{vf.Type().In(0), vf}
return cmp.FilterValues(ss.filter, cmp.Transformer("cmpopts.SortSlices", ss.sort))
}
type sliceSorter struct {
in reflect.Type // T
fnc reflect.Value // func(T, T) bool
}
func (ss sliceSorter) filter(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
if !(x != nil && y != nil && vx.Type() == vy.Type()) ||
!(vx.Kind() == reflect.Slice && vx.Type().Elem().AssignableTo(ss.in)) ||
(vx.Len() <= 1 && vy.Len() <= 1) {
return false
}
// Check whether the slices are already sorted to avoid an infinite
// recursion cycle applying the same transform to itself.
ok1 := sort.SliceIsSorted(x, func(i, j int) bool { return ss.less(vx, i, j) })
ok2 := sort.SliceIsSorted(y, func(i, j int) bool { return ss.less(vy, i, j) })
return !ok1 || !ok2
}
func (ss sliceSorter) sort(x interface{}) interface{} {
src := reflect.ValueOf(x)
dst := reflect.MakeSlice(src.Type(), src.Len(), src.Len())
for i := 0; i < src.Len(); i++ {
dst.Index(i).Set(src.Index(i))
}
sort.SliceStable(dst.Interface(), func(i, j int) bool { return ss.less(dst, i, j) })
ss.checkSort(dst)
return dst.Interface()
}
func (ss sliceSorter) checkSort(v reflect.Value) {
start := -1 // Start of a sequence of equal elements.
for i := 1; i < v.Len(); i++ {
if ss.less(v, i-1, i) {
// Check that first and last elements in v[start:i] are equal.
if start >= 0 && (ss.less(v, start, i-1) || ss.less(v, i-1, start)) {
panic(fmt.Sprintf("incomparable values detected: want equal elements: %v", v.Slice(start, i)))
}
start = -1
} else if start == -1 {
start = i
}
}
}
func (ss sliceSorter) less(v reflect.Value, i, j int) bool {
vx, vy := v.Index(i), v.Index(j)
return ss.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
}
// SortMaps returns a Transformer option that flattens map[K]V types to be a
// sorted []struct{K, V}. The less function must be of the form
// "func(T, T) bool" which is used to sort any map with key K that is
// assignable to T.
//
// Flattening the map into a slice has the property that cmp.Equal is able to
// use Comparers on K or the K.Equal method if it exists.
//
// The less function must be:
// • Deterministic: less(x, y) == less(x, y)
// • Irreflexive: !less(x, x)
// • Transitive: if !less(x, y) and !less(y, z), then !less(x, z)
// • Total: if x != y, then either less(x, y) or less(y, x)
//
// SortMaps can be used in conjunction with EquateEmpty.
func SortMaps(lessFunc interface{}) cmp.Option {
vf := reflect.ValueOf(lessFunc)
if !function.IsType(vf.Type(), function.Less) || vf.IsNil() {
panic(fmt.Sprintf("invalid less function: %T", lessFunc))
}
ms := mapSorter{vf.Type().In(0), vf}
return cmp.FilterValues(ms.filter, cmp.Transformer("cmpopts.SortMaps", ms.sort))
}
type mapSorter struct {
in reflect.Type // T
fnc reflect.Value // func(T, T) bool
}
func (ms mapSorter) filter(x, y interface{}) bool {
vx, vy := reflect.ValueOf(x), reflect.ValueOf(y)
return (x != nil && y != nil && vx.Type() == vy.Type()) &&
(vx.Kind() == reflect.Map && vx.Type().Key().AssignableTo(ms.in)) &&
(vx.Len() != 0 || vy.Len() != 0)
}
func (ms mapSorter) sort(x interface{}) interface{} {
src := reflect.ValueOf(x)
outType := reflect.StructOf([]reflect.StructField{
{Name: "K", Type: src.Type().Key()},
{Name: "V", Type: src.Type().Elem()},
})
dst := reflect.MakeSlice(reflect.SliceOf(outType), src.Len(), src.Len())
for i, k := range src.MapKeys() {
v := reflect.New(outType).Elem()
v.Field(0).Set(k)
v.Field(1).Set(src.MapIndex(k))
dst.Index(i).Set(v)
}
sort.Slice(dst.Interface(), func(i, j int) bool { return ms.less(dst, i, j) })
ms.checkSort(dst)
return dst.Interface()
}
func (ms mapSorter) checkSort(v reflect.Value) {
for i := 1; i < v.Len(); i++ {
if !ms.less(v, i-1, i) {
panic(fmt.Sprintf("partial order detected: want %v < %v", v.Index(i-1), v.Index(i)))
}
}
}
func (ms mapSorter) less(v reflect.Value, i, j int) bool {
vx, vy := v.Index(i).Field(0), v.Index(j).Field(0)
return ms.fnc.Call([]reflect.Value{vx, vy})[0].Bool()
}

View file

@ -1,182 +0,0 @@
// 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 cmpopts
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp"
)
// filterField returns a new Option where opt is only evaluated on paths that
// include a specific exported field on a single struct type.
// The struct type is specified by passing in a value of that type.
//
// The name may be a dot-delimited string (e.g., "Foo.Bar") to select a
// specific sub-field that is embedded or nested within the parent struct.
func filterField(typ interface{}, name string, opt cmp.Option) cmp.Option {
// TODO: This is currently unexported over concerns of how helper filters
// can be composed together easily.
// TODO: Add tests for FilterField.
sf := newStructFilter(typ, name)
return cmp.FilterPath(sf.filter, opt)
}
type structFilter struct {
t reflect.Type // The root struct type to match on
ft fieldTree // Tree of fields to match on
}
func newStructFilter(typ interface{}, names ...string) structFilter {
// TODO: Perhaps allow * as a special identifier to allow ignoring any
// number of path steps until the next field match?
// This could be useful when a concrete struct gets transformed into
// an anonymous struct where it is not possible to specify that by type,
// but the transformer happens to provide guarantees about the names of
// the transformed fields.
t := reflect.TypeOf(typ)
if t == nil || t.Kind() != reflect.Struct {
panic(fmt.Sprintf("%T must be a struct", typ))
}
var ft fieldTree
for _, name := range names {
cname, err := canonicalName(t, name)
if err != nil {
panic(fmt.Sprintf("%s: %v", strings.Join(cname, "."), err))
}
ft.insert(cname)
}
return structFilter{t, ft}
}
func (sf structFilter) filter(p cmp.Path) bool {
for i, ps := range p {
if ps.Type().AssignableTo(sf.t) && sf.ft.matchPrefix(p[i+1:]) {
return true
}
}
return false
}
// fieldTree represents a set of dot-separated identifiers.
//
// For example, inserting the following selectors:
// Foo
// Foo.Bar.Baz
// Foo.Buzz
// Nuka.Cola.Quantum
//
// Results in a tree of the form:
// {sub: {
// "Foo": {ok: true, sub: {
// "Bar": {sub: {
// "Baz": {ok: true},
// }},
// "Buzz": {ok: true},
// }},
// "Nuka": {sub: {
// "Cola": {sub: {
// "Quantum": {ok: true},
// }},
// }},
// }}
type fieldTree struct {
ok bool // Whether this is a specified node
sub map[string]fieldTree // The sub-tree of fields under this node
}
// insert inserts a sequence of field accesses into the tree.
func (ft *fieldTree) insert(cname []string) {
if ft.sub == nil {
ft.sub = make(map[string]fieldTree)
}
if len(cname) == 0 {
ft.ok = true
return
}
sub := ft.sub[cname[0]]
sub.insert(cname[1:])
ft.sub[cname[0]] = sub
}
// matchPrefix reports whether any selector in the fieldTree matches
// the start of path p.
func (ft fieldTree) matchPrefix(p cmp.Path) bool {
for _, ps := range p {
switch ps := ps.(type) {
case cmp.StructField:
ft = ft.sub[ps.Name()]
if ft.ok {
return true
}
if len(ft.sub) == 0 {
return false
}
case cmp.Indirect:
default:
return false
}
}
return false
}
// canonicalName returns a list of identifiers where any struct field access
// through an embedded field is expanded to include the names of the embedded
// types themselves.
//
// For example, suppose field "Foo" is not directly in the parent struct,
// but actually from an embedded struct of type "Bar". Then, the canonical name
// of "Foo" is actually "Bar.Foo".
//
// Suppose field "Foo" is not directly in the parent struct, but actually
// a field in two different embedded structs of types "Bar" and "Baz".
// Then the selector "Foo" causes a panic since it is ambiguous which one it
// refers to. The user must specify either "Bar.Foo" or "Baz.Foo".
func canonicalName(t reflect.Type, sel string) ([]string, error) {
var name string
sel = strings.TrimPrefix(sel, ".")
if sel == "" {
return nil, fmt.Errorf("name must not be empty")
}
if i := strings.IndexByte(sel, '.'); i < 0 {
name, sel = sel, ""
} else {
name, sel = sel[:i], sel[i:]
}
// Type must be a struct or pointer to struct.
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("%v must be a struct", t)
}
// Find the canonical name for this current field name.
// If the field exists in an embedded struct, then it will be expanded.
if !isExported(name) {
// Disallow unexported fields:
// * To discourage people from actually touching unexported fields
// * FieldByName is buggy (https://golang.org/issue/4876)
return []string{name}, fmt.Errorf("name must be exported")
}
sf, ok := t.FieldByName(name)
if !ok {
return []string{name}, fmt.Errorf("does not exist")
}
var ss []string
for i := range sf.Index {
ss = append(ss, t.FieldByIndex(sf.Index[:i+1]).Name)
}
if sel == "" {
return ss, nil
}
ssPost, err := canonicalName(sf.Type, sel)
return append(ss, ssPost...), err
}

View file

@ -1,35 +0,0 @@
// 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.md file.
package cmpopts
import (
"github.com/google/go-cmp/cmp"
)
type xformFilter struct{ xform cmp.Option }
func (xf xformFilter) filter(p cmp.Path) bool {
for _, ps := range p {
if t, ok := ps.(cmp.Transform); ok && t.Option() == xf.xform {
return false
}
}
return true
}
// AcyclicTransformer returns a Transformer with a filter applied that ensures
// that the transformer cannot be recursively applied upon its own output.
//
// An example use case is a transformer that splits a string by lines:
// AcyclicTransformer("SplitLines", func(s string) []string{
// return strings.Split(s, "\n")
// })
//
// Had this been an unfiltered Transformer instead, this would result in an
// infinite cycle converting a string to []string to [][]string and so on.
func AcyclicTransformer(name string, xformFunc interface{}) cmp.Option {
xf := xformFilter{cmp.Transformer(name, xformFunc)}
return cmp.FilterPath(xf.filter, xf.xform)
}

1
vendor/modules.txt vendored
View file

@ -67,7 +67,6 @@ github.com/google/go-cmp/cmp/internal/diff
github.com/google/go-cmp/cmp/internal/flags
github.com/google/go-cmp/cmp/internal/function
github.com/google/go-cmp/cmp/internal/value
github.com/google/go-cmp/cmp/cmpopts
# github.com/google/uuid v1.1.1
github.com/google/uuid
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af