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