summaryrefslogtreecommitdiffstats
path: root/src/go/doc/example_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/go/doc/example_test.go')
-rw-r--r--src/go/doc/example_test.go336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/go/doc/example_test.go b/src/go/doc/example_test.go
new file mode 100644
index 0000000..7919c3a
--- /dev/null
+++ b/src/go/doc/example_test.go
@@ -0,0 +1,336 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package doc_test
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/doc"
+ "go/format"
+ "go/parser"
+ "go/token"
+ "internal/diff"
+ "internal/txtar"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestExamples(t *testing.T) {
+ dir := filepath.Join("testdata", "examples")
+ filenames, err := filepath.Glob(filepath.Join(dir, "*.go"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, filename := range filenames {
+ t.Run(strings.TrimSuffix(filepath.Base(filename), ".go"), func(t *testing.T) {
+ fset := token.NewFileSet()
+ astFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ goldenFilename := strings.TrimSuffix(filename, ".go") + ".golden"
+ archive, err := txtar.ParseFile(goldenFilename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ golden := map[string]string{}
+ for _, f := range archive.Files {
+ golden[f.Name] = strings.TrimSpace(string(f.Data))
+ }
+
+ // Collect the results of doc.Examples in a map keyed by example name.
+ examples := map[string]*doc.Example{}
+ for _, e := range doc.Examples(astFile) {
+ examples[e.Name] = e
+ // Treat missing sections in the golden as empty.
+ for _, kind := range []string{"Play", "Output"} {
+ key := e.Name + "." + kind
+ if _, ok := golden[key]; !ok {
+ golden[key] = ""
+ }
+ }
+ }
+
+ // Each section in the golden file corresponds to an example we expect
+ // to see.
+ for sectionName, want := range golden {
+ name, kind, found := strings.Cut(sectionName, ".")
+ if !found {
+ t.Fatalf("bad section name %q, want EXAMPLE_NAME.KIND", sectionName)
+ }
+ ex := examples[name]
+ if ex == nil {
+ t.Fatalf("no example named %q", name)
+ }
+
+ var got string
+ switch kind {
+ case "Play":
+ got = strings.TrimSpace(formatFile(t, fset, ex.Play))
+
+ case "Output":
+ got = strings.TrimSpace(ex.Output)
+ default:
+ t.Fatalf("bad section kind %q", kind)
+ }
+
+ if got != want {
+ t.Errorf("%s mismatch:\n%s", sectionName,
+ diff.Diff("want", []byte(want), "got", []byte(got)))
+ }
+ }
+ })
+ }
+}
+
+func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string {
+ t.Helper()
+ if n == nil {
+ return "<nil>"
+ }
+ var buf bytes.Buffer
+ if err := format.Node(&buf, fset, n); err != nil {
+ t.Fatal(err)
+ }
+ return buf.String()
+}
+
+// This example illustrates how to use NewFromFiles
+// to compute package documentation with examples.
+func ExampleNewFromFiles() {
+ // src and test are two source files that make up
+ // a package whose documentation will be computed.
+ const src = `
+// This is the package comment.
+package p
+
+import "fmt"
+
+// This comment is associated with the Greet function.
+func Greet(who string) {
+ fmt.Printf("Hello, %s!\n", who)
+}
+`
+ const test = `
+package p_test
+
+// This comment is associated with the ExampleGreet_world example.
+func ExampleGreet_world() {
+ Greet("world")
+}
+`
+
+ // Create the AST by parsing src and test.
+ fset := token.NewFileSet()
+ files := []*ast.File{
+ mustParse(fset, "src.go", src),
+ mustParse(fset, "src_test.go", test),
+ }
+
+ // Compute package documentation with examples.
+ p, err := doc.NewFromFiles(fset, files, "example.com/p")
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("package %s - %s", p.Name, p.Doc)
+ fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc)
+ fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc)
+
+ // Output:
+ // package p - This is the package comment.
+ // func Greet - This comment is associated with the Greet function.
+ // ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example.
+}
+
+func TestClassifyExamples(t *testing.T) {
+ const src = `
+package p
+
+const Const1 = 0
+var Var1 = 0
+
+type (
+ Type1 int
+ Type1_Foo int
+ Type1_foo int
+ type2 int
+
+ Embed struct { Type1 }
+ Uembed struct { type2 }
+)
+
+func Func1() {}
+func Func1_Foo() {}
+func Func1_foo() {}
+func func2() {}
+
+func (Type1) Func1() {}
+func (Type1) Func1_Foo() {}
+func (Type1) Func1_foo() {}
+func (Type1) func2() {}
+
+func (type2) Func1() {}
+
+type (
+ Conflict int
+ Conflict_Conflict int
+ Conflict_conflict int
+)
+
+func (Conflict) Conflict() {}
+
+func GFunc[T any]() {}
+
+type GType[T any] int
+
+func (GType[T]) M() {}
+`
+ const test = `
+package p_test
+
+func ExampleConst1() {} // invalid - no support for consts and vars
+func ExampleVar1() {} // invalid - no support for consts and vars
+
+func Example() {}
+func Example_() {} // invalid - suffix must start with a lower-case letter
+func Example_suffix() {}
+func Example_suffix_xX_X_x() {}
+func Example_世界() {} // invalid - suffix must start with a lower-case letter
+func Example_123() {} // invalid - suffix must start with a lower-case letter
+func Example_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+
+func ExampleType1() {}
+func ExampleType1_() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_suffix() {}
+func ExampleType1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Foo() {}
+func ExampleType1_Foo_suffix() {}
+func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_foo() {}
+func ExampleType1_foo_suffix() {}
+func ExampleType1_foo_Suffix() {} // matches Type1, instead of Type1_foo
+func Exampletype2() {} // invalid - cannot match unexported
+
+func ExampleFunc1() {}
+func ExampleFunc1_() {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_suffix() {}
+func ExampleFunc1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_Foo() {}
+func ExampleFunc1_Foo_suffix() {}
+func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_foo() {}
+func ExampleFunc1_foo_suffix() {}
+func ExampleFunc1_foo_Suffix() {} // matches Func1, instead of Func1_foo
+func Examplefunc1() {} // invalid - cannot match unexported
+
+func ExampleType1_Func1() {}
+func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_suffix() {}
+func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_Foo() {}
+func ExampleType1_Func1_Foo_suffix() {}
+func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_foo() {}
+func ExampleType1_Func1_foo_suffix() {}
+func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo
+func ExampleType1_func2() {} // matches Type1, instead of Type1.func2
+
+func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type
+func ExampleUembed_Func1() {} // methods from embedding unexported types are OK
+func ExampleUembed_Func1_suffix() {}
+
+func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type
+func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type
+func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type
+func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type
+
+func ExampleGFunc() {}
+func ExampleGFunc_suffix() {}
+
+func ExampleGType_M() {}
+func ExampleGType_M_suffix() {}
+`
+
+ // Parse literal source code as a *doc.Package.
+ fset := token.NewFileSet()
+ files := []*ast.File{
+ mustParse(fset, "src.go", src),
+ mustParse(fset, "src_test.go", test),
+ }
+ p, err := doc.NewFromFiles(fset, files, "example.com/p")
+ if err != nil {
+ t.Fatalf("doc.NewFromFiles: %v", err)
+ }
+
+ // Collect the association of examples to top-level identifiers.
+ got := map[string][]string{}
+ got[""] = exampleNames(p.Examples)
+ for _, f := range p.Funcs {
+ got[f.Name] = exampleNames(f.Examples)
+ }
+ for _, t := range p.Types {
+ got[t.Name] = exampleNames(t.Examples)
+ for _, f := range t.Funcs {
+ got[f.Name] = exampleNames(f.Examples)
+ }
+ for _, m := range t.Methods {
+ got[t.Name+"."+m.Name] = exampleNames(m.Examples)
+ }
+ }
+
+ want := map[string][]string{
+ "": {"", "suffix", "suffix_xX_X_x"}, // Package-level examples.
+
+ "Type1": {"", "foo_Suffix", "func2", "suffix"},
+ "Type1_Foo": {"", "suffix"},
+ "Type1_foo": {"", "suffix"},
+
+ "Func1": {"", "foo_Suffix", "suffix"},
+ "Func1_Foo": {"", "suffix"},
+ "Func1_foo": {"", "suffix"},
+
+ "Type1.Func1": {"", "foo_Suffix", "suffix"},
+ "Type1.Func1_Foo": {"", "suffix"},
+ "Type1.Func1_foo": {"", "suffix"},
+
+ "Uembed.Func1": {"", "suffix"},
+
+ // These are implementation dependent due to the ambiguous parsing.
+ "Conflict_Conflict": {"", "suffix"},
+ "Conflict_conflict": {"", "suffix"},
+
+ "GFunc": {"", "suffix"},
+ "GType.M": {"", "suffix"},
+ }
+
+ for id := range got {
+ if !reflect.DeepEqual(got[id], want[id]) {
+ t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id])
+ }
+ delete(want, id)
+ }
+ if len(want) > 0 {
+ t.Errorf("did not find:\n%q", want)
+ }
+}
+
+func exampleNames(exs []*doc.Example) (out []string) {
+ for _, ex := range exs {
+ out = append(out, ex.Suffix)
+ }
+ return out
+}
+
+func mustParse(fset *token.FileSet, filename, src string) *ast.File {
+ f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+ if err != nil {
+ panic(err)
+ }
+ return f
+}