github.com/lovishpuri/go-40569/src@v0.0.0-20230519171745-f8623e7c56cf/go/doc/example_test.go (about) 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 "internal/diff" 16 "internal/txtar" 17 "path/filepath" 18 "reflect" 19 "strings" 20 "testing" 21 ) 22 23 func TestExamples(t *testing.T) { 24 dir := filepath.Join("testdata", "examples") 25 filenames, err := filepath.Glob(filepath.Join(dir, "*.go")) 26 if err != nil { 27 t.Fatal(err) 28 } 29 for _, filename := range filenames { 30 t.Run(strings.TrimSuffix(filepath.Base(filename), ".go"), func(t *testing.T) { 31 fset := token.NewFileSet() 32 astFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments) 33 if err != nil { 34 t.Fatal(err) 35 } 36 goldenFilename := strings.TrimSuffix(filename, ".go") + ".golden" 37 archive, err := txtar.ParseFile(goldenFilename) 38 if err != nil { 39 t.Fatal(err) 40 } 41 golden := map[string]string{} 42 for _, f := range archive.Files { 43 golden[f.Name] = strings.TrimSpace(string(f.Data)) 44 } 45 46 // Collect the results of doc.Examples in a map keyed by example name. 47 examples := map[string]*doc.Example{} 48 for _, e := range doc.Examples(astFile) { 49 examples[e.Name] = e 50 // Treat missing sections in the golden as empty. 51 for _, kind := range []string{"Play", "Output"} { 52 key := e.Name + "." + kind 53 if _, ok := golden[key]; !ok { 54 golden[key] = "" 55 } 56 } 57 } 58 59 // Each section in the golden file corresponds to an example we expect 60 // to see. 61 for sectionName, want := range golden { 62 name, kind, found := strings.Cut(sectionName, ".") 63 if !found { 64 t.Fatalf("bad section name %q, want EXAMPLE_NAME.KIND", sectionName) 65 } 66 ex := examples[name] 67 if ex == nil { 68 t.Fatalf("no example named %q", name) 69 } 70 71 var got string 72 switch kind { 73 case "Play": 74 got = strings.TrimSpace(formatFile(t, fset, ex.Play)) 75 76 case "Output": 77 got = strings.TrimSpace(ex.Output) 78 default: 79 t.Fatalf("bad section kind %q", kind) 80 } 81 82 if got != want { 83 t.Errorf("%s mismatch:\n%s", sectionName, 84 diff.Diff("want", []byte(want), "got", []byte(got))) 85 } 86 } 87 }) 88 } 89 } 90 91 func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string { 92 t.Helper() 93 if n == nil { 94 return "<nil>" 95 } 96 var buf bytes.Buffer 97 if err := format.Node(&buf, fset, n); err != nil { 98 t.Fatal(err) 99 } 100 return buf.String() 101 } 102 103 // This example illustrates how to use NewFromFiles 104 // to compute package documentation with examples. 105 func ExampleNewFromFiles() { 106 // src and test are two source files that make up 107 // a package whose documentation will be computed. 108 const src = ` 109 // This is the package comment. 110 package p 111 112 import "fmt" 113 114 // This comment is associated with the Greet function. 115 func Greet(who string) { 116 fmt.Printf("Hello, %s!\n", who) 117 } 118 ` 119 const test = ` 120 package p_test 121 122 // This comment is associated with the ExampleGreet_world example. 123 func ExampleGreet_world() { 124 Greet("world") 125 } 126 ` 127 128 // Create the AST by parsing src and test. 129 fset := token.NewFileSet() 130 files := []*ast.File{ 131 mustParse(fset, "src.go", src), 132 mustParse(fset, "src_test.go", test), 133 } 134 135 // Compute package documentation with examples. 136 p, err := doc.NewFromFiles(fset, files, "example.com/p") 137 if err != nil { 138 panic(err) 139 } 140 141 fmt.Printf("package %s - %s", p.Name, p.Doc) 142 fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc) 143 fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc) 144 145 // Output: 146 // package p - This is the package comment. 147 // func Greet - This comment is associated with the Greet function. 148 // ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example. 149 } 150 151 func TestClassifyExamples(t *testing.T) { 152 const src = ` 153 package p 154 155 const Const1 = 0 156 var Var1 = 0 157 158 type ( 159 Type1 int 160 Type1_Foo int 161 Type1_foo int 162 type2 int 163 164 Embed struct { Type1 } 165 Uembed struct { type2 } 166 ) 167 168 func Func1() {} 169 func Func1_Foo() {} 170 func Func1_foo() {} 171 func func2() {} 172 173 func (Type1) Func1() {} 174 func (Type1) Func1_Foo() {} 175 func (Type1) Func1_foo() {} 176 func (Type1) func2() {} 177 178 func (type2) Func1() {} 179 180 type ( 181 Conflict int 182 Conflict_Conflict int 183 Conflict_conflict int 184 ) 185 186 func (Conflict) Conflict() {} 187 188 func GFunc[T any]() {} 189 190 type GType[T any] int 191 192 func (GType[T]) M() {} 193 ` 194 const test = ` 195 package p_test 196 197 func ExampleConst1() {} // invalid - no support for consts and vars 198 func ExampleVar1() {} // invalid - no support for consts and vars 199 200 func Example() {} 201 func Example_() {} // invalid - suffix must start with a lower-case letter 202 func Example_suffix() {} 203 func Example_suffix_xX_X_x() {} 204 func Example_世界() {} // invalid - suffix must start with a lower-case letter 205 func Example_123() {} // invalid - suffix must start with a lower-case letter 206 func Example_BadSuffix() {} // invalid - suffix must start with a lower-case letter 207 208 func ExampleType1() {} 209 func ExampleType1_() {} // invalid - suffix must start with a lower-case letter 210 func ExampleType1_suffix() {} 211 func ExampleType1_BadSuffix() {} // invalid - suffix must start with a lower-case letter 212 func ExampleType1_Foo() {} 213 func ExampleType1_Foo_suffix() {} 214 func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter 215 func ExampleType1_foo() {} 216 func ExampleType1_foo_suffix() {} 217 func ExampleType1_foo_Suffix() {} // matches Type1, instead of Type1_foo 218 func Exampletype2() {} // invalid - cannot match unexported 219 220 func ExampleFunc1() {} 221 func ExampleFunc1_() {} // invalid - suffix must start with a lower-case letter 222 func ExampleFunc1_suffix() {} 223 func ExampleFunc1_BadSuffix() {} // invalid - suffix must start with a lower-case letter 224 func ExampleFunc1_Foo() {} 225 func ExampleFunc1_Foo_suffix() {} 226 func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter 227 func ExampleFunc1_foo() {} 228 func ExampleFunc1_foo_suffix() {} 229 func ExampleFunc1_foo_Suffix() {} // matches Func1, instead of Func1_foo 230 func Examplefunc1() {} // invalid - cannot match unexported 231 232 func ExampleType1_Func1() {} 233 func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter 234 func ExampleType1_Func1_suffix() {} 235 func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter 236 func ExampleType1_Func1_Foo() {} 237 func ExampleType1_Func1_Foo_suffix() {} 238 func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter 239 func ExampleType1_Func1_foo() {} 240 func ExampleType1_Func1_foo_suffix() {} 241 func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo 242 func ExampleType1_func2() {} // matches Type1, instead of Type1.func2 243 244 func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type 245 func ExampleUembed_Func1() {} // methods from embedding unexported types are OK 246 func ExampleUembed_Func1_suffix() {} 247 248 func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type 249 func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type 250 func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type 251 func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type 252 253 func ExampleGFunc() {} 254 func ExampleGFunc_suffix() {} 255 256 func ExampleGType_M() {} 257 func ExampleGType_M_suffix() {} 258 ` 259 260 // Parse literal source code as a *doc.Package. 261 fset := token.NewFileSet() 262 files := []*ast.File{ 263 mustParse(fset, "src.go", src), 264 mustParse(fset, "src_test.go", test), 265 } 266 p, err := doc.NewFromFiles(fset, files, "example.com/p") 267 if err != nil { 268 t.Fatalf("doc.NewFromFiles: %v", err) 269 } 270 271 // Collect the association of examples to top-level identifiers. 272 got := map[string][]string{} 273 got[""] = exampleNames(p.Examples) 274 for _, f := range p.Funcs { 275 got[f.Name] = exampleNames(f.Examples) 276 } 277 for _, t := range p.Types { 278 got[t.Name] = exampleNames(t.Examples) 279 for _, f := range t.Funcs { 280 got[f.Name] = exampleNames(f.Examples) 281 } 282 for _, m := range t.Methods { 283 got[t.Name+"."+m.Name] = exampleNames(m.Examples) 284 } 285 } 286 287 want := map[string][]string{ 288 "": {"", "suffix", "suffix_xX_X_x"}, // Package-level examples. 289 290 "Type1": {"", "foo_Suffix", "func2", "suffix"}, 291 "Type1_Foo": {"", "suffix"}, 292 "Type1_foo": {"", "suffix"}, 293 294 "Func1": {"", "foo_Suffix", "suffix"}, 295 "Func1_Foo": {"", "suffix"}, 296 "Func1_foo": {"", "suffix"}, 297 298 "Type1.Func1": {"", "foo_Suffix", "suffix"}, 299 "Type1.Func1_Foo": {"", "suffix"}, 300 "Type1.Func1_foo": {"", "suffix"}, 301 302 "Uembed.Func1": {"", "suffix"}, 303 304 // These are implementation dependent due to the ambiguous parsing. 305 "Conflict_Conflict": {"", "suffix"}, 306 "Conflict_conflict": {"", "suffix"}, 307 308 "GFunc": {"", "suffix"}, 309 "GType.M": {"", "suffix"}, 310 } 311 312 for id := range got { 313 if !reflect.DeepEqual(got[id], want[id]) { 314 t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id]) 315 } 316 delete(want, id) 317 } 318 if len(want) > 0 { 319 t.Errorf("did not find:\n%q", want) 320 } 321 } 322 323 func exampleNames(exs []*doc.Example) (out []string) { 324 for _, ex := range exs { 325 out = append(out, ex.Suffix) 326 } 327 return out 328 } 329 330 func mustParse(fset *token.FileSet, filename, src string) *ast.File { 331 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 332 if err != nil { 333 panic(err) 334 } 335 return f 336 }