github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/encoding/xml/read_test.go (about)

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