feat(07-01): add RawRule, LoadResult, validation, auto-freq to config package
- Add RawRule struct with Port *uint16, Protocol, Class fields - Add LoadResult struct with FreqCfgs, UserRules, ConfigPath fields - Change Load() signature to return LoadResult instead of bare map - Add validateRules: checks protocol required, class required, valid protocols - Add convertRules: converts RawRule slices to classify.Rule slices - Add autoAssignFreq: FNV-32a deterministic Hz in [1200-2350] range - Add addAutoFreqEntries: creates FreqConfig for new class names, skips built-ins - Reorder ops: addAutoFreqEntries before merge so sounds overrides apply to user classes - Update main.go call site to use LoadResult.FreqCfgs - Update all 8 existing tests to use LoadResult return type - Add 13 new tests covering rule parsing, validation, auto-freq, and LoadResult
This commit is contained in:
@@ -84,10 +84,11 @@ func run(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Load config (CFG-01 through CFG-05, D-11: fail fast before capture)
|
||||
freqCfgs, err := config.Load(configPath)
|
||||
loadResult, err := config.Load(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
freqCfgs := loadResult.FreqCfgs
|
||||
|
||||
// Resolve output path
|
||||
if outputPath == "" {
|
||||
|
||||
+104
-11
@@ -5,6 +5,7 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -23,9 +24,27 @@ type SoundOverride struct {
|
||||
Waveform *string `toml:"waveform"`
|
||||
}
|
||||
|
||||
// RawRule holds a user-defined classification rule as decoded from TOML.
|
||||
// Port is a pointer so we can distinguish "not set" (nil, matches any port) from port=0.
|
||||
type RawRule struct {
|
||||
Port *uint16 `toml:"port"`
|
||||
Protocol string `toml:"protocol"`
|
||||
Class string `toml:"class"`
|
||||
}
|
||||
|
||||
// rawConfig is the top-level TOML decode target.
|
||||
type rawConfig struct {
|
||||
Sounds map[string]SoundOverride `toml:"sounds"`
|
||||
Rules []RawRule `toml:"rules"`
|
||||
}
|
||||
|
||||
// LoadResult is the return type from Load(). It carries the merged FreqConfig map,
|
||||
// the user-defined classification rules (to be prepended before DefaultRules by the caller),
|
||||
// and the resolved config file path (empty string if no config was found).
|
||||
type LoadResult struct {
|
||||
FreqCfgs map[classify.TrafficClass]synth.FreqConfig
|
||||
UserRules []classify.Rule
|
||||
ConfigPath string
|
||||
}
|
||||
|
||||
// validWaveforms maps TOML waveform strings to WaveformType constants.
|
||||
@@ -39,33 +58,43 @@ var validWaveforms = map[string]synth.WaveformType{
|
||||
// Load finds, parses, validates, and merges a TOML config file.
|
||||
//
|
||||
// configPath is the --config flag value; empty string triggers auto-discovery.
|
||||
// Returns the merged FreqConfig map (defaults + overrides) ready for synth.NewBank.
|
||||
// Returns a LoadResult with the merged FreqConfig map, user-defined rules, and resolved path.
|
||||
// Returns an error on: explicit file not found, parse errors, unknown keys,
|
||||
// type mismatches, or invalid waveform values. Returns no error (uses defaults)
|
||||
// when no config is found during auto-discovery.
|
||||
func Load(configPath string) (map[classify.TrafficClass]synth.FreqConfig, error) {
|
||||
// type mismatches, invalid waveform values, or invalid rule definitions.
|
||||
// Returns no error (uses defaults) when no config is found during auto-discovery.
|
||||
func Load(configPath string) (LoadResult, error) {
|
||||
path, explicit, err := resolvePath(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return LoadResult{}, err
|
||||
}
|
||||
if path == "" {
|
||||
// No config found during auto-discovery — use defaults silently (CFG-02)
|
||||
return copyDefaults(), nil
|
||||
return LoadResult{FreqCfgs: copyDefaults(), UserRules: []classify.Rule{}, ConfigPath: ""}, nil
|
||||
}
|
||||
|
||||
raw, err := parseFile(path)
|
||||
if err != nil {
|
||||
if explicit && errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, fmt.Errorf("config file not found: %s", configPath)
|
||||
return LoadResult{}, fmt.Errorf("config file not found: %s", configPath)
|
||||
}
|
||||
return nil, err
|
||||
return LoadResult{}, err
|
||||
}
|
||||
|
||||
if err := validate(raw); err != nil {
|
||||
return nil, err
|
||||
return LoadResult{}, err
|
||||
}
|
||||
|
||||
return merge(copyDefaults(), raw.Sounds), nil
|
||||
userRules := convertRules(raw.Rules)
|
||||
freqCfgs := copyDefaults()
|
||||
// Add auto-freq entries BEFORE merge so that [sounds.X] overrides for user classes apply.
|
||||
addAutoFreqEntries(freqCfgs, userRules)
|
||||
merge(freqCfgs, raw.Sounds)
|
||||
|
||||
return LoadResult{
|
||||
FreqCfgs: freqCfgs,
|
||||
UserRules: userRules,
|
||||
ConfigPath: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// resolvePath resolves the config path from an explicit flag value or auto-discovery.
|
||||
@@ -119,7 +148,7 @@ func parseFile(path string) (rawConfig, error) {
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// validate checks waveform strings before merge so we fail fast at startup (D-11).
|
||||
// validate checks waveform strings and rules before merge so we fail fast at startup (D-11).
|
||||
func validate(raw rawConfig) error {
|
||||
for _, override := range raw.Sounds {
|
||||
if override.Waveform != nil {
|
||||
@@ -128,9 +157,73 @@ func validate(raw rawConfig) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
return validateRules(raw.Rules)
|
||||
}
|
||||
|
||||
// validateRules checks that each rule has a valid protocol and a non-empty class.
|
||||
func validateRules(rules []RawRule) error {
|
||||
validProtocols := map[string]bool{"tcp": true, "udp": true, "icmp": true}
|
||||
for i, r := range rules {
|
||||
if r.Protocol == "" {
|
||||
return fmt.Errorf("config: rules[%d]: protocol is required", i)
|
||||
}
|
||||
if !validProtocols[r.Protocol] {
|
||||
return fmt.Errorf("config: rules[%d]: invalid protocol %q -- valid: tcp, udp, icmp", i, r.Protocol)
|
||||
}
|
||||
if r.Class == "" {
|
||||
return fmt.Errorf("config: rules[%d]: class is required", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertRules converts a slice of RawRule (from TOML) into classify.Rule slice.
|
||||
func convertRules(raw []RawRule) []classify.Rule {
|
||||
result := make([]classify.Rule, len(raw))
|
||||
for i, r := range raw {
|
||||
var port uint16
|
||||
if r.Port != nil {
|
||||
port = *r.Port
|
||||
}
|
||||
result[i] = classify.Rule{
|
||||
Protocol: r.Protocol,
|
||||
DstPort: port,
|
||||
Class: classify.TrafficClass(r.Class),
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// autoAssignFreq computes a deterministic frequency in [1200, 2350] Hz for a class name
|
||||
// using FNV-32a hashing. Same input always produces the same output.
|
||||
func autoAssignFreq(className string) float64 {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(className))
|
||||
const (
|
||||
baseHz = 1200.0
|
||||
stepHz = 50.0
|
||||
numSteps = uint32(24)
|
||||
)
|
||||
return baseHz + float64(h.Sum32()%numSteps)*stepHz
|
||||
}
|
||||
|
||||
// addAutoFreqEntries adds a FreqConfig entry for each user-defined class that doesn't
|
||||
// already have one in the map. Built-in classes that appear in user rules are skipped.
|
||||
// Must be called AFTER merge() so that [sounds.X] overrides are already applied.
|
||||
func addAutoFreqEntries(cfgs map[classify.TrafficClass]synth.FreqConfig, userRules []classify.Rule) {
|
||||
for _, rule := range userRules {
|
||||
if _, exists := cfgs[rule.Class]; !exists {
|
||||
baseHz := autoAssignFreq(string(rule.Class))
|
||||
cfgs[rule.Class] = synth.FreqConfig{
|
||||
BaseHz: baseHz,
|
||||
WaveformType: synth.WaveformSine,
|
||||
Harmonics: synth.WaveformPresetHarmonics(synth.WaveformSine, baseHz, synth.SampleRate),
|
||||
Pan: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseWaveform converts a TOML waveform string to a WaveformType.
|
||||
func parseWaveform(s string) (synth.WaveformType, error) {
|
||||
if wt, ok := validWaveforms[s]; ok {
|
||||
|
||||
+283
-6
@@ -31,10 +31,11 @@ func writeTOML(t *testing.T, content string) string {
|
||||
func TestLoadPartialOverrideFrequency(t *testing.T) {
|
||||
path := writeTOML(t, "[sounds.ICMP]\nfrequency = 100.0\n")
|
||||
|
||||
cfgs, err := config.Load(path)
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
cfgs := result.FreqCfgs
|
||||
|
||||
if cfgs[classify.ClassICMP].BaseHz != 100.0 {
|
||||
t.Errorf("ICMP BaseHz: got %v, want 100.0", cfgs[classify.ClassICMP].BaseHz)
|
||||
@@ -54,10 +55,11 @@ func TestLoadPartialOverrideFrequency(t *testing.T) {
|
||||
func TestLoadPartialOverrideWaveform(t *testing.T) {
|
||||
path := writeTOML(t, "[sounds.ICMP]\nwaveform = \"square\"\n")
|
||||
|
||||
cfgs, err := config.Load(path)
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
cfgs := result.FreqCfgs
|
||||
|
||||
if cfgs[classify.ClassICMP].WaveformType != synth.WaveformSquare {
|
||||
t.Errorf("ICMP WaveformType: got %v, want WaveformSquare", cfgs[classify.ClassICMP].WaveformType)
|
||||
@@ -76,10 +78,11 @@ func TestLoadPartialOverrideWaveform(t *testing.T) {
|
||||
func TestLoadBothOverrides(t *testing.T) {
|
||||
path := writeTOML(t, "[sounds.ICMP]\nfrequency = 100.0\nwaveform = \"square\"\n")
|
||||
|
||||
cfgs, err := config.Load(path)
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
cfgs := result.FreqCfgs
|
||||
|
||||
if cfgs[classify.ClassICMP].BaseHz != 100.0 {
|
||||
t.Errorf("ICMP BaseHz: got %v, want 100.0", cfgs[classify.ClassICMP].BaseHz)
|
||||
@@ -107,10 +110,11 @@ func TestLoadNoConfig(t *testing.T) {
|
||||
// Chdir to a temp dir that has no netsynth.toml
|
||||
t.Chdir(t.TempDir())
|
||||
|
||||
cfgs, err := config.Load("")
|
||||
result, err := config.Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Load with no config: %v", err)
|
||||
}
|
||||
cfgs := result.FreqCfgs
|
||||
if len(cfgs) != 14 {
|
||||
t.Errorf("result map size: got %d, want 14", len(cfgs))
|
||||
}
|
||||
@@ -135,10 +139,11 @@ func TestLoadExplicitMissing(t *testing.T) {
|
||||
func TestLoadUnknownClass(t *testing.T) {
|
||||
path := writeTOML(t, "[sounds.BOGUS]\nfrequency = 100.0\n")
|
||||
|
||||
cfgs, err := config.Load(path)
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load with unknown class: %v", err)
|
||||
}
|
||||
cfgs := result.FreqCfgs
|
||||
if len(cfgs) != 14 {
|
||||
t.Errorf("result map size: got %d, want 14 (BOGUS should not appear)", len(cfgs))
|
||||
}
|
||||
@@ -165,10 +170,11 @@ func TestLoadInvalidWaveform(t *testing.T) {
|
||||
func TestLoadAllDefaultsPresent(t *testing.T) {
|
||||
path := writeTOML(t, "[sounds.ICMP]\nfrequency = 200.0\n")
|
||||
|
||||
cfgs, err := config.Load(path)
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
cfgs := result.FreqCfgs
|
||||
if len(cfgs) != 14 {
|
||||
t.Errorf("result map size: got %d, want 14", len(cfgs))
|
||||
}
|
||||
@@ -178,3 +184,274 @@ func TestLoadAllDefaultsPresent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- New tests for Phase 7 Plan 01 ---
|
||||
|
||||
// TestLoadCustomRules: TOML with [[rules]] block (port=8080, protocol="tcp", class="MyApp")
|
||||
// plus [sounds.MyApp] (frequency=300.0) parses successfully.
|
||||
func TestLoadCustomRules(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
port = 8080
|
||||
protocol = "tcp"
|
||||
class = "MyApp"
|
||||
|
||||
[sounds.MyApp]
|
||||
frequency = 300.0
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(result.UserRules) != 1 {
|
||||
t.Fatalf("UserRules len: got %d, want 1", len(result.UserRules))
|
||||
}
|
||||
rule := result.UserRules[0]
|
||||
if rule.Protocol != "tcp" {
|
||||
t.Errorf("UserRules[0].Protocol: got %q, want %q", rule.Protocol, "tcp")
|
||||
}
|
||||
if rule.DstPort != 8080 {
|
||||
t.Errorf("UserRules[0].DstPort: got %d, want 8080", rule.DstPort)
|
||||
}
|
||||
if rule.Class != "MyApp" {
|
||||
t.Errorf("UserRules[0].Class: got %q, want %q", rule.Class, "MyApp")
|
||||
}
|
||||
if result.FreqCfgs["MyApp"].BaseHz != 300.0 {
|
||||
t.Errorf("FreqCfgs[MyApp].BaseHz: got %v, want 300.0", result.FreqCfgs["MyApp"].BaseHz)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadCustomRuleNoPort: TOML with [[rules]] (protocol="udp", class="AllUDP", no port field)
|
||||
// parses; UserRules[0].DstPort == 0.
|
||||
func TestLoadCustomRuleNoPort(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "udp"
|
||||
class = "AllUDP"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(result.UserRules) != 1 {
|
||||
t.Fatalf("UserRules len: got %d, want 1", len(result.UserRules))
|
||||
}
|
||||
if result.UserRules[0].DstPort != 0 {
|
||||
t.Errorf("UserRules[0].DstPort: got %d, want 0", result.UserRules[0].DstPort)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadCustomRuleMissingProtocol: [[rules]] with class="X" but no protocol -> error containing "protocol is required".
|
||||
func TestLoadCustomRuleMissingProtocol(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
class = "X"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
_, err := config.Load(path)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing protocol, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "protocol is required") {
|
||||
t.Errorf("error should contain 'protocol is required', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadCustomRuleMissingClass: [[rules]] with protocol="tcp" but no class -> error containing "class is required".
|
||||
func TestLoadCustomRuleMissingClass(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "tcp"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
_, err := config.Load(path)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing class, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "class is required") {
|
||||
t.Errorf("error should contain 'class is required', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadCustomRuleInvalidProtocol: [[rules]] with protocol="ftp" -> error containing "invalid protocol".
|
||||
func TestLoadCustomRuleInvalidProtocol(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "ftp"
|
||||
class = "FTPTraffic"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
_, err := config.Load(path)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid protocol 'ftp', got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "invalid protocol") {
|
||||
t.Errorf("error should contain 'invalid protocol', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadCustomRuleUnknownField: [[rules]] with typo_field="bad" -> error containing "typo_field".
|
||||
func TestLoadCustomRuleUnknownField(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "tcp"
|
||||
class = "SomeClass"
|
||||
typo_field = "bad"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
_, err := config.Load(path)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for unknown field 'typo_field', got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "typo_field") {
|
||||
t.Errorf("error should contain 'typo_field', got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUserRulesPrepend: Load returns UserRules separately from FreqCfgs;
|
||||
// caller can do append(result.UserRules, classify.DefaultRules...) to get user rules first.
|
||||
func TestUserRulesPrepend(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "tcp"
|
||||
port = 9000
|
||||
class = "MyService"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
combined := append(result.UserRules, classify.DefaultRules...)
|
||||
if len(combined) != len(classify.DefaultRules)+1 {
|
||||
t.Errorf("combined rules len: got %d, want %d", len(combined), len(classify.DefaultRules)+1)
|
||||
}
|
||||
// User rule should be first
|
||||
if combined[0].Class != "MyService" {
|
||||
t.Errorf("first rule should be user rule 'MyService', got %q", combined[0].Class)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAutoFreqAssignment: TOML with [[rules]] (class="GameServer", protocol="tcp") and
|
||||
// NO [sounds.GameServer] -> FreqCfgs contains "GameServer" entry with BaseHz in [1200, 2350]
|
||||
// and WaveformType == WaveformSine.
|
||||
func TestAutoFreqAssignment(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "tcp"
|
||||
class = "GameServer"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
cfg, ok := result.FreqCfgs["GameServer"]
|
||||
if !ok {
|
||||
t.Fatal("FreqCfgs should contain 'GameServer' entry from auto-freq assignment")
|
||||
}
|
||||
if cfg.BaseHz < 1200.0 || cfg.BaseHz > 2350.0 {
|
||||
t.Errorf("GameServer BaseHz: got %v, want in [1200, 2350]", cfg.BaseHz)
|
||||
}
|
||||
if cfg.WaveformType != synth.WaveformSine {
|
||||
t.Errorf("GameServer WaveformType: got %v, want WaveformSine", cfg.WaveformType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAutoFreqDeterministic: Two Load() calls with same class name produce same BaseHz.
|
||||
func TestAutoFreqDeterministic(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "tcp"
|
||||
class = "MyDeterministicClass"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
result1, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load (1): %v", err)
|
||||
}
|
||||
result2, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load (2): %v", err)
|
||||
}
|
||||
hz1 := result1.FreqCfgs["MyDeterministicClass"].BaseHz
|
||||
hz2 := result2.FreqCfgs["MyDeterministicClass"].BaseHz
|
||||
if hz1 != hz2 {
|
||||
t.Errorf("auto-freq not deterministic: first=%v, second=%v", hz1, hz2)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAutoFreqSkipsBuiltins: TOML with [[rules]] (class="HTTPS", protocol="tcp", port=443)
|
||||
// -> FreqCfgs["HTTPS"].BaseHz == 175.0 (the default), NOT an auto-assigned value.
|
||||
func TestAutoFreqSkipsBuiltins(t *testing.T) {
|
||||
toml := `
|
||||
[[rules]]
|
||||
protocol = "tcp"
|
||||
port = 443
|
||||
class = "HTTPS"
|
||||
`
|
||||
path := writeTOML(t, toml)
|
||||
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if result.FreqCfgs[classify.ClassHTTPS].BaseHz != 175.0 {
|
||||
t.Errorf("HTTPS BaseHz: got %v, want 175.0 (default, not auto-assigned)", result.FreqCfgs[classify.ClassHTTPS].BaseHz)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadResultConfigPath: Load(explicit_path) -> LoadResult.ConfigPath == explicit_path;
|
||||
// Load("") with no file -> LoadResult.ConfigPath == "".
|
||||
func TestLoadResultConfigPath(t *testing.T) {
|
||||
// explicit path
|
||||
path := writeTOML(t, "[sounds.ICMP]\nfrequency = 100.0\n")
|
||||
result, err := config.Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if result.ConfigPath != path {
|
||||
t.Errorf("ConfigPath: got %q, want %q", result.ConfigPath, path)
|
||||
}
|
||||
|
||||
// empty path with no config file
|
||||
t.Chdir(t.TempDir())
|
||||
result2, err := config.Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Load with no config: %v", err)
|
||||
}
|
||||
if result2.ConfigPath != "" {
|
||||
t.Errorf("ConfigPath for no-config: got %q, want %q", result2.ConfigPath, "")
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadNoConfigReturnsLoadResult: Load("") in empty dir returns LoadResult with
|
||||
// len(FreqCfgs)==14, len(UserRules)==0, ConfigPath=="".
|
||||
func TestLoadNoConfigReturnsLoadResult(t *testing.T) {
|
||||
t.Chdir(t.TempDir())
|
||||
|
||||
result, err := config.Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(result.FreqCfgs) != 14 {
|
||||
t.Errorf("FreqCfgs len: got %d, want 14", len(result.FreqCfgs))
|
||||
}
|
||||
if len(result.UserRules) != 0 {
|
||||
t.Errorf("UserRules len: got %d, want 0", len(result.UserRules))
|
||||
}
|
||||
if result.ConfigPath != "" {
|
||||
t.Errorf("ConfigPath: got %q, want %q", result.ConfigPath, "")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user