knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/kmp/reporters_test.go (about)

     1  /*
     2  Copyright 2019 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kmp
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/google/go-cmp/cmp/cmpopts"
    24  )
    25  
    26  type testStruct struct {
    27  	A           string `json:"a"`
    28  	StringField string `json:"foo"`
    29  	IntField    int
    30  	StructField childStruct `json:"child"`
    31  	Omit        string      `json:"omit,omitempty"`
    32  	Ignore      string      `json:"-"`
    33  	Dash        string      `json:"-,"`
    34  	//nolint:staticcheck // We actually want to test this broken json options work
    35  	MultiComma string `json:"multi,omitempty,somethingelse"`
    36  }
    37  
    38  type childStruct struct {
    39  	ChildString string
    40  	ChildInt    int
    41  }
    42  
    43  type privateStruct struct {
    44  	privateField string
    45  }
    46  
    47  func TestFieldListReporter(t *testing.T) {
    48  	tests := []struct {
    49  		name string
    50  		x    interface{}
    51  		y    interface{}
    52  		want []string
    53  		opts []cmp.Option
    54  	}{{
    55  		name: "No diff",
    56  		x: testStruct{
    57  			StringField: "foo",
    58  		},
    59  		y: testStruct{
    60  			StringField: "foo",
    61  		},
    62  		want: nil,
    63  	}, {
    64  		name: "Both nil objects",
    65  		want: nil,
    66  	}, {
    67  		name: "Nil second object",
    68  		x: testStruct{
    69  			StringField: "foo",
    70  		},
    71  		want: []string{"{any}"},
    72  	}, {
    73  		name: "Single character field name",
    74  		x: testStruct{
    75  			A: "foo",
    76  		},
    77  		y:    testStruct{},
    78  		want: []string{"a"},
    79  	}, {
    80  		name: "Single field",
    81  		x: testStruct{
    82  			StringField: "foo",
    83  		},
    84  		y: testStruct{
    85  			StringField: "bar",
    86  		},
    87  		want: []string{"foo"},
    88  	}, {
    89  		name: "Multi field",
    90  		x: testStruct{
    91  			StringField: "foo",
    92  			IntField:    5,
    93  		},
    94  		y: testStruct{
    95  			StringField: "bar",
    96  			IntField:    6,
    97  		},
    98  		want: []string{"IntField", "foo"},
    99  	}, {
   100  		name: "Missing field",
   101  		x: testStruct{
   102  			StringField: "test",
   103  		},
   104  		y:    testStruct{},
   105  		want: []string{"foo"},
   106  	}, {
   107  		name: "Nested field",
   108  		x: testStruct{
   109  			StructField: childStruct{
   110  				ChildString: "baz",
   111  			},
   112  		},
   113  		y: testStruct{
   114  			StructField: childStruct{
   115  				ChildString: "zap",
   116  				ChildInt:    1,
   117  			},
   118  		},
   119  		want: []string{"child"},
   120  	}, {
   121  		name: "Non Struct",
   122  		x:    "foo",
   123  		y:    "bar",
   124  		want: []string{"{string}"},
   125  	}, {
   126  		name: "Private field allowed",
   127  		x: privateStruct{
   128  			privateField: "Foo",
   129  		},
   130  		y: privateStruct{
   131  			privateField: "Bar",
   132  		},
   133  		want: []string{"privateField"},
   134  		opts: []cmp.Option{cmp.AllowUnexported(privateStruct{})},
   135  	}, {
   136  		name: "Private field ignored",
   137  		x: privateStruct{
   138  			privateField: "Foo",
   139  		},
   140  		y: privateStruct{
   141  			privateField: "Bar",
   142  		},
   143  		want: nil,
   144  		opts: []cmp.Option{cmpopts.IgnoreUnexported(privateStruct{})},
   145  	}, {
   146  		name: "Omit empty",
   147  		x: testStruct{
   148  			Omit: "Foo",
   149  		},
   150  		y: testStruct{
   151  			Omit: "Bar",
   152  		},
   153  		want: []string{"omit"},
   154  	}, {
   155  		name: "Ignore JSON",
   156  		x: testStruct{
   157  			Ignore: "Foo",
   158  		},
   159  		y: testStruct{
   160  			Ignore: "Bar",
   161  		},
   162  		want: []string{"Ignore"},
   163  	}, {
   164  		name: "Dash json name",
   165  		x: testStruct{
   166  			Dash: "Foo",
   167  		},
   168  		y: testStruct{
   169  			Dash: "Bar",
   170  		},
   171  		want: []string{"-"},
   172  	}, {
   173  		name: "Multi comma in json tag",
   174  		x: testStruct{
   175  			MultiComma: "Foo",
   176  		},
   177  		y: testStruct{
   178  			MultiComma: "Bar",
   179  		},
   180  		want: []string{"multi"},
   181  	}}
   182  
   183  	for _, test := range tests {
   184  		t.Run(test.name, func(t *testing.T) {
   185  			reporter := new(fieldListReporter)
   186  			cmp.Equal(test.x, test.y, append(test.opts, cmp.Reporter(reporter))...)
   187  			got := reporter.Fields()
   188  			if diff := cmp.Diff(got, test.want); diff != "" {
   189  				t.Errorf("%s: Fields() = %v, Want %v", test.name, got, test.want)
   190  			}
   191  		})
   192  	}
   193  }
   194  
   195  func TestImmutableReporter(t *testing.T) {
   196  	tests := []struct {
   197  		name      string
   198  		x         interface{}
   199  		y         interface{}
   200  		want      string
   201  		expectErr bool
   202  		opts      []cmp.Option
   203  	}{{
   204  		name: "No diff",
   205  		x: testStruct{
   206  			StringField: "foo",
   207  		},
   208  		y: testStruct{
   209  			StringField: "foo",
   210  		},
   211  		want: "",
   212  	}, {
   213  		name: "Both nil objects",
   214  		want: "",
   215  	}, {
   216  		name: "Nil second object",
   217  		x: testStruct{
   218  			StringField: "foo",
   219  		},
   220  		want: `{any}:
   221  	-: "{A: StringField:foo IntField:0 StructField:{ChildString: ChildInt:0} Omit: Ignore: Dash: MultiComma:}"
   222  `,
   223  	}, {
   224  		name: "Single character field name",
   225  		x: testStruct{
   226  			A: "foo",
   227  		},
   228  		y: testStruct{},
   229  		want: `{kmp.testStruct}.A:
   230  	-: "foo"
   231  	+: ""
   232  `,
   233  	}, {
   234  		name: "Single field",
   235  		x: testStruct{
   236  			StringField: "foo",
   237  		},
   238  		y: testStruct{
   239  			StringField: "bar",
   240  		},
   241  		want: `{kmp.testStruct}.StringField:
   242  	-: "foo"
   243  	+: "bar"
   244  `,
   245  	}, {
   246  		name: "Multi field",
   247  		x: testStruct{
   248  			StringField: "foo",
   249  			IntField:    5,
   250  		},
   251  		y: testStruct{
   252  			StringField: "bar",
   253  			IntField:    6,
   254  		},
   255  		want: `{kmp.testStruct}.StringField:
   256  	-: "foo"
   257  	+: "bar"
   258  {kmp.testStruct}.IntField:
   259  	-: "5"
   260  	+: "6"
   261  `,
   262  	}, {
   263  		name: "Missing field",
   264  		x: testStruct{
   265  			StringField: "foo",
   266  		},
   267  		y: testStruct{},
   268  		want: `{kmp.testStruct}.StringField:
   269  	-: "foo"
   270  	+: ""
   271  `,
   272  	}, {
   273  		name: "Nested field",
   274  		x: testStruct{
   275  			StructField: childStruct{
   276  				ChildString: "baz",
   277  			},
   278  		},
   279  		y: testStruct{
   280  			StructField: childStruct{
   281  				ChildString: "zap",
   282  				ChildInt:    1,
   283  			},
   284  		},
   285  		want: `{kmp.testStruct}.StructField.ChildString:
   286  	-: "baz"
   287  	+: "zap"
   288  {kmp.testStruct}.StructField.ChildInt:
   289  	-: "0"
   290  	+: "1"
   291  `,
   292  	}, {
   293  		name: "Non Struct",
   294  		x:    "foo",
   295  		y:    "bar",
   296  		want: `{string}:
   297  	-: "foo"
   298  	+: "bar"
   299  `,
   300  	}, {
   301  		name: "Private field allowed",
   302  		x: privateStruct{
   303  			privateField: "Foo",
   304  		},
   305  		y: privateStruct{
   306  			privateField: "Bar",
   307  		},
   308  		want: `{kmp.privateStruct}.privateField:
   309  	-: "Foo"
   310  	+: "Bar"
   311  `,
   312  		opts: []cmp.Option{cmp.AllowUnexported(privateStruct{})},
   313  	}, {
   314  		name: "Private field ignored",
   315  		x: privateStruct{
   316  			privateField: "Foo",
   317  		},
   318  		y: privateStruct{
   319  			privateField: "Bar",
   320  		},
   321  		want: "",
   322  		opts: []cmp.Option{cmpopts.IgnoreUnexported(privateStruct{})},
   323  	}, {
   324  		name: "Map with a missing key",
   325  		x: map[string]string{
   326  			"Foo": "Bar",
   327  		},
   328  		y: map[string]string{},
   329  		want: `{map[string]string}["Foo"]:
   330  	-: "Bar"
   331  `,
   332  	}, {
   333  		name: "Map add a key",
   334  		x:    map[string]string{},
   335  		y: map[string]string{
   336  			"Foo": "Bar",
   337  		},
   338  		want: `{map[string]string}["Foo"]:
   339  	+: "Bar"
   340  `,
   341  	}}
   342  
   343  	for _, test := range tests {
   344  		t.Run(test.name, func(t *testing.T) {
   345  			reporter := new(shortDiffReporter)
   346  			cmp.Equal(test.x, test.y, append(test.opts, cmp.Reporter(reporter))...)
   347  			got, err := reporter.Diff()
   348  			if test.expectErr {
   349  				if err == nil {
   350  					t.Fatalf("%s: Diff(), expected err, got nil", test.name)
   351  				}
   352  			} else {
   353  				if err != nil {
   354  					t.Fatalf("%s: Diff(), unexpected err: %v", test.name, err)
   355  				}
   356  				if diff := cmp.Diff(test.want, got); diff != "" {
   357  					t.Errorf("%s: Diff() (-want, +got):\n %s", test.name, diff)
   358  				}
   359  			}
   360  		})
   361  	}
   362  }