Black Lives Matter. Support the Equal Justice Initiative.

Source file src/go/doc/example_test.go

Documentation: go/doc

     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 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  	// 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  
   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  // This example illustrates how to use NewFromFiles
   530  // to compute package documentation with examples.
   531  func 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.
   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  	// 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  
   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  	// 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  
   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