package config import ( "os" "path/filepath" "regexp" "strings" "testing" ) // TestReadmeNamesEverySetting checks that the Configuration section of // README.md names every key of the settings table in backticks. It also // checks that every name in backticks there written like a key with an // underscore, such as state_dir, is a key of the table. The other keys, // such as port and metrics.username, are checked only the first way. func TestReadmeNamesEverySetting(t *testing.T) { t.Parallel() readme, err := os.ReadFile(filepath.Join("..", "..", "README.md")) if err != nil { t.Fatalf("failed to read README.md: %v", err) } _, section, found := strings.Cut(string(readme), "\n### Configuration\n") if !found { t.Fatal("README.md has no Configuration section") } // The section ends at the next heading of its level or above. section, _, _ = strings.Cut(section, "\n## ") section, _, _ = strings.Cut(section, "\n### ") named := map[string]bool{} for _, match := range regexp.MustCompile("`([^`]+)`"). FindAllStringSubmatch(section, -1) { named[match[1]] = true } for _, s := range new(Config).settings() { if !named[s.key] { t.Errorf("README.md's Configuration section does not name %s", s.key) } delete(named, s.key) } keyWithUnderscore := regexp.MustCompile(`^[a-z]+(_[a-z]+)+$`) for name := range named { if keyWithUnderscore.MatchString(name) { t.Errorf("README.md's Configuration section names %s, "+ "which is not a setting", name) } } } // TestExampleConfigNamesEverySetting checks that // configs/config.example.yml names exactly the keys of the settings // table, each one set or commented out with "# ". A key line holds the // key, a colon and at most a one-word value, which tells it from a // comment line that starts with a word and a colon. An indented key is // inside the map of the key above it, as username is inside metrics. func TestExampleConfigNamesEverySetting(t *testing.T) { t.Parallel() const examplePath = "configs/config.example.yml" example, err := os.ReadFile(filepath.Join("..", "..", examplePath)) if err != nil { t.Fatalf("failed to read %s: %v", examplePath, err) } keyLine := regexp.MustCompile(`(?m)^(?:# )?( *)([a-z_]+):(?: \S+)?$`) named := map[string]bool{} var mapKey string for _, match := range keyLine.FindAllStringSubmatch(string(example), -1) { indent, key := match[1], match[2] if indent == "" { mapKey = key named[key] = true continue } // The key holding the map is not itself a setting. delete(named, mapKey) named[mapKey+"."+key] = true } // The env section sets environment variables; it is not a setting. delete(named, "env") for _, s := range new(Config).settings() { if !named[s.key] { t.Errorf("%s does not name %s", examplePath, s.key) } delete(named, s.key) } for key := range named { t.Errorf("%s names %s, which is not a setting", examplePath, key) } }