Source file
src/go/doc/doc_test.go
Documentation: go/doc
1
2
3
4
5 package doc
6
7 import (
8 "bytes"
9 "flag"
10 "fmt"
11 "go/ast"
12 "go/parser"
13 "go/printer"
14 "go/token"
15 "io/fs"
16 "os"
17 "path/filepath"
18 "regexp"
19 "strings"
20 "testing"
21 "text/template"
22 )
23
24 var update = flag.Bool("update", false, "update golden (.out) files")
25 var files = flag.String("files", "", "consider only Go test files matching this regular expression")
26
27 const dataDir = "testdata"
28
29 var templateTxt = readTemplate("template.txt")
30
31 func readTemplate(filename string) *template.Template {
32 t := template.New(filename)
33 t.Funcs(template.FuncMap{
34 "node": nodeFmt,
35 "synopsis": synopsisFmt,
36 "indent": indentFmt,
37 })
38 return template.Must(t.ParseFiles(filepath.Join(dataDir, filename)))
39 }
40
41 func nodeFmt(node any, fset *token.FileSet) string {
42 var buf bytes.Buffer
43 printer.Fprint(&buf, fset, node)
44 return strings.ReplaceAll(strings.TrimSpace(buf.String()), "\n", "\n\t")
45 }
46
47 func synopsisFmt(s string) string {
48 const n = 64
49 if len(s) > n {
50
51 s = s[0:n]
52 if i := strings.LastIndexAny(s, "\t\n "); i >= 0 {
53 s = s[0:i]
54 }
55 s = strings.TrimSpace(s) + " ..."
56 }
57 return "// " + strings.ReplaceAll(s, "\n", " ")
58 }
59
60 func indentFmt(indent, s string) string {
61 end := ""
62 if strings.HasSuffix(s, "\n") {
63 end = "\n"
64 s = s[:len(s)-1]
65 }
66 return indent + strings.ReplaceAll(s, "\n", "\n"+indent) + end
67 }
68
69 func isGoFile(fi fs.FileInfo) bool {
70 name := fi.Name()
71 return !fi.IsDir() &&
72 len(name) > 0 && name[0] != '.' &&
73 filepath.Ext(name) == ".go"
74 }
75
76 type bundle struct {
77 *Package
78 FSet *token.FileSet
79 }
80
81 func test(t *testing.T, mode Mode) {
82
83 filter := isGoFile
84 if *files != "" {
85 rx, err := regexp.Compile(*files)
86 if err != nil {
87 t.Fatal(err)
88 }
89 filter = func(fi fs.FileInfo) bool {
90 return isGoFile(fi) && rx.MatchString(fi.Name())
91 }
92 }
93
94
95 fset := token.NewFileSet()
96 pkgs, err := parser.ParseDir(fset, dataDir, filter, parser.ParseComments)
97 if err != nil {
98 t.Fatal(err)
99 }
100
101
102 for _, pkg := range pkgs {
103 t.Run(pkg.Name, func(t *testing.T) {
104 importPath := dataDir + "/" + pkg.Name
105 var files []*ast.File
106 for _, f := range pkg.Files {
107 files = append(files, f)
108 }
109 doc, err := NewFromFiles(fset, files, importPath, mode)
110 if err != nil {
111 t.Fatal(err)
112 }
113
114
115 for i, filename := range doc.Filenames {
116 doc.Filenames[i] = filepath.ToSlash(filename)
117 }
118
119
120 var buf bytes.Buffer
121 if err := templateTxt.Execute(&buf, bundle{doc, fset}); err != nil {
122 t.Fatal(err)
123 }
124 got := buf.Bytes()
125
126
127 golden := filepath.Join(dataDir, fmt.Sprintf("%s.%d.golden", pkg.Name, mode))
128 if *update {
129 err := os.WriteFile(golden, got, 0644)
130 if err != nil {
131 t.Fatal(err)
132 }
133 }
134
135
136 want, err := os.ReadFile(golden)
137 if err != nil {
138 t.Fatal(err)
139 }
140
141
142 if !bytes.Equal(got, want) {
143 t.Errorf("package %s\n\tgot:\n%s\n\twant:\n%s", pkg.Name, got, want)
144 }
145 })
146 }
147 }
148
149 func Test(t *testing.T) {
150 t.Run("default", func(t *testing.T) { test(t, 0) })
151 t.Run("AllDecls", func(t *testing.T) { test(t, AllDecls) })
152 t.Run("AllMethods", func(t *testing.T) { test(t, AllMethods) })
153 }
154
155 func TestFuncs(t *testing.T) {
156 fset := token.NewFileSet()
157 file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments)
158 if err != nil {
159 t.Fatal(err)
160 }
161 doc, err := NewFromFiles(fset, []*ast.File{file}, "importPath", Mode(0))
162 if err != nil {
163 t.Fatal(err)
164 }
165
166 for _, f := range doc.Funcs {
167 f.Decl = nil
168 }
169 for _, ty := range doc.Types {
170 for _, f := range ty.Funcs {
171 f.Decl = nil
172 }
173 for _, m := range ty.Methods {
174 m.Decl = nil
175 }
176 }
177
178 compareFuncs := func(t *testing.T, msg string, got, want *Func) {
179
180 got.Decl = nil
181 got.Examples = nil
182 if !(got.Doc == want.Doc &&
183 got.Name == want.Name &&
184 got.Recv == want.Recv &&
185 got.Orig == want.Orig &&
186 got.Level == want.Level) {
187 t.Errorf("%s:\ngot %+v\nwant %+v", msg, got, want)
188 }
189 }
190
191 compareSlices(t, "Funcs", doc.Funcs, funcsPackage.Funcs, compareFuncs)
192 compareSlices(t, "Types", doc.Types, funcsPackage.Types, func(t *testing.T, msg string, got, want *Type) {
193 if got.Name != want.Name {
194 t.Errorf("%s.Name: got %q, want %q", msg, got.Name, want.Name)
195 } else {
196 compareSlices(t, got.Name+".Funcs", got.Funcs, want.Funcs, compareFuncs)
197 compareSlices(t, got.Name+".Methods", got.Methods, want.Methods, compareFuncs)
198 }
199 })
200 }
201
202 func compareSlices[E any](t *testing.T, name string, got, want []E, compareElem func(*testing.T, string, E, E)) {
203 if len(got) != len(want) {
204 t.Errorf("%s: got %d, want %d", name, len(got), len(want))
205 }
206 for i := 0; i < len(got) && i < len(want); i++ {
207 compareElem(t, fmt.Sprintf("%s[%d]", name, i), got[i], want[i])
208 }
209 }
210
211 const funcsTestFile = `
212 package funcs
213
214 func F() {}
215
216 type S1 struct {
217 S2 // embedded, exported
218 s3 // embedded, unexported
219 }
220
221 func NewS1() S1 {return S1{} }
222 func NewS1p() *S1 { return &S1{} }
223
224 func (S1) M1() {}
225 func (r S1) M2() {}
226 func(S1) m3() {} // unexported not shown
227 func (*S1) P1() {} // pointer receiver
228
229 type S2 int
230 func (S2) M3() {} // shown on S2
231
232 type s3 int
233 func (s3) M4() {} // shown on S1
234
235 type G1[T any] struct {
236 *s3
237 }
238
239 func NewG1[T any]() G1[T] { return G1[T]{} }
240
241 func (G1[T]) MG1() {}
242 func (*G1[U]) MG2() {}
243
244 type G2[T, U any] struct {}
245
246 func NewG2[T, U any]() G2[T, U] { return G2[T, U]{} }
247
248 func (G2[T, U]) MG3() {}
249 func (*G2[A, B]) MG4() {}
250
251
252 `
253
254 var funcsPackage = &Package{
255 Funcs: []*Func{{Name: "F"}},
256 Types: []*Type{
257 {
258 Name: "G1",
259 Funcs: []*Func{{Name: "NewG1"}},
260 Methods: []*Func{
261 {Name: "M4", Recv: "G1",
262 Orig: "s3", Level: 1},
263 {Name: "MG1", Recv: "G1[T]", Orig: "G1[T]", Level: 0},
264 {Name: "MG2", Recv: "*G1[U]", Orig: "*G1[U]", Level: 0},
265 },
266 },
267 {
268 Name: "G2",
269 Funcs: []*Func{{Name: "NewG2"}},
270 Methods: []*Func{
271 {Name: "MG3", Recv: "G2[T, U]", Orig: "G2[T, U]", Level: 0},
272 {Name: "MG4", Recv: "*G2[A, B]", Orig: "*G2[A, B]", Level: 0},
273 },
274 },
275 {
276 Name: "S1",
277 Funcs: []*Func{{Name: "NewS1"}, {Name: "NewS1p"}},
278 Methods: []*Func{
279 {Name: "M1", Recv: "S1", Orig: "S1", Level: 0},
280 {Name: "M2", Recv: "S1", Orig: "S1", Level: 0},
281 {Name: "M4", Recv: "S1", Orig: "s3", Level: 1},
282 {Name: "P1", Recv: "*S1", Orig: "*S1", Level: 0},
283 },
284 },
285 {
286 Name: "S2",
287 Methods: []*Func{
288 {Name: "M3", Recv: "S2", Orig: "S2", Level: 0},
289 },
290 },
291 },
292 }
293
View as plain text