package config import ( "os" "path/filepath" "regexp" "strings" "testing" ) // TestReadmeNamesEverySetting checks that the list of settings in // README.md names exactly the keys of the settings table, one item each. // The list is the first one in the Configuration section, and it ends at // the first blank line. Each item starts with its key in backticks. 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### ") _, list, found := strings.Cut(section, "\n- ") if !found { t.Fatal("README.md's Configuration section has no list") } list, _, _ = strings.Cut("- "+list, "\n\n") itemKey := regexp.MustCompile("^- `([^`]+)`") named := map[string]bool{} for line := range strings.SplitSeq(list, "\n") { // A line that does not start with "- " goes on with the item above. if !strings.HasPrefix(line, "- ") { continue } match := itemKey.FindStringSubmatch(line) if match == nil { t.Errorf("README.md's list of settings has an item "+ "that does not start with a key in backticks: %s", line) continue } if named[match[1]] { t.Errorf("README.md's list of settings names %s twice", match[1]) } named[match[1]] = true } for _, s := range new(Config).settings() { if !named[s.key] { t.Errorf("README.md's list of settings does not name %s", s.key) } delete(named, s.key) } for key := range named { t.Errorf("README.md's list of settings names %s, "+ "which is not a setting", key) } } // 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) } }