1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package xml 6 7import ( 8 "io" 9 "reflect" 10 "strings" 11 "testing" 12 "time" 13) 14 15// Stripped down Atom feed data structures. 16 17func TestUnmarshalFeed(t *testing.T) { 18 var f Feed 19 if err := Unmarshal([]byte(atomFeedString), &f); err != nil { 20 t.Fatalf("Unmarshal: %s", err) 21 } 22 if !reflect.DeepEqual(f, atomFeed) { 23 t.Fatalf("have %#v\nwant %#v", f, atomFeed) 24 } 25} 26 27// hget http://codereview.appspot.com/rss/mine/rsc 28const atomFeedString = ` 29<?xml version="1.0" encoding="utf-8"?> 30<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us" updated="2009-10-04T01:35:58+00:00"><title>Code Review - My issues</title><link href="http://codereview.appspot.com/" rel="alternate"></link><link href="http://codereview.appspot.com/rss/mine/rsc" rel="self"></link><id>http://codereview.appspot.com/</id><author><name>rietveld<></name></author><entry><title>rietveld: an attempt at pubsubhubbub 31</title><link href="http://codereview.appspot.com/126085" rel="alternate"></link><updated>2009-10-04T01:35:58+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:134d9179c41f806be79b3a5f7877d19a</id><summary type="html"> 32 An attempt at adding pubsubhubbub support to Rietveld. 33http://code.google.com/p/pubsubhubbub 34http://code.google.com/p/rietveld/issues/detail?id=155 35 36The server side of the protocol is trivial: 37 1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all 38 feeds that will be pubsubhubbubbed. 39 2. every time one of those feeds changes, tell the hub 40 with a simple POST request. 41 42I have tested this by adding debug prints to a local hub 43server and checking that the server got the right publish 44requests. 45 46I can&#39;t quite get the server to work, but I think the bug 47is not in my code. I think that the server expects to be 48able to grab the feed and see the feed&#39;s actual URL in 49the link rel=&quot;self&quot;, but the default value for that drops 50the :port from the URL, and I cannot for the life of me 51figure out how to get the Atom generator deep inside 52django not to do that, or even where it is doing that, 53or even what code is running to generate the Atom feed. 54(I thought I knew but I added some assert False statements 55and it kept running!) 56 57Ignoring that particular problem, I would appreciate 58feedback on the right way to get the two values at 59the top of feeds.py marked NOTE(rsc). 60 61 62</summary></entry><entry><title>rietveld: correct tab handling 63</title><link href="http://codereview.appspot.com/124106" rel="alternate"></link><updated>2009-10-03T23:02:17+00:00</updated><author><name>email-address-removed</name></author><id>urn:md5:0a2a4f19bb815101f0ba2904aed7c35a</id><summary type="html"> 64 This fixes the buggy tab rendering that can be seen at 65http://codereview.appspot.com/116075/diff/1/2 66 67The fundamental problem was that the tab code was 68not being told what column the text began in, so it 69didn&#39;t know where to put the tab stops. Another problem 70was that some of the code assumed that string byte 71offsets were the same as column offsets, which is only 72true if there are no tabs. 73 74In the process of fixing this, I cleaned up the arguments 75to Fold and ExpandTabs and renamed them Break and 76_ExpandTabs so that I could be sure that I found all the 77call sites. I also wanted to verify that ExpandTabs was 78not being used from outside intra_region_diff.py. 79 80 81</summary></entry></feed> ` 82 83type Feed struct { 84 XMLName Name `xml:"http://www.w3.org/2005/Atom feed"` 85 Title string `xml:"title"` 86 Id string `xml:"id"` 87 Link []Link `xml:"link"` 88 Updated time.Time `xml:"updated,attr"` 89 Author Person `xml:"author"` 90 Entry []Entry `xml:"entry"` 91} 92 93type Entry struct { 94 Title string `xml:"title"` 95 Id string `xml:"id"` 96 Link []Link `xml:"link"` 97 Updated time.Time `xml:"updated"` 98 Author Person `xml:"author"` 99 Summary Text `xml:"summary"` 100} 101 102type Link struct { 103 Rel string `xml:"rel,attr,omitempty"` 104 Href string `xml:"href,attr"` 105} 106 107type Person struct { 108 Name string `xml:"name"` 109 URI string `xml:"uri"` 110 Email string `xml:"email"` 111 InnerXML string `xml:",innerxml"` 112} 113 114type Text struct { 115 Type string `xml:"type,attr,omitempty"` 116 Body string `xml:",chardata"` 117} 118 119var atomFeed = Feed{ 120 XMLName: Name{"http://www.w3.org/2005/Atom", "feed"}, 121 Title: "Code Review - My issues", 122 Link: []Link{ 123 {Rel: "alternate", Href: "http://codereview.appspot.com/"}, 124 {Rel: "self", Href: "http://codereview.appspot.com/rss/mine/rsc"}, 125 }, 126 Id: "http://codereview.appspot.com/", 127 Updated: ParseTime("2009-10-04T01:35:58+00:00"), 128 Author: Person{ 129 Name: "rietveld<>", 130 InnerXML: "<name>rietveld<></name>", 131 }, 132 Entry: []Entry{ 133 { 134 Title: "rietveld: an attempt at pubsubhubbub\n", 135 Link: []Link{ 136 {Rel: "alternate", Href: "http://codereview.appspot.com/126085"}, 137 }, 138 Updated: ParseTime("2009-10-04T01:35:58+00:00"), 139 Author: Person{ 140 Name: "email-address-removed", 141 InnerXML: "<name>email-address-removed</name>", 142 }, 143 Id: "urn:md5:134d9179c41f806be79b3a5f7877d19a", 144 Summary: Text{ 145 Type: "html", 146 Body: ` 147 An attempt at adding pubsubhubbub support to Rietveld. 148http://code.google.com/p/pubsubhubbub 149http://code.google.com/p/rietveld/issues/detail?id=155 150 151The server side of the protocol is trivial: 152 1. add a <link rel="hub" href="hub-server"> tag to all 153 feeds that will be pubsubhubbubbed. 154 2. every time one of those feeds changes, tell the hub 155 with a simple POST request. 156 157I have tested this by adding debug prints to a local hub 158server and checking that the server got the right publish 159requests. 160 161I can't quite get the server to work, but I think the bug 162is not in my code. I think that the server expects to be 163able to grab the feed and see the feed's actual URL in 164the link rel="self", but the default value for that drops 165the :port from the URL, and I cannot for the life of me 166figure out how to get the Atom generator deep inside 167django not to do that, or even where it is doing that, 168or even what code is running to generate the Atom feed. 169(I thought I knew but I added some assert False statements 170and it kept running!) 171 172Ignoring that particular problem, I would appreciate 173feedback on the right way to get the two values at 174the top of feeds.py marked NOTE(rsc). 175 176 177`, 178 }, 179 }, 180 { 181 Title: "rietveld: correct tab handling\n", 182 Link: []Link{ 183 {Rel: "alternate", Href: "http://codereview.appspot.com/124106"}, 184 }, 185 Updated: ParseTime("2009-10-03T23:02:17+00:00"), 186 Author: Person{ 187 Name: "email-address-removed", 188 InnerXML: "<name>email-address-removed</name>", 189 }, 190 Id: "urn:md5:0a2a4f19bb815101f0ba2904aed7c35a", 191 Summary: Text{ 192 Type: "html", 193 Body: ` 194 This fixes the buggy tab rendering that can be seen at 195http://codereview.appspot.com/116075/diff/1/2 196 197The fundamental problem was that the tab code was 198not being told what column the text began in, so it 199didn't know where to put the tab stops. Another problem 200was that some of the code assumed that string byte 201offsets were the same as column offsets, which is only 202true if there are no tabs. 203 204In the process of fixing this, I cleaned up the arguments 205to Fold and ExpandTabs and renamed them Break and 206_ExpandTabs so that I could be sure that I found all the 207call sites. I also wanted to verify that ExpandTabs was 208not being used from outside intra_region_diff.py. 209 210 211`, 212 }, 213 }, 214 }, 215} 216 217const pathTestString = ` 218<Result> 219 <Before>1</Before> 220 <Items> 221 <Item1> 222 <Value>A</Value> 223 </Item1> 224 <Item2> 225 <Value>B</Value> 226 </Item2> 227 <Item1> 228 <Value>C</Value> 229 <Value>D</Value> 230 </Item1> 231 <_> 232 <Value>E</Value> 233 </_> 234 </Items> 235 <After>2</After> 236</Result> 237` 238 239type PathTestItem struct { 240 Value string 241} 242 243type PathTestA struct { 244 Items []PathTestItem `xml:">Item1"` 245 Before, After string 246} 247 248type PathTestB struct { 249 Other []PathTestItem `xml:"Items>Item1"` 250 Before, After string 251} 252 253type PathTestC struct { 254 Values1 []string `xml:"Items>Item1>Value"` 255 Values2 []string `xml:"Items>Item2>Value"` 256 Before, After string 257} 258 259type PathTestSet struct { 260 Item1 []PathTestItem 261} 262 263type PathTestD struct { 264 Other PathTestSet `xml:"Items"` 265 Before, After string 266} 267 268type PathTestE struct { 269 Underline string `xml:"Items>_>Value"` 270 Before, After string 271} 272 273var pathTests = []interface{}{ 274 &PathTestA{Items: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"}, 275 &PathTestB{Other: []PathTestItem{{"A"}, {"D"}}, Before: "1", After: "2"}, 276 &PathTestC{Values1: []string{"A", "C", "D"}, Values2: []string{"B"}, Before: "1", After: "2"}, 277 &PathTestD{Other: PathTestSet{Item1: []PathTestItem{{"A"}, {"D"}}}, Before: "1", After: "2"}, 278 &PathTestE{Underline: "E", Before: "1", After: "2"}, 279} 280 281func TestUnmarshalPaths(t *testing.T) { 282 for _, pt := range pathTests { 283 v := reflect.New(reflect.TypeOf(pt).Elem()).Interface() 284 if err := Unmarshal([]byte(pathTestString), v); err != nil { 285 t.Fatalf("Unmarshal: %s", err) 286 } 287 if !reflect.DeepEqual(v, pt) { 288 t.Fatalf("have %#v\nwant %#v", v, pt) 289 } 290 } 291} 292 293type BadPathTestA struct { 294 First string `xml:"items>item1"` 295 Other string `xml:"items>item2"` 296 Second string `xml:"items"` 297} 298 299type BadPathTestB struct { 300 Other string `xml:"items>item2>value"` 301 First string `xml:"items>item1"` 302 Second string `xml:"items>item1>value"` 303} 304 305type BadPathTestC struct { 306 First string 307 Second string `xml:"First"` 308} 309 310type BadPathTestD struct { 311 BadPathEmbeddedA 312 BadPathEmbeddedB 313} 314 315type BadPathEmbeddedA struct { 316 First string 317} 318 319type BadPathEmbeddedB struct { 320 Second string `xml:"First"` 321} 322 323var badPathTests = []struct { 324 v, e interface{} 325}{ 326 {&BadPathTestA{}, &TagPathError{reflect.TypeOf(BadPathTestA{}), "First", "items>item1", "Second", "items"}}, 327 {&BadPathTestB{}, &TagPathError{reflect.TypeOf(BadPathTestB{}), "First", "items>item1", "Second", "items>item1>value"}}, 328 {&BadPathTestC{}, &TagPathError{reflect.TypeOf(BadPathTestC{}), "First", "", "Second", "First"}}, 329 {&BadPathTestD{}, &TagPathError{reflect.TypeOf(BadPathTestD{}), "First", "", "Second", "First"}}, 330} 331 332func TestUnmarshalBadPaths(t *testing.T) { 333 for _, tt := range badPathTests { 334 err := Unmarshal([]byte(pathTestString), tt.v) 335 if !reflect.DeepEqual(err, tt.e) { 336 t.Fatalf("Unmarshal with %#v didn't fail properly:\nhave %#v,\nwant %#v", tt.v, err, tt.e) 337 } 338 } 339} 340 341const OK = "OK" 342const withoutNameTypeData = ` 343<?xml version="1.0" charset="utf-8"?> 344<Test3 Attr="OK" />` 345 346type TestThree struct { 347 XMLName Name `xml:"Test3"` 348 Attr string `xml:",attr"` 349} 350 351func TestUnmarshalWithoutNameType(t *testing.T) { 352 var x TestThree 353 if err := Unmarshal([]byte(withoutNameTypeData), &x); err != nil { 354 t.Fatalf("Unmarshal: %s", err) 355 } 356 if x.Attr != OK { 357 t.Fatalf("have %v\nwant %v", x.Attr, OK) 358 } 359} 360 361func TestUnmarshalAttr(t *testing.T) { 362 type ParamVal struct { 363 Int int `xml:"int,attr"` 364 } 365 366 type ParamPtr struct { 367 Int *int `xml:"int,attr"` 368 } 369 370 type ParamStringPtr struct { 371 Int *string `xml:"int,attr"` 372 } 373 374 x := []byte(`<Param int="1" />`) 375 376 p1 := &ParamPtr{} 377 if err := Unmarshal(x, p1); err != nil { 378 t.Fatalf("Unmarshal: %s", err) 379 } 380 if p1.Int == nil { 381 t.Fatalf("Unmarshal failed in to *int field") 382 } else if *p1.Int != 1 { 383 t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p1.Int, 1) 384 } 385 386 p2 := &ParamVal{} 387 if err := Unmarshal(x, p2); err != nil { 388 t.Fatalf("Unmarshal: %s", err) 389 } 390 if p2.Int != 1 { 391 t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p2.Int, 1) 392 } 393 394 p3 := &ParamStringPtr{} 395 if err := Unmarshal(x, p3); err != nil { 396 t.Fatalf("Unmarshal: %s", err) 397 } 398 if p3.Int == nil { 399 t.Fatalf("Unmarshal failed in to *string field") 400 } else if *p3.Int != "1" { 401 t.Fatalf("Unmarshal with %s failed:\nhave %#v,\n want %#v", x, p3.Int, 1) 402 } 403} 404 405type Tables struct { 406 HTable string `xml:"http://www.w3.org/TR/html4/ table"` 407 FTable string `xml:"http://www.w3schools.com/furniture table"` 408} 409 410var tables = []struct { 411 xml string 412 tab Tables 413 ns string 414}{ 415 { 416 xml: `<Tables>` + 417 `<table xmlns="http://www.w3.org/TR/html4/">hello</table>` + 418 `<table xmlns="http://www.w3schools.com/furniture">world</table>` + 419 `</Tables>`, 420 tab: Tables{"hello", "world"}, 421 }, 422 { 423 xml: `<Tables>` + 424 `<table xmlns="http://www.w3schools.com/furniture">world</table>` + 425 `<table xmlns="http://www.w3.org/TR/html4/">hello</table>` + 426 `</Tables>`, 427 tab: Tables{"hello", "world"}, 428 }, 429 { 430 xml: `<Tables xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/">` + 431 `<f:table>world</f:table>` + 432 `<h:table>hello</h:table>` + 433 `</Tables>`, 434 tab: Tables{"hello", "world"}, 435 }, 436 { 437 xml: `<Tables>` + 438 `<table>bogus</table>` + 439 `</Tables>`, 440 tab: Tables{}, 441 }, 442 { 443 xml: `<Tables>` + 444 `<table>only</table>` + 445 `</Tables>`, 446 tab: Tables{HTable: "only"}, 447 ns: "http://www.w3.org/TR/html4/", 448 }, 449 { 450 xml: `<Tables>` + 451 `<table>only</table>` + 452 `</Tables>`, 453 tab: Tables{FTable: "only"}, 454 ns: "http://www.w3schools.com/furniture", 455 }, 456 { 457 xml: `<Tables>` + 458 `<table>only</table>` + 459 `</Tables>`, 460 tab: Tables{}, 461 ns: "something else entirely", 462 }, 463} 464 465func TestUnmarshalNS(t *testing.T) { 466 for i, tt := range tables { 467 var dst Tables 468 var err error 469 if tt.ns != "" { 470 d := NewDecoder(strings.NewReader(tt.xml)) 471 d.DefaultSpace = tt.ns 472 err = d.Decode(&dst) 473 } else { 474 err = Unmarshal([]byte(tt.xml), &dst) 475 } 476 if err != nil { 477 t.Errorf("#%d: Unmarshal: %v", i, err) 478 continue 479 } 480 want := tt.tab 481 if dst != want { 482 t.Errorf("#%d: dst=%+v, want %+v", i, dst, want) 483 } 484 } 485} 486 487func TestMarshalNS(t *testing.T) { 488 dst := Tables{"hello", "world"} 489 data, err := Marshal(&dst) 490 if err != nil { 491 t.Fatalf("Marshal: %v", err) 492 } 493 want := `<Tables><table xmlns="http://www.w3.org/TR/html4/">hello</table><table xmlns="http://www.w3schools.com/furniture">world</table></Tables>` 494 str := string(data) 495 if str != want { 496 t.Errorf("have: %q\nwant: %q\n", str, want) 497 } 498} 499 500type TableAttrs struct { 501 TAttr TAttr 502} 503 504type TAttr struct { 505 HTable string `xml:"http://www.w3.org/TR/html4/ table,attr"` 506 FTable string `xml:"http://www.w3schools.com/furniture table,attr"` 507 Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"` 508 Other1 string `xml:"http://golang.org/xml/ other,attr,omitempty"` 509 Other2 string `xml:"http://golang.org/xmlfoo/ other,attr,omitempty"` 510 Other3 string `xml:"http://golang.org/json/ other,attr,omitempty"` 511 Other4 string `xml:"http://golang.org/2/json/ other,attr,omitempty"` 512} 513 514var tableAttrs = []struct { 515 xml string 516 tab TableAttrs 517 ns string 518}{ 519 { 520 xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` + 521 `h:table="hello" f:table="world" ` + 522 `/></TableAttrs>`, 523 tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}}, 524 }, 525 { 526 xml: `<TableAttrs><TAttr xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` + 527 `h:table="hello" f:table="world" ` + 528 `/></TableAttrs>`, 529 tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}}, 530 }, 531 { 532 xml: `<TableAttrs><TAttr ` + 533 `h:table="hello" f:table="world" xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` + 534 `/></TableAttrs>`, 535 tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}}, 536 }, 537 { 538 // Default space does not apply to attribute names. 539 xml: `<TableAttrs xmlns="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` + 540 `h:table="hello" table="world" ` + 541 `/></TableAttrs>`, 542 tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}}, 543 }, 544 { 545 // Default space does not apply to attribute names. 546 xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr xmlns="http://www.w3.org/TR/html4/" ` + 547 `table="hello" f:table="world" ` + 548 `/></TableAttrs>`, 549 tab: TableAttrs{TAttr{HTable: "", FTable: "world"}}, 550 }, 551 { 552 xml: `<TableAttrs><TAttr ` + 553 `table="bogus" ` + 554 `/></TableAttrs>`, 555 tab: TableAttrs{}, 556 }, 557 { 558 // Default space does not apply to attribute names. 559 xml: `<TableAttrs xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` + 560 `h:table="hello" table="world" ` + 561 `/></TableAttrs>`, 562 tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}}, 563 ns: "http://www.w3schools.com/furniture", 564 }, 565 { 566 // Default space does not apply to attribute names. 567 xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr ` + 568 `table="hello" f:table="world" ` + 569 `/></TableAttrs>`, 570 tab: TableAttrs{TAttr{HTable: "", FTable: "world"}}, 571 ns: "http://www.w3.org/TR/html4/", 572 }, 573 { 574 xml: `<TableAttrs><TAttr ` + 575 `table="bogus" ` + 576 `/></TableAttrs>`, 577 tab: TableAttrs{}, 578 ns: "something else entirely", 579 }, 580} 581 582func TestUnmarshalNSAttr(t *testing.T) { 583 for i, tt := range tableAttrs { 584 var dst TableAttrs 585 var err error 586 if tt.ns != "" { 587 d := NewDecoder(strings.NewReader(tt.xml)) 588 d.DefaultSpace = tt.ns 589 err = d.Decode(&dst) 590 } else { 591 err = Unmarshal([]byte(tt.xml), &dst) 592 } 593 if err != nil { 594 t.Errorf("#%d: Unmarshal: %v", i, err) 595 continue 596 } 597 want := tt.tab 598 if dst != want { 599 t.Errorf("#%d: dst=%+v, want %+v", i, dst, want) 600 } 601 } 602} 603 604func TestMarshalNSAttr(t *testing.T) { 605 src := TableAttrs{TAttr{"hello", "world", "en_US", "other1", "other2", "other3", "other4"}} 606 data, err := Marshal(&src) 607 if err != nil { 608 t.Fatalf("Marshal: %v", err) 609 } 610 want := `<TableAttrs><TAttr xmlns:html4="http://www.w3.org/TR/html4/" html4:table="hello" xmlns:furniture="http://www.w3schools.com/furniture" furniture:table="world" xml:lang="en_US" xmlns:_xml="http://golang.org/xml/" _xml:other="other1" xmlns:_xmlfoo="http://golang.org/xmlfoo/" _xmlfoo:other="other2" xmlns:json="http://golang.org/json/" json:other="other3" xmlns:json_1="http://golang.org/2/json/" json_1:other="other4"></TAttr></TableAttrs>` 611 str := string(data) 612 if str != want { 613 t.Errorf("Marshal:\nhave: %#q\nwant: %#q\n", str, want) 614 } 615 616 var dst TableAttrs 617 if err := Unmarshal(data, &dst); err != nil { 618 t.Errorf("Unmarshal: %v", err) 619 } 620 621 if dst != src { 622 t.Errorf("Unmarshal = %q, want %q", dst, src) 623 } 624} 625 626type MyCharData struct { 627 body string 628} 629 630func (m *MyCharData) UnmarshalXML(d *Decoder, start StartElement) error { 631 for { 632 t, err := d.Token() 633 if err == io.EOF { // found end of element 634 break 635 } 636 if err != nil { 637 return err 638 } 639 if char, ok := t.(CharData); ok { 640 m.body += string(char) 641 } 642 } 643 return nil 644} 645 646var _ Unmarshaler = (*MyCharData)(nil) 647 648func (m *MyCharData) UnmarshalXMLAttr(attr Attr) error { 649 panic("must not call") 650} 651 652type MyAttr struct { 653 attr string 654} 655 656func (m *MyAttr) UnmarshalXMLAttr(attr Attr) error { 657 m.attr = attr.Value 658 return nil 659} 660 661var _ UnmarshalerAttr = (*MyAttr)(nil) 662 663type MyStruct struct { 664 Data *MyCharData 665 Attr *MyAttr `xml:",attr"` 666 667 Data2 MyCharData 668 Attr2 MyAttr `xml:",attr"` 669} 670 671func TestUnmarshaler(t *testing.T) { 672 xml := `<?xml version="1.0" encoding="utf-8"?> 673 <MyStruct Attr="attr1" Attr2="attr2"> 674 <Data>hello <!-- comment -->world</Data> 675 <Data2>howdy <!-- comment -->world</Data2> 676 </MyStruct> 677 ` 678 679 var m MyStruct 680 if err := Unmarshal([]byte(xml), &m); err != nil { 681 t.Fatal(err) 682 } 683 684 if m.Data == nil || m.Attr == nil || m.Data.body != "hello world" || m.Attr.attr != "attr1" || m.Data2.body != "howdy world" || m.Attr2.attr != "attr2" { 685 t.Errorf("m=%#+v\n", m) 686 } 687} 688