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