Source file src/go/doc/example_test.go

     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  
     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  	// Verify that "bytes" and "io" are imported. See issue #28492.
   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  	// We are interested in the first example only.
   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  // This example illustrates how to use NewFromFiles
   536  // to compute package documentation with examples.
   537  func ExampleNewFromFiles() {
   538  	// src and test are two source files that make up
   539  	// a package whose documentation will be computed.
   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  	// Create the AST by parsing src and test.
   561  	fset := token.NewFileSet()
   562  	files := []*ast.File{
   563  		mustParse(fset, "src.go", src),
   564  		mustParse(fset, "src_test.go", test),
   565  	}
   566  
   567  	// Compute package documentation with examples.
   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  	// Output:
   578  	// package p - This is the package comment.
   579  	// func Greet - This comment is associated with the Greet function.
   580  	//  ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example.
   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  	// Parse literal source code as a *doc.Package.
   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  	// Collect the association of examples to top-level identifiers.
   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"}, // Package-level examples.
   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  		// These are implementation dependent due to the ambiguous parsing.
   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