test(11.1-06): branch-level tests for every docsite stage (94.8% coverage)
- load, render, emit, snippet, highlight, serve and checker branch tests; docs:build --check, docs:sync and docs:serve output tests in cmd/summer - fix: a CRLF or BOM page is reported as such, not as a page without a frontmatter block (TestFrontmatterEncodingProblems) - fix: src=#Type.Method finds a method with a parenthesised receiver (TestSnippetGenericsAndGroups, Box.Paren) - fix: docs:serve adds its watches before announcing the server, so an edit made right after the serving line rebuilds (TestServeWatchRebuilds)
This commit is contained in:
@@ -409,3 +409,160 @@ func TestDocsCommandsMirrorGeneratedMain(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanDocsFixture is the internal/docsite fixture that passes every check.
|
||||
const cleanDocsFixture = "../../internal/docsite/testdata/clean"
|
||||
|
||||
// scratchDocsRoot copies the clean docs fixture into a temp dir.
|
||||
func scratchDocsRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
err := filepath.WalkDir(cleanDocsFixture, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rel, _ := filepath.Rel(cleanDocsFixture, p)
|
||||
target := filepath.Join(root, rel)
|
||||
if d.IsDir() {
|
||||
return os.MkdirAll(target, 0o755)
|
||||
}
|
||||
data, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(target, data, 0o644)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// runSummer runs the summer tool with args and returns its output.
|
||||
func runSummer(t *testing.T, args ...string) (string, error) {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
root, err := bonfire.NewRoot("summer", toolCommands(), &buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root.SetArgs(args)
|
||||
err = root.Execute()
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
func appendDoc(t *testing.T, root, name, text string) {
|
||||
t.Helper()
|
||||
f, err := os.OpenFile(filepath.Join(root, filepath.FromSlash(name)), os.O_APPEND|os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.WriteString(text); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsBuildCheckOutput(t *testing.T) {
|
||||
root := scratchDocsRoot(t)
|
||||
out, err := runSummer(t, "docs:build", "--check", "--root", root)
|
||||
if err != nil || out != "docs:build: no problems found\n" {
|
||||
t.Fatalf("docs:build --check on the clean fixture = %q, %v", out, err)
|
||||
}
|
||||
|
||||
appendDoc(t, root, "docs/extras/faq.md", "\nSee `demo.Missing` and `summer no:such`.\n")
|
||||
site := filepath.Join(t.TempDir(), "site")
|
||||
for _, args := range [][]string{
|
||||
{"docs:build", "--check", "--root", root},
|
||||
{"docs:build", "--root", root, "--out", site},
|
||||
} {
|
||||
out, err := runSummer(t, args...)
|
||||
if err == nil || err.Error() != "docs:build failed" {
|
||||
t.Fatalf("%v = %v, want docs:build failed", args, err)
|
||||
}
|
||||
want := "docs/extras/faq.md:17: identifier: demo.Missing does not exist in modules/demo\n" +
|
||||
"docs/extras/faq.md:17: command: \"no:such\" is not a summer or application command\n" +
|
||||
"docs:build: 2 problems, nothing written\n"
|
||||
if out != want {
|
||||
t.Fatalf("%v output =\n%s\nwant\n%s", args, out, want)
|
||||
}
|
||||
}
|
||||
if _, err := os.Stat(site); err == nil {
|
||||
t.Fatal("docs:build wrote output despite problems")
|
||||
}
|
||||
|
||||
// A refused --out is an error without problem lines.
|
||||
if _, err := runSummer(t, "docs:build", "--root", root, "--out", filepath.Join(root, "docs", "site")); err == nil ||
|
||||
!strings.Contains(err.Error(), "--out must not be inside --src") {
|
||||
t.Fatalf("docs:build --out inside docs = %v", err)
|
||||
}
|
||||
// A missing site.yaml is an error for --check too.
|
||||
if _, err := runSummer(t, "docs:build", "--check", "--root", t.TempDir()); err == nil || !strings.Contains(err.Error(), "read site config") {
|
||||
t.Fatalf("docs:build --check without site.yaml = %v", err)
|
||||
}
|
||||
|
||||
// --src and --base-url are honoured.
|
||||
clean := scratchDocsRoot(t)
|
||||
out, err = runSummer(t, "docs:build", "--root", clean, "--src", filepath.Join(clean, "docs"), "--out", site, "--base-url", "/v2/")
|
||||
if err != nil || !strings.HasPrefix(out, "docs:build: wrote 6 pages to ") {
|
||||
t.Fatalf("docs:build --src --base-url = %q, %v", out, err)
|
||||
}
|
||||
if html, err := os.ReadFile(filepath.Join(site, "index.html")); err != nil || !strings.Contains(string(html), `href="/v2/assets/site.css"`) {
|
||||
t.Fatalf("--base-url not applied: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsSyncOutput(t *testing.T) {
|
||||
root := scratchDocsRoot(t)
|
||||
out, err := runSummer(t, "docs:sync", "--root", root)
|
||||
if err != nil || out != "docs:sync: all snippets up to date\n" {
|
||||
t.Fatalf("docs:sync on the clean fixture = %q, %v", out, err)
|
||||
}
|
||||
|
||||
start := filepath.Join(root, "docs", "guide", "start.md")
|
||||
raw, err := os.ReadFile(start)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
drifted := strings.Replace(string(raw), `demo.Hello("blog")`, `demo.Hello("stale")`, 1)
|
||||
if err := os.WriteFile(start, []byte(drifted), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err = runSummer(t, "docs:sync", "--root", root)
|
||||
if err != nil || out != "docs:sync: updated 1 snippets in 1 files\n" {
|
||||
t.Fatalf("docs:sync with drift = %q, %v", out, err)
|
||||
}
|
||||
if got, _ := os.ReadFile(start); string(got) != string(raw) {
|
||||
t.Fatal("docs:sync did not restore the source text")
|
||||
}
|
||||
|
||||
appendDoc(t, root, "docs/extras/faq.md", "\n```go src=modules/demo/demo.go#Nope\n```\n")
|
||||
out, err = runSummer(t, "docs:sync", "--root", root)
|
||||
if err == nil || err.Error() != "docs:sync failed" {
|
||||
t.Fatalf("docs:sync with a broken ref = %v", err)
|
||||
}
|
||||
if want := "docs/extras/faq.md:17: snippet: modules/demo/demo.go#Nope not found\ndocs:sync: 1 problems, nothing written\n"; out != want {
|
||||
t.Fatalf("docs:sync output =\n%s\nwant\n%s", out, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocsServeRefusal(t *testing.T) {
|
||||
root := scratchDocsRoot(t)
|
||||
for _, addr := range []string{"0.0.0.0:8088", "[::]:8088", "192.0.2.10:8088"} {
|
||||
_, err := runSummer(t, "docs:serve", "--root", root, "--addr", addr)
|
||||
want := "docs:serve: refusing to listen on " + addr + ": not a loopback address. Pass --allow-remote to serve on the network."
|
||||
if err == nil || err.Error() != want {
|
||||
t.Errorf("docs:serve --addr %s = %v, want %q", addr, err, want)
|
||||
}
|
||||
}
|
||||
// --allow-remote passes the address check; the planted problem then
|
||||
// stops the first build before anything listens.
|
||||
appendDoc(t, root, "docs/extras/faq.md", "\n`summer no:such`\n")
|
||||
out, err := runSummer(t, "docs:serve", "--root", root, "--addr", "0.0.0.0:0", "--allow-remote")
|
||||
if err == nil || err.Error() != "docs:serve: build failed" || !strings.Contains(out, `"no:such"`) {
|
||||
t.Fatalf("docs:serve --allow-remote with a problem = %q, %v", out, err)
|
||||
}
|
||||
if strings.Contains(out, "Serving docs") {
|
||||
t.Fatal("docs:serve listened despite a failed build")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user