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) TestMarshal(c *C) { 403 defer os.Setenv("TZ", os.Getenv("TZ")) 404 os.Setenv("TZ", "UTC") 405 for i, item := range marshalTests { 406 c.Logf("test %d: %q", i, item.data) 407 data, err := yaml.Marshal(item.value) 408 c.Assert(err, IsNil) 409 c.Assert(string(data), Equals, item.data) 410 } 411} 412 413func (s *S) TestEncoderSingleDocument(c *C) { 414 for i, item := range marshalTests { 415 c.Logf("test %d. %q", i, item.data) 416 var buf bytes.Buffer 417 enc := yaml.NewEncoder(&buf) 418 err := enc.Encode(item.value) 419 c.Assert(err, Equals, nil) 420 err = enc.Close() 421 c.Assert(err, Equals, nil) 422 c.Assert(buf.String(), Equals, item.data) 423 } 424} 425 426func (s *S) TestEncoderMultipleDocuments(c *C) { 427 var buf bytes.Buffer 428 enc := yaml.NewEncoder(&buf) 429 err := enc.Encode(map[string]string{"a": "b"}) 430 c.Assert(err, Equals, nil) 431 err = enc.Encode(map[string]string{"c": "d"}) 432 c.Assert(err, Equals, nil) 433 err = enc.Close() 434 c.Assert(err, Equals, nil) 435 c.Assert(buf.String(), Equals, "a: b\n---\nc: d\n") 436} 437 438func (s *S) TestEncoderWriteError(c *C) { 439 enc := yaml.NewEncoder(errorWriter{}) 440 err := enc.Encode(map[string]string{"a": "b"}) 441 c.Assert(err, ErrorMatches, `yaml: write error: some write error`) // Data not flushed yet 442} 443 444type errorWriter struct{} 445 446func (errorWriter) Write([]byte) (int, error) { 447 return 0, fmt.Errorf("some write error") 448} 449 450var marshalErrorTests = []struct { 451 value interface{} 452 error string 453 panic string 454}{{ 455 value: &struct { 456 B int 457 inlineB ",inline" 458 }{1, inlineB{2, inlineC{3}}}, 459 panic: `Duplicated key 'b' in struct struct \{ B int; .*`, 460}, { 461 value: &struct { 462 A int 463 B map[string]int ",inline" 464 }{1, map[string]int{"a": 2}}, 465 panic: `Can't have key "a" in inlined map; conflicts with struct field`, 466}} 467 468func (s *S) TestMarshalErrors(c *C) { 469 for _, item := range marshalErrorTests { 470 if item.panic != "" { 471 c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic) 472 } else { 473 _, err := yaml.Marshal(item.value) 474 c.Assert(err, ErrorMatches, item.error) 475 } 476 } 477} 478 479func (s *S) TestMarshalTypeCache(c *C) { 480 var data []byte 481 var err error 482 func() { 483 type T struct{ A int } 484 data, err = yaml.Marshal(&T{}) 485 c.Assert(err, IsNil) 486 }() 487 func() { 488 type T struct{ B int } 489 data, err = yaml.Marshal(&T{}) 490 c.Assert(err, IsNil) 491 }() 492 c.Assert(string(data), Equals, "b: 0\n") 493} 494 495var marshalerTests = []struct { 496 data string 497 value interface{} 498}{ 499 {"_:\n hi: there\n", map[interface{}]interface{}{"hi": "there"}}, 500 {"_:\n- 1\n- A\n", []interface{}{1, "A"}}, 501 {"_: 10\n", 10}, 502 {"_: null\n", nil}, 503 {"_: BAR!\n", "BAR!"}, 504} 505 506type marshalerType struct { 507 value interface{} 508} 509 510func (o marshalerType) MarshalText() ([]byte, error) { 511 panic("MarshalText called on type with MarshalYAML") 512} 513 514func (o marshalerType) MarshalYAML() (interface{}, error) { 515 return o.value, nil 516} 517 518type marshalerValue struct { 519 Field marshalerType "_" 520} 521 522func (s *S) TestMarshaler(c *C) { 523 for _, item := range marshalerTests { 524 obj := &marshalerValue{} 525 obj.Field.value = item.value 526 data, err := yaml.Marshal(obj) 527 c.Assert(err, IsNil) 528 c.Assert(string(data), Equals, string(item.data)) 529 } 530} 531 532func (s *S) TestMarshalerWholeDocument(c *C) { 533 obj := &marshalerType{} 534 obj.value = map[string]string{"hello": "world!"} 535 data, err := yaml.Marshal(obj) 536 c.Assert(err, IsNil) 537 c.Assert(string(data), Equals, "hello: world!\n") 538} 539 540type failingMarshaler struct{} 541 542func (ft *failingMarshaler) MarshalYAML() (interface{}, error) { 543 return nil, failingErr 544} 545 546func (s *S) TestMarshalerError(c *C) { 547 _, err := yaml.Marshal(&failingMarshaler{}) 548 c.Assert(err, Equals, failingErr) 549} 550 551func (s *S) TestSortedOutput(c *C) { 552 order := []interface{}{ 553 false, 554 true, 555 1, 556 uint(1), 557 1.0, 558 1.1, 559 1.2, 560 2, 561 uint(2), 562 2.0, 563 2.1, 564 "", 565 ".1", 566 ".2", 567 ".a", 568 "1", 569 "2", 570 "a!10", 571 "a/0001", 572 "a/002", 573 "a/3", 574 "a/10", 575 "a/11", 576 "a/0012", 577 "a/100", 578 "a~10", 579 "ab/1", 580 "b/1", 581 "b/01", 582 "b/2", 583 "b/02", 584 "b/3", 585 "b/03", 586 "b1", 587 "b01", 588 "b3", 589 "c2.10", 590 "c10.2", 591 "d1", 592 "d7", 593 "d7abc", 594 "d12", 595 "d12a", 596 } 597 m := make(map[interface{}]int) 598 for _, k := range order { 599 m[k] = 1 600 } 601 data, err := yaml.Marshal(m) 602 c.Assert(err, IsNil) 603 out := "\n" + string(data) 604 last := 0 605 for i, k := range order { 606 repr := fmt.Sprint(k) 607 if s, ok := k.(string); ok { 608 if _, err = strconv.ParseFloat(repr, 32); s == "" || err == nil { 609 repr = `"` + repr + `"` 610 } 611 } 612 index := strings.Index(out, "\n"+repr+":") 613 if index == -1 { 614 c.Fatalf("%#v is not in the output: %#v", k, out) 615 } 616 if index < last { 617 c.Fatalf("%#v was generated before %#v: %q", k, order[i-1], out) 618 } 619 last = index 620 } 621} 622 623func newTime(t time.Time) *time.Time { 624 return &t 625} 626