github.com/AndrienkoAleksandr/go@v0.0.19/src/go/doc/doc_test.go (about) 1 // Copyright 2012 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 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 // cut off excess text and go back to a word boundary 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] != '.' && // ignore .files 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 // determine file filter 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 // get packages 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 // test packages 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 // golden files always use / in filenames - canonicalize them 115 for i, filename := range doc.Filenames { 116 doc.Filenames[i] = filepath.ToSlash(filename) 117 } 118 119 // print documentation 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 // update golden file if necessary 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 // get golden file 136 want, err := os.ReadFile(golden) 137 if err != nil { 138 t.Fatal(err) 139 } 140 141 // compare 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 // ignore Decl and Examples 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", // TODO: synthesize a param for 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 }