зеркало из https://github.com/golang/build.git
299 строки
6.4 KiB
Go
299 строки
6.4 KiB
Go
// Copyright 2023 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 relnote
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"golang.org/x/tools/txtar"
|
|
md "rsc.io/markdown"
|
|
)
|
|
|
|
func TestCheckFragment(t *testing.T) {
|
|
for _, test := range []struct {
|
|
in string
|
|
// part of err.Error(), or empty if success
|
|
want string
|
|
}{
|
|
{
|
|
// has a TODO
|
|
"# heading\nTODO(jba)",
|
|
"",
|
|
},
|
|
{
|
|
// has a sentence
|
|
"# heading\nSomething.",
|
|
"",
|
|
},
|
|
{
|
|
// sentence is inside some formatting
|
|
"# heading\n- _Some_*thing.*",
|
|
"",
|
|
},
|
|
{
|
|
// questions and exclamations are OK
|
|
"# H1\n Are questions ok? \n# H2\n Must write this note!",
|
|
"",
|
|
},
|
|
{
|
|
"",
|
|
"must contain a complete sentence",
|
|
},
|
|
{
|
|
"# heading",
|
|
"must contain a complete sentence",
|
|
},
|
|
} {
|
|
got := CheckFragment(test.in)
|
|
if test.want == "" {
|
|
if got != nil {
|
|
t.Errorf("%q: got %q, want nil", test.in, got)
|
|
}
|
|
} else if got == nil || !strings.Contains(got.Error(), test.want) {
|
|
t.Errorf("%q: got %q, want error containing %q", test.in, got, test.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMerge(t *testing.T) {
|
|
testFiles, err := filepath.Glob(filepath.Join("testdata", "merge", "*.txt"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(testFiles) == 0 {
|
|
t.Fatal("no tests")
|
|
}
|
|
for _, f := range testFiles {
|
|
t.Run(strings.TrimSuffix(filepath.Base(f), ".txt"), func(t *testing.T) {
|
|
fsys, want, err := parseTestFile(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gotDoc, err := Merge(fsys)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := md.ToMarkdown(gotDoc)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("mismatch (-want, +got)\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStdlibPackage(t *testing.T) {
|
|
for _, test := range []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{"", ""},
|
|
{"net/a.md", ""},
|
|
{"stdlib/net/a.md", ""},
|
|
{"stdlib/minor/net/a.md", "net"},
|
|
{"stdlib/minor/heading.md", ""},
|
|
{"stdlib/minor/net/http/a.md", "net/http"},
|
|
} {
|
|
got := stdlibPackage(test.in)
|
|
if w := test.want; got != w {
|
|
t.Errorf("%q: got %q, want %q", test.in, got, w)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStdlibPackageHeading(t *testing.T) {
|
|
h := stdlibPackageHeading("net/http", 1)
|
|
got := md.ToMarkdown(h)
|
|
want := "#### [net/http](/pkg/net/http/)\n"
|
|
if got != want {
|
|
t.Errorf("\ngot %q\nwant %q", got, want)
|
|
}
|
|
}
|
|
|
|
// parseTestFile translates a txtar archive into an fs.FS, except for the
|
|
// file "want", whose contents are returned separately.
|
|
func parseTestFile(filename string) (fsys fs.FS, want string, err error) {
|
|
ar, err := txtar.ParseFile(filename)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
mfs := make(fstest.MapFS)
|
|
for _, f := range ar.Files {
|
|
if f.Name == "want" {
|
|
want = string(f.Data)
|
|
} else {
|
|
mfs[f.Name] = &fstest.MapFile{Data: f.Data}
|
|
}
|
|
}
|
|
if want == "" {
|
|
return nil, "", fmt.Errorf("%s: missing 'want'", filename)
|
|
}
|
|
return mfs, want, nil
|
|
}
|
|
|
|
func TestSortedMarkdownFilenames(t *testing.T) {
|
|
want := []string{
|
|
"a.md",
|
|
"b.md",
|
|
"b/a.md",
|
|
"b/c.md",
|
|
"ba/a.md",
|
|
}
|
|
mfs := make(fstest.MapFS)
|
|
for _, fn := range want {
|
|
mfs[fn] = &fstest.MapFile{}
|
|
}
|
|
mfs["README"] = &fstest.MapFile{}
|
|
mfs["b/other.txt"] = &fstest.MapFile{}
|
|
got, err := sortedMarkdownFilenames(mfs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !slices.Equal(got, want) {
|
|
t.Errorf("\ngot %v\nwant %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRemoveEmptySections(t *testing.T) {
|
|
doc := NewParser().Parse(`
|
|
# h1
|
|
not empty
|
|
|
|
# h2
|
|
|
|
## h3
|
|
|
|
### h4
|
|
|
|
#### h5
|
|
|
|
### h6
|
|
|
|
### h7
|
|
|
|
## h8
|
|
something
|
|
|
|
## h9
|
|
|
|
# h10
|
|
`)
|
|
bs := removeEmptySections(doc.Blocks)
|
|
got := md.ToMarkdown(&md.Document{Blocks: bs})
|
|
want := md.ToMarkdown(NewParser().Parse(`
|
|
# h1
|
|
not empty
|
|
|
|
# h2
|
|
|
|
## h8
|
|
something
|
|
`))
|
|
if got != want {
|
|
t.Errorf("\ngot:\n%s\nwant:\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestParseAPIFile(t *testing.T) {
|
|
fsys := fstest.MapFS{
|
|
"123.next": &fstest.MapFile{Data: []byte(`
|
|
pkg p1, type T struct
|
|
pkg p2, func F(int, bool) #123
|
|
pkg syscall (windows-386), const WSAENOPROTOOPT = 10042 #62254
|
|
`)},
|
|
}
|
|
got, err := parseAPIFile(fsys, "123.next")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []APIFeature{
|
|
{"p1", "", "type T struct", 0},
|
|
{"p2", "", "func F(int, bool)", 123},
|
|
{"syscall", "(windows-386)", "const WSAENOPROTOOPT = 10042", 62254},
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("\ngot %#v\nwant %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestCheckAPIFile(t *testing.T) {
|
|
testFiles, err := filepath.Glob(filepath.Join("testdata", "checkAPIFile", "*.txt"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(testFiles) == 0 {
|
|
t.Fatal("no tests")
|
|
}
|
|
for _, f := range testFiles {
|
|
t.Run(strings.TrimSuffix(filepath.Base(f), ".txt"), func(t *testing.T) {
|
|
fsys, want, err := parseTestFile(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got string
|
|
gotErr := CheckAPIFile(fsys, "api.txt", fsys, "doc/next")
|
|
if gotErr != nil {
|
|
got = gotErr.Error()
|
|
}
|
|
want = strings.TrimSpace(want)
|
|
if got != want {
|
|
t.Errorf("\ngot %s\nwant %s", got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAllAPIFilesForErrors(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping in short mode")
|
|
}
|
|
fsys := os.DirFS(filepath.Join(runtime.GOROOT(), "api"))
|
|
apiFiles, err := fs.Glob(fsys, "*.txt")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, f := range apiFiles {
|
|
if _, err := parseAPIFile(fsys, f); err != nil {
|
|
t.Errorf("parseTestFile(%q) failed with %v", f, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSymbolLinks(t *testing.T) {
|
|
for _, test := range []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{"a b", "a b"},
|
|
{"a [b", "a [b"},
|
|
{"a b[X]", "a b[X]"},
|
|
{"a [Buffer] b", "a [Buffer](/pkg/bytes#Buffer) b"},
|
|
{"a [Buffer]\nb", "a [Buffer](/pkg/bytes#Buffer)\nb"},
|
|
{"a [bytes.Buffer], b", "a [bytes.Buffer](/pkg/bytes#Buffer), b"},
|
|
{"[bytes.Buffer.String]", "[bytes.Buffer.String](/pkg/bytes#Buffer.String)"},
|
|
{"a--[encoding/json.Marshal].", "a--[encoding/json.Marshal](/pkg/encoding/json#Marshal)."},
|
|
{"a [math] and s[math] and [NewBuffer].", "a [math](/pkg/math) and s[math] and [NewBuffer](/pkg/bytes#NewBuffer)."},
|
|
{"A [*log/slog.Logger]", "A [*log/slog.Logger](/pkg/log/slog#Logger)"},
|
|
{"Not in code `[math]`.", "Not in code `[math]`."},
|
|
} {
|
|
doc := NewParser().Parse(test.in)
|
|
addSymbolLinks(doc, "bytes")
|
|
got := strings.TrimSpace(md.ToMarkdown(doc))
|
|
if got != test.want {
|
|
t.Errorf("\nin: %s\ngot: %s\nwant: %s", test.in, got, test.want)
|
|
}
|
|
}
|
|
|
|
}
|