Source file
src/go/doc/example_test.go
Documentation: go/doc
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 Example() {
311 fmt.Println("Hello, world!")
312 // Output: Hello, world!
313 }
314 `
315
316 const exampleWholeFileOutput = `package main
317
318 type X int
319
320 func (X) Foo() {
321 }
322
323 func (X) TestBlah() {
324 }
325
326 func (X) BenchmarkFoo() {
327 }
328
329 func main() {
330 fmt.Println("Hello, world!")
331 }
332 `
333
334 const exampleWholeFileFunction = `package foo_test
335
336 func Foo(x int) {
337 }
338
339 func Example() {
340 fmt.Println("Hello, world!")
341 // Output: Hello, world!
342 }
343 `
344
345 const exampleWholeFileFunctionOutput = `package main
346
347 func Foo(x int) {
348 }
349
350 func main() {
351 fmt.Println("Hello, world!")
352 }
353 `
354
355 const exampleWholeFileExternalFunction = `package foo_test
356
357 func foo(int)
358
359 func Example() {
360 foo(42)
361 // Output:
362 }
363 `
364
365 const exampleWholeFileExternalFunctionOutput = `package main
366
367 func foo(int)
368
369 func main() {
370 foo(42)
371 }
372 `
373
374 var exampleWholeFileTestCases = []struct {
375 Title, Source, Play, Output string
376 }{
377 {
378 "Methods",
379 exampleWholeFile,
380 exampleWholeFileOutput,
381 "Hello, world!\n",
382 },
383 {
384 "Function",
385 exampleWholeFileFunction,
386 exampleWholeFileFunctionOutput,
387 "Hello, world!\n",
388 },
389 {
390 "ExternalFunction",
391 exampleWholeFileExternalFunction,
392 exampleWholeFileExternalFunctionOutput,
393 "",
394 },
395 }
396
397 func TestExamplesWholeFile(t *testing.T) {
398 for _, c := range exampleWholeFileTestCases {
399 fset := token.NewFileSet()
400 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(c.Source), parser.ParseComments)
401 if err != nil {
402 t.Fatal(err)
403 }
404 es := doc.Examples(file)
405 if len(es) != 1 {
406 t.Fatalf("%s: wrong number of examples; got %d want 1", c.Title, len(es))
407 }
408 e := es[0]
409 if e.Name != "" {
410 t.Errorf("%s: got Name == %q, want %q", c.Title, e.Name, "")
411 }
412 if g, w := formatFile(t, fset, e.Play), c.Play; g != w {
413 t.Errorf("%s: got Play == %q, want %q", c.Title, g, w)
414 }
415 if g, w := e.Output, c.Output; g != w {
416 t.Errorf("%s: got Output == %q, want %q", c.Title, g, w)
417 }
418 }
419 }
420
421 const exampleInspectSignature = `package foo_test
422
423 import (
424 "bytes"
425 "io"
426 )
427
428 func getReader() io.Reader { return nil }
429
430 func do(b bytes.Reader) {}
431
432 func Example() {
433 getReader()
434 do()
435 // Output:
436 }
437
438 func ExampleIgnored() {
439 }
440 `
441
442 const exampleInspectSignatureOutput = `package main
443
444 import (
445 "bytes"
446 "io"
447 )
448
449 func getReader() io.Reader { return nil }
450
451 func do(b bytes.Reader) {}
452
453 func main() {
454 getReader()
455 do()
456 }
457 `
458
459 func TestExampleInspectSignature(t *testing.T) {
460
461 fset := token.NewFileSet()
462 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleInspectSignature), parser.ParseComments)
463 if err != nil {
464 t.Fatal(err)
465 }
466 es := doc.Examples(file)
467 if len(es) != 2 {
468 t.Fatalf("wrong number of examples; got %d want 2", len(es))
469 }
470
471 e := es[0]
472 if e.Name != "" {
473 t.Errorf("got Name == %q, want %q", e.Name, "")
474 }
475 if g, w := formatFile(t, fset, e.Play), exampleInspectSignatureOutput; g != w {
476 t.Errorf("got Play == %q, want %q", g, w)
477 }
478 if g, w := e.Output, ""; g != w {
479 t.Errorf("got Output == %q, want %q", g, w)
480 }
481 }
482
483 const exampleEmpty = `
484 package p
485 func Example() {}
486 func Example_a()
487 `
488
489 const exampleEmptyOutput = `package main
490
491 func main() {}
492 func main()
493 `
494
495 func TestExampleEmpty(t *testing.T) {
496 fset := token.NewFileSet()
497 file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleEmpty), parser.ParseComments)
498 if err != nil {
499 t.Fatal(err)
500 }
501
502 es := doc.Examples(file)
503 if len(es) != 1 {
504 t.Fatalf("wrong number of examples; got %d want 1", len(es))
505 }
506 e := es[0]
507 if e.Name != "" {
508 t.Errorf("got Name == %q, want %q", e.Name, "")
509 }
510 if g, w := formatFile(t, fset, e.Play), exampleEmptyOutput; g != w {
511 t.Errorf("got Play == %q, want %q", g, w)
512 }
513 if g, w := e.Output, ""; g != w {
514 t.Errorf("got Output == %q, want %q", g, w)
515 }
516 }
517
518 func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string {
519 if n == nil {
520 return "<nil>"
521 }
522 var buf bytes.Buffer
523 if err := format.Node(&buf, fset, n); err != nil {
524 t.Fatal(err)
525 }
526 return buf.String()
527 }
528
529
530
531 func ExampleNewFromFiles() {
532
533
534 const src = `
535 // This is the package comment.
536 package p
537
538 import "fmt"
539
540 // This comment is associated with the Greet function.
541 func Greet(who string) {
542 fmt.Printf("Hello, %s!\n", who)
543 }
544 `
545 const test = `
546 package p_test
547
548 // This comment is associated with the ExampleGreet_world example.
549 func ExampleGreet_world() {
550 Greet("world")
551 }
552 `
553
554
555 fset := token.NewFileSet()
556 files := []*ast.File{
557 mustParse(fset, "src.go", src),
558 mustParse(fset, "src_test.go", test),
559 }
560
561
562 p, err := doc.NewFromFiles(fset, files, "example.com/p")
563 if err != nil {
564 panic(err)
565 }
566
567 fmt.Printf("package %s - %s", p.Name, p.Doc)
568 fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc)
569 fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc)
570
571
572
573
574
575 }
576
577 func TestClassifyExamples(t *testing.T) {
578 const src = `
579 package p
580
581 const Const1 = 0
582 var Var1 = 0
583
584 type (
585 Type1 int
586 Type1_Foo int
587 Type1_foo int
588 type2 int
589
590 Embed struct { Type1 }
591 Uembed struct { type2 }
592 )
593
594 func Func1() {}
595 func Func1_Foo() {}
596 func Func1_foo() {}
597 func func2() {}
598
599 func (Type1) Func1() {}
600 func (Type1) Func1_Foo() {}
601 func (Type1) Func1_foo() {}
602 func (Type1) func2() {}
603
604 func (type2) Func1() {}
605
606 type (
607 Conflict int
608 Conflict_Conflict int
609 Conflict_conflict int
610 )
611
612 func (Conflict) Conflict() {}
613 `
614 const test = `
615 package p_test
616
617 func ExampleConst1() {} // invalid - no support for consts and vars
618 func ExampleVar1() {} // invalid - no support for consts and vars
619
620 func Example() {}
621 func Example_() {} // invalid - suffix must start with a lower-case letter
622 func Example_suffix() {}
623 func Example_suffix_xX_X_x() {}
624 func Example_世界() {} // invalid - suffix must start with a lower-case letter
625 func Example_123() {} // invalid - suffix must start with a lower-case letter
626 func Example_BadSuffix() {} // invalid - suffix must start with a lower-case letter
627
628 func ExampleType1() {}
629 func ExampleType1_() {} // invalid - suffix must start with a lower-case letter
630 func ExampleType1_suffix() {}
631 func ExampleType1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
632 func ExampleType1_Foo() {}
633 func ExampleType1_Foo_suffix() {}
634 func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
635 func ExampleType1_foo() {}
636 func ExampleType1_foo_suffix() {}
637 func ExampleType1_foo_Suffix() {} // matches Type1, instead of Type1_foo
638 func Exampletype2() {} // invalid - cannot match unexported
639
640 func ExampleFunc1() {}
641 func ExampleFunc1_() {} // invalid - suffix must start with a lower-case letter
642 func ExampleFunc1_suffix() {}
643 func ExampleFunc1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
644 func ExampleFunc1_Foo() {}
645 func ExampleFunc1_Foo_suffix() {}
646 func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
647 func ExampleFunc1_foo() {}
648 func ExampleFunc1_foo_suffix() {}
649 func ExampleFunc1_foo_Suffix() {} // matches Func1, instead of Func1_foo
650 func Examplefunc1() {} // invalid - cannot match unexported
651
652 func ExampleType1_Func1() {}
653 func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter
654 func ExampleType1_Func1_suffix() {}
655 func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
656 func ExampleType1_Func1_Foo() {}
657 func ExampleType1_Func1_Foo_suffix() {}
658 func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
659 func ExampleType1_Func1_foo() {}
660 func ExampleType1_Func1_foo_suffix() {}
661 func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo
662 func ExampleType1_func2() {} // matches Type1, instead of Type1.func2
663
664 func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type
665 func ExampleUembed_Func1() {} // methods from embedding unexported types are OK
666 func ExampleUembed_Func1_suffix() {}
667
668 func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type
669 func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type
670 func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type
671 func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type
672 `
673
674
675 fset := token.NewFileSet()
676 files := []*ast.File{
677 mustParse(fset, "src.go", src),
678 mustParse(fset, "src_test.go", test),
679 }
680 p, err := doc.NewFromFiles(fset, files, "example.com/p")
681 if err != nil {
682 t.Fatalf("doc.NewFromFiles: %v", err)
683 }
684
685
686 got := map[string][]string{}
687 got[""] = exampleNames(p.Examples)
688 for _, f := range p.Funcs {
689 got[f.Name] = exampleNames(f.Examples)
690 }
691 for _, t := range p.Types {
692 got[t.Name] = exampleNames(t.Examples)
693 for _, f := range t.Funcs {
694 got[f.Name] = exampleNames(f.Examples)
695 }
696 for _, m := range t.Methods {
697 got[t.Name+"."+m.Name] = exampleNames(m.Examples)
698 }
699 }
700
701 want := map[string][]string{
702 "": {"", "suffix", "suffix_xX_X_x"},
703
704 "Type1": {"", "foo_Suffix", "func2", "suffix"},
705 "Type1_Foo": {"", "suffix"},
706 "Type1_foo": {"", "suffix"},
707
708 "Func1": {"", "foo_Suffix", "suffix"},
709 "Func1_Foo": {"", "suffix"},
710 "Func1_foo": {"", "suffix"},
711
712 "Type1.Func1": {"", "foo_Suffix", "suffix"},
713 "Type1.Func1_Foo": {"", "suffix"},
714 "Type1.Func1_foo": {"", "suffix"},
715
716 "Uembed.Func1": {"", "suffix"},
717
718
719 "Conflict_Conflict": {"", "suffix"},
720 "Conflict_conflict": {"", "suffix"},
721 }
722
723 for id := range got {
724 if !reflect.DeepEqual(got[id], want[id]) {
725 t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id])
726 }
727 }
728 }
729
730 func exampleNames(exs []*doc.Example) (out []string) {
731 for _, ex := range exs {
732 out = append(out, ex.Suffix)
733 }
734 return out
735 }
736
737 func mustParse(fset *token.FileSet, filename, src string) *ast.File {
738 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
739 if err != nil {
740 panic(err)
741 }
742 return f
743 }
744
View as plain text