Source file
src/go/doc/example_test.go
1
2
3
4
5 package doc_test
6
7 import (
8 "bytes"
9 "fmt"
10 "go/ast"
11 "go/doc"
12 "go/format"
13 "go/parser"
14 "go/token"
15 "reflect"
16 "strings"
17 "testing"
18 )
19
20 const exampleTestFile = `
21 package foo_test
22
23 import (
24 "flag"
25 "fmt"
26 "log"
27 "sort"
28 "os/exec"
29 )
30
31 func ExampleHello() {
32 fmt.Println("Hello, world!")
33 // Output: Hello, world!
34 }
35
36 func ExampleImport() {
37 out, err := exec.Command("date").Output()
38 if err != nil {
39 log.Fatal(err)
40 }
41 fmt.Printf("The date is %s\n", out)
42 }
43
44 func ExampleKeyValue() {
45 v := struct {
46 a string
47 b int
48 }{
49 a: "A",
50 b: 1,
51 }
52 fmt.Print(v)
53 // Output: a: "A", b: 1
54 }
55
56 func ExampleKeyValueImport() {
57 f := flag.Flag{
58 Name: "play",
59 }
60 fmt.Print(f)
61 // Output: Name: "play"
62 }
63
64 var keyValueTopDecl = struct {
65 a string
66 b int
67 }{
68 a: "B",
69 b: 2,
70 }
71
72 func ExampleKeyValueTopDecl() {
73 fmt.Print(keyValueTopDecl)
74 // Output: a: "B", b: 2
75 }
76
77 // Person represents a person by name and age.
78 type Person struct {
79 Name string
80 Age int
81 }
82
83 // String returns a string representation of the Person.
84 func (p Person) String() string {
85 return fmt.Sprintf("%s: %d", p.Name, p.Age)
86 }
87
88 // ByAge implements sort.Interface for []Person based on
89 // the Age field.
90 type ByAge []Person
91
92 // Len returns the number of elements in ByAge.
93 func (a (ByAge)) Len() int { return len(a) }
94
95 // Swap swaps the elements in ByAge.
96 func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
97 func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
98
99 // people is the array of Person
100 var people = []Person{
101 {"Bob", 31},
102 {"John", 42},
103 {"Michael", 17},
104 {"Jenny", 26},
105 }
106
107 func ExampleSort() {
108 fmt.Println(people)
109 sort.Sort(ByAge(people))
110 fmt.Println(people)
111 // Output:
112 // [Bob: 31 John: 42 Michael: 17 Jenny: 26]
113 // [Michael: 17 Jenny: 26 Bob: 31 John: 42]
114 }
115 `
116
117 var exampleTestCases = []struct {
118 Name, Play, Output string
119 }{
120 {
121 Name: "Hello",
122 Play: exampleHelloPlay,
123 Output: "Hello, world!\n",
124 },
125 {
126 Name: "Import",
127 Play: exampleImportPlay,
128 },
129 {
130 Name: "KeyValue",
131 Play: exampleKeyValuePlay,
132 Output: "a: \"A\", b: 1\n",
133 },
134 {
135 Name: "KeyValueImport",
136 Play: exampleKeyValueImportPlay,
137 Output: "Name: \"play\"\n",
138 },
139 {
140 Name: "KeyValueTopDecl",
141 Play: exampleKeyValueTopDeclPlay,
142 Output: "a: \"B\", b: 2\n",
143 },
144 {
145 Name: "Sort",
146 Play: exampleSortPlay,
147 Output: "[Bob: 31 John: 42 Michael: 17 Jenny: 26]\n[Michael: 17 Jenny: 26 Bob: 31 John: 42]\n",
148 },
149 }
150
151 const exampleHelloPlay = `package main
152
153 import (
154 "fmt"
155 )
156
157 func main() {
158 fmt.Println("Hello, world!")
159 }
160 `
161 const exampleImportPlay = `package main
162
163 import (
164 "fmt"
165 "log"
166 "os/exec"
167 )
168
169 func main() {
170 out, err := exec.Command("date").Output()
171 if err != nil {
172 log.Fatal(err)
173 }
174 fmt.Printf("The date is %s\n", out)
175 }
176 `
177
178 const exampleKeyValuePlay = `package main
179
180 import (
181 "fmt"
182 )
183
184 func main() {
185 v := struct {
186 a string
187 b int
188 }{
189 a: "A",
190 b: 1,
191 }
192 fmt.Print(v)
193 }
194 `
195
196 const exampleKeyValueImportPlay = `package main
197
198 import (
199 "flag"
200 "fmt"
201 )
202
203 func main() {
204 f := flag.Flag{
205 Name: "play",
206 }
207 fmt.Print(f)
208 }
209 `
210
211 const exampleKeyValueTopDeclPlay = `package main
212
213 import (
214 "fmt"
215 )
216
217 var keyValueTopDecl = struct {
218 a string
219 b int
220 }{
221 a: "B",
222 b: 2,
223 }
224
225 func main() {
226 fmt.Print(keyValueTopDecl)
227 }
228 `
229
230 const exampleSortPlay = `package main
231
232 import (
233 "fmt"
234 "sort"
235 )
236
237 // Person represents a person by name and age.
238 type Person struct {
239 Name string
240 Age int
241 }
242
243 // String returns a string representation of the Person.
244 func (p Person) String() string {
245 return fmt.Sprintf("%s: %d", p.Name, p.Age)
246 }
247
248 // ByAge implements sort.Interface for []Person based on
249 // the Age field.
250 type ByAge []Person
251
252 // Len returns the number of elements in ByAge.
253 func (a ByAge) Len() int { return len(a) }
254
255 // Swap swaps the elements in ByAge.
256 func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
257 func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
258
259 // people is the array of Person
260 var people = []Person{
261 {"Bob", 31},
262 {"John", 42},
263 {"Michael", 17},
264 {"Jenny", 26},
265 }
266
267 func main() {
268 fmt.Println(people)
269 sort.Sort(ByAge(people))
270 fmt.Println(people)
271 }
272 `
273
274 func TestExamples(t *testing.T) {
275 fset := token.NewFileSet()
276 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments)
277 if err != nil {
278 t.Fatal(err)
279 }
280 for i, e := range doc.Examples(file) {
281 c := exampleTestCases[i]
282 if e.Name != c.Name {
283 t.Errorf("got Name == %q, want %q", e.Name, c.Name)
284 }
285 if w := c.Play; w != "" {
286 g := formatFile(t, fset, e.Play)
287 if g != w {
288 t.Errorf("%s: got Play == %q, want %q", c.Name, g, w)
289 }
290 }
291 if g, w := e.Output, c.Output; g != w {
292 t.Errorf("%s: got Output == %q, want %q", c.Name, g, w)
293 }
294 }
295 }
296
297 const exampleWholeFile = `package foo_test
298
299 type X int
300
301 func (X) Foo() {
302 }
303
304 func (X) TestBlah() {
305 }
306
307 func (X) BenchmarkFoo() {
308 }
309
310 func (X) FuzzFoo() {
311 }
312
313 func Example() {
314 fmt.Println("Hello, world!")
315 // Output: Hello, world!
316 }
317 `
318
319 const exampleWholeFileOutput = `package main
320
321 type X int
322
323 func (X) Foo() {
324 }
325
326 func (X) TestBlah() {
327 }
328
329 func (X) BenchmarkFoo() {
330 }
331
332 func (X) FuzzFoo() {
333 }
334
335 func main() {
336 fmt.Println("Hello, world!")
337 }
338 `
339
340 const exampleWholeFileFunction = `package foo_test
341
342 func Foo(x int) {
343 }
344
345 func Example() {
346 fmt.Println("Hello, world!")
347 // Output: Hello, world!
348 }
349 `
350
351 const exampleWholeFileFunctionOutput = `package main
352
353 func Foo(x int) {
354 }
355
356 func main() {
357 fmt.Println("Hello, world!")
358 }
359 `
360
361 const exampleWholeFileExternalFunction = `package foo_test
362
363 func foo(int)
364
365 func Example() {
366 foo(42)
367 // Output:
368 }
369 `
370
371 const exampleWholeFileExternalFunctionOutput = `package main
372
373 func foo(int)
374
375 func main() {
376 foo(42)
377 }
378 `
379
380 var exampleWholeFileTestCases = []struct {
381 Title, Source, Play, Output string
382 }{
383 {
384 "Methods",
385 exampleWholeFile,
386 exampleWholeFileOutput,
387 "Hello, world!\n",
388 },
389 {
390 "Function",
391 exampleWholeFileFunction,
392 exampleWholeFileFunctionOutput,
393 "Hello, world!\n",
394 },
395 {
396 "ExternalFunction",
397 exampleWholeFileExternalFunction,
398 exampleWholeFileExternalFunctionOutput,
399 "",
400 },
401 }
402
403 func TestExamplesWholeFile(t *testing.T) {
404 for _, c := range exampleWholeFileTestCases {
405 fset := token.NewFileSet()
406 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(c.Source), parser.ParseComments)
407 if err != nil {
408 t.Fatal(err)
409 }
410 es := doc.Examples(file)
411 if len(es) != 1 {
412 t.Fatalf("%s: wrong number of examples; got %d want 1", c.Title, len(es))
413 }
414 e := es[0]
415 if e.Name != "" {
416 t.Errorf("%s: got Name == %q, want %q", c.Title, e.Name, "")
417 }
418 if g, w := formatFile(t, fset, e.Play), c.Play; g != w {
419 t.Errorf("%s: got Play == %q, want %q", c.Title, g, w)
420 }
421 if g, w := e.Output, c.Output; g != w {
422 t.Errorf("%s: got Output == %q, want %q", c.Title, g, w)
423 }
424 }
425 }
426
427 const exampleInspectSignature = `package foo_test
428
429 import (
430 "bytes"
431 "io"
432 )
433
434 func getReader() io.Reader { return nil }
435
436 func do(b bytes.Reader) {}
437
438 func Example() {
439 getReader()
440 do()
441 // Output:
442 }
443
444 func ExampleIgnored() {
445 }
446 `
447
448 const exampleInspectSignatureOutput = `package main
449
450 import (
451 "bytes"
452 "io"
453 )
454
455 func getReader() io.Reader { return nil }
456
457 func do(b bytes.Reader) {}
458
459 func main() {
460 getReader()
461 do()
462 }
463 `
464
465 func TestExampleInspectSignature(t *testing.T) {
466
467 fset := token.NewFileSet()
468 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleInspectSignature), parser.ParseComments)
469 if err != nil {
470 t.Fatal(err)
471 }
472 es := doc.Examples(file)
473 if len(es) != 2 {
474 t.Fatalf("wrong number of examples; got %d want 2", len(es))
475 }
476
477 e := es[0]
478 if e.Name != "" {
479 t.Errorf("got Name == %q, want %q", e.Name, "")
480 }
481 if g, w := formatFile(t, fset, e.Play), exampleInspectSignatureOutput; g != w {
482 t.Errorf("got Play == %q, want %q", g, w)
483 }
484 if g, w := e.Output, ""; g != w {
485 t.Errorf("got Output == %q, want %q", g, w)
486 }
487 }
488
489 const exampleEmpty = `
490 package p
491 func Example() {}
492 func Example_a()
493 `
494
495 const exampleEmptyOutput = `package main
496
497 func main() {}
498 func main()
499 `
500
501 func TestExampleEmpty(t *testing.T) {
502 fset := token.NewFileSet()
503 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleEmpty), parser.ParseComments)
504 if err != nil {
505 t.Fatal(err)
506 }
507
508 es := doc.Examples(file)
509 if len(es) != 1 {
510 t.Fatalf("wrong number of examples; got %d want 1", len(es))
511 }
512 e := es[0]
513 if e.Name != "" {
514 t.Errorf("got Name == %q, want %q", e.Name, "")
515 }
516 if g, w := formatFile(t, fset, e.Play), exampleEmptyOutput; g != w {
517 t.Errorf("got Play == %q, want %q", g, w)
518 }
519 if g, w := e.Output, ""; g != w {
520 t.Errorf("got Output == %q, want %q", g, w)
521 }
522 }
523
524 func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string {
525 if n == nil {
526 return "<nil>"
527 }
528 var buf bytes.Buffer
529 if err := format.Node(&buf, fset, n); err != nil {
530 t.Fatal(err)
531 }
532 return buf.String()
533 }
534
535
536
537 func ExampleNewFromFiles() {
538
539
540 const src = `
541 // This is the package comment.
542 package p
543
544 import "fmt"
545
546 // This comment is associated with the Greet function.
547 func Greet(who string) {
548 fmt.Printf("Hello, %s!\n", who)
549 }
550 `
551 const test = `
552 package p_test
553
554 // This comment is associated with the ExampleGreet_world example.
555 func ExampleGreet_world() {
556 Greet("world")
557 }
558 `
559
560
561 fset := token.NewFileSet()
562 files := []*ast.File{
563 mustParse(fset, "src.go", src),
564 mustParse(fset, "src_test.go", test),
565 }
566
567
568 p, err := doc.NewFromFiles(fset, files, "example.com/p")
569 if err != nil {
570 panic(err)
571 }
572
573 fmt.Printf("package %s - %s", p.Name, p.Doc)
574 fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc)
575 fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc)
576
577
578
579
580
581 }
582
583 func TestClassifyExamples(t *testing.T) {
584 const src = `
585 package p
586
587 const Const1 = 0
588 var Var1 = 0
589
590 type (
591 Type1 int
592 Type1_Foo int
593 Type1_foo int
594 type2 int
595
596 Embed struct { Type1 }
597 Uembed struct { type2 }
598 )
599
600 func Func1() {}
601 func Func1_Foo() {}
602 func Func1_foo() {}
603 func func2() {}
604
605 func (Type1) Func1() {}
606 func (Type1) Func1_Foo() {}
607 func (Type1) Func1_foo() {}
608 func (Type1) func2() {}
609
610 func (type2) Func1() {}
611
612 type (
613 Conflict int
614 Conflict_Conflict int
615 Conflict_conflict int
616 )
617
618 func (Conflict) Conflict() {}
619 `
620 const test = `
621 package p_test
622
623 func ExampleConst1() {} // invalid - no support for consts and vars
624 func ExampleVar1() {} // invalid - no support for consts and vars
625
626 func Example() {}
627 func Example_() {} // invalid - suffix must start with a lower-case letter
628 func Example_suffix() {}
629 func Example_suffix_xX_X_x() {}
630 func Example_世界() {} // invalid - suffix must start with a lower-case letter
631 func Example_123() {} // invalid - suffix must start with a lower-case letter
632 func Example_BadSuffix() {} // invalid - suffix must start with a lower-case letter
633
634 func ExampleType1() {}
635 func ExampleType1_() {} // invalid - suffix must start with a lower-case letter
636 func ExampleType1_suffix() {}
637 func ExampleType1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
638 func ExampleType1_Foo() {}
639 func ExampleType1_Foo_suffix() {}
640 func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
641 func ExampleType1_foo() {}
642 func ExampleType1_foo_suffix() {}
643 func ExampleType1_foo_Suffix() {} // matches Type1, instead of Type1_foo
644 func Exampletype2() {} // invalid - cannot match unexported
645
646 func ExampleFunc1() {}
647 func ExampleFunc1_() {} // invalid - suffix must start with a lower-case letter
648 func ExampleFunc1_suffix() {}
649 func ExampleFunc1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
650 func ExampleFunc1_Foo() {}
651 func ExampleFunc1_Foo_suffix() {}
652 func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
653 func ExampleFunc1_foo() {}
654 func ExampleFunc1_foo_suffix() {}
655 func ExampleFunc1_foo_Suffix() {} // matches Func1, instead of Func1_foo
656 func Examplefunc1() {} // invalid - cannot match unexported
657
658 func ExampleType1_Func1() {}
659 func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter
660 func ExampleType1_Func1_suffix() {}
661 func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
662 func ExampleType1_Func1_Foo() {}
663 func ExampleType1_Func1_Foo_suffix() {}
664 func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
665 func ExampleType1_Func1_foo() {}
666 func ExampleType1_Func1_foo_suffix() {}
667 func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo
668 func ExampleType1_func2() {} // matches Type1, instead of Type1.func2
669
670 func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type
671 func ExampleUembed_Func1() {} // methods from embedding unexported types are OK
672 func ExampleUembed_Func1_suffix() {}
673
674 func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type
675 func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type
676 func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type
677 func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type
678 `
679
680
681 fset := token.NewFileSet()
682 files := []*ast.File{
683 mustParse(fset, "src.go", src),
684 mustParse(fset, "src_test.go", test),
685 }
686 p, err := doc.NewFromFiles(fset, files, "example.com/p")
687 if err != nil {
688 t.Fatalf("doc.NewFromFiles: %v", err)
689 }
690
691
692 got := map[string][]string{}
693 got[""] = exampleNames(p.Examples)
694 for _, f := range p.Funcs {
695 got[f.Name] = exampleNames(f.Examples)
696 }
697 for _, t := range p.Types {
698 got[t.Name] = exampleNames(t.Examples)
699 for _, f := range t.Funcs {
700 got[f.Name] = exampleNames(f.Examples)
701 }
702 for _, m := range t.Methods {
703 got[t.Name+"."+m.Name] = exampleNames(m.Examples)
704 }
705 }
706
707 want := map[string][]string{
708 "": {"", "suffix", "suffix_xX_X_x"},
709
710 "Type1": {"", "foo_Suffix", "func2", "suffix"},
711 "Type1_Foo": {"", "suffix"},
712 "Type1_foo": {"", "suffix"},
713
714 "Func1": {"", "foo_Suffix", "suffix"},
715 "Func1_Foo": {"", "suffix"},
716 "Func1_foo": {"", "suffix"},
717
718 "Type1.Func1": {"", "foo_Suffix", "suffix"},
719 "Type1.Func1_Foo": {"", "suffix"},
720 "Type1.Func1_foo": {"", "suffix"},
721
722 "Uembed.Func1": {"", "suffix"},
723
724
725 "Conflict_Conflict": {"", "suffix"},
726 "Conflict_conflict": {"", "suffix"},
727 }
728
729 for id := range got {
730 if !reflect.DeepEqual(got[id], want[id]) {
731 t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id])
732 }
733 }
734 }
735
736 func exampleNames(exs []*doc.Example) (out []string) {
737 for _, ex := range exs {
738 out = append(out, ex.Suffix)
739 }
740 return out
741 }
742
743 func mustParse(fset *token.FileSet, filename, src string) *ast.File {
744 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
745 if err != nil {
746 panic(err)
747 }
748 return f
749 }
750
View as plain text