blob: 7d28c5d7d6ca41cbe360e113772150e6462c7528 [file] [log] [blame]
Han Shena58ca972012-11-16 21:47:261// 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 "reflect"
Han Shenb42cc222013-11-18 20:49:229 "strings"
Han Shena58ca972012-11-16 21:47:2610 "testing"
11 "time"
12)
13
14// Stripped down Atom feed data structures.
15
16func 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
27const 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.
32http://code.google.com/p/pubsubhubbub
33http://code.google.com/p/rietveld/issues/detail?id=155
34
35The 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
41I have tested this by adding debug prints to a local hub
42server and checking that the server got the right publish
43requests.
44
45I can&amp;#39;t quite get the server to work, but I think the bug
46is not in my code. I think that the server expects to be
47able to grab the feed and see the feed&amp;#39;s actual URL in
48the link rel=&amp;quot;self&amp;quot;, but the default value for that drops
49the :port from the URL, and I cannot for the life of me
50figure out how to get the Atom generator deep inside
51django not to do that, or even where it is doing that,
52or even what code is running to generate the Atom feed.
53(I thought I knew but I added some assert False statements
54and it kept running!)
55
56Ignoring that particular problem, I would appreciate
57feedback on the right way to get the two values at
58the 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
64http://codereview.appspot.com/116075/diff/1/2
65
66The fundamental problem was that the tab code was
67not being told what column the text began in, so it
68didn&amp;#39;t know where to put the tab stops. Another problem
69was that some of the code assumed that string byte
70offsets were the same as column offsets, which is only
71true if there are no tabs.
72
73In the process of fixing this, I cleaned up the arguments
74to Fold and ExpandTabs and renamed them Break and
75_ExpandTabs so that I could be sure that I found all the
76call sites. I also wanted to verify that ExpandTabs was
77not being used from outside intra_region_diff.py.
78
79
80</summary></entry></feed> `
81
82type 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
92type 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
101type Link struct {
102 Rel string `xml:"rel,attr,omitempty"`
103 Href string `xml:"href,attr"`
104}
105
106type Person struct {
107 Name string `xml:"name"`
108 URI string `xml:"uri"`
109 Email string `xml:"email"`
110 InnerXML string `xml:",innerxml"`
111}
112
113type Text struct {
114 Type string `xml:"type,attr,omitempty"`
115 Body string `xml:",chardata"`
116}
117
118var 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.
147http://code.google.com/p/pubsubhubbub
148http://code.google.com/p/rietveld/issues/detail?id=155
149
150The 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
156I have tested this by adding debug prints to a local hub
157server and checking that the server got the right publish
158requests.
159
160I can&#39;t quite get the server to work, but I think the bug
161is not in my code. I think that the server expects to be
162able to grab the feed and see the feed&#39;s actual URL in
163the link rel=&quot;self&quot;, but the default value for that drops
164the :port from the URL, and I cannot for the life of me
165figure out how to get the Atom generator deep inside
166django not to do that, or even where it is doing that,
167or even what code is running to generate the Atom feed.
168(I thought I knew but I added some assert False statements
169and it kept running!)
170
171Ignoring that particular problem, I would appreciate
172feedback on the right way to get the two values at
173the 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
194http://codereview.appspot.com/116075/diff/1/2
195
196The fundamental problem was that the tab code was
197not being told what column the text began in, so it
198didn&#39;t know where to put the tab stops. Another problem
199was that some of the code assumed that string byte
200offsets were the same as column offsets, which is only
201true if there are no tabs.
202
203In the process of fixing this, I cleaned up the arguments
204to Fold and ExpandTabs and renamed them Break and
205_ExpandTabs so that I could be sure that I found all the
206call sites. I also wanted to verify that ExpandTabs was
207not being used from outside intra_region_diff.py.
208
209
210`,
211 },
212 },
213 },
214}
215
216const 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
238type PathTestItem struct {
239 Value string
240}
241
242type PathTestA struct {
243 Items []PathTestItem `xml:">Item1"`
244 Before, After string
245}
246
247type PathTestB struct {
248 Other []PathTestItem `xml:"Items>Item1"`
249 Before, After string
250}
251
252type PathTestC struct {
253 Values1 []string `xml:"Items>Item1>Value"`
254 Values2 []string `xml:"Items>Item2>Value"`
255 Before, After string
256}
257
258type PathTestSet struct {
259 Item1 []PathTestItem
260}
261
262type PathTestD struct {
263 Other PathTestSet `xml:"Items"`
264 Before, After string
265}
266
267type PathTestE struct {
268 Underline string `xml:"Items>_>Value"`
269 Before, After string
270}
271
272var 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
280func 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
292type BadPathTestA struct {
293 First string `xml:"items>item1"`
294 Other string `xml:"items>item2"`
295 Second string `xml:"items"`
296}
297
298type BadPathTestB struct {
299 Other string `xml:"items>item2>value"`
300 First string `xml:"items>item1"`
301 Second string `xml:"items>item1>value"`
302}
303
304type BadPathTestC struct {
305 First string
306 Second string `xml:"First"`
307}
308
309type BadPathTestD struct {
310 BadPathEmbeddedA
311 BadPathEmbeddedB
312}
313
314type BadPathEmbeddedA struct {
315 First string
316}
317
318type BadPathEmbeddedB struct {
319 Second string `xml:"First"`
320}
321
322var 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
331func 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
340const OK = "OK"
341const withoutNameTypeData = `
342<?xml version="1.0" charset="utf-8"?>
343<Test3 Attr="OK" />`
344
345type TestThree struct {
346 XMLName Name `xml:"Test3"`
347 Attr string `xml:",attr"`
348}
349
350func 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}
Han Shenb42cc222013-11-18 20:49:22359
360func 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
404type 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
409var 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
464func 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
486func 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
499type TableAttrs struct {
500 TAttr TAttr
501}
502
503type 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
513var 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
581func 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
603func 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}