github.com/hashicorp/hcl/v2@v2.20.0/merged_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hcl
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/davecgh/go-spew/spew"
    12  )
    13  
    14  func TestMergedBodiesContent(t *testing.T) {
    15  	tests := []struct {
    16  		Bodies    []Body
    17  		Schema    *BodySchema
    18  		Want      *BodyContent
    19  		DiagCount int
    20  	}{
    21  		{
    22  			[]Body{},
    23  			&BodySchema{},
    24  			&BodyContent{
    25  				Attributes: map[string]*Attribute{},
    26  			},
    27  			0,
    28  		},
    29  		{
    30  			[]Body{},
    31  			&BodySchema{
    32  				Attributes: []AttributeSchema{
    33  					{
    34  						Name: "name",
    35  					},
    36  				},
    37  			},
    38  			&BodyContent{
    39  				Attributes: map[string]*Attribute{},
    40  			},
    41  			0,
    42  		},
    43  		{
    44  			[]Body{},
    45  			&BodySchema{
    46  				Attributes: []AttributeSchema{
    47  					{
    48  						Name:     "name",
    49  						Required: true,
    50  					},
    51  				},
    52  			},
    53  			&BodyContent{
    54  				Attributes: map[string]*Attribute{},
    55  			},
    56  			1,
    57  		},
    58  		{
    59  			[]Body{
    60  				&testMergedBodiesVictim{
    61  					HasAttributes: []string{"name"},
    62  				},
    63  			},
    64  			&BodySchema{
    65  				Attributes: []AttributeSchema{
    66  					{
    67  						Name: "name",
    68  					},
    69  				},
    70  			},
    71  			&BodyContent{
    72  				Attributes: map[string]*Attribute{
    73  					"name": &Attribute{
    74  						Name: "name",
    75  					},
    76  				},
    77  			},
    78  			0,
    79  		},
    80  		{
    81  			[]Body{
    82  				&testMergedBodiesVictim{
    83  					Name:          "first",
    84  					HasAttributes: []string{"name"},
    85  				},
    86  				&testMergedBodiesVictim{
    87  					Name:          "second",
    88  					HasAttributes: []string{"name"},
    89  				},
    90  			},
    91  			&BodySchema{
    92  				Attributes: []AttributeSchema{
    93  					{
    94  						Name: "name",
    95  					},
    96  				},
    97  			},
    98  			&BodyContent{
    99  				Attributes: map[string]*Attribute{
   100  					"name": &Attribute{
   101  						Name:      "name",
   102  						NameRange: Range{Filename: "first"},
   103  					},
   104  				},
   105  			},
   106  			1,
   107  		},
   108  		{
   109  			[]Body{
   110  				&testMergedBodiesVictim{
   111  					Name:          "first",
   112  					HasAttributes: []string{"name"},
   113  				},
   114  				&testMergedBodiesVictim{
   115  					Name:          "second",
   116  					HasAttributes: []string{"age"},
   117  				},
   118  			},
   119  			&BodySchema{
   120  				Attributes: []AttributeSchema{
   121  					{
   122  						Name: "name",
   123  					},
   124  					{
   125  						Name: "age",
   126  					},
   127  				},
   128  			},
   129  			&BodyContent{
   130  				Attributes: map[string]*Attribute{
   131  					"name": &Attribute{
   132  						Name:      "name",
   133  						NameRange: Range{Filename: "first"},
   134  					},
   135  					"age": &Attribute{
   136  						Name:      "age",
   137  						NameRange: Range{Filename: "second"},
   138  					},
   139  				},
   140  			},
   141  			0,
   142  		},
   143  		{
   144  			[]Body{},
   145  			&BodySchema{
   146  				Blocks: []BlockHeaderSchema{
   147  					{
   148  						Type: "pizza",
   149  					},
   150  				},
   151  			},
   152  			&BodyContent{
   153  				Attributes: map[string]*Attribute{},
   154  			},
   155  			0,
   156  		},
   157  		{
   158  			[]Body{
   159  				&testMergedBodiesVictim{
   160  					HasBlocks: map[string]int{
   161  						"pizza": 1,
   162  					},
   163  				},
   164  			},
   165  			&BodySchema{
   166  				Blocks: []BlockHeaderSchema{
   167  					{
   168  						Type: "pizza",
   169  					},
   170  				},
   171  			},
   172  			&BodyContent{
   173  				Attributes: map[string]*Attribute{},
   174  				Blocks: Blocks{
   175  					{
   176  						Type: "pizza",
   177  					},
   178  				},
   179  			},
   180  			0,
   181  		},
   182  		{
   183  			[]Body{
   184  				&testMergedBodiesVictim{
   185  					HasBlocks: map[string]int{
   186  						"pizza": 2,
   187  					},
   188  				},
   189  			},
   190  			&BodySchema{
   191  				Blocks: []BlockHeaderSchema{
   192  					{
   193  						Type: "pizza",
   194  					},
   195  				},
   196  			},
   197  			&BodyContent{
   198  				Attributes: map[string]*Attribute{},
   199  				Blocks: Blocks{
   200  					{
   201  						Type: "pizza",
   202  					},
   203  					{
   204  						Type: "pizza",
   205  					},
   206  				},
   207  			},
   208  			0,
   209  		},
   210  		{
   211  			[]Body{
   212  				&testMergedBodiesVictim{
   213  					Name: "first",
   214  					HasBlocks: map[string]int{
   215  						"pizza": 1,
   216  					},
   217  				},
   218  				&testMergedBodiesVictim{
   219  					Name: "second",
   220  					HasBlocks: map[string]int{
   221  						"pizza": 1,
   222  					},
   223  				},
   224  			},
   225  			&BodySchema{
   226  				Blocks: []BlockHeaderSchema{
   227  					{
   228  						Type: "pizza",
   229  					},
   230  				},
   231  			},
   232  			&BodyContent{
   233  				Attributes: map[string]*Attribute{},
   234  				Blocks: Blocks{
   235  					{
   236  						Type:     "pizza",
   237  						DefRange: Range{Filename: "first"},
   238  					},
   239  					{
   240  						Type:     "pizza",
   241  						DefRange: Range{Filename: "second"},
   242  					},
   243  				},
   244  			},
   245  			0,
   246  		},
   247  		{
   248  			[]Body{
   249  				&testMergedBodiesVictim{
   250  					Name: "first",
   251  				},
   252  				&testMergedBodiesVictim{
   253  					Name: "second",
   254  					HasBlocks: map[string]int{
   255  						"pizza": 2,
   256  					},
   257  				},
   258  			},
   259  			&BodySchema{
   260  				Blocks: []BlockHeaderSchema{
   261  					{
   262  						Type: "pizza",
   263  					},
   264  				},
   265  			},
   266  			&BodyContent{
   267  				Attributes: map[string]*Attribute{},
   268  				Blocks: Blocks{
   269  					{
   270  						Type:     "pizza",
   271  						DefRange: Range{Filename: "second"},
   272  					},
   273  					{
   274  						Type:     "pizza",
   275  						DefRange: Range{Filename: "second"},
   276  					},
   277  				},
   278  			},
   279  			0,
   280  		},
   281  		{
   282  			[]Body{
   283  				&testMergedBodiesVictim{
   284  					Name: "first",
   285  					HasBlocks: map[string]int{
   286  						"pizza": 2,
   287  					},
   288  				},
   289  				&testMergedBodiesVictim{
   290  					Name: "second",
   291  				},
   292  			},
   293  			&BodySchema{
   294  				Blocks: []BlockHeaderSchema{
   295  					{
   296  						Type: "pizza",
   297  					},
   298  				},
   299  			},
   300  			&BodyContent{
   301  				Attributes: map[string]*Attribute{},
   302  				Blocks: Blocks{
   303  					{
   304  						Type:     "pizza",
   305  						DefRange: Range{Filename: "first"},
   306  					},
   307  					{
   308  						Type:     "pizza",
   309  						DefRange: Range{Filename: "first"},
   310  					},
   311  				},
   312  			},
   313  			0,
   314  		},
   315  		{
   316  			[]Body{
   317  				&testMergedBodiesVictim{
   318  					Name: "first",
   319  				},
   320  				&testMergedBodiesVictim{
   321  					Name: "second",
   322  				},
   323  			},
   324  			&BodySchema{
   325  				Blocks: []BlockHeaderSchema{
   326  					{
   327  						Type: "pizza",
   328  					},
   329  				},
   330  			},
   331  			&BodyContent{
   332  				Attributes: map[string]*Attribute{},
   333  			},
   334  			0,
   335  		},
   336  	}
   337  
   338  	for i, test := range tests {
   339  		t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
   340  			merged := MergeBodies(test.Bodies)
   341  			got, diags := merged.Content(test.Schema)
   342  
   343  			if len(diags) != test.DiagCount {
   344  				t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
   345  				for _, diag := range diags {
   346  					t.Logf(" - %s", diag)
   347  				}
   348  			}
   349  
   350  			if !reflect.DeepEqual(got, test.Want) {
   351  				t.Errorf("wrong result\ngot:  %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
   352  			}
   353  		})
   354  	}
   355  }
   356  
   357  func TestMergeBodiesPartialContent(t *testing.T) {
   358  	tests := []struct {
   359  		Bodies      []Body
   360  		Schema      *BodySchema
   361  		WantContent *BodyContent
   362  		WantRemain  Body
   363  		DiagCount   int
   364  	}{
   365  		{
   366  			[]Body{},
   367  			&BodySchema{},
   368  			&BodyContent{
   369  				Attributes: map[string]*Attribute{},
   370  			},
   371  			mergedBodies{},
   372  			0,
   373  		},
   374  		{
   375  			[]Body{
   376  				&testMergedBodiesVictim{
   377  					Name:          "first",
   378  					HasAttributes: []string{"name", "age"},
   379  				},
   380  			},
   381  			&BodySchema{
   382  				Attributes: []AttributeSchema{
   383  					{
   384  						Name: "name",
   385  					},
   386  				},
   387  			},
   388  			&BodyContent{
   389  				Attributes: map[string]*Attribute{
   390  					"name": &Attribute{
   391  						Name:      "name",
   392  						NameRange: Range{Filename: "first"},
   393  					},
   394  				},
   395  			},
   396  			mergedBodies{
   397  				&testMergedBodiesVictim{
   398  					Name:          "first",
   399  					HasAttributes: []string{"age"},
   400  				},
   401  			},
   402  			0,
   403  		},
   404  		{
   405  			[]Body{
   406  				&testMergedBodiesVictim{
   407  					Name:          "first",
   408  					HasAttributes: []string{"name", "age"},
   409  				},
   410  				&testMergedBodiesVictim{
   411  					Name:          "second",
   412  					HasAttributes: []string{"name", "pizza"},
   413  				},
   414  			},
   415  			&BodySchema{
   416  				Attributes: []AttributeSchema{
   417  					{
   418  						Name: "name",
   419  					},
   420  				},
   421  			},
   422  			&BodyContent{
   423  				Attributes: map[string]*Attribute{
   424  					"name": &Attribute{
   425  						Name:      "name",
   426  						NameRange: Range{Filename: "first"},
   427  					},
   428  				},
   429  			},
   430  			mergedBodies{
   431  				&testMergedBodiesVictim{
   432  					Name:          "first",
   433  					HasAttributes: []string{"age"},
   434  				},
   435  				&testMergedBodiesVictim{
   436  					Name:          "second",
   437  					HasAttributes: []string{"pizza"},
   438  				},
   439  			},
   440  			1,
   441  		},
   442  		{
   443  			[]Body{
   444  				&testMergedBodiesVictim{
   445  					Name:          "first",
   446  					HasAttributes: []string{"name", "age"},
   447  				},
   448  				&testMergedBodiesVictim{
   449  					Name:          "second",
   450  					HasAttributes: []string{"pizza", "soda"},
   451  				},
   452  			},
   453  			&BodySchema{
   454  				Attributes: []AttributeSchema{
   455  					{
   456  						Name: "name",
   457  					},
   458  					{
   459  						Name: "soda",
   460  					},
   461  				},
   462  			},
   463  			&BodyContent{
   464  				Attributes: map[string]*Attribute{
   465  					"name": &Attribute{
   466  						Name:      "name",
   467  						NameRange: Range{Filename: "first"},
   468  					},
   469  					"soda": &Attribute{
   470  						Name:      "soda",
   471  						NameRange: Range{Filename: "second"},
   472  					},
   473  				},
   474  			},
   475  			mergedBodies{
   476  				&testMergedBodiesVictim{
   477  					Name:          "first",
   478  					HasAttributes: []string{"age"},
   479  				},
   480  				&testMergedBodiesVictim{
   481  					Name:          "second",
   482  					HasAttributes: []string{"pizza"},
   483  				},
   484  			},
   485  			0,
   486  		},
   487  		{
   488  			[]Body{
   489  				&testMergedBodiesVictim{
   490  					Name: "first",
   491  					HasBlocks: map[string]int{
   492  						"pizza": 1,
   493  					},
   494  				},
   495  				&testMergedBodiesVictim{
   496  					Name: "second",
   497  					HasBlocks: map[string]int{
   498  						"pizza": 1,
   499  						"soda":  2,
   500  					},
   501  				},
   502  			},
   503  			&BodySchema{
   504  				Blocks: []BlockHeaderSchema{
   505  					{
   506  						Type: "pizza",
   507  					},
   508  				},
   509  			},
   510  			&BodyContent{
   511  				Attributes: map[string]*Attribute{},
   512  				Blocks: Blocks{
   513  					{
   514  						Type:     "pizza",
   515  						DefRange: Range{Filename: "first"},
   516  					},
   517  					{
   518  						Type:     "pizza",
   519  						DefRange: Range{Filename: "second"},
   520  					},
   521  				},
   522  			},
   523  			mergedBodies{
   524  				&testMergedBodiesVictim{
   525  					Name:          "first",
   526  					HasAttributes: []string{},
   527  					HasBlocks:     map[string]int{},
   528  				},
   529  				&testMergedBodiesVictim{
   530  					Name:          "second",
   531  					HasAttributes: []string{},
   532  					HasBlocks: map[string]int{
   533  						"soda": 2,
   534  					},
   535  				},
   536  			},
   537  			0,
   538  		},
   539  	}
   540  
   541  	for i, test := range tests {
   542  		t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
   543  			merged := MergeBodies(test.Bodies)
   544  			got, gotRemain, diags := merged.PartialContent(test.Schema)
   545  
   546  			if len(diags) != test.DiagCount {
   547  				t.Errorf("Wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
   548  				for _, diag := range diags {
   549  					t.Logf(" - %s", diag)
   550  				}
   551  			}
   552  
   553  			if !reflect.DeepEqual(got, test.WantContent) {
   554  				t.Errorf("wrong content result\ngot:  %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.WantContent))
   555  			}
   556  
   557  			if !reflect.DeepEqual(gotRemain, test.WantRemain) {
   558  				t.Errorf("wrong remaining result\ngot:  %s\nwant: %s", spew.Sdump(gotRemain), spew.Sdump(test.WantRemain))
   559  			}
   560  		})
   561  	}
   562  }
   563  
   564  type testMergedBodiesVictim struct {
   565  	Name          string
   566  	HasAttributes []string
   567  	HasBlocks     map[string]int
   568  	DiagCount     int
   569  }
   570  
   571  func (v *testMergedBodiesVictim) Content(schema *BodySchema) (*BodyContent, Diagnostics) {
   572  	c, _, d := v.PartialContent(schema)
   573  	return c, d
   574  }
   575  
   576  func (v *testMergedBodiesVictim) PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics) {
   577  	remain := &testMergedBodiesVictim{
   578  		Name:          v.Name,
   579  		HasAttributes: []string{},
   580  	}
   581  
   582  	hasAttrs := map[string]struct{}{}
   583  	for _, n := range v.HasAttributes {
   584  		hasAttrs[n] = struct{}{}
   585  
   586  		var found bool
   587  		for _, attrS := range schema.Attributes {
   588  			if n == attrS.Name {
   589  				found = true
   590  				break
   591  			}
   592  		}
   593  		if !found {
   594  			remain.HasAttributes = append(remain.HasAttributes, n)
   595  		}
   596  	}
   597  
   598  	content := &BodyContent{
   599  		Attributes: map[string]*Attribute{},
   600  	}
   601  
   602  	rng := Range{
   603  		Filename: v.Name,
   604  	}
   605  
   606  	for _, attrS := range schema.Attributes {
   607  		_, has := hasAttrs[attrS.Name]
   608  		if has {
   609  			content.Attributes[attrS.Name] = &Attribute{
   610  				Name:      attrS.Name,
   611  				NameRange: rng,
   612  			}
   613  		}
   614  	}
   615  
   616  	if v.HasBlocks != nil {
   617  		for _, blockS := range schema.Blocks {
   618  			num := v.HasBlocks[blockS.Type]
   619  			for i := 0; i < num; i++ {
   620  				content.Blocks = append(content.Blocks, &Block{
   621  					Type:     blockS.Type,
   622  					DefRange: rng,
   623  				})
   624  			}
   625  		}
   626  
   627  		remain.HasBlocks = map[string]int{}
   628  		for n := range v.HasBlocks {
   629  			var found bool
   630  			for _, blockS := range schema.Blocks {
   631  				if blockS.Type == n {
   632  					found = true
   633  					break
   634  				}
   635  			}
   636  			if !found {
   637  				remain.HasBlocks[n] = v.HasBlocks[n]
   638  			}
   639  		}
   640  	}
   641  
   642  	diags := make(Diagnostics, v.DiagCount)
   643  	for i := range diags {
   644  		diags[i] = &Diagnostic{
   645  			Severity: DiagError,
   646  			Summary:  fmt.Sprintf("Fake diagnostic %d", i),
   647  			Detail:   "For testing only.",
   648  			Context:  &rng,
   649  		}
   650  	}
   651  
   652  	return content, remain, diags
   653  }
   654  
   655  func (v *testMergedBodiesVictim) JustAttributes() (Attributes, Diagnostics) {
   656  	attrs := make(map[string]*Attribute)
   657  
   658  	rng := Range{
   659  		Filename: v.Name,
   660  	}
   661  
   662  	for _, name := range v.HasAttributes {
   663  		attrs[name] = &Attribute{
   664  			Name:      name,
   665  			NameRange: rng,
   666  		}
   667  	}
   668  
   669  	diags := make(Diagnostics, v.DiagCount)
   670  	for i := range diags {
   671  		diags[i] = &Diagnostic{
   672  			Severity: DiagError,
   673  			Summary:  fmt.Sprintf("Fake diagnostic %d", i),
   674  			Detail:   "For testing only.",
   675  			Context:  &rng,
   676  		}
   677  	}
   678  
   679  	return attrs, diags
   680  }
   681  
   682  func (v *testMergedBodiesVictim) MissingItemRange() Range {
   683  	return Range{
   684  		Filename: v.Name,
   685  	}
   686  }