aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/tests/test_config.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_config.py')
-rw-r--r--tests/test_config.py194
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])