tailscale/pkgdoc_test.go
Brad Fitzpatrick f3b2f9b0ef all: fix duplicate package docs and tighten TestPackageDocs
TestPackageDocs walked into directories starting with "." (such as
.claude worktrees) and only logged warnings on duplicate package docs
across files in a directory. Skip dot-directories (which covers the
old .git but also .claude), ignore files with "//go:build ignore" so
command files don't falsely trip the duplicate check, and promote the
duplicate-doc warning to a t.Errorf.

While here, deduplicate the package docs that were previously only
logged: drop the redundant comment from client/systray/startup-creator.go,
move the comprehensive taildrop doc into feature/taildrop/doc.go, and
remove a leftover doc fragment from feature/condlite/expvar/omit.go.

The tstest/integration/vms allowlist is no longer needed since the
//go:build ignore filter now handles its dns_tester.go and udp_tester.go
files generically.

Fixes #19526

Change-Id: Id794d96bd728826a1883a054e4a244f90fa05d3d
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-04-24 19:01:43 -07:00

92 lines
2.1 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package tailscaleroot
import (
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
func hasIgnoreBuildTag(f *ast.File) bool {
for _, cg := range f.Comments {
for _, c := range cg.List {
if c.Text == "//go:build ignore" {
return true
}
}
}
return false
}
func TestPackageDocs(t *testing.T) {
switch runtime.GOOS {
case "darwin", "linux":
// Enough coverage for CI+devs.
default:
t.Skipf("skipping on %s", runtime.GOOS)
}
var goFiles []string
err := filepath.Walk(".", func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if fi.Mode().IsDir() && path != "." && strings.HasPrefix(filepath.Base(path), ".") {
return filepath.SkipDir // No documentation lives in dot directories (.git, .claude, etc)
}
if fi.Mode().IsRegular() && strings.HasSuffix(path, ".go") {
if strings.HasSuffix(path, "_test.go") {
return nil
}
goFiles = append(goFiles, path)
}
return nil
})
if err != nil {
t.Fatal(err)
}
byDir := map[string][]string{} // dir => files
for _, fileName := range goFiles {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, fileName, nil, parser.PackageClauseOnly|parser.ParseComments)
if err != nil {
t.Fatalf("failed to ParseFile %q: %v", fileName, err)
}
if hasIgnoreBuildTag(f) {
continue
}
dir := filepath.Dir(fileName)
if _, ok := byDir[dir]; !ok {
byDir[dir] = nil
}
if f.Doc != nil {
byDir[dir] = append(byDir[dir], fileName)
txt := f.Doc.Text()
if strings.Contains(txt, "SPDX-License-Identifier") {
t.Errorf("the copyright header for %s became its package doc due to missing blank line", fileName)
}
}
}
for dir, ff := range byDir {
if len(ff) > 1 {
t.Errorf("multiple files with package doc in %s: %q", dir, ff)
}
if len(ff) == 0 {
if strings.HasPrefix(dir, "gokrazy/") {
// Ignore gokrazy appliances. Their *.go file is only for deps.
continue
}
t.Errorf("no package doc in %s", dir)
}
}
t.Logf("parsed %d files", len(goFiles))
}