Source file
src/go/doc/example_test.go
1
2
3
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
47 examples := map[string]*doc.Example{}
48 for _, e := range doc.Examples(astFile) {
49 examples[e.Name] = e
50
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
60
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
104
105 func ExampleNewFromFiles() {
106
107
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
129 fset := token.NewFileSet()
130 files := []*ast.File{
131 mustParse(fset, "src.go", src),
132 mustParse(fset, "src_test.go", test),
133 }
134
135
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
146
147
148
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 func ExampleFunc1_params(a int) {} // invalid - has parameter
232 func ExampleFunc1_results() int {} // invalid - has results
233
234 func ExampleType1_Func1() {}
235 func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter
236 func ExampleType1_Func1_suffix() {}
237 func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
238 func ExampleType1_Func1_Foo() {}
239 func ExampleType1_Func1_Foo_suffix() {}
240 func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
241 func ExampleType1_Func1_foo() {}
242 func ExampleType1_Func1_foo_suffix() {}
243 func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo
244 func ExampleType1_func2() {} // matches Type1, instead of Type1.func2
245
246 func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type
247 func ExampleUembed_Func1() {} // methods from embedding unexported types are OK
248 func ExampleUembed_Func1_suffix() {}
249
250 func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type
251 func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type
252 func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type
253 func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type
254
255 func ExampleGFunc() {}
256 func ExampleGFunc_suffix() {}
257
258 func ExampleGType_M() {}
259 func ExampleGType_M_suffix() {}
260 `
261
262
263 fset := token.NewFileSet()
264 files := []*ast.File{
265 mustParse(fset, "src.go", src),
266 mustParse(fset, "src_test.go", test),
267 }
268 p, err := doc.NewFromFiles(fset, files, "example.com/p")
269 if err != nil {
270 t.Fatalf("doc.NewFromFiles: %v", err)
271 }
272
273
274 got := map[string][]string{}
275 got[""] = exampleNames(p.Examples)
276 for _, f := range p.Funcs {
277 got[f.Name] = exampleNames(f.Examples)
278 }
279 for _, t := range p.Types {
280 got[t.Name] = exampleNames(t.Examples)
281 for _, f := range t.Funcs {
282 got[f.Name] = exampleNames(f.Examples)
283 }
284 for _, m := range t.Methods {
285 got[t.Name+"."+m.Name] = exampleNames(m.Examples)
286 }
287 }
288
289 want := map[string][]string{
290 "": {"", "suffix", "suffix_xX_X_x"},
291
292 "Type1": {"", "foo_Suffix", "func2", "suffix"},
293 "Type1_Foo": {"", "suffix"},
294 "Type1_foo": {"", "suffix"},
295
296 "Func1": {"", "foo_Suffix", "suffix"},
297 "Func1_Foo": {"", "suffix"},
298 "Func1_foo": {"", "suffix"},
299
300 "Type1.Func1": {"", "foo_Suffix", "suffix"},
301 "Type1.Func1_Foo": {"", "suffix"},
302 "Type1.Func1_foo": {"", "suffix"},
303
304 "Uembed.Func1": {"", "suffix"},
305
306
307 "Conflict_Conflict": {"", "suffix"},
308 "Conflict_conflict": {"", "suffix"},
309
310 "GFunc": {"", "suffix"},
311 "GType.M": {"", "suffix"},
312 }
313
314 for id := range got {
315 if !reflect.DeepEqual(got[id], want[id]) {
316 t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id])
317 }
318 delete(want, id)
319 }
320 if len(want) > 0 {
321 t.Errorf("did not find:\n%q", want)
322 }
323 }
324
325 func exampleNames(exs []*doc.Example) (out []string) {
326 for _, ex := range exs {
327 out = append(out, ex.Suffix)
328 }
329 return out
330 }
331
332 func mustParse(fset *token.FileSet, filename, src string) *ast.File {
333 f, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.SkipObjectResolution)
334 if err != nil {
335 panic(err)
336 }
337 return f
338 }
339
View as plain text