check / check (push) Canceled after 0s
Config.settings lists each setting once: its key, its environment variable and the function that loads it. Most rows load through field, whose arguments give the type, the default and the check. db_url and cache_max_bytes keep their own load functions. Loading, the unknown-key and unknown-variable checks and the names in error messages all read the table. allowlist_hosts now loads through parseHostList. isKnownConfigKey, envVarNames, strictLoader, validate, validateAllowlistHostsValue and getStringSlice are gone, with the getStringSlice tests. The key constants move to the tests, their only users. README.md lists the settings one item each instead of in a table, with shorter sentences above the list. A new test checks that this list and configs/config.example.yml name exactly the keys of the table. Model: opus-5-5
128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
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)
|
|
}
|
|
}
|