| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package godoc |
| |
| import ( |
| "bytes" |
| "go/parser" |
| "go/token" |
| "strings" |
| "testing" |
| ) |
| |
| func TestPkgLinkFunc(t *testing.T) { |
| for _, tc := range []struct { |
| path string |
| want string |
| }{ |
| {"/src/fmt", "pkg/fmt"}, |
| {"src/fmt", "pkg/fmt"}, |
| {"/fmt", "pkg/fmt"}, |
| {"fmt", "pkg/fmt"}, |
| } { |
| if got := pkgLinkFunc(tc.path); got != tc.want { |
| t.Errorf("pkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want) |
| } |
| } |
| } |
| |
| func TestSrcPosLinkFunc(t *testing.T) { |
| for _, tc := range []struct { |
| src string |
| line int |
| low int |
| high int |
| want string |
| }{ |
| {"/src/fmt/print.go", 42, 30, 50, "/src/fmt/print.go?s=30:50#L32"}, |
| {"/src/fmt/print.go", 2, 1, 5, "/src/fmt/print.go?s=1:5#L1"}, |
| {"/src/fmt/print.go", 2, 0, 0, "/src/fmt/print.go#L2"}, |
| {"/src/fmt/print.go", 0, 0, 0, "/src/fmt/print.go"}, |
| {"/src/fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"}, |
| {"fmt/print.go", 0, 0, 0, "/src/fmt/print.go"}, |
| {"fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"}, |
| } { |
| if got := srcPosLinkFunc(tc.src, tc.line, tc.low, tc.high); got != tc.want { |
| t.Errorf("srcLinkFunc(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want) |
| } |
| } |
| } |
| |
| func TestSrcLinkFunc(t *testing.T) { |
| for _, tc := range []struct { |
| src string |
| want string |
| }{ |
| {"/src/fmt/print.go", "/src/fmt/print.go"}, |
| {"src/fmt/print.go", "/src/fmt/print.go"}, |
| {"/fmt/print.go", "/src/fmt/print.go"}, |
| {"fmt/print.go", "/src/fmt/print.go"}, |
| } { |
| if got := srcLinkFunc(tc.src); got != tc.want { |
| t.Errorf("srcLinkFunc(%v) = %v; want %v", tc.src, got, tc.want) |
| } |
| } |
| } |
| |
| func TestQueryLinkFunc(t *testing.T) { |
| for _, tc := range []struct { |
| src string |
| query string |
| line int |
| want string |
| }{ |
| {"/src/fmt/print.go", "Sprintf", 33, "/src/fmt/print.go?h=Sprintf#L33"}, |
| {"/src/fmt/print.go", "Sprintf", 0, "/src/fmt/print.go?h=Sprintf"}, |
| {"src/fmt/print.go", "EOF", 33, "/src/fmt/print.go?h=EOF#L33"}, |
| {"src/fmt/print.go", "a%3f+%26b", 1, "/src/fmt/print.go?h=a%3f+%26b#L1"}, |
| } { |
| if got := queryLinkFunc(tc.src, tc.query, tc.line); got != tc.want { |
| t.Errorf("queryLinkFunc(%v, %v, %v) = %v; want %v", tc.src, tc.query, tc.line, got, tc.want) |
| } |
| } |
| } |
| |
| func TestDocLinkFunc(t *testing.T) { |
| for _, tc := range []struct { |
| src string |
| ident string |
| want string |
| }{ |
| {"fmt", "Sprintf", "/pkg/fmt/#Sprintf"}, |
| {"fmt", "EOF", "/pkg/fmt/#EOF"}, |
| } { |
| if got := docLinkFunc(tc.src, tc.ident); got != tc.want { |
| t.Errorf("docLinkFunc(%v, %v) = %v; want %v", tc.src, tc.ident, got, tc.want) |
| } |
| } |
| } |
| |
| func TestSanitizeFunc(t *testing.T) { |
| for _, tc := range []struct { |
| src string |
| want string |
| }{ |
| {}, |
| {"foo", "foo"}, |
| {"func f()", "func f()"}, |
| {"func f(a int,)", "func f(a int)"}, |
| {"func f(a int,\n)", "func f(a int)"}, |
| {"func f(\n\ta int,\n\tb int,\n\tc int,\n)", "func f(a int, b int, c int)"}, |
| {" ( a, b, c ) ", "(a, b, c)"}, |
| {"( a, b, c int, foo bar , )", "(a, b, c int, foo bar)"}, |
| {"{ a, b}", "{a, b}"}, |
| {"[ a, b]", "[a, b]"}, |
| } { |
| if got := sanitizeFunc(tc.src); got != tc.want { |
| t.Errorf("sanitizeFunc(%v) = %v; want %v", tc.src, got, tc.want) |
| } |
| } |
| } |
| |
| // Test that we add <span id="StructName.FieldName"> elements |
| // to the HTML of struct fields. |
| func TestStructFieldsIDAttributes(t *testing.T) { |
| got := linkifySource(t, []byte(` |
| package foo |
| |
| type T struct { |
| NoDoc string |
| |
| // Doc has a comment. |
| Doc string |
| |
| // Opt, if non-nil, is an option. |
| Opt *int |
| |
| // Опция - другое поле. |
| Опция bool |
| } |
| `)) |
| want := `type T struct { |
| <span id="T.NoDoc"></span>NoDoc <a href="/pkg/builtin/#string">string</a> |
| |
| <span id="T.Doc"></span><span class="comment">// Doc has a comment.</span> |
| Doc <a href="/pkg/builtin/#string">string</a> |
| |
| <span id="T.Opt"></span><span class="comment">// Opt, if non-nil, is an option.</span> |
| Opt *<a href="/pkg/builtin/#int">int</a> |
| |
| <span id="T.Опция"></span><span class="comment">// Опция - другое поле.</span> |
| Опция <a href="/pkg/builtin/#bool">bool</a> |
| }` |
| if got != want { |
| t.Errorf("got: %s\n\nwant: %s\n", got, want) |
| } |
| } |
| |
| // Test that we add <span id="ConstName"> elements to the HTML |
| // of definitions in const and var specs. |
| func TestValueSpecIDAttributes(t *testing.T) { |
| got := linkifySource(t, []byte(` |
| package foo |
| |
| const ( |
| NoDoc string = "NoDoc" |
| |
| // Doc has a comment |
| Doc = "Doc" |
| |
| NoVal |
| )`)) |
| want := `const ( |
| <span id="NoDoc">NoDoc</span> <a href="/pkg/builtin/#string">string</a> = "NoDoc" |
| |
| <span class="comment">// Doc has a comment</span> |
| <span id="Doc">Doc</span> = "Doc" |
| |
| <span id="NoVal">NoVal</span> |
| )` |
| if got != want { |
| t.Errorf("got: %s\n\nwant: %s\n", got, want) |
| } |
| } |
| |
| func TestCompositeLitLinkFields(t *testing.T) { |
| got := linkifySource(t, []byte(` |
| package foo |
| |
| type T struct { |
| X int |
| } |
| |
| var S T = T{X: 12}`)) |
| want := `type T struct { |
| <span id="T.X"></span>X <a href="/pkg/builtin/#int">int</a> |
| } |
| var <span id="S">S</span> <a href="#T">T</a> = <a href="#T">T</a>{<a href="#T.X">X</a>: 12}` |
| if got != want { |
| t.Errorf("got: %s\n\nwant: %s\n", got, want) |
| } |
| } |
| |
| func TestFuncDeclNotLink(t *testing.T) { |
| // Function. |
| got := linkifySource(t, []byte(` |
| package http |
| |
| func Get(url string) (resp *Response, err error)`)) |
| want := `func Get(url <a href="/pkg/builtin/#string">string</a>) (resp *<a href="#Response">Response</a>, err <a href="/pkg/builtin/#error">error</a>)` |
| if got != want { |
| t.Errorf("got: %s\n\nwant: %s\n", got, want) |
| } |
| |
| // Method. |
| got = linkifySource(t, []byte(` |
| package http |
| |
| func (h Header) Get(key string) string`)) |
| want = `func (h <a href="#Header">Header</a>) Get(key <a href="/pkg/builtin/#string">string</a>) <a href="/pkg/builtin/#string">string</a>` |
| if got != want { |
| t.Errorf("got: %s\n\nwant: %s\n", got, want) |
| } |
| } |
| |
| func linkifySource(t *testing.T, src []byte) string { |
| p := &Presentation{ |
| DeclLinks: true, |
| } |
| fset := token.NewFileSet() |
| af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| var buf bytes.Buffer |
| pi := &PageInfo{ |
| FSet: fset, |
| } |
| sep := "" |
| for _, decl := range af.Decls { |
| buf.WriteString(sep) |
| sep = "\n" |
| buf.WriteString(p.node_htmlFunc(pi, decl, true)) |
| } |
| return buf.String() |
| } |
| |
| func TestScanIdentifier(t *testing.T) { |
| tests := []struct { |
| in, want string |
| }{ |
| {"foo bar", "foo"}, |
| {"foo/bar", "foo"}, |
| {" foo", ""}, |
| {"фоо", "фоо"}, |
| {"f123", "f123"}, |
| {"123f", ""}, |
| } |
| for _, tt := range tests { |
| got := scanIdentifier([]byte(tt.in)) |
| if string(got) != tt.want { |
| t.Errorf("scanIdentifier(%q) = %q; want %q", tt.in, got, tt.want) |
| } |
| } |
| } |
| |
| func TestReplaceLeadingIndentation(t *testing.T) { |
| oldIndent := strings.Repeat(" ", 2) |
| newIndent := strings.Repeat(" ", 4) |
| tests := []struct { |
| src, want string |
| }{ |
| {" foo\n bar\n baz", " foo\n bar\n baz"}, |
| {" '`'\n '`'\n", " '`'\n '`'\n"}, |
| {" '\\''\n '`'\n", " '\\''\n '`'\n"}, |
| {" \"`\"\n \"`\"\n", " \"`\"\n \"`\"\n"}, |
| {" `foo\n bar`", " `foo\n bar`"}, |
| {" `foo\\`\n bar", " `foo\\`\n bar"}, |
| {" '\\`'`foo\n bar", " '\\`'`foo\n bar"}, |
| { |
| " if true {\n foo := `One\n \tTwo\nThree`\n }\n", |
| " if true {\n foo := `One\n \tTwo\n Three`\n }\n", |
| }, |
| } |
| for _, tc := range tests { |
| if got := replaceLeadingIndentation(tc.src, oldIndent, newIndent); got != tc.want { |
| t.Errorf("replaceLeadingIndentation:\n%v\n---\nhave:\n%v\n---\nwant:\n%v\n", |
| tc.src, got, tc.want) |
| } |
| } |
| } |
| |
| func TestSrcBreadcrumbFunc(t *testing.T) { |
| for _, tc := range []struct { |
| path string |
| want string |
| }{ |
| {"src/", `<span class="text-muted">src/</span>`}, |
| {"src/fmt/", `<a href="/src">src</a>/<span class="text-muted">fmt/</span>`}, |
| {"src/fmt/print.go", `<a href="/src">src</a>/<a href="/src/fmt">fmt</a>/<span class="text-muted">print.go</span>`}, |
| } { |
| if got := srcBreadcrumbFunc(tc.path); got != tc.want { |
| t.Errorf("srcBreadcrumbFunc(%v) = %v; want %v", tc.path, got, tc.want) |
| } |
| } |
| } |
| |
| func TestSrcToPkgLinkFunc(t *testing.T) { |
| for _, tc := range []struct { |
| path string |
| want string |
| }{ |
| {"src/", `<a href="/pkg">Index</a>`}, |
| {"src/fmt/", `<a href="/pkg/fmt">fmt</a>`}, |
| {"pkg/", `<a href="/pkg">Index</a>`}, |
| {"pkg/LICENSE", `<a href="/pkg">Index</a>`}, |
| } { |
| if got := srcToPkgLinkFunc(tc.path); got != tc.want { |
| t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want) |
| } |
| } |
| } |
| |
| func TestFilterOutBuildAnnotations(t *testing.T) { |
| // TODO: simplify this by using a multiline string once we stop |
| // using go vet from 1.10 on the build dashboard. |
| // https://golang.org/issue/26627 |
| src := []byte("// +build !foo\n" + |
| "// +build !anothertag\n" + |
| "\n" + |
| "// non-tag comment\n" + |
| "\n" + |
| "package foo\n" + |
| "\n" + |
| "func bar() int {\n" + |
| " return 42\n" + |
| "}\n") |
| |
| fset := token.NewFileSet() |
| af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var found bool |
| for _, cg := range af.Comments { |
| if strings.HasPrefix(cg.Text(), "+build ") { |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Errorf("TestFilterOutBuildAnnotations is broken: missing build tag in test input") |
| } |
| |
| found = false |
| for _, cg := range filterOutBuildAnnotations(af.Comments) { |
| if strings.HasPrefix(cg.Text(), "+build ") { |
| t.Errorf("filterOutBuildAnnotations failed to filter build tag") |
| } |
| |
| if strings.Contains(cg.Text(), "non-tag comment") { |
| found = true |
| } |
| } |
| if !found { |
| t.Errorf("filterOutBuildAnnotations should not remove non-build tag comment") |
| } |
| } |
| |
| func TestLinkifyGenerics(t *testing.T) { |
| got := linkifySource(t, []byte(` |
| package foo |
| |
| type T struct { |
| field *T |
| } |
| |
| type ParametricStruct[T any] struct { |
| field *T |
| } |
| |
| func F1[T any](arg T) { } |
| |
| func F2(arg T) { } |
| |
| func (*ParametricStruct[T]) M(arg T) { } |
| |
| func (*T) M(arg T) { } |
| |
| type ParametricStruct2[T1, T2 any] struct { |
| a T1 |
| b T2 |
| } |
| |
| func (*ParametricStruct2[T1, T2]) M(a T1, b T2) { } |
| |
| |
| `)) |
| |
| want := `type T struct { |
| <span id="T.field"></span>field *<a href="#T">T</a> |
| } |
| type ParametricStruct[T <a href="/pkg/builtin/#any">any</a>] struct { |
| <span id="ParametricStruct.field"></span>field *T |
| } |
| func F1[T <a href="/pkg/builtin/#any">any</a>](arg T) {} |
| func F2(arg <a href="#T">T</a>) {} |
| func (*<a href="#ParametricStruct">ParametricStruct</a>[T]) M(arg T) {} |
| func (*<a href="#T">T</a>) M(arg <a href="#T">T</a>) {} |
| type ParametricStruct2[T1, T2 <a href="/pkg/builtin/#any">any</a>] struct { |
| <span id="ParametricStruct2.a"></span>a T1 |
| <span id="ParametricStruct2.b"></span>b T2 |
| } |
| func (*<a href="#ParametricStruct2">ParametricStruct2</a>[T1, T2]) M(a T1, b T2) {}` |
| |
| if got != want { |
| t.Errorf("got: %s\n\nwant: %s\n", got, want) |
| } |
| } |