diff options
Diffstat (limited to 'cmd/prognosis/docs_test.go')
| -rw-r--r-- | cmd/prognosis/docs_test.go | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/cmd/prognosis/docs_test.go b/cmd/prognosis/docs_test.go new file mode 100644 index 0000000..6aa97c0 --- /dev/null +++ b/cmd/prognosis/docs_test.go @@ -0,0 +1,122 @@ +package main + +import ( + "bytes" + "flag" + "io" + "os" + "regexp" + "strings" + "testing" + + "github.com/lukaszkasprzak/prognosis/internal/config" +) + +func readRepoFile(t *testing.T, rel string) string { + t.Helper() + // Tests run in the package directory; the docs live at the repo root. + b, err := os.ReadFile("../../" + rel) + if err != nil { + t.Fatalf("cannot read %s: %v", rel, err) + } + // roff escapes a literal hyphen as \- , so "-no-warnings" is written + // "\-no\-warnings". Undo that before searching, or every multi-word flag + // looks undocumented when it is not. + return strings.ReplaceAll(string(b), `\-`, "-") +} + +// Every flag must appear in -h, in the README and in the man page. +// +// The flag set comes from defineFlags, the same function run() uses, so this +// cannot be satisfied by a stale hand-written list: adding a flag and +// forgetting to document it fails the build. +func TestEveryFlagIsDocumented(t *testing.T) { + fs := flag.NewFlagSet("prognosis", flag.ContinueOnError) + fs.SetOutput(io.Discard) + defineFlags(fs) + + var help bytes.Buffer + usageTo(&help) + + docs := map[string]string{ + "-h": help.String(), + "README.md": readRepoFile(t, "README.md"), + "man/prognosis.1": readRepoFile(t, "man/prognosis.1"), + } + + fs.VisitAll(func(f *flag.Flag) { + for where, text := range docs { + if !strings.Contains(text, "-"+f.Name) { + t.Errorf("flag -%s is not documented in %s", f.Name, where) + } + } + }) +} + +// The reverse: -h must not advertise a flag that does not exist, which would +// send someone chasing a typo. +func TestHelpAdvertisesNoPhantomFlags(t *testing.T) { + fs := flag.NewFlagSet("prognosis", flag.ContinueOnError) + fs.SetOutput(io.Discard) + defineFlags(fs) + + real := map[string]bool{} + fs.VisitAll(func(f *flag.Flag) { real[f.Name] = true }) + + var help bytes.Buffer + usageTo(&help) + for _, m := range regexp.MustCompile(`(?m)^ -([a-z-]+)`).FindAllStringSubmatch(help.String(), -1) { + if !real[m[1]] { + t.Errorf("-h lists -%s, which is not a real flag", m[1]) + } + } +} + +// Every key the generated config file contains must be documented in the man +// page. The generated file is the authoritative list of user-facing settings, +// so this catches a new key that never reached the documentation. +func TestEveryConfigKeyIsDocumented(t *testing.T) { + path := t.TempDir() + "/config" + if err := config.WriteDefault(path, config.Default()); err != nil { + t.Fatal(err) + } + generated, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + man := readRepoFile(t, "man/prognosis.1") + + seen := map[string]bool{} + for _, line := range strings.Split(string(generated), "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + key, _, ok := strings.Cut(line, "=") + if !ok || seen[key] { + continue + } + seen[key] = true + if !strings.Contains(man, key) { + t.Errorf("config key %q is written into the generated config but not documented in the man page", key) + } + } + if len(seen) < 8 { + t.Fatalf("only found %d config keys; the parser above is probably wrong", len(seen)) + } +} + +// The columns a user can name must all be documented, or the error message +// listing them points at something the man page never explains. +func TestEveryColumnIsDocumented(t *testing.T) { + man := readRepoFile(t, "man/prognosis.1") + readme := readRepoFile(t, "README.md") + for _, col := range config.ValidColumns() { + if !strings.Contains(man, col) { + t.Errorf("column %q is not documented in the man page", col) + } + if !strings.Contains(readme, col) { + t.Errorf("column %q is not documented in the README", col) + } + } +} |
