istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/tpath/tree_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tpath
    16  
    17  import (
    18  	"testing"
    19  
    20  	"sigs.k8s.io/yaml"
    21  
    22  	"istio.io/istio/operator/pkg/util"
    23  )
    24  
    25  func TestWritePathContext(t *testing.T) {
    26  	rootYAML := `
    27  a:
    28    b:
    29    - name: n1
    30      value: v1
    31    - name: n2
    32      list:
    33      - v1
    34      - v2
    35      - v3_regex
    36  `
    37  	tests := []struct {
    38  		desc      string
    39  		path      string
    40  		value     any
    41  		want      string
    42  		wantFound bool
    43  		wantErr   string
    44  	}{
    45  		{
    46  			desc:      "AddListEntry",
    47  			path:      `a.b.[name:n2].list`,
    48  			value:     `foo`,
    49  			wantFound: true,
    50  			want: `
    51  a:
    52    b:
    53    - name: n1
    54      value: v1
    55    - name: n2
    56      list:
    57      - v1
    58      - v2
    59      - v3_regex
    60      - foo
    61  `,
    62  		},
    63  		{
    64  			desc:      "ModifyListEntryValue",
    65  			path:      `a.b.[name:n1].value`,
    66  			value:     `v2`,
    67  			wantFound: true,
    68  			want: `
    69  a:
    70    b:
    71    - name: n1
    72      value: v2
    73    - list:
    74      - v1
    75      - v2
    76      - v3_regex
    77      name: n2
    78  `,
    79  		},
    80  		{
    81  			desc:      "ModifyListEntryValueQuoted",
    82  			path:      `a.b.[name:n1].value`,
    83  			value:     `v2`,
    84  			wantFound: true,
    85  			want: `
    86  a:
    87    b:
    88    - name: "n1"
    89      value: v2
    90    - list:
    91      - v1
    92      - v2
    93      - v3_regex
    94      name: n2
    95  `,
    96  		},
    97  		{
    98  			desc:      "ModifyListEntry",
    99  			path:      `a.b.[name:n2].list.[:v2]`,
   100  			value:     `v3`,
   101  			wantFound: true,
   102  			want: `
   103  a:
   104    b:
   105    - name: n1
   106      value: v1
   107    - list:
   108      - v1
   109      - v3
   110      - v3_regex
   111      name: n2
   112  `,
   113  		},
   114  		{
   115  			desc: "ModifyListEntryMapValue",
   116  			path: `a.b.[name:n2]`,
   117  			value: `name: n2
   118  list: 
   119    - nk1: nv1
   120    - nk2: nv2`,
   121  			wantFound: true,
   122  			want: `
   123  a:
   124    b:
   125    - name: n1
   126      value: v1
   127    - name: n2
   128      list:
   129      - nk1: nv1
   130      - nk2: nv2
   131  `,
   132  		},
   133  		{
   134  			desc:      "ModifyNthListEntry",
   135  			path:      `a.b.[1].list.[:v2]`,
   136  			value:     `v-the-second`,
   137  			wantFound: true,
   138  			want: `
   139  a:
   140    b:
   141    - name: n1
   142      value: v1
   143    - list:
   144      - v1
   145      - v-the-second
   146      - v3_regex
   147      name: n2
   148  `,
   149  		},
   150  		{
   151  			desc:      "ModifyNthLeafListEntry",
   152  			path:      `a.b.[1].list.[2]`,
   153  			value:     `v-the-third`,
   154  			wantFound: true,
   155  			want: `
   156  a:
   157    b:
   158    - name: n1
   159      value: v1
   160    - list:
   161      - v1
   162      - v2
   163      - v-the-third
   164      name: n2
   165  `,
   166  		},
   167  		{
   168  			desc:      "ModifyListEntryValueDotless",
   169  			path:      `a.b[name:n1].value`,
   170  			value:     `v2`,
   171  			wantFound: true,
   172  			want: `
   173  a:
   174    b:
   175    - name: n1
   176      value: v2
   177    - list:
   178      - v1
   179      - v2
   180      - v3_regex
   181      name: n2
   182  `,
   183  		},
   184  		{
   185  			desc:      "DeleteListEntry",
   186  			path:      `a.b.[name:n1]`,
   187  			wantFound: true,
   188  			want: `
   189  a:
   190    b:
   191    - list:
   192      - v1
   193      - v2
   194      - v3_regex
   195      name: n2
   196  `,
   197  		},
   198  		{
   199  			desc:      "DeleteListEntryValue",
   200  			path:      `a.b.[name:n2].list.[:v2]`,
   201  			wantFound: true,
   202  			want: `
   203  a:
   204    b:
   205    - name: n1
   206      value: v1
   207    - list:
   208      - v1
   209      - v3_regex
   210      name: n2
   211  `,
   212  		},
   213  		{
   214  			desc:      "DeleteListEntryIndex",
   215  			path:      `a.b.[name:n2].list.[1]`,
   216  			wantFound: true,
   217  			want: `
   218  a:
   219    b:
   220    - name: n1
   221      value: v1
   222    - list:
   223      - v1
   224      - v3_regex
   225      name: n2
   226  `,
   227  		},
   228  		{
   229  			desc:      "DeleteListEntryValueRegex",
   230  			path:      `a.b.[name:n2].list.[:v3]`,
   231  			wantFound: true,
   232  			want: `
   233  a:
   234    b:
   235    - name: n1
   236      value: v1
   237    - list:
   238      - v1
   239      - v2
   240      name: n2
   241  `,
   242  		},
   243  		{
   244  			desc:      "DeleteListLeafEntryBogusIndex",
   245  			path:      `a.b.[name:n2].list.[-200]`,
   246  			wantFound: false,
   247  			wantErr:   `path a.b.[name:n2].list.[-200]: element [-200] not found`,
   248  		},
   249  		{
   250  			desc:      "DeleteListEntryBogusIndex",
   251  			path:      `a.b.[1000000].list.[:v2]`,
   252  			wantFound: false,
   253  			wantErr:   `index 1000000 exceeds list length 2 at path [1000000].list.[:v2]`,
   254  		},
   255  		{
   256  			desc:      "AddMapEntry",
   257  			path:      `a.new_key`,
   258  			value:     `new_val`,
   259  			wantFound: true,
   260  			want: `
   261  a:
   262    b:
   263    - name: n1
   264      value: v1
   265    - name: n2
   266      list:
   267      - v1
   268      - v2
   269      - v3_regex
   270    new_key: new_val
   271  `,
   272  		},
   273  		{
   274  			desc: "AddMapEntryMapValue",
   275  			path: `a.new_key`,
   276  			value: `new_key:
   277    nk1:
   278      nk2: nv2`,
   279  			wantFound: true,
   280  			want: `
   281  a:
   282    b:
   283    - name: n1
   284      value: v1
   285    - name: n2
   286      list:
   287      - v1
   288      - v2
   289      - v3_regex
   290    new_key:
   291      nk1:
   292        nk2: nv2
   293  `,
   294  		},
   295  		{
   296  			desc: "ModifyMapEntryMapValue",
   297  			path: `a.b`,
   298  			value: `nk1:
   299    nk2: nv2`,
   300  			wantFound: true,
   301  			want: `
   302  a:
   303    nk1:
   304      nk2: nv2
   305  `,
   306  		},
   307  		{
   308  			desc:      "DeleteMapEntry",
   309  			path:      `a.b`,
   310  			wantFound: true,
   311  			want: `
   312  a: {}
   313  `,
   314  		},
   315  		{
   316  			desc:      "path not found",
   317  			path:      `a.c.[name:n2].list.[:v3]`,
   318  			wantFound: false,
   319  			wantErr:   `path not found at element c in path a.c.[name:n2].list.[:v3]`,
   320  		},
   321  		{
   322  			desc:      "error key",
   323  			path:      `a.b.[].list`,
   324  			wantFound: false,
   325  			wantErr:   `path a.b.[].list: [] is not a valid key:value path element`,
   326  		},
   327  		{
   328  			desc:      "invalid index",
   329  			path:      `a.c.[n2].list.[:v3]`,
   330  			wantFound: false,
   331  			wantErr:   `path not found at element c in path a.c.[n2].list.[:v3]`,
   332  		},
   333  	}
   334  	for _, tt := range tests {
   335  		t.Run(tt.desc, func(t *testing.T) {
   336  			root := make(map[string]any)
   337  			if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
   338  				t.Fatal(err)
   339  			}
   340  			pc, gotFound, gotErr := GetPathContext(root, util.PathFromString(tt.path), false)
   341  			if gotErr, wantErr := errToString(gotErr), tt.wantErr; gotErr != wantErr {
   342  				t.Fatalf("GetPathContext(%s): gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
   343  			}
   344  			if gotFound != tt.wantFound {
   345  				t.Fatalf("GetPathContext(%s): gotFound:%v, wantFound:%v", tt.desc, gotFound, tt.wantFound)
   346  			}
   347  			if tt.wantErr != "" || !tt.wantFound {
   348  				if tt.want != "" {
   349  					t.Error("tt.want is set but never checked")
   350  				}
   351  				return
   352  			}
   353  
   354  			err := WritePathContext(pc, tt.value, false)
   355  			if err != nil {
   356  				t.Fatal(err)
   357  			}
   358  
   359  			gotYAML := util.ToYAML(root)
   360  			diff := util.YAMLDiff(gotYAML, tt.want)
   361  			if diff != "" {
   362  				t.Errorf("%s: (got:-, want:+):\n%s\n", tt.desc, diff)
   363  			}
   364  		})
   365  	}
   366  }
   367  
   368  func TestWriteNode(t *testing.T) {
   369  	testTreeYAML := `
   370  a:
   371    b:
   372      c: val1
   373      list1:
   374      - i1: val1
   375      - i2: val2
   376      - i3a: key1
   377        i3b:
   378          list2:
   379          - i1: val1
   380          - i2: val2
   381          - i3a: key1
   382            i3b:
   383              i1: va11
   384  `
   385  	tests := []struct {
   386  		desc     string
   387  		baseYAML string
   388  		path     string
   389  		value    string
   390  		want     string
   391  		wantErr  string
   392  	}{
   393  		{
   394  			desc:  "insert empty",
   395  			path:  "a.b.c",
   396  			value: "val1",
   397  			want: `
   398  a:
   399    b:
   400      c: val1
   401  `,
   402  		},
   403  		{
   404  			desc:     "overwrite",
   405  			baseYAML: testTreeYAML,
   406  			path:     "a.b.c",
   407  			value:    "val2",
   408  			want: `
   409  a:
   410    b:
   411      c: val2
   412      list1:
   413      - i1: val1
   414      - i2: val2
   415      - i3a: key1
   416        i3b:
   417          list2:
   418          - i1: val1
   419          - i2: val2
   420          - i3a: key1
   421            i3b:
   422              i1: va11
   423  `,
   424  		},
   425  		{
   426  			desc:     "partial create",
   427  			baseYAML: testTreeYAML,
   428  			path:     "a.b.d",
   429  			value:    "val3",
   430  			want: `
   431  a:
   432    b:
   433      c: val1
   434      d: val3
   435      list1:
   436      - i1: val1
   437      - i2: val2
   438      - i3a: key1
   439        i3b:
   440          list2:
   441          - i1: val1
   442          - i2: val2
   443          - i3a: key1
   444            i3b:
   445              i1: va11
   446  `,
   447  		},
   448  		{
   449  			desc:     "list keys",
   450  			baseYAML: testTreeYAML,
   451  			path:     "a.b.list1.[i3a:key1].i3b.list2.[i3a:key1].i3b.i1",
   452  			value:    "val2",
   453  			want: `
   454  a:
   455    b:
   456      c: val1
   457      list1:
   458      - i1: val1
   459      - i2: val2
   460      - i3a: key1
   461        i3b:
   462          list2:
   463          - i1: val1
   464          - i2: val2
   465          - i3a: key1
   466            i3b:
   467              i1: val2
   468  `,
   469  		},
   470  		// For https://github.com/istio/istio/issues/20950
   471  		{
   472  			desc: "with initial list",
   473  			baseYAML: `
   474  components:
   475    ingressGateways:
   476      - enabled: true
   477  `,
   478  			path:  "components.ingressGateways[0].enabled",
   479  			value: "false",
   480  			want: `
   481  components:
   482    ingressGateways:
   483      - enabled: "false"
   484  `,
   485  		},
   486  		{
   487  			desc:     "no initial list",
   488  			baseYAML: "",
   489  			path:     "components.ingressGateways[0].enabled",
   490  			value:    "false",
   491  			want: `
   492  components:
   493    ingressGateways:
   494      - enabled: "false"
   495  `,
   496  		},
   497  		{
   498  			desc: "no initial list for entry",
   499  			baseYAML: `
   500  a: {}
   501  `,
   502  			path:  "a.list.[0]",
   503  			value: "v1",
   504  			want: `
   505  a:
   506    list:
   507      - v1		
   508  `,
   509  		},
   510  		{
   511  			desc: "ExtendNthLeafListEntry",
   512  			baseYAML: `
   513  a:
   514    list:
   515      - v1
   516  `,
   517  			path:  `a.list.[1]`,
   518  			value: `v2`,
   519  			want: `
   520  a:
   521    list: 
   522    - v1
   523    - v2
   524  `,
   525  		},
   526  		{
   527  			desc: "ExtendLeafListEntryLargeIndex",
   528  			baseYAML: `
   529  a:
   530    list:
   531      - v1
   532  `,
   533  			path:  `a.list.[999]`,
   534  			value: `v2`,
   535  			want: `
   536  a:
   537    list:
   538    - v1
   539    - v2
   540  `,
   541  		},
   542  		{
   543  			desc: "ExtendLeafListEntryNegativeIndex",
   544  			baseYAML: `
   545  a:
   546    list:
   547      - v1
   548  `,
   549  			path:  `a.list.[-1]`,
   550  			value: `v2`,
   551  			want: `
   552  a:
   553    list:
   554    - v1
   555    - v2
   556  `,
   557  		},
   558  		{
   559  			desc: "ExtendNthListEntry",
   560  			baseYAML: `
   561  a:
   562    list:
   563    - name: foo
   564  `,
   565  			path:  `a.list.[1].name`,
   566  			value: `bar`,
   567  			want: `
   568  a:
   569    list:
   570    - name: foo
   571    - name: bar
   572  `,
   573  		},
   574  	}
   575  	for _, tt := range tests {
   576  		t.Run(tt.desc, func(t *testing.T) {
   577  			root := make(map[string]any)
   578  			if tt.baseYAML != "" {
   579  				if err := yaml.Unmarshal([]byte(tt.baseYAML), &root); err != nil {
   580  					t.Fatal(err)
   581  				}
   582  			}
   583  			p := util.PathFromString(tt.path)
   584  			err := WriteNode(root, p, tt.value)
   585  			if gotErr, wantErr := errToString(err), tt.wantErr; gotErr != wantErr {
   586  				t.Errorf("%s: gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
   587  				return
   588  			}
   589  			if got, want := util.ToYAML(root), tt.want; err == nil && util.YAMLDiff(got, want) != "" {
   590  				t.Errorf("%s: got:\n%s\nwant:\n%s\ndiff:\n%s\n", tt.desc, got, want, util.YAMLDiff(got, want))
   591  			}
   592  		})
   593  	}
   594  }
   595  
   596  func TestMergeNode(t *testing.T) {
   597  	testTreeYAML := `
   598  a:
   599    b:
   600      c: val1
   601      list1:
   602      - i1: val1
   603      - i2: val2
   604  `
   605  	tests := []struct {
   606  		desc     string
   607  		baseYAML string
   608  		path     string
   609  		value    string
   610  		want     string
   611  		wantErr  string
   612  	}{
   613  		{
   614  			desc:     "merge list entry",
   615  			baseYAML: testTreeYAML,
   616  			path:     "a.b.list1.[i1:val1]",
   617  			value: `
   618  i2b: val2`,
   619  			want: `
   620  a:
   621    b:
   622      c: val1
   623      list1:
   624      - i1: val1
   625        i2b: val2
   626      - i2: val2
   627  `,
   628  		},
   629  		{
   630  			desc:     "merge list 2",
   631  			baseYAML: testTreeYAML,
   632  			path:     "a.b.list1",
   633  			value: `
   634  i3:
   635    a: val3
   636  `,
   637  			want: `
   638  a:
   639    b:
   640      c: val1
   641      list1:
   642      - i1: val1
   643      - i2: val2
   644      - i3:
   645          a: val3
   646  `,
   647  		},
   648  	}
   649  	for _, tt := range tests {
   650  		t.Run(tt.desc, func(t *testing.T) {
   651  			root := make(map[string]any)
   652  			if tt.baseYAML != "" {
   653  				if err := yaml.Unmarshal([]byte(tt.baseYAML), &root); err != nil {
   654  					t.Fatal(err)
   655  				}
   656  			}
   657  			p := util.PathFromString(tt.path)
   658  			iv := make(map[string]any)
   659  			err := yaml.Unmarshal([]byte(tt.value), &iv)
   660  			if err != nil {
   661  				t.Fatal(err)
   662  			}
   663  			err = MergeNode(root, p, iv)
   664  			if gotErr, wantErr := errToString(err), tt.wantErr; gotErr != wantErr {
   665  				t.Errorf("%s: gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
   666  				return
   667  			}
   668  			if got, want := util.ToYAML(root), tt.want; err == nil && util.YAMLDiff(got, want) != "" {
   669  				t.Errorf("%s: got:\n%s\nwant:\n%s\ndiff:\n%s\n", tt.desc, got, want, util.YAMLDiff(got, want))
   670  			}
   671  		})
   672  	}
   673  }
   674  
   675  // errToString returns the string representation of err and the empty string if
   676  // err is nil.
   677  func errToString(err error) string {
   678  	if err == nil {
   679  		return ""
   680  	}
   681  	return err.Error()
   682  }
   683  
   684  // TestSecretVolumes simulates https://github.com/istio/istio/issues/20381
   685  func TestSecretVolumes(t *testing.T) {
   686  	rootYAML := `
   687  values:
   688     gateways:
   689        istio-egressgateway:
   690           secretVolumes: []
   691  `
   692  	root := make(map[string]any)
   693  	if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
   694  		t.Fatal(err)
   695  	}
   696  	overrides := []struct {
   697  		path  string
   698  		value any
   699  	}{
   700  		{
   701  			path:  "values.gateways.istio-egressgateway.secretVolumes[0].name",
   702  			value: "egressgateway-certs",
   703  		},
   704  		{
   705  			path:  "values.gateways.istio-egressgateway.secretVolumes[0].secretName",
   706  			value: "istio-egressgateway-certs",
   707  		},
   708  		{
   709  			path:  "values.gateways.istio-egressgateway.secretVolumes[0].mountPath",
   710  			value: "/etc/istio/egressgateway-certs",
   711  		},
   712  		{
   713  			path:  "values.gateways.istio-egressgateway.secretVolumes[1].name",
   714  			value: "egressgateway-ca-certs",
   715  		},
   716  		{
   717  			path:  "values.gateways.istio-egressgateway.secretVolumes[1].secretName",
   718  			value: "istio-egressgateway-ca-certs",
   719  		},
   720  		{
   721  			path:  "values.gateways.istio-egressgateway.secretVolumes[1].mountPath",
   722  			value: "/etc/istio/egressgateway-ca-certs",
   723  		},
   724  		{
   725  			path:  "values.gateways.istio-egressgateway.secretVolumes[2].name",
   726  			value: "nginx-client-certs",
   727  		},
   728  		{
   729  			path:  "values.gateways.istio-egressgateway.secretVolumes[2].secretName",
   730  			value: "nginx-client-certs",
   731  		},
   732  		{
   733  			path:  "values.gateways.istio-egressgateway.secretVolumes[2].mountPath",
   734  			value: "/etc/istio/nginx-client-certs",
   735  		},
   736  		{
   737  			path:  "values.gateways.istio-egressgateway.secretVolumes[3].name",
   738  			value: "nginx-ca-certs",
   739  		},
   740  		{
   741  			path:  "values.gateways.istio-egressgateway.secretVolumes[3].secretName",
   742  			value: "nginx-ca-certs",
   743  		},
   744  		{
   745  			path:  "values.gateways.istio-egressgateway.secretVolumes[3].mountPath",
   746  			value: "/etc/istio/nginx-ca-certs",
   747  		},
   748  	}
   749  
   750  	for _, override := range overrides {
   751  
   752  		pc, _, err := GetPathContext(root, util.PathFromString(override.path), true)
   753  		if err != nil {
   754  			t.Fatalf("GetPathContext(%q): %v", override.path, err)
   755  		}
   756  		err = WritePathContext(pc, override.value, false)
   757  		if err != nil {
   758  			t.Fatalf("WritePathContext(%q): %v", override.path, err)
   759  		}
   760  	}
   761  
   762  	want := `
   763  values:
   764     gateways:
   765        istio-egressgateway:
   766           secretVolumes:
   767           - mountPath: /etc/istio/egressgateway-certs
   768             name: egressgateway-certs
   769             secretName: istio-egressgateway-certs
   770           - mountPath: /etc/istio/egressgateway-ca-certs
   771             name: egressgateway-ca-certs
   772             secretName: istio-egressgateway-ca-certs
   773           - mountPath: /etc/istio/nginx-client-certs
   774             name: nginx-client-certs
   775             secretName: nginx-client-certs
   776           - mountPath: /etc/istio/nginx-ca-certs
   777             name: nginx-ca-certs
   778             secretName: nginx-ca-certs
   779  `
   780  	gotYAML := util.ToYAML(root)
   781  	diff := util.YAMLDiff(gotYAML, want)
   782  	if diff != "" {
   783  		t.Errorf("TestSecretVolumes: diff:\n%s\n", diff)
   784  	}
   785  }
   786  
   787  // Simulates https://github.com/istio/istio/issues/19196
   788  func TestWriteEscapedPathContext(t *testing.T) {
   789  	rootYAML := `
   790  values:
   791    sidecarInjectorWebhook:
   792      injectedAnnotations: {}
   793  `
   794  	tests := []struct {
   795  		desc      string
   796  		path      string
   797  		value     any
   798  		want      string
   799  		wantFound bool
   800  		wantErr   string
   801  	}{
   802  		{
   803  			desc:      "ModifyEscapedPathValue",
   804  			path:      `values.sidecarInjectorWebhook.injectedAnnotations.container\.apparmor\.security\.beta\.kubernetes\.io/istio-proxy`,
   805  			value:     `runtime/default`,
   806  			wantFound: true,
   807  			want: `
   808  values:
   809    sidecarInjectorWebhook:
   810      injectedAnnotations:
   811        container.apparmor.security.beta.kubernetes.io/istio-proxy: runtime/default
   812  `,
   813  		},
   814  	}
   815  	for _, tt := range tests {
   816  		t.Run(tt.desc, func(t *testing.T) {
   817  			root := make(map[string]any)
   818  			if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
   819  				t.Fatal(err)
   820  			}
   821  			pc, gotFound, gotErr := GetPathContext(root, util.PathFromString(tt.path), false)
   822  			if gotErr, wantErr := errToString(gotErr), tt.wantErr; gotErr != wantErr {
   823  				t.Fatalf("GetPathContext(%s): gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
   824  			}
   825  			if gotFound != tt.wantFound {
   826  				t.Fatalf("GetPathContext(%s): gotFound:%v, wantFound:%v", tt.desc, gotFound, tt.wantFound)
   827  			}
   828  			if tt.wantErr != "" || !tt.wantFound {
   829  				return
   830  			}
   831  
   832  			err := WritePathContext(pc, tt.value, false)
   833  			if err != nil {
   834  				t.Fatal(err)
   835  			}
   836  
   837  			gotYAML := util.ToYAML(root)
   838  			diff := util.YAMLDiff(gotYAML, tt.want)
   839  			if diff != "" {
   840  				t.Errorf("%s: diff:\n%s\n", tt.desc, diff)
   841  			}
   842  		})
   843  	}
   844  }