Source file
src/go/doc/doc_test.go
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 TestAnchorID(t *testing.T) {
156 const in = "Important Things 2 Know & Stuff"
157 const want = "hdr-Important_Things_2_Know___Stuff"
158 got := anchorID(in)
159 if got != want {
160 t.Errorf("anchorID(%q) = %q; want %q", in, got, want)
161 }
162 }
163
164 func TestFuncs(t *testing.T) {
165 fset := token.NewFileSet()
166 file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments)
167 if err != nil {
168 t.Fatal(err)
169 }
170 doc, err := NewFromFiles(fset, []*ast.File{file}, "importPath", Mode(0))
171 if err != nil {
172 t.Fatal(err)
173 }
174
175 for _, f := range doc.Funcs {
176 f.Decl = nil
177 }
178 for _, ty := range doc.Types {
179 for _, f := range ty.Funcs {
180 f.Decl = nil
181 }
182 for _, m := range ty.Methods {
183 m.Decl = nil
184 }
185 }
186
187 compareFuncs := func(t *testing.T, msg string, got, want *Func) {
188
189 got.Decl = nil
190 got.Examples = nil
191 if !(got.Doc == want.Doc &&
192 got.Name == want.Name &&
193 got.Recv == want.Recv &&
194 got.Orig == want.Orig &&
195 got.Level == want.Level) {
196 t.Errorf("%s:\ngot %+v\nwant %+v", msg, got, want)
197 }
198 }
199
200 compareSlices(t, "Funcs", doc.Funcs, funcsPackage.Funcs, compareFuncs)
201 compareSlices(t, "Types", doc.Types, funcsPackage.Types, func(t *testing.T, msg string, got, want *Type) {
202 if got.Name != want.Name {
203 t.Errorf("%s.Name: got %q, want %q", msg, got.Name, want.Name)
204 } else {
205 compareSlices(t, got.Name+".Funcs", got.Funcs, want.Funcs, compareFuncs)
206 compareSlices(t, got.Name+".Methods", got.Methods, want.Methods, compareFuncs)
207 }
208 })
209 }
210
211 func compareSlices[E any](t *testing.T, name string, got, want []E, compareElem func(*testing.T, string, E, E)) {
212 if len(got) != len(want) {
213 t.Errorf("%s: got %d, want %d", name, len(got), len(want))
214 }
215 for i := 0; i < len(got) && i < len(want); i++ {
216 compareElem(t, fmt.Sprintf("%s[%d]", name, i), got[i], want[i])
217 }
218 }
219
220 const funcsTestFile = `
221 package funcs
222
223 func F() {}
224
225 type S1 struct {
226 S2 // embedded, exported
227 s3 // embedded, unexported
228 }
229
230 func NewS1() S1 {return S1{} }
231 func NewS1p() *S1 { return &S1{} }
232
233 func (S1) M1() {}
234 func (r S1) M2() {}
235 func(S1) m3() {} // unexported not shown
236 func (*S1) P1() {} // pointer receiver
237
238 type S2 int
239 func (S2) M3() {} // shown on S2
240
241 type s3 int
242 func (s3) M4() {} // shown on S1
243
244 type G1[T any] struct {
245 *s3
246 }
247
248 func NewG1[T any]() G1[T] { return G1[T]{} }
249
250 func (G1[T]) MG1() {}
251 func (*G1[U]) MG2() {}
252
253 type G2[T, U any] struct {}
254
255 func NewG2[T, U any]() G2[T, U] { return G2[T, U]{} }
256
257 func (G2[T, U]) MG3() {}
258 func (*G2[A, B]) MG4() {}
259
260
261 `
262
263 var funcsPackage = &Package{
264 Funcs: []*Func{{Name: "F"}},
265 Types: []*Type{
266 {
267 Name: "G1",
268 Funcs: []*Func{{Name: "NewG1"}},
269 Methods: []*Func{
270 {Name: "M4", Recv: "G1",
271 Orig: "s3", Level: 1},
272 {Name: "MG1", Recv: "G1[T]", Orig: "G1[T]", Level: 0},
273 {Name: "MG2", Recv: "*G1[U]", Orig: "*G1[U]", Level: 0},
274 },
275 },
276 {
277 Name: "G2",
278 Funcs: []*Func{{Name: "NewG2"}},
279 Methods: []*Func{
280 {Name: "MG3", Recv: "G2[T, U]", Orig: "G2[T, U]", Level: 0},
281 {Name: "MG4", Recv: "*G2[A, B]", Orig: "*G2[A, B]", Level: 0},
282 },
283 },
284 {
285 Name: "S1",
286 Funcs: []*Func{{Name: "NewS1"}, {Name: "NewS1p"}},
287 Methods: []*Func{
288 {Name: "M1", Recv: "S1", Orig: "S1", Level: 0},
289 {Name: "M2", Recv: "S1", Orig: "S1", Level: 0},
290 {Name: "M4", Recv: "S1", Orig: "s3", Level: 1},
291 {Name: "P1", Recv: "*S1", Orig: "*S1", Level: 0},
292 },
293 },
294 {
295 Name: "S2",
296 Methods: []*Func{
297 {Name: "M3", Recv: "S2", Orig: "S2", Level: 0},
298 },
299 },
300 },
301 }
302
View as plain text