OLD | NEW |
| (Empty) |
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 template | |
6 | |
7 import ( | |
8 "bytes" | |
9 "encoding/json" | |
10 "fmt" | |
11 "io" | |
12 "io/ioutil" | |
13 "os" | |
14 "strings" | |
15 "testing" | |
16 ) | |
17 | |
18 type Test struct { | |
19 in, out, err string | |
20 } | |
21 | |
22 type T struct { | |
23 Item string | |
24 Value string | |
25 } | |
26 | |
27 type U struct { | |
28 Mp map[string]int | |
29 } | |
30 | |
31 type S struct { | |
32 Header string | |
33 HeaderPtr *string | |
34 Integer int | |
35 IntegerPtr *int | |
36 NilPtr *int | |
37 InnerT T | |
38 InnerPointerT *T | |
39 Data []T | |
40 Pdata []*T | |
41 Empty []*T | |
42 Emptystring string | |
43 Null []*T | |
44 Vec []interface{} | |
45 True bool | |
46 False bool | |
47 Mp map[string]string | |
48 JSON interface{} | |
49 Innermap U | |
50 Stringmap map[string]string | |
51 Ptrmap map[string]*string | |
52 Iface interface{} | |
53 Ifaceptr interface{} | |
54 } | |
55 | |
56 func (s *S) PointerMethod() string { return "ptrmethod!" } | |
57 | |
58 func (s S) ValueMethod() string { return "valmethod!" } | |
59 | |
60 var t1 = T{"ItemNumber1", "ValueNumber1"} | |
61 var t2 = T{"ItemNumber2", "ValueNumber2"} | |
62 | |
63 func uppercase(v interface{}) string { | |
64 s := v.(string) | |
65 t := "" | |
66 for i := 0; i < len(s); i++ { | |
67 c := s[i] | |
68 if 'a' <= c && c <= 'z' { | |
69 c = c + 'A' - 'a' | |
70 } | |
71 t += string(c) | |
72 } | |
73 return t | |
74 } | |
75 | |
76 func plus1(v interface{}) string { | |
77 i := v.(int) | |
78 return fmt.Sprint(i + 1) | |
79 } | |
80 | |
81 func writer(f func(interface{}) string) func(io.Writer, string, ...interface{})
{ | |
82 return func(w io.Writer, format string, v ...interface{}) { | |
83 if len(v) != 1 { | |
84 panic("test writer expected one arg") | |
85 } | |
86 io.WriteString(w, f(v[0])) | |
87 } | |
88 } | |
89 | |
90 func multiword(w io.Writer, format string, value ...interface{}) { | |
91 for _, v := range value { | |
92 fmt.Fprintf(w, "<%v>", v) | |
93 } | |
94 } | |
95 | |
96 func printf(w io.Writer, format string, v ...interface{}) { | |
97 io.WriteString(w, fmt.Sprintf(v[0].(string), v[1:]...)) | |
98 } | |
99 | |
100 var formatters = FormatterMap{ | |
101 "uppercase": writer(uppercase), | |
102 "+1": writer(plus1), | |
103 "multiword": multiword, | |
104 "printf": printf, | |
105 } | |
106 | |
107 var tests = []*Test{ | |
108 // Simple | |
109 {"", "", ""}, | |
110 {"abc", "abc", ""}, | |
111 {"abc\ndef\n", "abc\ndef\n", ""}, | |
112 {" {.meta-left} \n", "{", ""}, | |
113 {" {.meta-right} \n", "}", ""}, | |
114 {" {.space} \n", " ", ""}, | |
115 {" {.tab} \n", "\t", ""}, | |
116 {" {#comment} \n", "", ""}, | |
117 {"\tSome Text\t\n", "\tSome Text\t\n", ""}, | |
118 {" {.meta-right} {.meta-right} {.meta-right} \n", " } } } \n", ""}, | |
119 | |
120 // Variables at top level | |
121 { | |
122 in: "{Header}={Integer}\n", | |
123 | |
124 out: "Header=77\n", | |
125 }, | |
126 | |
127 { | |
128 in: "Pointers: {*HeaderPtr}={*IntegerPtr}\n", | |
129 | |
130 out: "Pointers: Header=77\n", | |
131 }, | |
132 | |
133 { | |
134 in: "Stars but not pointers: {*Header}={*Integer}\n", | |
135 | |
136 out: "Stars but not pointers: Header=77\n", | |
137 }, | |
138 | |
139 { | |
140 in: "nil pointer: {*NilPtr}={*Integer}\n", | |
141 | |
142 out: "nil pointer: <nil>=77\n", | |
143 }, | |
144 | |
145 { | |
146 in: `{"Strings" ":"} {""} {"|"} {"\t\u0123 \x23\\"} {"\"}{\\"}`, | |
147 | |
148 out: "Strings: | \t\u0123 \x23\\ \"}{\\", | |
149 }, | |
150 | |
151 { | |
152 in: "{`Raw strings` `:`} {``} {`|`} {`\\t\\u0123 \\x23\\`} {`}{\
\`}", | |
153 | |
154 out: "Raw strings: | \\t\\u0123 \\x23\\ }{\\", | |
155 }, | |
156 | |
157 { | |
158 in: "Characters: {'a'} {'\\u0123'} {' '} {'{'} {'|'} {'}'}", | |
159 | |
160 out: "Characters: 97 291 32 123 124 125", | |
161 }, | |
162 | |
163 { | |
164 in: "Integers: {1} {-2} {+42} {0777} {0x0a}", | |
165 | |
166 out: "Integers: 1 -2 42 511 10", | |
167 }, | |
168 | |
169 { | |
170 in: "Floats: {.5} {-.5} {1.1} {-2.2} {+42.1} {1e10} {1.2e-3} {1.
2e3} {-1.2e3}", | |
171 | |
172 out: "Floats: 0.5 -0.5 1.1 -2.2 42.1 1e+10 0.0012 1200 -1200", | |
173 }, | |
174 | |
175 // Method at top level | |
176 { | |
177 in: "ptrmethod={PointerMethod}\n", | |
178 | |
179 out: "ptrmethod=ptrmethod!\n", | |
180 }, | |
181 | |
182 { | |
183 in: "valmethod={ValueMethod}\n", | |
184 | |
185 out: "valmethod=valmethod!\n", | |
186 }, | |
187 | |
188 // Section | |
189 { | |
190 in: "{.section Data }\n" + | |
191 "some text for the section\n" + | |
192 "{.end}\n", | |
193 | |
194 out: "some text for the section\n", | |
195 }, | |
196 { | |
197 in: "{.section Data }\n" + | |
198 "{Header}={Integer}\n" + | |
199 "{.end}\n", | |
200 | |
201 out: "Header=77\n", | |
202 }, | |
203 { | |
204 in: "{.section Pdata }\n" + | |
205 "{Header}={Integer}\n" + | |
206 "{.end}\n", | |
207 | |
208 out: "Header=77\n", | |
209 }, | |
210 { | |
211 in: "{.section Pdata }\n" + | |
212 "data present\n" + | |
213 "{.or}\n" + | |
214 "data not present\n" + | |
215 "{.end}\n", | |
216 | |
217 out: "data present\n", | |
218 }, | |
219 { | |
220 in: "{.section Empty }\n" + | |
221 "data present\n" + | |
222 "{.or}\n" + | |
223 "data not present\n" + | |
224 "{.end}\n", | |
225 | |
226 out: "data not present\n", | |
227 }, | |
228 { | |
229 in: "{.section Null }\n" + | |
230 "data present\n" + | |
231 "{.or}\n" + | |
232 "data not present\n" + | |
233 "{.end}\n", | |
234 | |
235 out: "data not present\n", | |
236 }, | |
237 { | |
238 in: "{.section Pdata }\n" + | |
239 "{Header}={Integer}\n" + | |
240 "{.section @ }\n" + | |
241 "{Header}={Integer}\n" + | |
242 "{.end}\n" + | |
243 "{.end}\n", | |
244 | |
245 out: "Header=77\n" + | |
246 "Header=77\n", | |
247 }, | |
248 | |
249 { | |
250 in: "{.section Data}{.end} {Header}\n", | |
251 | |
252 out: " Header\n", | |
253 }, | |
254 | |
255 { | |
256 in: "{.section Integer}{@}{.end}", | |
257 | |
258 out: "77", | |
259 }, | |
260 | |
261 // Repeated | |
262 { | |
263 in: "{.section Pdata }\n" + | |
264 "{.repeated section @ }\n" + | |
265 "{Item}={Value}\n" + | |
266 "{.end}\n" + | |
267 "{.end}\n", | |
268 | |
269 out: "ItemNumber1=ValueNumber1\n" + | |
270 "ItemNumber2=ValueNumber2\n", | |
271 }, | |
272 { | |
273 in: "{.section Pdata }\n" + | |
274 "{.repeated section @ }\n" + | |
275 "{Item}={Value}\n" + | |
276 "{.or}\n" + | |
277 "this should not appear\n" + | |
278 "{.end}\n" + | |
279 "{.end}\n", | |
280 | |
281 out: "ItemNumber1=ValueNumber1\n" + | |
282 "ItemNumber2=ValueNumber2\n", | |
283 }, | |
284 { | |
285 in: "{.section @ }\n" + | |
286 "{.repeated section Empty }\n" + | |
287 "{Item}={Value}\n" + | |
288 "{.or}\n" + | |
289 "this should appear: empty field\n" + | |
290 "{.end}\n" + | |
291 "{.end}\n", | |
292 | |
293 out: "this should appear: empty field\n", | |
294 }, | |
295 { | |
296 in: "{.repeated section Pdata }\n" + | |
297 "{Item}\n" + | |
298 "{.alternates with}\n" + | |
299 "is\nover\nmultiple\nlines\n" + | |
300 "{.end}\n", | |
301 | |
302 out: "ItemNumber1\n" + | |
303 "is\nover\nmultiple\nlines\n" + | |
304 "ItemNumber2\n", | |
305 }, | |
306 { | |
307 in: "{.repeated section Pdata }\n" + | |
308 "{Item}\n" + | |
309 "{.alternates with}\n" + | |
310 "is\nover\nmultiple\nlines\n" + | |
311 " {.end}\n", | |
312 | |
313 out: "ItemNumber1\n" + | |
314 "is\nover\nmultiple\nlines\n" + | |
315 "ItemNumber2\n", | |
316 }, | |
317 { | |
318 in: "{.section Pdata }\n" + | |
319 "{.repeated section @ }\n" + | |
320 "{Item}={Value}\n" + | |
321 "{.alternates with}DIVIDER\n" + | |
322 "{.or}\n" + | |
323 "this should not appear\n" + | |
324 "{.end}\n" + | |
325 "{.end}\n", | |
326 | |
327 out: "ItemNumber1=ValueNumber1\n" + | |
328 "DIVIDER\n" + | |
329 "ItemNumber2=ValueNumber2\n", | |
330 }, | |
331 { | |
332 in: "{.repeated section Vec }\n" + | |
333 "{@}\n" + | |
334 "{.end}\n", | |
335 | |
336 out: "elt1\n" + | |
337 "elt2\n", | |
338 }, | |
339 // Same but with a space before {.end}: was a bug. | |
340 { | |
341 in: "{.repeated section Vec }\n" + | |
342 "{@} {.end}\n", | |
343 | |
344 out: "elt1 elt2 \n", | |
345 }, | |
346 { | |
347 in: "{.repeated section Integer}{.end}", | |
348 | |
349 err: "line 1: .repeated: cannot repeat Integer (type int)", | |
350 }, | |
351 | |
352 // Nested names | |
353 { | |
354 in: "{.section @ }\n" + | |
355 "{InnerT.Item}={InnerT.Value}\n" + | |
356 "{.end}", | |
357 | |
358 out: "ItemNumber1=ValueNumber1\n", | |
359 }, | |
360 { | |
361 in: "{.section @ }\n" + | |
362 "{InnerT.Item}={.section InnerT}{.section Value}{@}{.end
}{.end}\n" + | |
363 "{.end}", | |
364 | |
365 out: "ItemNumber1=ValueNumber1\n", | |
366 }, | |
367 | |
368 { | |
369 in: "{.section Emptystring}emptystring{.end}\n" + | |
370 "{.section Header}header{.end}\n", | |
371 | |
372 out: "\nheader\n", | |
373 }, | |
374 | |
375 { | |
376 in: "{.section True}1{.or}2{.end}\n" + | |
377 "{.section False}3{.or}4{.end}\n", | |
378 | |
379 out: "1\n4\n", | |
380 }, | |
381 | |
382 // Maps | |
383 | |
384 { | |
385 in: "{Mp.mapkey}\n", | |
386 | |
387 out: "Ahoy!\n", | |
388 }, | |
389 { | |
390 in: "{Innermap.Mp.innerkey}\n", | |
391 | |
392 out: "55\n", | |
393 }, | |
394 { | |
395 in: "{.section Innermap}{.section Mp}{innerkey}{.end}{.end}\n", | |
396 | |
397 out: "55\n", | |
398 }, | |
399 { | |
400 in: "{.section JSON}{.repeated section maps}{a}{b}{.end}{.end}\n
", | |
401 | |
402 out: "1234\n", | |
403 }, | |
404 { | |
405 in: "{Stringmap.stringkey1}\n", | |
406 | |
407 out: "stringresult\n", | |
408 }, | |
409 { | |
410 in: "{.repeated section Stringmap}\n" + | |
411 "{@}\n" + | |
412 "{.end}", | |
413 | |
414 out: "stringresult\n" + | |
415 "stringresult\n", | |
416 }, | |
417 { | |
418 in: "{.repeated section Stringmap}\n" + | |
419 "\t{@}\n" + | |
420 "{.end}", | |
421 | |
422 out: "\tstringresult\n" + | |
423 "\tstringresult\n", | |
424 }, | |
425 { | |
426 in: "{*Ptrmap.stringkey1}\n", | |
427 | |
428 out: "pointedToString\n", | |
429 }, | |
430 { | |
431 in: "{.repeated section Ptrmap}\n" + | |
432 "{*@}\n" + | |
433 "{.end}", | |
434 | |
435 out: "pointedToString\n" + | |
436 "pointedToString\n", | |
437 }, | |
438 | |
439 // Interface values | |
440 | |
441 { | |
442 in: "{Iface}", | |
443 | |
444 out: "[1 2 3]", | |
445 }, | |
446 { | |
447 in: "{.repeated section Iface}{@}{.alternates with} {.end}", | |
448 | |
449 out: "1 2 3", | |
450 }, | |
451 { | |
452 in: "{.section Iface}{@}{.end}", | |
453 | |
454 out: "[1 2 3]", | |
455 }, | |
456 { | |
457 in: "{.section Ifaceptr}{Item} {Value}{.end}", | |
458 | |
459 out: "Item Value", | |
460 }, | |
461 } | |
462 | |
463 func TestAll(t *testing.T) { | |
464 // Parse | |
465 testAll(t, func(test *Test) (*Template, error) { return Parse(test.in, f
ormatters) }) | |
466 // ParseFile | |
467 f, err := ioutil.TempFile("", "template-test") | |
468 if err != nil { | |
469 t.Fatal(err) | |
470 } | |
471 defer func() { | |
472 name := f.Name() | |
473 f.Close() | |
474 os.Remove(name) | |
475 }() | |
476 testAll(t, func(test *Test) (*Template, error) { | |
477 err := ioutil.WriteFile(f.Name(), []byte(test.in), 0600) | |
478 if err != nil { | |
479 t.Error("unexpected write error:", err) | |
480 return nil, err | |
481 } | |
482 return ParseFile(f.Name(), formatters) | |
483 }) | |
484 // tmpl.ParseFile | |
485 testAll(t, func(test *Test) (*Template, error) { | |
486 err := ioutil.WriteFile(f.Name(), []byte(test.in), 0600) | |
487 if err != nil { | |
488 t.Error("unexpected write error:", err) | |
489 return nil, err | |
490 } | |
491 tmpl := New(formatters) | |
492 return tmpl, tmpl.ParseFile(f.Name()) | |
493 }) | |
494 } | |
495 | |
496 func testAll(t *testing.T, parseFunc func(*Test) (*Template, error)) { | |
497 s := new(S) | |
498 // initialized by hand for clarity. | |
499 s.Header = "Header" | |
500 s.HeaderPtr = &s.Header | |
501 s.Integer = 77 | |
502 s.IntegerPtr = &s.Integer | |
503 s.InnerT = t1 | |
504 s.Data = []T{t1, t2} | |
505 s.Pdata = []*T{&t1, &t2} | |
506 s.Empty = []*T{} | |
507 s.Null = nil | |
508 s.Vec = []interface{}{"elt1", "elt2"} | |
509 s.True = true | |
510 s.False = false | |
511 s.Mp = make(map[string]string) | |
512 s.Mp["mapkey"] = "Ahoy!" | |
513 json.Unmarshal([]byte(`{"maps":[{"a":1,"b":2},{"a":3,"b":4}]}`), &s.JSON
) | |
514 s.Innermap.Mp = make(map[string]int) | |
515 s.Innermap.Mp["innerkey"] = 55 | |
516 s.Stringmap = make(map[string]string) | |
517 s.Stringmap["stringkey1"] = "stringresult" // the same value so repeated
section is order-independent | |
518 s.Stringmap["stringkey2"] = "stringresult" | |
519 s.Ptrmap = make(map[string]*string) | |
520 x := "pointedToString" | |
521 s.Ptrmap["stringkey1"] = &x // the same value so repeated section is ord
er-independent | |
522 s.Ptrmap["stringkey2"] = &x | |
523 s.Iface = []int{1, 2, 3} | |
524 s.Ifaceptr = &T{"Item", "Value"} | |
525 | |
526 var buf bytes.Buffer | |
527 for _, test := range tests { | |
528 buf.Reset() | |
529 tmpl, err := parseFunc(test) | |
530 if err != nil { | |
531 t.Error("unexpected parse error: ", err) | |
532 continue | |
533 } | |
534 err = tmpl.Execute(&buf, s) | |
535 if test.err == "" { | |
536 if err != nil { | |
537 t.Error("unexpected execute error:", err) | |
538 } | |
539 } else { | |
540 if err == nil { | |
541 t.Errorf("expected execute error %q, got nil", t
est.err) | |
542 } else if err.Error() != test.err { | |
543 t.Errorf("expected execute error %q, got %q", te
st.err, err.Error()) | |
544 } | |
545 } | |
546 if buf.String() != test.out { | |
547 t.Errorf("for %q: expected %q got %q", test.in, test.out
, buf.String()) | |
548 } | |
549 } | |
550 } | |
551 | |
552 func TestMapDriverType(t *testing.T) { | |
553 mp := map[string]string{"footer": "Ahoy!"} | |
554 tmpl, err := Parse("template: {footer}", nil) | |
555 if err != nil { | |
556 t.Error("unexpected parse error:", err) | |
557 } | |
558 var b bytes.Buffer | |
559 err = tmpl.Execute(&b, mp) | |
560 if err != nil { | |
561 t.Error("unexpected execute error:", err) | |
562 } | |
563 s := b.String() | |
564 expect := "template: Ahoy!" | |
565 if s != expect { | |
566 t.Errorf("failed passing string as data: expected %q got %q", ex
pect, s) | |
567 } | |
568 } | |
569 | |
570 func TestMapNoEntry(t *testing.T) { | |
571 mp := make(map[string]int) | |
572 tmpl, err := Parse("template: {notthere}!", nil) | |
573 if err != nil { | |
574 t.Error("unexpected parse error:", err) | |
575 } | |
576 var b bytes.Buffer | |
577 err = tmpl.Execute(&b, mp) | |
578 if err != nil { | |
579 t.Error("unexpected execute error:", err) | |
580 } | |
581 s := b.String() | |
582 expect := "template: 0!" | |
583 if s != expect { | |
584 t.Errorf("failed passing string as data: expected %q got %q", ex
pect, s) | |
585 } | |
586 } | |
587 | |
588 func TestStringDriverType(t *testing.T) { | |
589 tmpl, err := Parse("template: {@}", nil) | |
590 if err != nil { | |
591 t.Error("unexpected parse error:", err) | |
592 } | |
593 var b bytes.Buffer | |
594 err = tmpl.Execute(&b, "hello") | |
595 if err != nil { | |
596 t.Error("unexpected execute error:", err) | |
597 } | |
598 s := b.String() | |
599 expect := "template: hello" | |
600 if s != expect { | |
601 t.Errorf("failed passing string as data: expected %q got %q", ex
pect, s) | |
602 } | |
603 } | |
604 | |
605 func TestTwice(t *testing.T) { | |
606 tmpl, err := Parse("template: {@}", nil) | |
607 if err != nil { | |
608 t.Error("unexpected parse error:", err) | |
609 } | |
610 var b bytes.Buffer | |
611 err = tmpl.Execute(&b, "hello") | |
612 if err != nil { | |
613 t.Error("unexpected parse error:", err) | |
614 } | |
615 s := b.String() | |
616 expect := "template: hello" | |
617 if s != expect { | |
618 t.Errorf("failed passing string as data: expected %q got %q", ex
pect, s) | |
619 } | |
620 err = tmpl.Execute(&b, "hello") | |
621 if err != nil { | |
622 t.Error("unexpected parse error:", err) | |
623 } | |
624 s = b.String() | |
625 expect += expect | |
626 if s != expect { | |
627 t.Errorf("failed passing string as data: expected %q got %q", ex
pect, s) | |
628 } | |
629 } | |
630 | |
631 func TestCustomDelims(t *testing.T) { | |
632 // try various lengths. zero should catch error. | |
633 for i := 0; i < 7; i++ { | |
634 for j := 0; j < 7; j++ { | |
635 tmpl := New(nil) | |
636 // first two chars deliberately the same to test equal l
eft and right delims | |
637 ldelim := "$!#$%^&"[0:i] | |
638 rdelim := "$*&^%$!"[0:j] | |
639 tmpl.SetDelims(ldelim, rdelim) | |
640 // if braces, this would be template: {@}{.meta-left}{.m
eta-right} | |
641 text := "template: " + | |
642 ldelim + "@" + rdelim + | |
643 ldelim + ".meta-left" + rdelim + | |
644 ldelim + ".meta-right" + rdelim | |
645 err := tmpl.Parse(text) | |
646 if err != nil { | |
647 if i == 0 || j == 0 { // expected | |
648 continue | |
649 } | |
650 t.Error("unexpected parse error:", err) | |
651 } else if i == 0 || j == 0 { | |
652 t.Errorf("expected parse error for empty delimit
er: %d %d %q %q", i, j, ldelim, rdelim) | |
653 continue | |
654 } | |
655 var b bytes.Buffer | |
656 err = tmpl.Execute(&b, "hello") | |
657 s := b.String() | |
658 if s != "template: hello"+ldelim+rdelim { | |
659 t.Errorf("failed delim check(%q %q) %q got %q",
ldelim, rdelim, text, s) | |
660 } | |
661 } | |
662 } | |
663 } | |
664 | |
665 // Test that a variable evaluates to the field itself and does not further indir
ection | |
666 func TestVarIndirection(t *testing.T) { | |
667 s := new(S) | |
668 // initialized by hand for clarity. | |
669 s.InnerPointerT = &t1 | |
670 | |
671 var buf bytes.Buffer | |
672 input := "{.section @}{InnerPointerT}{.end}" | |
673 tmpl, err := Parse(input, nil) | |
674 if err != nil { | |
675 t.Fatal("unexpected parse error:", err) | |
676 } | |
677 err = tmpl.Execute(&buf, s) | |
678 if err != nil { | |
679 t.Fatal("unexpected execute error:", err) | |
680 } | |
681 expect := fmt.Sprintf("%v", &t1) // output should be hex address of t1 | |
682 if buf.String() != expect { | |
683 t.Errorf("for %q: expected %q got %q", input, expect, buf.String
()) | |
684 } | |
685 } | |
686 | |
687 func TestHTMLFormatterWithByte(t *testing.T) { | |
688 s := "Test string." | |
689 b := []byte(s) | |
690 var buf bytes.Buffer | |
691 HTMLFormatter(&buf, "", b) | |
692 bs := buf.String() | |
693 if bs != s { | |
694 t.Errorf("munged []byte, expected: %s got: %s", s, bs) | |
695 } | |
696 } | |
697 | |
698 type UF struct { | |
699 I int | |
700 s string | |
701 } | |
702 | |
703 func TestReferenceToUnexported(t *testing.T) { | |
704 u := &UF{3, "hello"} | |
705 var buf bytes.Buffer | |
706 input := "{.section @}{I}{s}{.end}" | |
707 tmpl, err := Parse(input, nil) | |
708 if err != nil { | |
709 t.Fatal("unexpected parse error:", err) | |
710 } | |
711 err = tmpl.Execute(&buf, u) | |
712 if err == nil { | |
713 t.Fatal("expected execute error, got none") | |
714 } | |
715 if strings.Index(err.Error(), "not exported") < 0 { | |
716 t.Fatal("expected unexported error; got", err) | |
717 } | |
718 } | |
719 | |
720 var formatterTests = []Test{ | |
721 { | |
722 in: "{Header|uppercase}={Integer|+1}\n" + | |
723 "{Header|html}={Integer|str}\n", | |
724 | |
725 out: "HEADER=78\n" + | |
726 "Header=77\n", | |
727 }, | |
728 | |
729 { | |
730 in: "{Header|uppercase}={Integer Header|multiword}\n" + | |
731 "{Header|html}={Header Integer|multiword}\n" + | |
732 "{Header|html}={Header Integer}\n", | |
733 | |
734 out: "HEADER=<77><Header>\n" + | |
735 "Header=<Header><77>\n" + | |
736 "Header=Header77\n", | |
737 }, | |
738 { | |
739 in: "{Raw}\n" + | |
740 "{Raw|html}\n", | |
741 | |
742 out: "a <&> b\n" + | |
743 "a <&> b\n", | |
744 }, | |
745 { | |
746 in: "{Bytes}", | |
747 out: "hello", | |
748 }, | |
749 { | |
750 in: "{Raw|uppercase|html|html}", | |
751 out: "A &lt;&amp;&gt; B", | |
752 }, | |
753 { | |
754 in: "{Header Integer|multiword|html}", | |
755 out: "<Header><77>", | |
756 }, | |
757 { | |
758 in: "{Integer|no_formatter|html}", | |
759 err: `unknown formatter: "no_formatter"`, | |
760 }, | |
761 { | |
762 in: "{Integer|||||}", // empty string is a valid formatter | |
763 out: "77", | |
764 }, | |
765 { | |
766 in: `{"%.02f 0x%02X" 1.1 10|printf}`, | |
767 out: "1.10 0x0A", | |
768 }, | |
769 { | |
770 in: `{""|}{""||}{""|printf}`, // Issue #1896. | |
771 out: "", | |
772 }, | |
773 } | |
774 | |
775 func TestFormatters(t *testing.T) { | |
776 data := map[string]interface{}{ | |
777 "Header": "Header", | |
778 "Integer": 77, | |
779 "Raw": "a <&> b", | |
780 "Bytes": []byte("hello"), | |
781 } | |
782 for _, c := range formatterTests { | |
783 tmpl, err := Parse(c.in, formatters) | |
784 if err != nil { | |
785 if c.err == "" { | |
786 t.Error("unexpected parse error:", err) | |
787 continue | |
788 } | |
789 if strings.Index(err.Error(), c.err) < 0 { | |
790 t.Errorf("unexpected error: expected %q, got %q"
, c.err, err.Error()) | |
791 continue | |
792 } | |
793 } else { | |
794 if c.err != "" { | |
795 t.Errorf("For %q, expected error, got none.", c.
in) | |
796 continue | |
797 } | |
798 var buf bytes.Buffer | |
799 err = tmpl.Execute(&buf, data) | |
800 if err != nil { | |
801 t.Error("unexpected Execute error: ", err) | |
802 continue | |
803 } | |
804 actual := buf.String() | |
805 if actual != c.out { | |
806 t.Errorf("for %q: expected %q but got %q.", c.in
, c.out, actual) | |
807 } | |
808 } | |
809 } | |
810 } | |
OLD | NEW |