1// Copyright 2013 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 doc_test
6
7import (
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
20const exampleTestFile = `
21package foo_test
22
23import (
24	"flag"
25	"fmt"
26	"log"
27	"sort"
28	"os/exec"
29)
30
31func ExampleHello() {
32	fmt.Println("Hello, world!")
33	// Output: Hello, world!
34}
35
36func 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
44func 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
56func ExampleKeyValueImport() {
57	f := flag.Flag{
58		Name: "play",
59	}
60	fmt.Print(f)
61	// Output: Name: "play"
62}
63
64var keyValueTopDecl = struct {
65	a string
66	b int
67}{
68	a: "B",
69	b: 2,
70}
71
72func ExampleKeyValueTopDecl() {
73	fmt.Print(keyValueTopDecl)
74	// Output: a: "B", b: 2
75}
76
77// Person represents a person by name and age.
78type Person struct {
79    Name string
80    Age  int
81}
82
83// String returns a string representation of the Person.
84func (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.
90type ByAge []Person
91
92// Len returns the number of elements in ByAge.
93func (a (ByAge)) Len() int { return len(a) }
94
95// Swap swaps the elements in ByAge.
96func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
97func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
98
99// people is the array of Person
100var people = []Person{
101	{"Bob", 31},
102	{"John", 42},
103	{"Michael", 17},
104	{"Jenny", 26},
105}
106
107func 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
117var 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
151const exampleHelloPlay = `package main
152
153import (
154	"fmt"
155)
156
157func main() {
158	fmt.Println("Hello, world!")
159}
160`
161const exampleImportPlay = `package main
162
163import (
164	"fmt"
165	"log"
166	"os/exec"
167)
168
169func 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
178const exampleKeyValuePlay = `package main
179
180import (
181	"fmt"
182)
183
184func 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
196const exampleKeyValueImportPlay = `package main
197
198import (
199	"flag"
200	"fmt"
201)
202
203func main() {
204	f := flag.Flag{
205		Name: "play",
206	}
207	fmt.Print(f)
208}
209`
210
211const exampleKeyValueTopDeclPlay = `package main
212
213import (
214	"fmt"
215)
216
217var keyValueTopDecl = struct {
218	a string
219	b int
220}{
221	a: "B",
222	b: 2,
223}
224
225func main() {
226	fmt.Print(keyValueTopDecl)
227}
228`
229
230const exampleSortPlay = `package main
231
232import (
233	"fmt"
234	"sort"
235)
236
237// Person represents a person by name and age.
238type Person struct {
239	Name string
240	Age  int
241}
242
243// String returns a string representation of the Person.
244func (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.
250type ByAge []Person
251
252// Len returns the number of elements in ByAge.
253func (a ByAge) Len() int { return len(a) }
254
255// Swap swaps the elements in ByAge.
256func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
257func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
258
259// people is the array of Person
260var people = []Person{
261	{"Bob", 31},
262	{"John", 42},
263	{"Michael", 17},
264	{"Jenny", 26},
265}
266
267func main() {
268	fmt.Println(people)
269	sort.Sort(ByAge(people))
270	fmt.Println(people)
271}
272`
273
274func 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
297const exampleWholeFile = `package foo_test
298
299type X int
300
301func (X) Foo() {
302}
303
304func (X) TestBlah() {
305}
306
307func (X) BenchmarkFoo() {
308}
309
310func Example() {
311	fmt.Println("Hello, world!")
312	// Output: Hello, world!
313}
314`
315
316const exampleWholeFileOutput = `package main
317
318type X int
319
320func (X) Foo() {
321}
322
323func (X) TestBlah() {
324}
325
326func (X) BenchmarkFoo() {
327}
328
329func main() {
330	fmt.Println("Hello, world!")
331}
332`
333
334const exampleWholeFileFunction = `package foo_test
335
336func Foo(x int) {
337}
338
339func Example() {
340	fmt.Println("Hello, world!")
341	// Output: Hello, world!
342}
343`
344
345const exampleWholeFileFunctionOutput = `package main
346
347func Foo(x int) {
348}
349
350func main() {
351	fmt.Println("Hello, world!")
352}
353`
354
355const exampleWholeFileExternalFunction = `package foo_test
356
357func foo(int)
358
359func Example() {
360	foo(42)
361	// Output:
362}
363`
364
365const exampleWholeFileExternalFunctionOutput = `package main
366
367func foo(int)
368
369func main() {
370	foo(42)
371}
372`
373
374var 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
397func 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
421const exampleInspectSignature = `package foo_test
422
423import (
424	"bytes"
425	"io"
426)
427
428func getReader() io.Reader { return nil }
429
430func do(b bytes.Reader) {}
431
432func Example() {
433	getReader()
434	do()
435	// Output:
436}
437
438func ExampleIgnored() {
439}
440`
441
442const exampleInspectSignatureOutput = `package main
443
444import (
445	"bytes"
446	"io"
447)
448
449func getReader() io.Reader { return nil }
450
451func do(b bytes.Reader) {}
452
453func main() {
454	getReader()
455	do()
456}
457`
458
459func TestExampleInspectSignature(t *testing.T) {
460	// Verify that "bytes" and "io" are imported. See issue #28492.
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	// We are interested in the first example only.
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
483const exampleEmpty = `
484package p
485func Example() {}
486func Example_a()
487`
488
489const exampleEmptyOutput = `package main
490
491func main() {}
492func main()
493`
494
495func 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
518func 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// This example illustrates how to use NewFromFiles
530// to compute package documentation with examples.
531func ExampleNewFromFiles() {
532	// src and test are two source files that make up
533	// a package whose documentation will be computed.
534	const src = `
535// This is the package comment.
536package p
537
538import "fmt"
539
540// This comment is associated with the Greet function.
541func Greet(who string) {
542	fmt.Printf("Hello, %s!\n", who)
543}
544`
545	const test = `
546package p_test
547
548// This comment is associated with the ExampleGreet_world example.
549func ExampleGreet_world() {
550	Greet("world")
551}
552`
553
554	// Create the AST by parsing src and test.
555	fset := token.NewFileSet()
556	files := []*ast.File{
557		mustParse(fset, "src.go", src),
558		mustParse(fset, "src_test.go", test),
559	}
560
561	// Compute package documentation with examples.
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	// Output:
572	// package p - This is the package comment.
573	// func Greet - This comment is associated with the Greet function.
574	//  ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example.
575}
576
577func TestClassifyExamples(t *testing.T) {
578	const src = `
579package p
580
581const Const1 = 0
582var   Var1   = 0
583
584type (
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
594func Func1()     {}
595func Func1_Foo() {}
596func Func1_foo() {}
597func func2()     {}
598
599func (Type1) Func1() {}
600func (Type1) Func1_Foo() {}
601func (Type1) Func1_foo() {}
602func (Type1) func2() {}
603
604func (type2) Func1() {}
605
606type (
607	Conflict          int
608	Conflict_Conflict int
609	Conflict_conflict int
610)
611
612func (Conflict) Conflict() {}
613`
614	const test = `
615package p_test
616
617func ExampleConst1() {} // invalid - no support for consts and vars
618func ExampleVar1()   {} // invalid - no support for consts and vars
619
620func Example()               {}
621func Example_()              {} // invalid - suffix must start with a lower-case letter
622func Example_suffix()        {}
623func Example_suffix_xX_X_x() {}
624func Example_世界()           {} // invalid - suffix must start with a lower-case letter
625func Example_123()           {} // invalid - suffix must start with a lower-case letter
626func Example_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
627
628func ExampleType1()               {}
629func ExampleType1_()              {} // invalid - suffix must start with a lower-case letter
630func ExampleType1_suffix()        {}
631func ExampleType1_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
632func ExampleType1_Foo()           {}
633func ExampleType1_Foo_suffix()    {}
634func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
635func ExampleType1_foo()           {}
636func ExampleType1_foo_suffix()    {}
637func ExampleType1_foo_Suffix()    {} // matches Type1, instead of Type1_foo
638func Exampletype2()               {} // invalid - cannot match unexported
639
640func ExampleFunc1()               {}
641func ExampleFunc1_()              {} // invalid - suffix must start with a lower-case letter
642func ExampleFunc1_suffix()        {}
643func ExampleFunc1_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
644func ExampleFunc1_Foo()           {}
645func ExampleFunc1_Foo_suffix()    {}
646func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
647func ExampleFunc1_foo()           {}
648func ExampleFunc1_foo_suffix()    {}
649func ExampleFunc1_foo_Suffix()    {} // matches Func1, instead of Func1_foo
650func Examplefunc1()               {} // invalid - cannot match unexported
651
652func ExampleType1_Func1()               {}
653func ExampleType1_Func1_()              {} // invalid - suffix must start with a lower-case letter
654func ExampleType1_Func1_suffix()        {}
655func ExampleType1_Func1_BadSuffix()     {} // invalid - suffix must start with a lower-case letter
656func ExampleType1_Func1_Foo()           {}
657func ExampleType1_Func1_Foo_suffix()    {}
658func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
659func ExampleType1_Func1_foo()           {}
660func ExampleType1_Func1_foo_suffix()    {}
661func ExampleType1_Func1_foo_Suffix()    {} // matches Type1.Func1, instead of Type1.Func1_foo
662func ExampleType1_func2()               {} // matches Type1, instead of Type1.func2
663
664func ExampleEmbed_Func1()         {} // invalid - no support for forwarded methods from embedding exported type
665func ExampleUembed_Func1()        {} // methods from embedding unexported types are OK
666func ExampleUembed_Func1_suffix() {}
667
668func ExampleConflict_Conflict()        {} // ambiguous with either Conflict or Conflict_Conflict type
669func ExampleConflict_conflict()        {} // ambiguous with either Conflict or Conflict_conflict type
670func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type
671func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type
672`
673
674	// Parse literal source code as a *doc.Package.
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	// Collect the association of examples to top-level identifiers.
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"}, // Package-level examples.
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		// These are implementation dependent due to the ambiguous parsing.
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
730func exampleNames(exs []*doc.Example) (out []string) {
731	for _, ex := range exs {
732		out = append(out, ex.Suffix)
733	}
734	return out
735}
736
737func 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