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