github.com/pietrocarrara/hugo@v0.47.1/parser/frontmatter_test.go (about)

     1  // Copyright 2015 The Hugo Authors. All rights reserved.
     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  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package parser
    15  
    16  import (
    17  	"bytes"
    18  	"fmt"
    19  	"reflect"
    20  	"strings"
    21  	"testing"
    22  )
    23  
    24  func TestInterfaceToConfig(t *testing.T) {
    25  	cases := []struct {
    26  		input interface{}
    27  		mark  byte
    28  		want  []byte
    29  		isErr bool
    30  	}{
    31  		// TOML
    32  		{map[string]interface{}{}, TOMLLead[0], nil, false},
    33  		{
    34  			map[string]interface{}{"title": "test 1"},
    35  			TOMLLead[0],
    36  			[]byte("title = \"test 1\"\n"),
    37  			false,
    38  		},
    39  
    40  		// YAML
    41  		{map[string]interface{}{}, YAMLLead[0], []byte("{}\n"), false},
    42  		{
    43  			map[string]interface{}{"title": "test 1"},
    44  			YAMLLead[0],
    45  			[]byte("title: test 1\n"),
    46  			false,
    47  		},
    48  
    49  		// JSON
    50  		{map[string]interface{}{}, JSONLead[0], []byte("{}\n"), false},
    51  		{
    52  			map[string]interface{}{"title": "test 1"},
    53  			JSONLead[0],
    54  			[]byte("{\n   \"title\": \"test 1\"\n}\n"),
    55  			false,
    56  		},
    57  
    58  		// Errors
    59  		{nil, TOMLLead[0], nil, true},
    60  		{map[string]interface{}{}, '$', nil, true},
    61  	}
    62  
    63  	for i, c := range cases {
    64  		var buf bytes.Buffer
    65  
    66  		err := InterfaceToConfig(c.input, rune(c.mark), &buf)
    67  		if err != nil {
    68  			if c.isErr {
    69  				continue
    70  			}
    71  			t.Fatalf("[%d] unexpected error value: %v", i, err)
    72  		}
    73  
    74  		if !reflect.DeepEqual(buf.Bytes(), c.want) {
    75  			t.Errorf("[%d] not equal:\nwant %q,\n got %q", i, c.want, buf.Bytes())
    76  		}
    77  	}
    78  }
    79  
    80  func TestInterfaceToFrontMatter(t *testing.T) {
    81  	cases := []struct {
    82  		input interface{}
    83  		mark  rune
    84  		want  []byte
    85  		isErr bool
    86  	}{
    87  		// TOML
    88  		{map[string]interface{}{}, '+', []byte("+++\n\n+++\n"), false},
    89  		{
    90  			map[string]interface{}{"title": "test 1"},
    91  			'+',
    92  			[]byte("+++\ntitle = \"test 1\"\n\n+++\n"),
    93  			false,
    94  		},
    95  
    96  		// YAML
    97  		{map[string]interface{}{}, '-', []byte("---\n{}\n---\n"), false}, //
    98  		{
    99  			map[string]interface{}{"title": "test 1"},
   100  			'-',
   101  			[]byte("---\ntitle: test 1\n---\n"),
   102  			false,
   103  		},
   104  
   105  		// JSON
   106  		{map[string]interface{}{}, '{', []byte("{}\n"), false},
   107  		{
   108  			map[string]interface{}{"title": "test 1"},
   109  			'{',
   110  			[]byte("{\n   \"title\": \"test 1\"\n}\n"),
   111  			false,
   112  		},
   113  
   114  		// Errors
   115  		{nil, '+', nil, true},
   116  		{map[string]interface{}{}, '$', nil, true},
   117  	}
   118  
   119  	for i, c := range cases {
   120  		var buf bytes.Buffer
   121  		err := InterfaceToFrontMatter(c.input, c.mark, &buf)
   122  		if err != nil {
   123  			if c.isErr {
   124  				continue
   125  			}
   126  			t.Fatalf("[%d] unexpected error value: %v", i, err)
   127  		}
   128  
   129  		if !reflect.DeepEqual(buf.Bytes(), c.want) {
   130  			t.Errorf("[%d] not equal:\nwant %q,\n got %q", i, c.want, buf.Bytes())
   131  		}
   132  	}
   133  }
   134  
   135  func TestHandleTOMLMetaData(t *testing.T) {
   136  	cases := []struct {
   137  		input []byte
   138  		want  interface{}
   139  		isErr bool
   140  	}{
   141  		{nil, map[string]interface{}{}, false},
   142  		{[]byte("title = \"test 1\""), map[string]interface{}{"title": "test 1"}, false},
   143  		{[]byte("a = [1, 2, 3]"), map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}, false},
   144  		{[]byte("b = [\n[1, 2],\n[3, 4]\n]"), map[string]interface{}{"b": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}, false},
   145  		// errors
   146  		{[]byte("z = [\n[1, 2]\n[3, 4]\n]"), nil, true},
   147  	}
   148  
   149  	for i, c := range cases {
   150  		res, err := HandleTOMLMetaData(c.input)
   151  		if err != nil {
   152  			if c.isErr {
   153  				continue
   154  			}
   155  			t.Fatalf("[%d] unexpected error value: %v", i, err)
   156  		}
   157  
   158  		if !reflect.DeepEqual(res, c.want) {
   159  			t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res)
   160  		}
   161  	}
   162  }
   163  
   164  func TestHandleYAMLMetaData(t *testing.T) {
   165  	cases := []struct {
   166  		input []byte
   167  		want  interface{}
   168  		isErr bool
   169  	}{
   170  		{nil, map[string]interface{}{}, false},
   171  		{[]byte("title: test 1"), map[string]interface{}{"title": "test 1"}, false},
   172  		{[]byte("a: Easy!\nb:\n  c: 2\n  d: [3, 4]"), map[string]interface{}{"a": "Easy!", "b": map[string]interface{}{"c": 2, "d": []interface{}{3, 4}}}, false},
   173  		{[]byte("a:\n  true: 1\n  false: 2"), map[string]interface{}{"a": map[string]interface{}{"true": 1, "false": 2}}, false},
   174  		// errors
   175  		{[]byte("z = not toml"), nil, true},
   176  	}
   177  
   178  	for i, c := range cases {
   179  		res, err := HandleYAMLMetaData(c.input)
   180  		if err != nil {
   181  			if c.isErr {
   182  				continue
   183  			}
   184  			t.Fatalf("[%d] unexpected error value: %v", i, err)
   185  		}
   186  
   187  		if !reflect.DeepEqual(res, c.want) {
   188  			t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res)
   189  		}
   190  	}
   191  }
   192  
   193  func TestHandleJSONMetaData(t *testing.T) {
   194  	cases := []struct {
   195  		input []byte
   196  		want  interface{}
   197  		isErr bool
   198  	}{
   199  		{nil, map[string]interface{}{}, false},
   200  		{[]byte("{\"title\": \"test 1\"}"), map[string]interface{}{"title": "test 1"}, false},
   201  		// errors
   202  		{[]byte("{noquotes}"), nil, true},
   203  	}
   204  
   205  	for i, c := range cases {
   206  		res, err := HandleJSONMetaData(c.input)
   207  		if err != nil {
   208  			if c.isErr {
   209  				continue
   210  			}
   211  			t.Fatalf("[%d] unexpected error value: %v", i, err)
   212  		}
   213  
   214  		if !reflect.DeepEqual(res, c.want) {
   215  			t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res)
   216  		}
   217  	}
   218  }
   219  
   220  func TestHandleOrgMetaData(t *testing.T) {
   221  	cases := []struct {
   222  		input []byte
   223  		want  interface{}
   224  		isErr bool
   225  	}{
   226  		{nil, map[string]interface{}{}, false},
   227  		{[]byte("#+title: test 1\n"), map[string]interface{}{"title": "test 1"}, false},
   228  	}
   229  
   230  	for i, c := range cases {
   231  		res, err := HandleOrgMetaData(c.input)
   232  		if err != nil {
   233  			if c.isErr {
   234  				continue
   235  			}
   236  			t.Fatalf("[%d] unexpected error value: %v", i, err)
   237  		}
   238  
   239  		if !reflect.DeepEqual(res, c.want) {
   240  			t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res)
   241  		}
   242  	}
   243  }
   244  
   245  func TestFormatToLeadRune(t *testing.T) {
   246  	for i, this := range []struct {
   247  		kind   string
   248  		expect rune
   249  	}{
   250  		{"yaml", '-'},
   251  		{"yml", '-'},
   252  		{"toml", '+'},
   253  		{"tml", '+'},
   254  		{"json", '{'},
   255  		{"js", '{'},
   256  		{"org", '#'},
   257  		{"unknown", '+'},
   258  	} {
   259  		result := FormatToLeadRune(this.kind)
   260  
   261  		if result != this.expect {
   262  			t.Errorf("[%d] got %q but expected %q", i, result, this.expect)
   263  		}
   264  	}
   265  }
   266  
   267  func TestDetectFrontMatter(t *testing.T) {
   268  	cases := []struct {
   269  		mark rune
   270  		want *FrontmatterType
   271  	}{
   272  		// funcs are uncomparable, so we ignore FrontmatterType.Parse in these tests
   273  		{'-', &FrontmatterType{nil, []byte(YAMLDelim), []byte(YAMLDelim), false}},
   274  		{'+', &FrontmatterType{nil, []byte(TOMLDelim), []byte(TOMLDelim), false}},
   275  		{'{', &FrontmatterType{nil, []byte("{"), []byte("}"), true}},
   276  		{'#', &FrontmatterType{nil, []byte("#+"), []byte("\n"), false}},
   277  		{'$', nil},
   278  	}
   279  
   280  	for _, c := range cases {
   281  		res := DetectFrontMatter(c.mark)
   282  		if res == nil {
   283  			if c.want == nil {
   284  				continue
   285  			}
   286  
   287  			t.Fatalf("want %v, got %v", *c.want, res)
   288  		}
   289  
   290  		if !reflect.DeepEqual(res.markstart, c.want.markstart) {
   291  			t.Errorf("markstart mismatch: want %v, got %v", c.want.markstart, res.markstart)
   292  		}
   293  		if !reflect.DeepEqual(res.markend, c.want.markend) {
   294  			t.Errorf("markend mismatch: want %v, got %v", c.want.markend, res.markend)
   295  		}
   296  		if !reflect.DeepEqual(res.includeMark, c.want.includeMark) {
   297  			t.Errorf("includeMark mismatch: want %v, got %v", c.want.includeMark, res.includeMark)
   298  		}
   299  	}
   300  }
   301  
   302  func TestRemoveTOMLIdentifier(t *testing.T) {
   303  	cases := []struct {
   304  		input string
   305  		want  string
   306  	}{
   307  		{"a = 1", "a = 1"},
   308  		{"a = 1\r\n", "a = 1\r\n"},
   309  		{"+++\r\na = 1\r\n+++\r\n", "a = 1\r\n"},
   310  		{"+++\na = 1\n+++\n", "a = 1\n"},
   311  		{"+++\nb = \"+++ oops +++\"\n+++\n", "b = \"+++ oops +++\"\n"},
   312  		{"+++\nc = \"\"\"+++\noops\n+++\n\"\"\"\"\n+++\n", "c = \"\"\"+++\noops\n+++\n\"\"\"\"\n"},
   313  		{"+++\nd = 1\n+++", "d = 1\n"},
   314  	}
   315  
   316  	for i, c := range cases {
   317  		res := removeTOMLIdentifier([]byte(c.input))
   318  		if string(res) != c.want {
   319  			t.Errorf("[%d] given %q\nwant: %q\n got: %q", i, c.input, c.want, res)
   320  		}
   321  	}
   322  }
   323  
   324  func TestStringifyYAMLMapKeys(t *testing.T) {
   325  	cases := []struct {
   326  		input    interface{}
   327  		want     interface{}
   328  		replaced bool
   329  	}{
   330  		{
   331  			map[interface{}]interface{}{"a": 1, "b": 2},
   332  			map[string]interface{}{"a": 1, "b": 2},
   333  			true,
   334  		},
   335  		{
   336  			map[interface{}]interface{}{"a": []interface{}{1, map[interface{}]interface{}{"b": 2}}},
   337  			map[string]interface{}{"a": []interface{}{1, map[string]interface{}{"b": 2}}},
   338  			true,
   339  		},
   340  		{
   341  			map[interface{}]interface{}{true: 1, "b": false},
   342  			map[string]interface{}{"true": 1, "b": false},
   343  			true,
   344  		},
   345  		{
   346  			map[interface{}]interface{}{1: "a", 2: "b"},
   347  			map[string]interface{}{"1": "a", "2": "b"},
   348  			true,
   349  		},
   350  		{
   351  			map[interface{}]interface{}{"a": map[interface{}]interface{}{"b": 1}},
   352  			map[string]interface{}{"a": map[string]interface{}{"b": 1}},
   353  			true,
   354  		},
   355  		{
   356  			map[string]interface{}{"a": map[string]interface{}{"b": 1}},
   357  			map[string]interface{}{"a": map[string]interface{}{"b": 1}},
   358  			false,
   359  		},
   360  		{
   361  			[]interface{}{map[interface{}]interface{}{1: "a", 2: "b"}},
   362  			[]interface{}{map[string]interface{}{"1": "a", "2": "b"}},
   363  			false,
   364  		},
   365  	}
   366  
   367  	for i, c := range cases {
   368  		res, replaced := stringifyMapKeys(c.input)
   369  
   370  		if c.replaced != replaced {
   371  			t.Fatalf("[%d] Replaced mismatch: %t", i, replaced)
   372  		}
   373  		if !c.replaced {
   374  			res = c.input
   375  		}
   376  		if !reflect.DeepEqual(res, c.want) {
   377  			t.Errorf("[%d] given %q\nwant: %q\n got: %q", i, c.input, c.want, res)
   378  		}
   379  	}
   380  }
   381  
   382  func BenchmarkFrontmatterTags(b *testing.B) {
   383  
   384  	for _, frontmatter := range []string{"JSON", "YAML", "YAML2", "TOML"} {
   385  		for i := 1; i < 60; i += 20 {
   386  			doBenchmarkFrontmatter(b, frontmatter, i)
   387  		}
   388  	}
   389  }
   390  
   391  func BenchmarkStringifyMapKeysStringsOnlyInterfaceMaps(b *testing.B) {
   392  	maps := make([]map[interface{}]interface{}, b.N)
   393  	for i := 0; i < b.N; i++ {
   394  		maps[i] = map[interface{}]interface{}{
   395  			"a": map[interface{}]interface{}{
   396  				"b": 32,
   397  				"c": 43,
   398  				"d": map[interface{}]interface{}{
   399  					"b": 32,
   400  					"c": 43,
   401  				},
   402  			},
   403  			"b": []interface{}{"a", "b"},
   404  			"c": "d",
   405  		}
   406  	}
   407  	b.ResetTimer()
   408  	for i := 0; i < b.N; i++ {
   409  		stringifyMapKeys(maps[i])
   410  	}
   411  }
   412  
   413  func BenchmarkStringifyMapKeysStringsOnlyStringMaps(b *testing.B) {
   414  	m := map[string]interface{}{
   415  		"a": map[string]interface{}{
   416  			"b": 32,
   417  			"c": 43,
   418  			"d": map[string]interface{}{
   419  				"b": 32,
   420  				"c": 43,
   421  			},
   422  		},
   423  		"b": []interface{}{"a", "b"},
   424  		"c": "d",
   425  	}
   426  
   427  	b.ResetTimer()
   428  	for i := 0; i < b.N; i++ {
   429  		stringifyMapKeys(m)
   430  	}
   431  }
   432  
   433  func BenchmarkStringifyMapKeysIntegers(b *testing.B) {
   434  	maps := make([]map[interface{}]interface{}, b.N)
   435  	for i := 0; i < b.N; i++ {
   436  		maps[i] = map[interface{}]interface{}{
   437  			1: map[interface{}]interface{}{
   438  				4: 32,
   439  				5: 43,
   440  				6: map[interface{}]interface{}{
   441  					7: 32,
   442  					8: 43,
   443  				},
   444  			},
   445  			2: []interface{}{"a", "b"},
   446  			3: "d",
   447  		}
   448  	}
   449  	b.ResetTimer()
   450  	for i := 0; i < b.N; i++ {
   451  		stringifyMapKeys(maps[i])
   452  	}
   453  }
   454  func doBenchmarkFrontmatter(b *testing.B, fileformat string, numTags int) {
   455  	yamlTemplate := `---
   456  name: "Tags"
   457  tags:
   458  %s
   459  ---
   460  `
   461  
   462  	yaml2Template := `---
   463  name: "Tags"
   464  tags: %s
   465  ---
   466  `
   467  	tomlTemplate := `+++
   468  name = "Tags"
   469  tags = %s
   470  +++
   471  `
   472  
   473  	jsonTemplate := `{
   474  	"name": "Tags",
   475  	"tags": [
   476  		%s
   477  	]
   478  }`
   479  	name := fmt.Sprintf("%s:%d", fileformat, numTags)
   480  	b.Run(name, func(b *testing.B) {
   481  		tags := make([]string, numTags)
   482  		var (
   483  			tagsStr             string
   484  			frontmatterTemplate string
   485  		)
   486  		for i := 0; i < numTags; i++ {
   487  			tags[i] = fmt.Sprintf("Hugo %d", i+1)
   488  		}
   489  		if fileformat == "TOML" {
   490  			frontmatterTemplate = tomlTemplate
   491  			tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1)
   492  		} else if fileformat == "JSON" {
   493  			frontmatterTemplate = jsonTemplate
   494  			tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1)
   495  		} else if fileformat == "YAML2" {
   496  			frontmatterTemplate = yaml2Template
   497  			tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1)
   498  		} else {
   499  			frontmatterTemplate = yamlTemplate
   500  			for _, tag := range tags {
   501  				tagsStr += "\n- " + tag
   502  			}
   503  		}
   504  
   505  		frontmatter := fmt.Sprintf(frontmatterTemplate, tagsStr)
   506  
   507  		p := page{frontmatter: []byte(frontmatter)}
   508  
   509  		b.ResetTimer()
   510  		for i := 0; i < b.N; i++ {
   511  			meta, err := p.Metadata()
   512  			if err != nil {
   513  				b.Fatal(err)
   514  			}
   515  			if meta == nil {
   516  				b.Fatal("Meta is nil")
   517  			}
   518  		}
   519  	})
   520  }