MAINT-21: tests read and patch the Controller through tools/main_files.py

Prepares the split of app/main.py's Controller into files by job, with no app
change. tools/main_files.py gives a test the text and the AST of main.py plus
the files split from it, and `live`, which sets a global in every file whose
functions read it (and raises when none does). Tests that read main.py's
source, and tests and replay tools that patch a main global (datetime,
compute, resolve, _mono, WRITE_BUDGET_S, ...), go through it, so they pass on
this layout and on the split one. test_split_patches.py fails when a test or
tool patches an app.main attribute that main.py no longer reads, or that
another file also reads. tools/maint21_same_ast.py proves every function,
method and module-level assignment of main.py at the base is found unchanged
in its new file.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-11-09 00:27:07 +01:00
co-authored by Claude
parent 56fc41fffa
commit 0d49027daa
50 changed files with 707 additions and 179 deletions
+7 -6
View File
@@ -61,6 +61,7 @@ sys.path.insert(0, os.path.join(HERE, "tools"))
logging.disable(logging.WARNING)
import opt11p3_replay as rp # noqa: E402
import main_files # noqa: E402
import app.main as m # noqa: E402
from app import homeview, solarfc # noqa: E402
from app.main import Controller # noqa: E402
@@ -348,14 +349,14 @@ def run_night(spec):
async def go():
cur = store
real_dt, real_hv = m.datetime, homeview.datetime
real_dt, real_hv = main_files.live.datetime, homeview.datetime
class _DT(real_dt):
@classmethod
def now(cls, tz=None):
return clock[0] if tz is None else clock[0].astimezone(tz)
m.datetime = homeview.datetime = _DT
main_files.live.datetime = homeview.datetime = _DT
try:
# TEST-37: built under the scenario's clock, not the real one:
# Controller.__init__ checks a stored latch's end against now().
@@ -420,7 +421,7 @@ def run_night(spec):
out["soc"] = (hass.plants["a"].soc, hass.plants["b"].soc)
out["latch"] = getattr(c, "_charge_latch", None) is not None
finally:
m.datetime, homeview.datetime = real_dt, real_hv
main_files.live.datetime, homeview.datetime = real_dt, real_hv
return out
return asyncio.run(go())
@@ -722,7 +723,7 @@ from types import SimpleNamespace # noqa: E402
from app import modetrade, priceruns # noqa: E402
CLOCK = [at(4, 1, 0)]
_dt0 = m.datetime
_dt0 = main_files.live.datetime
class _FixedDT(_dt0):
@@ -731,7 +732,7 @@ class _FixedDT(_dt0):
return CLOCK[0] if tz is None else CLOCK[0].astimezone(tz)
m.datetime = homeview.datetime = _FixedDT
main_files.live.datetime = homeview.datetime = _FixedDT
_tmp = tempfile.mkdtemp(prefix="o34-")
lc = Controller(dict(opts()), rp.Hass(("a", "b"), lambda t: 300.0,
{"a": 40.0, "b": 40.0}),
@@ -858,7 +859,7 @@ lc._charge_latch = (at(4, 6, 10), {A: 80.0}, at(4, 1, 0))
lt, _s = latch_step(None, resting, None, now=at(4, 6, 5), runs=r3)
check("F4: a run that starts AT the bell (06:00-09:00) does carry it on, "
"to its end + one slot", lt is not None and lt[0] == at(4, 9, 15))
m.datetime = homeview.datetime = _dt0
main_files.live.datetime = homeview.datetime = _dt0
print()
if fails: