135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
"testing"
|
|
|
|
"countrygame/web"
|
|
)
|
|
|
|
var (
|
|
indIDs = []string{"order", "stability", "civilRights", "opinion", "economy", "hardPower", "softPower", "unrest", "armyLoyalty"}
|
|
varIDs = []string{"growth", "unemployment", "inflation"}
|
|
taxIDs = []string{"income", "corporate", "vat"}
|
|
devKeys = []string{"growth", "unemployment", "inflation", "debt", "deficit", "taxPressure", "taxGrowthDrag", "crackdown"}
|
|
)
|
|
|
|
type obj = map[string]any
|
|
|
|
func load(t *testing.T, path string) obj {
|
|
t.Helper()
|
|
raw, err := web.Files.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read %s: %v", path, err)
|
|
}
|
|
var out obj
|
|
if err := json.Unmarshal(raw, &out); err != nil {
|
|
t.Fatalf("parse %s: %v", path, err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func keys(m obj) []string {
|
|
out := make([]string, 0, len(m))
|
|
for k := range m {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
func set(list []string) map[string]bool {
|
|
m := make(map[string]bool, len(list))
|
|
for _, s := range list {
|
|
m[s] = true
|
|
}
|
|
return m
|
|
}
|
|
|
|
func sameKeys(t *testing.T, what string, got obj, want []string) {
|
|
t.Helper()
|
|
w := append([]string(nil), want...)
|
|
sort.Strings(w)
|
|
g := keys(got)
|
|
if len(g) != len(w) {
|
|
t.Errorf("%s: keys %v, want %v", what, g, w)
|
|
return
|
|
}
|
|
for i := range g {
|
|
if g[i] != w[i] {
|
|
t.Errorf("%s: keys %v, want %v", what, g, w)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// checkWeights verifies every key of def[field] is in the allowed set.
|
|
func checkWeights(t *testing.T, who string, def obj, field string, allowed map[string]bool) {
|
|
t.Helper()
|
|
m, ok := def[field].(obj)
|
|
if !ok {
|
|
return // optional section
|
|
}
|
|
for k := range m {
|
|
if !allowed[k] {
|
|
t.Errorf("%s.%s references unknown %q", who, field, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBalanceAndScenario(t *testing.T) {
|
|
bal := load(t, "data/balance.json")
|
|
scn := load(t, "data/scenarios/default.json")
|
|
|
|
sectors := scn["sectors"].(obj)
|
|
sectorSet := make(map[string]bool)
|
|
for id := range sectors {
|
|
sectorSet[id] = true
|
|
}
|
|
|
|
// Scenario consistency.
|
|
sameKeys(t, "scenario.indicators", scn["indicators"].(obj), indIDs)
|
|
sameKeys(t, "scenario.economy.taxes", scn["economy"].(obj)["taxes"].(obj), taxIDs)
|
|
limit := bal["limits"].(obj)["allocMaxRatio"].(float64)
|
|
for id, raw := range sectors {
|
|
s := raw.(obj)
|
|
need, alloc := s["need"].(float64), s["alloc"].(float64)
|
|
if need <= 0 || alloc < 0 || alloc > need*limit {
|
|
t.Errorf("scenario sector %s: need=%v alloc=%v out of range", id, need, alloc)
|
|
}
|
|
}
|
|
for id, v := range scn["indicators"].(obj) {
|
|
if f := v.(float64); f < 0 || f > 100 {
|
|
t.Errorf("scenario indicator %s=%v outside 0..100", id, f)
|
|
}
|
|
}
|
|
|
|
// Balance coverage.
|
|
sameKeys(t, "balance.sectorTau", bal["sectorTau"].(obj), keys(sectors))
|
|
sameKeys(t, "balance.indicators", bal["indicators"].(obj), indIDs)
|
|
sameKeys(t, "balance.vars", bal["vars"].(obj), varIDs)
|
|
sameKeys(t, "balance.tax", bal["tax"].(obj), taxIDs)
|
|
for id, tau := range bal["sectorTau"].(obj) {
|
|
if tau.(float64) <= 0 {
|
|
t.Errorf("sectorTau.%s must be positive", id)
|
|
}
|
|
}
|
|
|
|
// Every weight points at something that exists.
|
|
dev, ind := set(devKeys), set(indIDs)
|
|
groups := []obj{bal["indicators"].(obj), bal["vars"].(obj)}
|
|
for _, g := range groups {
|
|
for id, raw := range g {
|
|
def := raw.(obj)
|
|
if def["tau"].(float64) <= 0 {
|
|
t.Errorf("%s.tau must be positive", id)
|
|
}
|
|
checkWeights(t, id, def, "sectors", sectorSet)
|
|
checkWeights(t, id, def, "alloc", sectorSet)
|
|
checkWeights(t, id, def, "econ", dev)
|
|
checkWeights(t, id, def, "ind", ind)
|
|
}
|
|
}
|
|
}
|