1// 2// Copyright (c) 2011-2019 Canonical Ltd 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 16package yaml_test 17 18import ( 19 "bytes" 20 "fmt" 21 "math" 22 "strconv" 23 "strings" 24 "time" 25 26 "net" 27 "os" 28 29 . "gopkg.in/check.v1" 30 "gopkg.in/yaml.v3" 31) 32 33var marshalIntTest = 123 34 35var marshalTests = []struct { 36 value interface{} 37 data string 38}{ 39 { 40 nil, 41 "null\n", 42 }, { 43 (*marshalerType)(nil), 44 "null\n", 45 }, { 46 &struct{}{}, 47 "{}\n", 48 }, { 49 map[string]string{"v": "hi"}, 50 "v: hi\n", 51 }, { 52 map[string]interface{}{"v": "hi"}, 53 "v: hi\n", 54 }, { 55 map[string]string{"v": "true"}, 56 "v: \"true\"\n", 57 }, { 58 map[string]string{"v": "false"}, 59 "v: \"false\"\n", 60 }, { 61 map[string]interface{}{"v": true}, 62 "v: true\n", 63 }, { 64 map[string]interface{}{"v": false}, 65 "v: false\n", 66 }, { 67 map[string]interface{}{"v": 10}, 68 "v: 10\n", 69 }, { 70 map[string]interface{}{"v": -10}, 71 "v: -10\n", 72 }, { 73 map[string]uint{"v": 42}, 74 "v: 42\n", 75 }, { 76 map[string]interface{}{"v": int64(4294967296)}, 77 "v: 4294967296\n", 78 }, { 79 map[string]int64{"v": int64(4294967296)}, 80 "v: 4294967296\n", 81 }, { 82 map[string]uint64{"v": 4294967296}, 83 "v: 4294967296\n", 84 }, { 85 map[string]interface{}{"v": "10"}, 86 "v: \"10\"\n", 87 }, { 88 map[string]interface{}{"v": 0.1}, 89 "v: 0.1\n", 90 }, { 91 map[string]interface{}{"v": float64(0.1)}, 92 "v: 0.1\n", 93 }, { 94 map[string]interface{}{"v": float32(0.99)}, 95 "v: 0.99\n", 96 }, { 97 map[string]interface{}{"v": -0.1}, 98 "v: -0.1\n", 99 }, { 100 map[string]interface{}{"v": math.Inf(+1)}, 101 "v: .inf\n", 102 }, { 103 map[string]interface{}{"v": math.Inf(-1)}, 104 "v: -.inf\n", 105 }, { 106 map[string]interface{}{"v": math.NaN()}, 107 "v: .nan\n", 108 }, { 109 map[string]interface{}{"v": nil}, 110 "v: null\n", 111 }, { 112 map[string]interface{}{"v": ""}, 113 "v: \"\"\n", 114 }, { 115 map[string][]string{"v": []string{"A", "B"}}, 116 "v:\n - A\n - B\n", 117 }, { 118 map[string][]string{"v": []string{"A", "B\nC"}}, 119 "v:\n - A\n - |-\n B\n C\n", 120 }, { 121 map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}}, 122 "v:\n - A\n - 1\n - B:\n - 2\n - 3\n", 123 }, { 124 map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}, 125 "a:\n b: c\n", 126 }, { 127 map[string]interface{}{"a": "-"}, 128 "a: '-'\n", 129 }, 130 131 // Simple values. 132 { 133 &marshalIntTest, 134 "123\n", 135 }, 136 137 // Structures 138 { 139 &struct{ Hello string }{"world"}, 140 "hello: world\n", 141 }, { 142 &struct { 143 A struct { 144 B string 145 } 146 }{struct{ B string }{"c"}}, 147 "a:\n b: c\n", 148 }, { 149 &struct { 150 A *struct { 151 B string 152 } 153 }{&struct{ B string }{"c"}}, 154 "a:\n b: c\n", 155 }, { 156 &struct { 157 A *struct { 158 B string 159 } 160 }{}, 161 "a: null\n", 162 }, { 163 &struct{ A int }{1}, 164 "a: 1\n", 165 }, { 166 &struct{ A []int }{[]int{1, 2}}, 167 "a:\n - 1\n - 2\n", 168 }, { 169 &struct{ A [2]int }{[2]int{1, 2}}, 170 "a:\n - 1\n - 2\n", 171 }, { 172 &struct { 173 B int "a" 174 }{1}, 175 "a: 1\n", 176 }, { 177 &struct{ A bool }{true}, 178 "a: true\n", 179 }, { 180 &struct{ A string }{"true"}, 181 "a: \"true\"\n", 182 }, { 183 &struct{ A string }{"off"}, 184 "a: \"off\"\n", 185 }, 186 187 // Conditional flag 188 { 189 &struct { 190 A int "a,omitempty" 191 B int "b,omitempty" 192 }{1, 0}, 193 "a: 1\n", 194 }, { 195 &struct { 196 A int "a,omitempty" 197 B int "b,omitempty" 198 }{0, 0}, 199 "{}\n", 200 }, { 201 &struct { 202 A *struct{ X, y int } "a,omitempty,flow" 203 }{&struct{ X, y int }{1, 2}}, 204 "a: {x: 1}\n", 205 }, { 206 &struct { 207 A *struct{ X, y int } "a,omitempty,flow" 208 }{nil}, 209 "{}\n", 210 }, { 211 &struct { 212 A *struct{ X, y int } "a,omitempty,flow" 213 }{&struct{ X, y int }{}}, 214 "a: {x: 0}\n", 215 }, { 216 &struct { 217 A struct{ X, y int } "a,omitempty,flow" 218 }{struct{ X, y int }{1, 2}}, 219 "a: {x: 1}\n", 220 }, { 221 &struct { 222 A struct{ X, y int } "a,omitempty,flow" 223 }{struct{ X, y int }{0, 1}}, 224 "{}\n", 225 }, { 226 &struct { 227 A float64 "a,omitempty" 228 B float64 "b,omitempty" 229 }{1, 0}, 230 "a: 1\n", 231 }, 232 { 233 &struct { 234 T1 time.Time "t1,omitempty" 235 T2 time.Time "t2,omitempty" 236 T3 *time.Time "t3,omitempty" 237 T4 *time.Time "t4,omitempty" 238 }{ 239 T2: time.Date(2018, 1, 9, 10, 40, 47, 0, time.UTC), 240 T4: newTime(time.Date(2098, 1, 9, 10, 40, 47, 0, time.UTC)), 241 }, 242 "t2: 2018-01-09T10:40:47Z\nt4: 2098-01-09T10:40:47Z\n", 243 }, 244 // Nil interface that implements Marshaler. 245 { 246 map[string]yaml.Marshaler{ 247 "a": nil, 248 }, 249 "a: null\n", 250 }, 251 252 // Flow flag 253 { 254 &struct { 255 A []int "a,flow" 256 }{[]int{1, 2}}, 257 "a: [1, 2]\n", 258 }, { 259 &struct { 260 A map[string]string "a,flow" 261 }{map[string]string{"b": "c", "d": "e"}}, 262 "a: {b: c, d: e}\n", 263 }, { 264 &struct { 265 A struct { 266 B, D string 267 } "a,flow" 268 }{struct{ B, D string }{"c", "e"}}, 269 "a: {b: c, d: e}\n", 270 }, { 271 &struct { 272 A string "a,flow" 273 }{"b\nc"}, 274 "a: \"b\\nc\"\n", 275 }, 276 277 // Unexported field 278 { 279 &struct { 280 u int 281 A int 282 }{0, 1}, 283 "a: 1\n", 284 }, 285 286 // Ignored field 287 { 288 &struct { 289 A int 290 B int "-" 291 }{1, 2}, 292 "a: 1\n", 293 }, 294 295 // Struct inlining 296 { 297 &struct { 298 A int 299 C inlineB `yaml:",inline"` 300 }{1, inlineB{2, inlineC{3}}}, 301 "a: 1\nb: 2\nc: 3\n", 302 }, 303 // Struct inlining as a pointer 304 { 305 &struct { 306 A int 307 C *inlineB `yaml:",inline"` 308 }{1, &inlineB{2, inlineC{3}}}, 309 "a: 1\nb: 2\nc: 3\n", 310 }, { 311 &struct { 312 A int 313 C *inlineB `yaml:",inline"` 314 }{1, nil}, 315 "a: 1\n", 316 }, { 317 &struct { 318 A int 319 D *inlineD `yaml:",inline"` 320 }{1, &inlineD{&inlineC{3}, 4}}, 321 "a: 1\nc: 3\nd: 4\n", 322 }, 323 324 // Map inlining 325 { 326 &struct { 327 A int 328 C map[string]int `yaml:",inline"` 329 }{1, map[string]int{"b": 2, "c": 3}}, 330 "a: 1\nb: 2\nc: 3\n", 331 }, 332 333 // Duration 334 { 335 map[string]time.Duration{"a": 3 * time.Second}, 336 "a: 3s\n", 337 }, 338 339 // Issue #24: bug in map merging logic. 340 { 341 map[string]string{"a": "<foo>"}, 342 "a: <foo>\n", 343 }, 344 345 // Issue #34: marshal unsupported base 60 floats quoted for compatibility 346 // with old YAML 1.1 parsers. 347 { 348 map[string]string{"a": "1:1"}, 349 "a: \"1:1\"\n", 350 }, 351 352 // Binary data. 353 { 354 map[string]string{"a": "\x00"}, 355 "a: \"\\0\"\n", 356 }, { 357 map[string]string{"a": "\x80\x81\x82"}, 358 "a: !!binary gIGC\n", 359 }, { 360 map[string]string{"a": strings.Repeat("\x90", 54)}, 361 "a: !!binary |\n " + strings.Repeat("kJCQ", 17) + "kJ\n CQ\n", 362 }, 363 364 // Encode unicode as utf-8 rather than in escaped form. 365 { 366 map[string]string{"a": "你好"}, 367 "a: 你好\n", 368 }, 369 370 // Support encoding.TextMarshaler. 371 { 372 map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)}, 373 "a: 1.2.3.4\n", 374 }, 375 // time.Time gets a timestamp tag. 376 { 377 map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC)}, 378 "a: 2015-02-24T18:19:39Z\n", 379 }, 380 { 381 map[string]*time.Time{"a": newTime(time.Date(2015, 2, 24, 18, 19, 39, 0, time.UTC))}, 382 "a: 2015-02-24T18:19:39Z\n", 383 }, 384 { 385 // This is confirmed to be properly decoded in Python (libyaml) without a timestamp tag. 386 map[string]time.Time{"a": time.Date(2015, 2, 24, 18, 19, 39, 123456789, time.FixedZone("FOO", -3*60*60))}, 387 "a: 2015-02-24T18:19:39.123456789-03:00\n", 388 }, 389 // Ensure timestamp-like strings are quoted. 390 { 391 map[string]string{"a": "2015-02-24T18:19:39Z"}, 392 "a: \"2015-02-24T18:19:39Z\"\n", 393 }, 394 395 // Ensure strings containing ": " are quoted (reported as PR #43, but not reproducible). 396 { 397 map[string]string{"a": "b: c"}, 398 "a: 'b: c'\n", 399 }, 400 401 // Containing hash mark ('#') in string should be quoted 402 { 403 map[string]string{"a": "Hello #comment"}, 404 "a: 'Hello #comment'\n", 405 }, 406 { 407 map[string]string{"a": "你好 #comment"}, 408 "a: '你好 #comment'\n", 409 }, 410 411 // Ensure MarshalYAML also gets called on the result of MarshalYAML itself. 412 { 413 &marshalerType{marshalerType{true}}, 414 "true\n", 415 }, { 416 &marshalerType{&marshalerType{true}}, 417 "true\n", 418 }, 419 420 // Check indentation of maps inside sequences inside maps. 421 { 422 map[string]interface{}{"a": map[string]interface{}{"b": []map[string]int{{"c": 1, "d": 2}}}}, 423 "a:\n b:\n - c: 1\n d: 2\n", 424 }, 425 426 // Strings with tabs were disallowed as literals (issue #471). 427 { 428 map[string]string{"a": "\tB\n\tC\n"}, 429 "a: |\n \tB\n \tC\n", 430 }, 431 432 // Ensure that strings do not wrap 433 { 434 map[string]string{"a": "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 "}, 435 "a: 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 '\n", 436 }, 437 438 // yaml.Node 439 { 440 &struct { 441 Value yaml.Node 442 }{ 443 yaml.Node{ 444 Kind: yaml.ScalarNode, 445 Tag: "!!str", 446 Value: "foo", 447 Style: yaml.SingleQuotedStyle, 448 }, 449 }, 450 "value: 'foo'\n", 451 }, { 452 yaml.Node{ 453 Kind: yaml.ScalarNode, 454 Tag: "!!str", 455 Value: "foo", 456 Style: yaml.SingleQuotedStyle, 457 }, 458 "'foo'\n", 459 }, 460 461 // Enforced tagging with shorthand notation (issue #616). 462 { 463 &struct { 464 Value yaml.Node 465 }{ 466 yaml.Node{ 467 Kind: yaml.ScalarNode, 468 Style: yaml.TaggedStyle, 469 Value: "foo", 470 Tag: "!!str", 471 }, 472 }, 473 "value: !!str foo\n", 474 }, { 475 &struct { 476 Value yaml.Node 477 }{ 478 yaml.Node{ 479 Kind: yaml.MappingNode, 480 Style: yaml.TaggedStyle, 481 Tag: "!!map", 482 }, 483 }, 484 "value: !!map {}\n", 485 }, { 486 &struct { 487 Value yaml.Node 488 }{ 489 yaml.Node{ 490 Kind: yaml.SequenceNode, 491 Style: yaml.TaggedStyle, 492 Tag: "!!seq", 493 }, 494 }, 495 "value: !!seq []\n", 496 }, 497} 498 499func (s *S) TestMarshal(c *C) { 500 defer os.Setenv("TZ", os.Getenv("TZ")) 501 os.Setenv("TZ", "UTC") 502 for i, item := range marshalTests { 503 c.Logf("test %d: %q", i, item.data) 504 data, err := yaml.Marshal(item.value) 505 c.Assert(err, IsNil) 506 c.Assert(string(data), Equals, item.data) 507 } 508} 509 510func (s *S) TestEncoderSingleDocument(c *C) { 511 for i, item := range marshalTests { 512 c.Logf("test %d. %q", i, item.data) 513 var buf bytes.Buffer 514 enc := yaml.NewEncoder(&buf) 515 err := enc.Encode(item.value) 516 c.Assert(err, Equals, nil) 517 err = enc.Close() 518 c.Assert(err, Equals, nil) 519 c.Assert(buf.String(), Equals, item.data) 520 } 521} 522 523func (s *S) TestEncoderMultipleDocuments(c *C) { 524 var buf bytes.Buffer 525 enc := yaml.NewEncoder(&buf) 526 err := enc.Encode(map[string]string{"a": "b"}) 527 c.Assert(err, Equals, nil) 528 err = enc.Encode(map[string]string{"c": "d"}) 529 c.Assert(err, Equals, nil) 530 err = enc.Close() 531 c.Assert(err, Equals, nil) 532 c.Assert(buf.String(), Equals, "a: b\n---\nc: d\n") 533} 534 535func (s *S) TestEncoderWriteError(c *C) { 536 enc := yaml.NewEncoder(errorWriter{}) 537 err := enc.Encode(map[string]string{"a": "b"}) 538 c.Assert(err, ErrorMatches, `yaml: write error: some write error`) // Data not flushed yet 539} 540 541type errorWriter struct{} 542 543func (errorWriter) Write([]byte) (int, error) { 544 return 0, fmt.Errorf("some write error") 545} 546 547var marshalErrorTests = []struct { 548 value interface{} 549 error string 550 panic string 551}{{ 552 value: &struct { 553 B int 554 inlineB ",inline" 555 }{1, inlineB{2, inlineC{3}}}, 556 panic: `duplicated key 'b' in struct struct \{ B int; .*`, 557}, { 558 value: &struct { 559 A int 560 B map[string]int ",inline" 561 }{1, map[string]int{"a": 2}}, 562 panic: `cannot have key "a" in inlined map: conflicts with struct field`, 563}} 564 565func (s *S) TestMarshalErrors(c *C) { 566 for _, item := range marshalErrorTests { 567 if item.panic != "" { 568 c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic) 569 } else { 570 _, err := yaml.Marshal(item.value) 571 c.Assert(err, ErrorMatches, item.error) 572 } 573 } 574} 575 576func (s *S) TestMarshalTypeCache(c *C) { 577 var data []byte 578 var err error 579 func() { 580 type T struct{ A int } 581 data, err = yaml.Marshal(&T{}) 582 c.Assert(err, IsNil) 583 }() 584 func() { 585 type T struct{ B int } 586 data, err = yaml.Marshal(&T{}) 587 c.Assert(err, IsNil) 588 }() 589 c.Assert(string(data), Equals, "b: 0\n") 590} 591 592var marshalerTests = []struct { 593 data string 594 value interface{} 595}{ 596 {"_:\n hi: there\n", map[interface{}]interface{}{"hi": "there"}}, 597 {"_:\n - 1\n - A\n", []interface{}{1, "A"}}, 598 {"_: 10\n", 10}, 599 {"_: null\n", nil}, 600 {"_: BAR!\n", "BAR!"}, 601} 602 603type marshalerType struct { 604 value interface{} 605} 606 607func (o marshalerType) MarshalText() ([]byte, error) { 608 panic("MarshalText called on type with MarshalYAML") 609} 610 611func (o marshalerType) MarshalYAML() (interface{}, error) { 612 return o.value, nil 613} 614 615type marshalerValue struct { 616 Field marshalerType "_" 617} 618 619func (s *S) TestMarshaler(c *C) { 620 for _, item := range marshalerTests { 621 obj := &marshalerValue{} 622 obj.Field.value = item.value 623 data, err := yaml.Marshal(obj) 624 c.Assert(err, IsNil) 625 c.Assert(string(data), Equals, string(item.data)) 626 } 627} 628 629func (s *S) TestMarshalerWholeDocument(c *C) { 630 obj := &marshalerType{} 631 obj.value = map[string]string{"hello": "world!"} 632 data, err := yaml.Marshal(obj) 633 c.Assert(err, IsNil) 634 c.Assert(string(data), Equals, "hello: world!\n") 635} 636 637type failingMarshaler struct{} 638 639func (ft *failingMarshaler) MarshalYAML() (interface{}, error) { 640 return nil, failingErr 641} 642 643func (s *S) TestMarshalerError(c *C) { 644 _, err := yaml.Marshal(&failingMarshaler{}) 645 c.Assert(err, Equals, failingErr) 646} 647 648func (s *S) TestSetIndent(c *C) { 649 var buf bytes.Buffer 650 enc := yaml.NewEncoder(&buf) 651 enc.SetIndent(8) 652 err := enc.Encode(map[string]interface{}{"a": map[string]interface{}{"b": map[string]string{"c": "d"}}}) 653 c.Assert(err, Equals, nil) 654 err = enc.Close() 655 c.Assert(err, Equals, nil) 656 c.Assert(buf.String(), Equals, "a:\n b:\n c: d\n") 657} 658 659func (s *S) TestSortedOutput(c *C) { 660 order := []interface{}{ 661 false, 662 true, 663 1, 664 uint(1), 665 1.0, 666 1.1, 667 1.2, 668 2, 669 uint(2), 670 2.0, 671 2.1, 672 "", 673 ".1", 674 ".2", 675 ".a", 676 "1", 677 "2", 678 "a!10", 679 "a/0001", 680 "a/002", 681 "a/3", 682 "a/10", 683 "a/11", 684 "a/0012", 685 "a/100", 686 "a~10", 687 "ab/1", 688 "b/1", 689 "b/01", 690 "b/2", 691 "b/02", 692 "b/3", 693 "b/03", 694 "b1", 695 "b01", 696 "b3", 697 "c2.10", 698 "c10.2", 699 "d1", 700 "d7", 701 "d7abc", 702 "d12", 703 "d12a", 704 "e2b", 705 "e4b", 706 "e21a", 707 } 708 m := make(map[interface{}]int) 709 for _, k := range order { 710 m[k] = 1 711 } 712 data, err := yaml.Marshal(m) 713 c.Assert(err, IsNil) 714 out := "\n" + string(data) 715 last := 0 716 for i, k := range order { 717 repr := fmt.Sprint(k) 718 if s, ok := k.(string); ok { 719 if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil { 720 repr = `"` + repr + `"` 721 } 722 } 723 index := strings.Index(out, "\n"+repr+":") 724 if index == -1 { 725 c.Fatalf("%#v is not in the output: %#v", k, out) 726 } 727 if index < last { 728 c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out) 729 } 730 last = index 731 } 732} 733 734func newTime(t time.Time) *time.Time { 735 return &t 736} 737