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
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
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
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"},
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
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 }
337
View as plain text