Complete M11 Creator Studio and M12 Trust & Safety for PC5
checkpoint / checkpoint (push) Failing after 3h14m9s
checkpoint / checkpoint (push) Failing after 3h14m9s
- Creator Studio: Visual behavior & property inspector, GLB model importer, binary persistence - Trust & Safety: 18+ TOS confirmation (DOB removed), creator guidelines & briefing card on join, in-game reporting HUD - Test Harness: Add --filter, --skip-backend, --skip-godot to run_checkpoint_tests.py
This commit is contained in:
@@ -62,50 +62,62 @@ def main():
|
||||
"override if that's already in use on this machine)")
|
||||
parser.add_argument("--backend-port", default=os.environ.get("CHECKPOINT_BACKEND_PORT", "18080"),
|
||||
help="Port to run the backend server on for the Godot smoke tests")
|
||||
parser.add_argument("--filter", "-k", default="",
|
||||
help="Run only smoke tests matching substring (e.g. -k creator or -k safe)")
|
||||
parser.add_argument("--skip-godot", action="store_true",
|
||||
help="Skip running Godot headless smoke tests")
|
||||
parser.add_argument("--skip-backend", action="store_true",
|
||||
help="Skip backend Postgres, migrations, and pytest")
|
||||
args = parser.parse_args()
|
||||
|
||||
results = {}
|
||||
|
||||
# --- Backend: bring up Postgres ---
|
||||
compose_env = os.environ.copy()
|
||||
compose_env["COMPOSE_PROJECT_NAME"] = "agp-checkpoint"
|
||||
compose_env["POSTGRES_HOST_PORT"] = args.postgres_port
|
||||
compose_env["POSTGRES_PORT"] = args.postgres_port # some branches use this name instead, see tools/definitions.py
|
||||
print("=== Backend: bringing up Postgres ===")
|
||||
run(["docker", "compose", "up", "-d", "postgres"], cwd=BACKEND, env=compose_env)
|
||||
|
||||
print("Waiting for Postgres to be ready...")
|
||||
pg_ready = False
|
||||
for _ in range(20):
|
||||
r = run(["docker", "compose", "exec", "-T", "postgres", "pg_isready", "-U", "agp_user", "-d", "agp_db"],
|
||||
cwd=BACKEND, env=compose_env, capture=True)
|
||||
if r.returncode == 0:
|
||||
pg_ready = True
|
||||
break
|
||||
time.sleep(1)
|
||||
if not pg_ready:
|
||||
print("Postgres never became ready -- aborting.")
|
||||
sys.exit(1)
|
||||
|
||||
db_url = f"postgresql+asyncpg://agp_user:agp_password@localhost:{args.postgres_port}/agp_db"
|
||||
backend_env = os.environ.copy()
|
||||
backend_env["DATABASE_URL"] = db_url
|
||||
# Every smoke test registers users against this one backend from one
|
||||
# client IP; the per-IP auth rate limit would trip mid-suite.
|
||||
# test_rate_limit.py exercises the limiter itself regardless.
|
||||
backend_env["RATE_LIMIT_ENABLED"] = "false"
|
||||
|
||||
print("\n=== Backend: alembic upgrade head ===")
|
||||
r = run([sys.executable, "-m", "alembic", "upgrade", "head"], cwd=BACKEND, env=backend_env)
|
||||
results["migrations (fresh DB)"] = r.returncode == 0
|
||||
if not args.skip_backend:
|
||||
# --- Backend: bring up Postgres ---
|
||||
compose_env = os.environ.copy()
|
||||
compose_env["COMPOSE_PROJECT_NAME"] = "agp-checkpoint"
|
||||
compose_env["POSTGRES_HOST_PORT"] = args.postgres_port
|
||||
compose_env["POSTGRES_PORT"] = args.postgres_port
|
||||
print("=== Backend: bringing up Postgres ===")
|
||||
run(["docker", "compose", "up", "-d", "postgres"], cwd=BACKEND, env=compose_env)
|
||||
|
||||
print("\n=== Backend: pytest ===")
|
||||
r = run([sys.executable, "-m", "pytest", "-v"], cwd=BACKEND, env=backend_env)
|
||||
results["backend pytest"] = r.returncode == 0
|
||||
print("Waiting for Postgres to be ready...")
|
||||
pg_ready = False
|
||||
for _ in range(20):
|
||||
r = run(["docker", "compose", "exec", "-T", "postgres", "pg_isready", "-U", "agp_user", "-d", "agp_db"],
|
||||
cwd=BACKEND, env=compose_env, capture=True)
|
||||
if r.returncode == 0:
|
||||
pg_ready = True
|
||||
break
|
||||
time.sleep(1)
|
||||
if not pg_ready:
|
||||
print("Postgres never became ready -- aborting.")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n=== Backend: alembic upgrade head ===")
|
||||
r = run([sys.executable, "-m", "alembic", "upgrade", "head"], cwd=BACKEND, env=backend_env)
|
||||
results["migrations (fresh DB)"] = r.returncode == 0
|
||||
|
||||
print("\n=== Backend: pytest ===")
|
||||
r = run([sys.executable, "-m", "pytest", "-v"], cwd=BACKEND, env=backend_env)
|
||||
results["backend pytest"] = r.returncode == 0
|
||||
else:
|
||||
print("\n=== Skipping Backend tests (--skip-backend) ===")
|
||||
|
||||
# --- Godot: headless smoke tests (need a live backend server) ---
|
||||
godot_bin = find_godot()
|
||||
smoke_tests = sorted(glob.glob(str(ROOT / "scripts" / "smoke_test_*.gd")))
|
||||
if args.skip_godot:
|
||||
smoke_tests = []
|
||||
results["godot smoke tests"] = None
|
||||
else:
|
||||
smoke_tests = sorted(glob.glob(str(ROOT / "scripts" / "smoke_test_*.gd")))
|
||||
if args.filter:
|
||||
smoke_tests = [t for t in smoke_tests if args.filter.lower() in os.path.basename(t).lower()]
|
||||
|
||||
if not godot_bin:
|
||||
print("\n=== Godot: binary not found (checked PATH and ~/godot/godot), skipping smoke tests ===")
|
||||
@@ -161,53 +173,47 @@ def main():
|
||||
game_server.kill()
|
||||
results["godot smoke tests"] = godot_ok
|
||||
|
||||
# 2-player multiplayer test (its own shell harness: server + 2
|
||||
# real client processes).
|
||||
mp_script = ROOT / "scripts" / "run_multiplayer_2p_test.sh"
|
||||
if mp_script.exists():
|
||||
print("\n--- scripts/run_multiplayer_2p_test.sh ---")
|
||||
mp_env = os.environ.copy()
|
||||
mp_env["GODOT_BIN"] = godot_bin
|
||||
r = run(["bash", str(mp_script)], env=mp_env)
|
||||
results["multiplayer 2p test"] = r.returncode == 0
|
||||
# Shell harnesses and export verification (only run for full checkpoint runs, not when filtering)
|
||||
if not args.filter:
|
||||
mp_script = ROOT / "scripts" / "run_multiplayer_2p_test.sh"
|
||||
if mp_script.exists():
|
||||
print("\n--- scripts/run_multiplayer_2p_test.sh ---")
|
||||
mp_env = os.environ.copy()
|
||||
mp_env["GODOT_BIN"] = godot_bin
|
||||
r = run(["bash", str(mp_script)], env=mp_env)
|
||||
results["multiplayer 2p test"] = r.returncode == 0
|
||||
|
||||
# Fresh-clone boot: a from-scratch tree must import + boot with
|
||||
# zero script errors (no reliance on a committed .godot cache).
|
||||
fc_script = ROOT / "scripts" / "verify_fresh_clone.sh"
|
||||
if fc_script.exists():
|
||||
print("\n--- scripts/verify_fresh_clone.sh ---")
|
||||
fc_env = os.environ.copy()
|
||||
fc_env["GODOT_BIN"] = godot_bin
|
||||
r = run(["bash", str(fc_script)], env=fc_env)
|
||||
results["fresh-clone boot"] = r.returncode == 0
|
||||
fc_script = ROOT / "scripts" / "verify_fresh_clone.sh"
|
||||
if fc_script.exists():
|
||||
print("\n--- scripts/verify_fresh_clone.sh ---")
|
||||
fc_env = os.environ.copy()
|
||||
fc_env["GODOT_BIN"] = godot_bin
|
||||
r = run(["bash", str(fc_script)], env=fc_env)
|
||||
results["fresh-clone boot"] = r.returncode == 0
|
||||
|
||||
# Export packaging: the game reads some files (catalog.json)
|
||||
# via raw FileAccess, which `all_resources` export doesn't
|
||||
# pack unless include_filter covers them. Passes from source,
|
||||
# bare capsule in the .exe. This guards that.
|
||||
ec_script = ROOT / "scripts" / "verify_export_contents.gd"
|
||||
if ec_script.exists():
|
||||
print("\n--- scripts/verify_export_contents.gd ---")
|
||||
r = run([godot_bin, "--headless", "--path", str(ROOT),
|
||||
"--script", "res://scripts/verify_export_contents.gd"], env=godot_env)
|
||||
results["export packaging"] = r.returncode == 0
|
||||
ec_script = ROOT / "scripts" / "verify_export_contents.gd"
|
||||
if ec_script.exists():
|
||||
print("\n--- scripts/verify_export_contents.gd ---")
|
||||
r = run([godot_bin, "--headless", "--path", str(ROOT),
|
||||
"--script", "res://scripts/verify_export_contents.gd"], env=godot_env)
|
||||
results["export packaging"] = r.returncode == 0
|
||||
|
||||
# Run a key smoke against the EXPORTED pack, not just source --
|
||||
# release-build resources differ (e.g. the runtime GLTFDocument
|
||||
# path produced no skeleton in a real export; load().instantiate()
|
||||
# of the imported .scn does). This runs the avatar smoke on
|
||||
# loads models via raw FileAccess, which can fail in .pck). Tests
|
||||
# exactly what ships.
|
||||
exported_pck = ROOT / "export" / "_ci_check.pck"
|
||||
exported_pck.parent.mkdir(exist_ok=True)
|
||||
ep = run([godot_bin, "--headless", "--path", str(ROOT),
|
||||
"--export-pack", "Linux", str(exported_pck)], env=godot_env)
|
||||
if ep.returncode == 0 and exported_pck.exists():
|
||||
r = run([godot_bin, "--headless", "--main-pack", str(exported_pck),
|
||||
"--script", "res://scripts/smoke_test_avatar_system.gd"], env=godot_env)
|
||||
results["avatar smoke (exported pack)"] = r.returncode == 0
|
||||
exported_pck.unlink(missing_ok=True)
|
||||
else:
|
||||
results["avatar smoke (exported pack)"] = False
|
||||
if not args.filter:
|
||||
exported_pck = ROOT / "export" / "_ci_check.pck"
|
||||
exported_pck.parent.mkdir(exist_ok=True)
|
||||
ep = run([godot_bin, "--headless", "--path", str(ROOT),
|
||||
"--export-pack", "Linux", str(exported_pck)], env=godot_env)
|
||||
if ep.returncode == 0 and exported_pck.exists():
|
||||
r = run([godot_bin, "--headless", "--main-pack", str(exported_pck),
|
||||
"--script", "res://scripts/smoke_test_avatar_system.gd"], env=godot_env)
|
||||
results["avatar smoke (exported pack)"] = r.returncode == 0
|
||||
exported_pck.unlink(missing_ok=True)
|
||||
else:
|
||||
results["avatar smoke (exported pack)"] = False
|
||||
finally:
|
||||
server_proc.terminate()
|
||||
try:
|
||||
@@ -215,7 +221,7 @@ def main():
|
||||
except subprocess.TimeoutExpired:
|
||||
server_proc.kill()
|
||||
|
||||
if not args.keep_postgres:
|
||||
if not args.skip_backend and not args.keep_postgres:
|
||||
print("\n=== Backend: tearing down checkpoint Postgres ===")
|
||||
run(["docker", "compose", "down", "-v", "postgres"], cwd=BACKEND, env=compose_env)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user