1 | // Copyright 2018 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 godoc |
6 | |
7 | import ( |
8 | "go/doc" |
9 | "net/http" |
10 | "net/http/httptest" |
11 | "net/url" |
12 | "sort" |
13 | "strings" |
14 | "testing" |
15 | "text/template" |
16 | |
17 | "golang.org/x/tools/godoc/vfs/mapfs" |
18 | "golang.org/x/tools/internal/typeparams" |
19 | ) |
20 | |
21 | // TestIgnoredGoFiles tests the scenario where a folder has no .go or .c files, |
22 | // but has an ignored go file. |
23 | func TestIgnoredGoFiles(t *testing.T) { |
24 | packagePath := "github.com/package" |
25 | packageComment := "main is documented in an ignored .go file" |
26 | |
27 | c := NewCorpus(mapfs.New(map[string]string{ |
28 | "src/" + packagePath + "/ignored.go": `// +build ignore |
29 | |
30 | // ` + packageComment + ` |
31 | package main`})) |
32 | srv := &handlerServer{ |
33 | p: &Presentation{ |
34 | Corpus: c, |
35 | }, |
36 | c: c, |
37 | } |
38 | pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, NoFiltering, "linux", "amd64") |
39 | |
40 | if pInfo.PDoc == nil { |
41 | t.Error("pInfo.PDoc = nil; want non-nil.") |
42 | } else { |
43 | if got, want := pInfo.PDoc.Doc, packageComment+"\n"; got != want { |
44 | t.Errorf("pInfo.PDoc.Doc = %q; want %q.", got, want) |
45 | } |
46 | if got, want := pInfo.PDoc.Name, "main"; got != want { |
47 | t.Errorf("pInfo.PDoc.Name = %q; want %q.", got, want) |
48 | } |
49 | if got, want := pInfo.PDoc.ImportPath, packagePath; got != want { |
50 | t.Errorf("pInfo.PDoc.ImportPath = %q; want %q.", got, want) |
51 | } |
52 | } |
53 | if pInfo.FSet == nil { |
54 | t.Error("pInfo.FSet = nil; want non-nil.") |
55 | } |
56 | } |
57 | |
58 | func TestIssue5247(t *testing.T) { |
59 | const packagePath = "example.com/p" |
60 | c := NewCorpus(mapfs.New(map[string]string{ |
61 | "src/" + packagePath + "/p.go": `package p |
62 | |
63 | //line notgen.go:3 |
64 | // F doc //line 1 should appear |
65 | // line 2 should appear |
66 | func F() |
67 | //line foo.go:100`})) // No newline at end to check corner cases. |
68 | |
69 | srv := &handlerServer{ |
70 | p: &Presentation{Corpus: c}, |
71 | c: c, |
72 | } |
73 | pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, 0, "linux", "amd64") |
74 | if got, want := pInfo.PDoc.Funcs[0].Doc, "F doc //line 1 should appear\nline 2 should appear\n"; got != want { |
75 | t.Errorf("pInfo.PDoc.Funcs[0].Doc = %q; want %q", got, want) |
76 | } |
77 | } |
78 | |
79 | func testServeBody(t *testing.T, p *Presentation, path, body string) { |
80 | t.Helper() |
81 | r := &http.Request{URL: &url.URL{Path: path}} |
82 | rw := httptest.NewRecorder() |
83 | p.ServeFile(rw, r) |
84 | if rw.Code != 200 || !strings.Contains(rw.Body.String(), body) { |
85 | t.Fatalf("GET %s: expected 200 w/ %q: got %d w/ body:\n%s", |
86 | path, body, rw.Code, rw.Body) |
87 | } |
88 | } |
89 | |
90 | func TestRedirectAndMetadata(t *testing.T) { |
91 | c := NewCorpus(mapfs.New(map[string]string{ |
92 | "doc/y/index.html": "Hello, y.", |
93 | "doc/x/index.html": `<!--{ |
94 | "Path": "/doc/x/" |
95 | }--> |
96 | |
97 | Hello, x. |
98 | `})) |
99 | c.updateMetadata() |
100 | p := &Presentation{ |
101 | Corpus: c, |
102 | GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)), |
103 | } |
104 | |
105 | // Test that redirect is sent back correctly. |
106 | // Used to panic. See golang.org/issue/40665. |
107 | for _, elem := range []string{"x", "y"} { |
108 | dir := "/doc/" + elem + "/" |
109 | |
110 | r := &http.Request{URL: &url.URL{Path: dir + "index.html"}} |
111 | rw := httptest.NewRecorder() |
112 | p.ServeFile(rw, r) |
113 | loc := rw.Result().Header.Get("Location") |
114 | if rw.Code != 301 || loc != dir { |
115 | t.Errorf("GET %s: expected 301 -> %q, got %d -> %q", r.URL.Path, dir, rw.Code, loc) |
116 | } |
117 | |
118 | testServeBody(t, p, dir, "Hello, "+elem) |
119 | } |
120 | } |
121 | |
122 | func TestMarkdown(t *testing.T) { |
123 | p := &Presentation{ |
124 | Corpus: NewCorpus(mapfs.New(map[string]string{ |
125 | "doc/test.md": "**bold**", |
126 | "doc/test2.md": `{{"*template*"}}`, |
127 | })), |
128 | GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)), |
129 | } |
130 | |
131 | testServeBody(t, p, "/doc/test.html", "<strong>bold</strong>") |
132 | testServeBody(t, p, "/doc/test2.html", "<em>template</em>") |
133 | } |
134 | |
135 | func TestGenerics(t *testing.T) { |
136 | if !typeparams.Enabled { |
137 | t.Skip("type params are not enabled at this Go version") |
138 | } |
139 | |
140 | c := NewCorpus(mapfs.New(map[string]string{ |
141 | "blah/blah.go": `package blah |
142 | |
143 | var A AStruct[int] |
144 | |
145 | type AStruct[T any] struct { |
146 | A string |
147 | X T |
148 | } |
149 | |
150 | func (a *AStruct[T]) Method() T { |
151 | return a.X |
152 | } |
153 | |
154 | func (a AStruct[T]) NonPointerMethod() T { |
155 | return a.X |
156 | } |
157 | |
158 | func NewAStruct[T any](arg T) *AStruct[T] { |
159 | return &AStruct[T]{ X: arg } |
160 | } |
161 | |
162 | type NonGenericStruct struct { |
163 | B int |
164 | } |
165 | |
166 | func (b *NonGenericStruct) NonGenericMethod() int { |
167 | return b.B |
168 | } |
169 | |
170 | func NewNonGenericStruct(arg int) *NonGenericStruct { |
171 | return &NonGenericStruct{arg} |
172 | } |
173 | |
174 | type Pair[K, V any] struct { |
175 | K K |
176 | V V |
177 | } |
178 | |
179 | func (p Pair[K, V]) Apply(kf func(K) K, vf func(V) V) Pair[K, V] { |
180 | return &Pair{ K: kf(p.K), V: vf(p.V) } |
181 | } |
182 | |
183 | func (p *Pair[K, V]) Set(k K, v V) { |
184 | p.K = k |
185 | p.V = v |
186 | } |
187 | |
188 | func NewPair[K, V any](k K, v V) Pair[K, V] { |
189 | return Pair[K, V]{ k, v } |
190 | } |
191 | `})) |
192 | |
193 | srv := &handlerServer{ |
194 | p: &Presentation{ |
195 | Corpus: c, |
196 | }, |
197 | c: c, |
198 | } |
199 | pInfo := srv.GetPageInfo("/blah/", "", NoFiltering, "linux", "amd64") |
200 | t.Logf("%v\n", pInfo) |
201 | |
202 | findType := func(name string) *doc.Type { |
203 | for _, typ := range pInfo.PDoc.Types { |
204 | if typ.Name == name { |
205 | return typ |
206 | } |
207 | } |
208 | return nil |
209 | } |
210 | |
211 | assertFuncs := func(typ *doc.Type, typFuncs []*doc.Func, funcs ...string) { |
212 | typfuncs := make([]string, len(typFuncs)) |
213 | for i := range typFuncs { |
214 | typfuncs[i] = typFuncs[i].Name |
215 | } |
216 | sort.Strings(typfuncs) |
217 | sort.Strings(funcs) |
218 | if len(typfuncs) != len(funcs) { |
219 | t.Errorf("function mismatch for type %q, got: %q, want: %q", typ.Name, typfuncs, funcs) |
220 | return |
221 | } |
222 | for i := range funcs { |
223 | if funcs[i] != typfuncs[i] { |
224 | t.Errorf("function mismatch for type %q: got: %q, want: %q", typ.Name, typfuncs, funcs) |
225 | return |
226 | } |
227 | } |
228 | } |
229 | |
230 | aStructType := findType("AStruct") |
231 | assertFuncs(aStructType, aStructType.Funcs, "NewAStruct") |
232 | assertFuncs(aStructType, aStructType.Methods, "Method", "NonPointerMethod") |
233 | |
234 | nonGenericStructType := findType("NonGenericStruct") |
235 | assertFuncs(nonGenericStructType, nonGenericStructType.Funcs, "NewNonGenericStruct") |
236 | assertFuncs(nonGenericStructType, nonGenericStructType.Methods, "NonGenericMethod") |
237 | |
238 | pairType := findType("Pair") |
239 | assertFuncs(pairType, pairType.Funcs, "NewPair") |
240 | assertFuncs(pairType, pairType.Methods, "Apply", "Set") |
241 | |
242 | if len(pInfo.PDoc.Funcs) > 0 { |
243 | t.Errorf("unexpected functions in package documentation") |
244 | } |
245 | } |
246 |
Members