test: 100s -> 1.6s

The suite spawned a python interpreter per nap, ~2000 of them. The CLI now
runs in-process; real processes are kept only for the cross-process lock
test, which is their subject. The 3000-step cover sweep is sampled: the
property is structural, not a function of the exact T.
This commit is contained in:
victortaelin 2026-07-25 17:15:33 -03:00
parent fdcc92eeea
commit dc5339c55b

67
test.py
View file

@ -4,17 +4,27 @@
Uses a fake compressor (join + truncate) so the run is deterministic and free. Uses a fake compressor (join + truncate) so the run is deterministic and free.
""" """
import contextlib
import datetime import datetime
import io
import os import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
from importlib.machinery import SourceFileLoader
HERE = os.path.dirname(os.path.realpath(__file__)) HERE = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from blocks import complete, cover # noqa: E402 from blocks import complete, cover # noqa: E402
MEMO = os.path.join(HERE, "memo")
cli = SourceFileLoader("memo_cli", MEMO).load_module()
# The shipped defaults. A fresh process starts from these, so an in-process
# call must too, or one store's config would leak into the next.
DEFAULTS = {k: getattr(cli, k) for k in
("ENTRY_CHARS", "WAKE_LINES", "PART_CHARS", "PART_LINES")}
N = 2000 N = 2000
WAKE_LINES = 256 WAKE_LINES = 256
PART_CHARS = 20000 PART_CHARS = 20000
@ -53,9 +63,11 @@ for T in list(range(1, 400)) + [1000, 4096, 10000, 65536, 100003]:
check(cover(300, 320) == [(i, i + 1) for i in range(300)], check(cover(300, 320) == [(i, i + 1) for i in range(300)],
"under budget, memory should be verbatim") "under budget, memory should be verbatim")
# every block a cover ever needs must be buildable # every block a cover ever needs must be buildable. cover() costs a 60-step
# binary search, so this walks every tree shape up to 300 and then samples:
# the property is structural, not a function of the exact T.
seen = set() seen = set()
for T in range(1, 3000): for T in list(range(1, 300)) + [512, 700, 1000, 1023, 1024, 2000, 2999]:
seen.update(b for b in cover(T, WAKE_LINES) if b[1] - b[0] > 1) seen.update(b for b in cover(T, WAKE_LINES) if b[1] - b[0] > 1)
buildable = set(complete(3000)) buildable = set(complete(3000))
check(seen <= buildable, "a cover wants a block that complete() never yields") check(seen <= buildable, "a cover wants a block that complete() never yields")
@ -71,13 +83,37 @@ check(worst <= 16, "a single memory created %d naps" % worst)
# ---- the real CLI ---------------------------------------------------- # ---- the real CLI ----------------------------------------------------
d = tempfile.mkdtemp(prefix="optmem-test-") d = tempfile.mkdtemp(prefix="optmem-test-")
env = dict(os.environ, MEMORY_DIR=d) memo = [sys.executable, MEMO]
memo = [sys.executable, os.path.join(HERE, "memo")]
def run(*args): class Result:
return subprocess.run(memo + list(args), env=env, capture_output=True, def __init__(self, returncode, stdout, stderr):
text=True) self.returncode, self.stdout, self.stderr = returncode, stdout, stderr
def run(*args, store=None):
"""One `memo` command, in-process. Spawning an interpreter per call cost
~40ms x ~2000 naps; the cross-process behaviour that genuinely needs real
processes (the lock) is tested with real processes below."""
os.environ["MEMORY_DIR"] = store or d
for k, v in DEFAULTS.items():
setattr(cli, k, v)
out, err, code = io.StringIO(), io.StringIO(), 0
try:
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
sd = cli.store()
cli.config(sd)
cli.COMMANDS[args[0]](sd, list(args[1:]))
except SystemExit as e:
code = e.code if isinstance(e.code, int) else 0
return Result(code, out.getvalue(), err.getvalue())
# the real entry point still has to work: shebang, argv parsing, exit code
smoke = subprocess.run(memo + ["wake"], env=dict(os.environ, MEMORY_DIR=d),
capture_output=True, text=True)
check(smoke.returncode == 0 and "No memories yet" in smoke.stdout,
"the memo CLI does not run: " + smoke.stdout + smoke.stderr)
r = run("note", "x" * 281) r = run("note", "x" * 281)
@ -222,7 +258,7 @@ check("Narrow the regex" in r.stdout, "recall did not say it had been capped")
d2 = tempfile.mkdtemp(prefix="optmem-race-") d2 = tempfile.mkdtemp(prefix="optmem-race-")
env2 = dict(os.environ, MEMORY_DIR=d2) env2 = dict(os.environ, MEMORY_DIR=d2)
P = 16 P = 16 # real processes: this is the cross-process lock under test
procs = [subprocess.Popen(memo + ["note", "parallel note %d" % i], env=env2, procs = [subprocess.Popen(memo + ["note", "parallel note %d" % i], env=env2,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
for i in range(P)] for i in range(P)]
@ -240,19 +276,24 @@ check(sorted(ids) == sorted("#%d" % i for i in range(P)),
# or every later record is misaligned forever # or every later record is misaligned forever
with open(os.path.join(d2, "LOG.txt"), "ab") as f: with open(os.path.join(d2, "LOG.txt"), "ab") as f:
f.write(b"#99 2026-01-01 a half-written record killed by a power cut") f.write(b"#99 2026-01-01 a half-written record killed by a power cut")
r = subprocess.run(memo + ["note", "the memory right after a torn write"], r = run("note", "the memory right after a torn write", store=d2)
env=env2, capture_output=True, text=True)
check(r.returncode == 0, "note failed after a torn write: " + r.stderr) check(r.returncode == 0, "note failed after a torn write: " + r.stderr)
sz = os.path.getsize(os.path.join(d2, "LOG.txt")) sz = os.path.getsize(os.path.join(d2, "LOG.txt"))
check(sz % 320 == 0, "LOG.txt left misaligned after a torn write: %d" % sz) check(sz % 320 == 0, "LOG.txt left misaligned after a torn write: %d" % sz)
check("saved as #%d" % P in r.stdout, "torn record was counted as a memory") check("saved as #%d" % P in r.stdout, "torn record was counted as a memory")
r = subprocess.run(memo + ["recall", "right after a torn write"], env=env2, r = run("recall", "right after a torn write", store=d2)
capture_output=True, text=True)
check("#%d " % P in r.stdout, "the memory after a torn write reads wrong") check("#%d " % P in r.stdout, "the memory after a torn write reads wrong")
# a memory small enough to fit one part must still end with the terminator # a memory small enough to fit one part must still end with the terminator
# the agent was told to wait for # the agent was told to wait for
r = subprocess.run(memo + ["wake"], env=env2, capture_output=True, text=True) while True:
r = run("sleep", store=d2)
if "You are awake" in r.stdout:
break
bid = [l for l in r.stdout.splitlines()
if l.strip().startswith("memo sleep ")][0].split()[2]
run("sleep", bid, "settled", store=d2)
r = run("wake", store=d2)
check(r.stdout.rstrip().endswith("awake."), check(r.stdout.rstrip().endswith("awake."),
"a one-part wake never says `awake.`:\n" + r.stdout) "a one-part wake never says `awake.`:\n" + r.stdout)