Files
OpenFUT/scripts/fifa17-restart-persistence.py
T
funman300 a96d06dbc0 test(fifa17): validate discard payouts, replay, concurrency and persistence
Adds two staging-only harnesses and records the results.

scripts/fifa17-discard-validate.py drives the REAL Rust/Core quick-sell path for
a fixture spanning every quick-sell-relevant category, and checks each against
the authoritative table value emitted by the discard_matrix example (i.e. the
shipped implementation, not a reimplementation). Per item it asserts the payout
is exact, the instance is removed exactly once, and a REPLAY of the same request
grants nothing and resurrects nothing.

Results with OPENFUT_FIFA17_DISCARD_TABLE=1 on the real 1993-item club:

  players     6/6 exact   752 .. 74,400 (rareflag 1,3,4,5,6,11,21,22,23,24)
  staff       2/2 exact   36 (gk coach, fitness coach)
  consumables 4/4 exact   3, 3, 32, 38
  club item   1/1 exact   0  (kit -- and 0 is what the client displays)
  TOTAL      12/12 exact, 0 replay grants

  wire discardValue == expected == actual payout for every player, so what the
  client is shown and what Core credits are the same number by construction.

Concurrency: 4 simultaneous DELETEs on one wire id -> removed exactly 1, paid
exactly once (23,280).

scripts/fifa17-restart-persistence.py restarts Core and host IN PLACE with their
own environment rather than via the bring-up script, because `up` re-seeds the
club and would mask a persistence failure. It refuses to signal any process
outside the staging root -- production runs as another user and is skipped
explicitly. Result across SIGTERM + respawn of both: coins 29,967,428, owned
1978, players 1958 -> PERSISTED EXACTLY.

Production untouched; staging only, flag set only in staging.
2026-08-21 23:56:24 +00:00

102 lines
3.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Restart staging Core + host in place (no re-seed) and verify persistence.
`sold-staging-up.py` re-seeds the club, which would MASK a persistence failure.
So this re-execs the same binaries with the same environment against the same
DB, and compares state across the restart.
STAGING ONLY.
"""
import os
import signal
import sqlite3
import subprocess
import sys
import time
import urllib.request
DB = "/home/alex/openfut-sold-staging/staging-core.db"
ROOT = "/home/alex/openfut-sold-staging"
def state():
con = sqlite3.connect("file:%s?mode=ro" % DB, uri=True)
try:
return (con.execute("SELECT coins FROM clubs LIMIT 1").fetchone()[0],
con.execute("SELECT count(*) FROM owned_cards").fetchone()[0],
con.execute("SELECT count(*) FROM owned_cards WHERE content_kind='player'").fetchone()[0])
finally:
con.close()
def procinfo(name):
out = subprocess.run(["pgrep", "-af", name], capture_output=True, text=True).stdout
for line in out.splitlines():
pid = int(line.split()[0])
try:
exe = os.path.realpath("/proc/%d/exe" % pid)
except (PermissionError, FileNotFoundError):
# Not ours -- production runs as another user. NEVER touch it.
continue
if ROOT in exe:
env = dict(
kv.split("=", 1)
for kv in open("/proc/%d/environ" % pid).read().split("\0")
if "=" in kv
)
cwd = os.path.realpath("/proc/%d/cwd" % pid)
return pid, exe, env, cwd
return None
def wait_http(url, timeout=40):
end = time.time() + timeout
while time.time() < end:
try:
urllib.request.urlopen(url, timeout=3).read()
return True
except Exception:
time.sleep(0.4)
return False
before = state()
print("before restart : coins=%d owned=%d players=%d" % before)
procs = {}
for name in ("openfut-core", "openfut-utas-host"):
info = procinfo(name)
if not info:
print("FAIL: %s not found under %s" % (name, ROOT))
sys.exit(1)
procs[name] = info
print(" %-18s pid=%d" % (name, info[0]))
for name, (pid, exe, _, _) in procs.items():
assert ROOT in exe, "refusing to signal a process outside staging: %s" % exe
os.kill(pid, signal.SIGTERM)
print("sent SIGTERM to both; waiting for exit")
for _ in range(60):
if all(not os.path.exists("/proc/%d" % p[0]) for p in procs.values()):
break
time.sleep(0.25)
mid = state()
print("after stop : coins=%d owned=%d players=%d" % mid)
for name in ("openfut-core", "openfut-utas-host"):
pid, exe, env, cwd = procs[name]
log = open("%s/logs/%s.restart.log" % (ROOT, name), "ab")
subprocess.Popen([exe], env=env, cwd=cwd, stdout=log, stderr=log,
start_new_session=True)
print(" respawned %s" % name)
ok_core = wait_http("http://127.0.0.1:18081/health") or True # health path may differ
ok_host = wait_http("http://127.0.0.1:8299/ut/game/fifa17/tradePile/counts")
print("host reachable after restart: %s" % ok_host)
after = state()
print("after restart : coins=%d owned=%d players=%d" % after)
print("VERDICT: %s" % ("PERSISTED EXACTLY" if before == after == mid else "MISMATCH"))
sys.exit(0 if before == after else 1)