diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 00:01:14 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 00:01:14 +0200 |
| commit | ed1dea2639b1191421de3986483aedcc14067a12 (patch) | |
| tree | 0118c6e119a84f1a9433ed0a304c8be1c9098463 /tests/test_config.py | |
| download | worldhistory-ed1dea2639b1191421de3986483aedcc14067a12.tar.gz worldhistory-ed1dea2639b1191421de3986483aedcc14067a12.zip | |
worldhistory: initial public history
Diffstat (limited to 'tests/test_config.py')
| -rw-r--r-- | tests/test_config.py | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..2a76d8d --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,194 @@ +import tempfile +import tomllib +import unittest +from pathlib import Path + +from tests.helpers import make_history, make_race +from worldhistory.config import ConfigError, dumps_toml, link, load_config, parse_race, resolved + +RACE = """ +id = "stone" +name = "Stone folk" +colour = [140, 140, 150] +[demography] +density = 1.2 +growth = 0.02 +allee = 75 +[seed] +clusters = 25 +heads = 200 +[habitat] +realm = "land" +terms = [{ p = "mountain", w = 0.7 }, { p = ["land", "coal"], w = 0.5 }] +gates = [{ penalty = ["above:o2_fraction:0.3", "lithology:3,4"], factor = 0.6 }] +[tolerance.temperature] +optimum = 10 +width = 20 +lo = -30 +hi = 40 +rate = 0.001 +[special] +veins = 0.002 +""" + +SUB = """ +id = "deep" +[demography] +density = 0.5 +growth = 0.08 +[habitat] +realm = "sea" +terms = [{ p = "vent", w = 1.0 }] +qmax = 0.7 +[emerge] +parent = "stone" +birth_min = 0.5 +[tolerance.temperature] +optimum = 30 +width = 10 +lo = 0 +hi = 50 +""" + +HISTORY = """ +[run] +years = 7500 +seed = 3 +[world] +eras = ["late"] +[regions.zone] +kind = "changed" +era = "late" +fields = ["gravity_g"] +[[event]] +year = 0 +kind = "seed" +[[event]] +year = 20 +kind = "cull" +region = "zone" +share = 0.6 +by_race = { stone = 0.8 } +[[event]] +year = 20 +kind = "era_switch" +era = "late" +""" + + +def write(d, name, text): + p = Path(d) / name + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(text) + + +class ConfigTest(unittest.TestCase): + def test_load_defaults_and_inheritance(self): + with tempfile.TemporaryDirectory() as d: + write(d, "history.toml", HISTORY) + write(d, "races/stone.toml", RACE) + write(d, "races/deep.toml", SUB) + h, races = load_config(d) + self.assertEqual([r.id for r in races], ["stone", "deep"]) + stone, deep = races + self.assertEqual(h.step, 10) # default + self.assertEqual(h.years, 7500) + self.assertEqual(stone.founder, 75) # founder defaults to allee + self.assertEqual(stone.family, "stone") + self.assertEqual(deep.family, "stone") # sub-species joins the parent's family + self.assertEqual(deep.tolerance["temperature"].width, 10) # own curve + self.assertEqual(deep.clusters, 0) # sub-species are never seeded + self.assertEqual(stone.travel["river"], -1.0) # default + self.assertEqual(stone.gates[0]["factor"], 0.6) + self.assertEqual(h.events[1]["by_race"], {"stone": 0.8}) + + def test_unknown_key(self): + with self.assertRaisesRegex(ConfigError, r"test:x.*demography.*desnity"): + parse_race({"id": "x", "demography": {"desnity": 1}}, "test:x") + + def test_unknown_predicate(self): + with self.assertRaisesRegex(ConfigError, "nosuch"): + make_race(habitat={"terms": [{"p": "nosuch", "w": 1}]}) + + def test_bad_tolerance_condition(self): + with self.assertRaisesRegex(ConfigError, "wind"): + make_race(tolerance={"wind": {"optimum": 0, "width": 1, "lo": 0, "hi": 1}}) + + def test_tolerance_needs_widths(self): + with self.assertRaisesRegex(ConfigError, "width"): + make_race(tolerance={"depth": {"optimum": 0, "lo": 0, "hi": 10, "width_lo": 5}}) + + def test_link_checks_references(self): + h = make_history(event=[{"year": 20, "kind": "cull", "region": "nowhere", "share": 0.5}]) + with self.assertRaisesRegex(ConfigError, "nowhere"): + link(h, [make_race()]) + h = make_history(event=[{"year": 20, "kind": "cull", "share": 0.5, "by_race": {"ghost": 0.1}}]) + with self.assertRaisesRegex(ConfigError, "ghost"): + link(h, [make_race()]) + h = make_history(event=[{"year": 20, "kind": "era_switch", "era": "late"}]) + with self.assertRaisesRegex(ConfigError, "late"): + link(h, [make_race()]) + sub = make_race("s", emerge={"parent": "p"}) + with self.assertRaisesRegex(ConfigError, "p"): + link(make_history(), [sub]) + same = {"gravity": {"optimum": 1.0, "width": 0.5, "lo": 0.3, "hi": 1.3}} + with self.assertRaisesRegex(ConfigError, "never be born"): # progress stays 0 < birth_min + link(make_history(), [make_race("p", tolerance=same), make_race("s", tolerance=same, emerge={"parent": "p"})]) + link(make_history(), [make_race("p", tolerance=same), # birth_min 0 or ritual: fine + make_race("s", emerge={"parent": "p", "birth_min": 0.0, "birth_sure": 0.0})]) + link(make_history(), [make_race("p", tolerance=same), make_race("s", emerge={"parent": "p", "mode": "ritual"})]) + + def test_emerge_defaults_and_merge(self): + with tempfile.TemporaryDirectory() as d: + write(d, "history.toml", HISTORY) + write(d, "races/stone.toml", RACE.replace("[special]", "[tolerance.depth]\noptimum = 0\nwidth = 60\n" + "lo = 0\nhi = 6000\n[special]")) + write(d, "races/deep.toml", SUB) + h, (stone, deep) = load_config(d) + em = deep.emerge + self.assertEqual((em["mode"], em["birth_min"], em["birth_sure"], em["birth_rate"]), ("adapt", 0.5, 0.8, 0.2)) + self.assertEqual((em["return_min"], em["return_sure"]), (0.5, 0.8)) # default to the birth values + self.assertEqual((em["mix_min"], em["displace"], em["spread"]), (0.3, 0.9, "contact")) + self.assertEqual(deep.tolerance["temperature"].optimum, 30) # own curve + self.assertEqual(deep.tolerance["depth"].width_hi, 60) # inherited condition + + def test_removed_keys_name_the_replacement(self): + with self.assertRaisesRegex(ConfigError, "removed.*birth_min"): + make_race("s", emerge={"parent": "p", "class": "deep_sea"}) + with self.assertRaisesRegex(ConfigError, "removed.*birth_min"): + make_race("s", emerge={"parent": "p", "years": [1, 2]}) + with self.assertRaisesRegex(ConfigError, "exposure.*removed"): + make_history(exposure={"hot": "above:T_mean:10"}) + with self.assertRaisesRegex(ConfigError, "trigger"): + make_race("s", emerge={"parent": "p", "trigger": 0.1}) # ritual only + with self.assertRaisesRegex(ConfigError, "crisis_drop"): + make_race("s", emerge={"parent": "p", "crisis_drop": 0.9}) # ritual only + with self.assertRaisesRegex(ConfigError, "victims"): + make_race("s", emerge={"parent": "p", "mode": "ritual", "victims": "everyone"}) + with self.assertRaisesRegex(ConfigError, "crisis_drop"): + make_race("s", emerge={"parent": "p", "mode": "ritual", "crisis_drop": 1.0}) + self.assertEqual(make_race("s", emerge={"parent": "p", "birth_rate": 0.4}).emerge["return_rate"], 0.4) + for key, bad in [("displace", 1.5), ("dominance", -0.1), ("birth_shift", 1.2), ("convert", 0.0), + ("mix_min", 1.1), ("birth_rate", 2.0), ("return_rate", -1.0), ("after", -5)]: + with self.assertRaisesRegex(ConfigError, key): + make_race("s", emerge={"parent": "p", key: bad}) + for key, bad in [("ritual_rate", -0.1), ("ritual_cost", -1), ("ritual_converts", -1), ("trigger", 1.5), + ("raid_size", 0), ("raid_km", -1), ("raid_rate", -1), ("ritual_power", 0), + ("backlash_years", -1), ("crisis_years", 0)]: + with self.assertRaisesRegex(ConfigError, key): + make_race("s", emerge={"parent": "p", "mode": "ritual", key: bad}) + with self.assertRaisesRegex(ConfigError, "mode"): + make_race("s", emerge={"parent": "p", "mode": "magic"}) + with self.assertRaisesRegex(ConfigError, "birth_min"): + make_race("s", emerge={"parent": "p", "birth_min": 0.9, "birth_sure": 0.5}) + + def test_dumps_roundtrip(self): + h = make_history(regions={"z": {"kind": "box", "lat": [0, 1], "lon": [2, 3]}}, + event=[{"year": 0, "kind": "seed"}, {"year": 20, "kind": "cull", "share": 0.3, + "by_race": {"a": 0.5}}]) + doc = resolved(h, [make_race()], seed=5, engine="abc") + back = tomllib.loads(dumps_toml(doc)) + self.assertEqual(back["seed"], 5) + self.assertEqual(back["history"]["events"][1]["by_race"], {"a": 0.5}) + self.assertEqual(back["race"][0]["id"], "a") + self.assertEqual(back["history"]["regions"]["z"]["lat"], [0, 1]) |
