diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:23:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:23:18 +0000 |
commit | 43a123c1ae6613b3efeed291fa552ecd909d3acf (patch) | |
tree | fd92518b7024bc74031f78a1cf9e454b65e73665 /src/go/doc/example_test.go | |
parent | Initial commit. (diff) | |
download | golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.tar.xz golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.zip |
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/doc/example_test.go')
-rw-r--r-- | src/go/doc/example_test.go | 336 |
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 +} |