github.com/SDLMoe/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 }