From b9228f3ca4ad232ab1a61f090cd9a8acf643e64a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 2 Jul 2026 19:03:49 -0400 Subject: [PATCH] artifacts --- .../tier2_state/wrap_dag_with_try.py | 108 ++++++++++++++ .../diag_runtime_state.py | 138 ++++++++++++++++++ .../diag_verify_autodock.py | 102 +++++++++++++ .../diag_visible.py | 69 +++++++++ scripts/tier2/artifacts/diag_audit_check.py | 23 +++ scripts/tier2/artifacts/diag_audit_check2.py | 27 ++++ 6 files changed, 467 insertions(+) create mode 100644 scripts/artifacts/tier2_state/wrap_dag_with_try.py create mode 100644 scripts/tier2/artifacts/default_layout_install_20260629/diag_runtime_state.py create mode 100644 scripts/tier2/artifacts/default_layout_install_20260629/diag_verify_autodock.py create mode 100644 scripts/tier2/artifacts/default_layout_install_20260629/diag_visible.py create mode 100644 scripts/tier2/artifacts/diag_audit_check.py create mode 100644 scripts/tier2/artifacts/diag_audit_check2.py diff --git a/scripts/artifacts/tier2_state/wrap_dag_with_try.py b/scripts/artifacts/tier2_state/wrap_dag_with_try.py new file mode 100644 index 00000000..e8d555f8 --- /dev/null +++ b/scripts/artifacts/tier2_state/wrap_dag_with_try.py @@ -0,0 +1,108 @@ +"""Wrap the body of the if (app.active_track or app.active_tickets) and app.node_editor_ctx: +block in render_task_dag_panel with a try/except. To do this without manually +reindenting ~195 lines, we use a Python script that finds the if line, dedents +the body, inserts try/except, and re-indents. +""" +import re +from pathlib import Path + +GUI_2 = Path(r"C:\projects\manual_slop_tier2\src\gui_2.py") +text = GUI_2.read_text(encoding="utf-8") +lines = text.splitlines(keepends=True) + +# Find the function start +fn_start = None +for i, line in enumerate(lines): + if "def render_task_dag_panel" in line: + fn_start = i + break + +if fn_start is None: + raise RuntimeError("render_task_dag_panel not found") + +# Find the if (app.active_track or app.active_tickets) and app.node_editor_ctx: line +if_start = None +for i in range(fn_start, len(lines)): + line = lines[i] + stripped = line.lstrip() + if stripped.startswith("if (app.active_track or app.active_tickets)"): + if_start = i + break + +if if_start is None: + raise RuntimeError("if not found") + +# Find the end of the if block (matching 0-indent next line, or next def) +if_end = None +for i in range(if_start + 1, len(lines)): + line = lines[i] + if line.strip() == "" or line.startswith(" ") or line.startswith("\t"): + continue + # Found a non-indented line that's not blank + if_end = i + break + +if if_end is None: + if_end = len(lines) + +# Find body bounds (indented lines) +body_start = if_start + 1 +while body_start < if_end and lines[body_start].strip() == "": + body_start += 1 + +# Find actual body end (last indented line before 0-indent) +body_end = body_end = if_end +for i in range(if_end - 1, if_start, -1): + if lines[i].strip() != "": + body_end = i + 1 + break + +# Get the indentation of the if line +if_indent = len(lines[if_start]) - len(lines[if_start].lstrip()) +# Body indent should be if_indent + 2 (next level) +body_indent = if_indent + 2 + +# Extract body lines (preserve content) +body_lines = lines[body_start:body_end] + +# Detent: strip body_indent spaces from each line +detented_body = [] +for ln in body_lines: + if ln.strip() == "": + detented_body.append("\n") + elif ln.startswith(" " * body_indent): + detented_body.append(" " + ln[body_indent:]) + else: + detented_body.append(ln) + +# Re-indent: add body_indent spaces to each line +reindented_body = [] +for ln in detented_body: + if ln.strip() == "": + reindented_body.append("\n") + else: + reindented_body.append(" " * (body_indent - 1) + ln) + +# Build the new content +new_lines = [] +new_lines.append(lines[if_start]) # the if line +new_lines.append(" " * (if_indent + 1) + "try:\n") +for ln in reindented_body: + new_lines.append(ln) +new_lines.append(" " * (if_indent + 1) + "except Exception as dag_err:\n") +new_lines.append(" " * (if_indent + 2) + "# imgui-node-editor can raise ImGui assertions (e.g. Missing PopID) that\n") +new_lines.append(" " * (if_indent + 2) + "# would otherwise kill the ImGui main loop and break every subsequent\n") +new_lines.append(" " * (if_indent + 2) + "# test in the same xdist worker that shares the live_gui subprocess.\n") +new_lines.append(" " * (if_indent + 2) + "if not hasattr(app, '_last_request_errors'): app._last_request_errors = []\n") +new_lines.append(" " * (if_indent + 2) + "app._last_request_errors.append(('render_task_dag_panel.dag_error', dag_err))\n") + +# Lines after the if body (0-indent) +for i in range(body_end, if_end): + new_lines.append(lines[i]) + +# Replace +new_text = "".join(lines[:if_start]) + "".join(new_lines) + "".join(lines[if_end:]) + +# Write back +GUI_2.write_text(new_text, encoding="utf-8") +print(f"OK: modified if block at line {if_start+1}, body lines {body_end - body_start}") diff --git a/scripts/tier2/artifacts/default_layout_install_20260629/diag_runtime_state.py b/scripts/tier2/artifacts/default_layout_install_20260629/diag_runtime_state.py new file mode 100644 index 00000000..fca5e498 --- /dev/null +++ b/scripts/tier2/artifacts/default_layout_install_20260629/diag_runtime_state.py @@ -0,0 +1,138 @@ +"""Verify runtime dock state by monkey-patching and registering a custom callback.""" +import sys, time, subprocess, shutil, urllib.request, json +from pathlib import Path + +PROJ = Path("C:/projects/manual_slop_tier2") +WORK = PROJ / "tests" / "artifacts" / "diag6_runtime_state" +LOG = PROJ / "tests" / "artifacts" / "diag6_runtime_state.log" +RUNTIME_INI = PROJ / "tests" / "artifacts" / "diag6_runtime_ini.txt" + +# Clean +if WORK.exists(): + shutil.rmtree(WORK) +WORK.mkdir(parents=True, exist_ok=True) +if LOG.exists(): + LOG.unlink() +if RUNTIME_INI.exists(): + RUNTIME_INI.unlink() + +# Wrapper that registers a custom callback BEFORE the app launches +WRAPPER = PROJ / "tests" / "artifacts" / "diag6_wrapper.py" +WRAPPER.write_text(''' +import sys, json +sys.path.insert(0, "C:/projects/manual_slop_tier2") +sys.argv = ["sloppy.py", "--enable-test-hooks"] + +# Import imgui and patch save_ini_settings_to_disk to also write to a known file +from imgui_bundle import imgui +_orig_save_disk = imgui.save_ini_settings_to_disk +def _hooked_save_disk(path): + res = _orig_save_disk(path) + try: + mem = imgui.save_ini_settings_to_memory() + with open("C:/projects/manual_slop_tier2/tests/artifacts/diag6_runtime_ini.txt", "w", encoding="utf-8") as f: + f.write(str(mem) if mem else "") + except Exception as e: + with open("C:/projects/manual_slop_tier2/tests/artifacts/diag6_runtime_ini.txt", "w", encoding="utf-8") as f: + f.write(f"ERR: {e}") + return res +imgui.save_ini_settings_to_disk = _hooked_save_disk + +# Also patch save_ini_settings_to_memory to capture runtime state +def _hooked_save_mem(): + res = imgui.save_ini_settings_to_memory() + try: + with open("C:/projects/manual_slop_tier2/tests/artifacts/diag6_runtime_ini.txt", "w", encoding="utf-8") as f: + f.write(str(res) if res else "") + except Exception: + pass + return res + +# Now import app +from src.gui_2 import App as _App +_orig_init = _App.__init__ +def _patched_init(self, *a, **kw): + _orig_init(self, *a, **kw) + # Register a custom callback that saves the runtime INI to a known location + self.controller._predefined_callbacks["_diag_save_runtime_ini"] = lambda: _hooked_save_mem() +_App.__init__ = _patched_init + +import argparse +from pathlib import Path +from src.paths import initialize_paths +initialize_paths(None) +from src.gui_2 import main as _sloppy_main +_sloppy_main() +''') + +# Spawn +print("Launching with patched callback...", flush=True) +proc = subprocess.Popen( + ["uv", "run", "python", "-u", str(WRAPPER.absolute())], + stdout=open(LOG, "w"), + stderr=subprocess.STDOUT, + cwd=str(WORK), + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0, +) + +ready = False +for i in range(30): + time.sleep(0.5) + try: + urllib.request.urlopen("http://127.0.0.1:8999/status", timeout=1) + ready = True + break + except Exception: + pass + +if not ready: + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + print("NO_HOOK", flush=True) + sys.exit(1) + +print(f"Hook ready. Waiting 15s for first frame + dock layout to settle...", flush=True) +time.sleep(15) + +# Trigger the custom callback to save runtime INI +print("Triggering _diag_save_runtime_ini callback...", flush=True) +try: + req = urllib.request.Request( + "http://127.0.0.1:8999/api/gui", + data=json.dumps({"action": "custom_callback", "callback": "_diag_save_runtime_ini", "args": []}).encode(), + headers={"Content-Type": "application/json"}, + method="POST", + ) + r = urllib.request.urlopen(req, timeout=5) + print(f" POST response: {r.read().decode()}", flush=True) +except Exception as e: + print(f" POST failed: {e}", flush=True) + +time.sleep(2) + +# Read runtime INI +if RUNTIME_INI.exists(): + runtime_text = RUNTIME_INI.read_text(encoding="utf-8", errors="replace") + print(f"\n=== RUNTIME INI ({len(runtime_text)} bytes) ===", flush=True) + for line in runtime_text.splitlines()[:60]: + print(f" {line}", flush=True) + print("=== END ===", flush=True) +else: + print(f"\nRUNTIME_INI not written to {RUNTIME_INI}", flush=True) + +# Compare with bundled +bundled = (PROJ / "layouts" / "default.ini").read_text(encoding="utf-8") +if RUNTIME_INI.exists(): + runtime_text = RUNTIME_INI.read_text(encoding="utf-8", errors="replace") + if runtime_text == bundled: + print("\n=> RUNTIME INI == BUNDLED INI (HelloImGui honored the layout)", flush=True) + else: + print("\n=> RUNTIME INI != BUNDLED INI (HelloImGui rebuilt the dock)", flush=True) + # Show diff + bundled_lines = bundled.splitlines() + runtime_lines = runtime_text.splitlines() + print(f" bundled: {len(bundled_lines)} lines", flush=True) + print(f" runtime: {len(runtime_lines)} lines", flush=True) + +# Kill +subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) +time.sleep(1) \ No newline at end of file diff --git a/scripts/tier2/artifacts/default_layout_install_20260629/diag_verify_autodock.py b/scripts/tier2/artifacts/default_layout_install_20260629/diag_verify_autodock.py new file mode 100644 index 00000000..f6fcc132 --- /dev/null +++ b/scripts/tier2/artifacts/default_layout_install_20260629/diag_verify_autodock.py @@ -0,0 +1,102 @@ +"""Regression test: verify the auto-dock layout actually takes effect at runtime. + +Launches sloppy.py, waits for first frame + a few seconds, then triggers +imgui.save_ini_settings_to_disk via custom callback. If HelloImGui honored +the bundled INI's dock structure, the saved INI matches the bundled one. +If HelloImGui rebuilt the dock (because runtime DockSpace ID didn't match +or windows weren't yet created when INI was read), the saved INI differs. +""" +import os, sys, time, subprocess, shutil, json, urllib.request +from pathlib import Path + +PROJ = Path("C:/projects/manual_slop_tier2") +WORK = PROJ / "tests" / "artifacts" / "diag5_verify_autodock" +LOG = PROJ / "tests" / "artifacts" / "diag5_verify_autodock.log" +INI_INSTALLED = WORK / "manualslop_layout.ini" +INI_AFTER_SAVE = WORK / "manualslop_layout.ini" # same path, post-save + +# Clean workspace +if WORK.exists(): + shutil.rmtree(WORK) +WORK.mkdir(parents=True, exist_ok=True) +if LOG.exists(): + LOG.unlink() + +# Bundle the layout INI for comparison (this is what gets installed) +BUNDLED_INI = (PROJ / "layouts" / "default.ini").read_text(encoding="utf-8") + +# Spawn sloppy.py with --enable-test-hooks so we can call API +print("Launching sloppy.py...", flush=True) +proc = subprocess.Popen( + ["uv", "run", "python", "-u", str(PROJ / "sloppy.py"), "--enable-test-hooks"], + stdout=open(LOG, "w"), + stderr=subprocess.STDOUT, + cwd=str(WORK), + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0, +) + +ready = False +for i in range(30): + time.sleep(0.5) + try: + urllib.request.urlopen("http://127.0.0.1:8999/status", timeout=1) + ready = True + break + except Exception: + pass + +if not ready: + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + print("NO_HOOK: hook server never started", flush=True) + sys.exit(1) + +print(f"Hook ready, waiting 5s for first frame + dock layout to settle...", flush=True) +time.sleep(5) + +# Check that the install wrote the INI +assert INI_INSTALLED.exists(), f"pre-run install did not write INI to {INI_INSTALLED}" +installed_size = INI_INSTALLED.stat().st_size +print(f"Installed INI: {installed_size} bytes at {INI_INSTALLED}", flush=True) + +# Now trigger save_ini_settings_to_disk via a custom callback +print("Triggering save_ini_settings_to_disk via custom callback...", flush=True) + +# Push a callback that calls imgui.save_ini_settings_to_disk +payload = { + "callback": "_trigger_save_ini", + "args": ["manualslop_layout.ini"] +} + +# Try direct POST to /api/gui +try: + req = urllib.request.Request( + "http://127.0.0.1:8999/api/gui", + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json"}, + method="POST", + ) + r = urllib.request.urlopen(req, timeout=5) + print(f" POST /api/gui response: {r.read().decode()}", flush=True) +except Exception as e: + print(f" POST /api/gui failed: {e}", flush=True) + +# Wait for save to complete +time.sleep(2) + +# Check the INI after save +if not INI_AFTER_SAVE.exists(): + print(f"NO_INI: file not found at {INI_AFTER_SAVE}", flush=True) +else: + after_text = INI_AFTER_SAVE.read_text(encoding="utf-8") + after_size = INI_AFTER_SAVE.stat().st_size + print(f"Post-save INI: {after_size} bytes", flush=True) + print(f"Same as bundled? {after_text == BUNDLED_INI}", flush=True) + + # Show first 20 lines of post-save INI + print("\n--- POST-SAVE INI (first 20 lines) ---", flush=True) + for line in after_text.splitlines()[:20]: + print(f" {line}", flush=True) + print("--- END ---", flush=True) + +# Kill +subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) \ No newline at end of file diff --git a/scripts/tier2/artifacts/default_layout_install_20260629/diag_visible.py b/scripts/tier2/artifacts/default_layout_install_20260629/diag_visible.py new file mode 100644 index 00000000..86d78916 --- /dev/null +++ b/scripts/tier2/artifacts/default_layout_install_20260629/diag_visible.py @@ -0,0 +1,69 @@ +"""Check what windows are visible in the runtime via the live_gui fixture.""" +import subprocess, time, urllib.request, json, sys +from pathlib import Path + +PROJ = Path("C:/projects/manual_slop_tier2") +WORK = PROJ / "tests" / "artifacts" / "diag7_visible" +LOG = PROJ / "tests" / "artifacts" / "diag7_visible.log" + +if WORK.exists(): + import shutil + shutil.rmtree(WORK) +WORK.mkdir(parents=True, exist_ok=True) +if LOG.exists(): + LOG.unlink() + +# Spawn +print("Launching...", flush=True) +proc = subprocess.Popen( + ["uv", "run", "python", "-u", str(PROJ / "sloppy.py"), "--enable-test-hooks"], + stdout=open(LOG, "w"), + stderr=subprocess.STDOUT, + cwd=str(WORK), + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0, +) + +ready = False +for i in range(30): + time.sleep(0.5) + try: + urllib.request.urlopen("http://127.0.0.1:8999/status", timeout=1) + ready = True + break + except Exception: + pass + +if not ready: + subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + print("NO_HOOK", flush=True) + sys.exit(1) + +print(f"Ready, waiting 10s...", flush=True) +time.sleep(10) + +# Query /api/gui/state for show_windows +try: + r = urllib.request.urlopen("http://127.0.0.1:8999/api/gui/state", timeout=2) + state = json.loads(r.read().decode()) + print(f"\nshow_windows state:", flush=True) + if "show_windows" in state: + for k, v in sorted(state["show_windows"].items()): + print(f" {k}: {v}", flush=True) +except Exception as e: + print(f"ERROR: {e}", flush=True) + +# Trigger save_ini to memory to capture runtime state +import json +try: + req = urllib.request.Request( + "http://127.0.0.1:8999/api/gui", + data=json.dumps({"action": "custom_callback", "callback": "noop", "args": []}).encode(), + headers={"Content-Type": "application/json"}, + method="POST", + ) + urllib.request.urlopen(req, timeout=2) +except: pass + +# Kill +subprocess.run(["taskkill", "/F", "/T", "/PID", str(proc.pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) +time.sleep(1) \ No newline at end of file diff --git a/scripts/tier2/artifacts/diag_audit_check.py b/scripts/tier2/artifacts/diag_audit_check.py new file mode 100644 index 00000000..b821dccd --- /dev/null +++ b/scripts/tier2/artifacts/diag_audit_check.py @@ -0,0 +1,23 @@ +import json +import subprocess +import sys + +result = subprocess.run( + ["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"], + capture_output=True, text=True, cwd=r"C:\projects\manual_slop_tier2", +) +if result.returncode != 0: + print("AUDIT FAILED:", result.stderr[:1000]) + sys.exit(1) + +data = json.loads(result.stdout) +gui = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0] +broad = [f for f in gui.get("findings", []) if f.get("category") == "INTERNAL_BROAD_CATCH"] +silent = [f for f in gui.get("findings", []) if f.get("category") == "INTERNAL_SILENT_SWALLOW"] +print("BROAD CATCHES:") +for f in broad: + print(f" L{f['line']} {f['context']}") +print("SILENT SWALLOWS:") +for f in silent: + print(f" L{f['line']} {f['context']}") +print(f"TOTAL: {len(broad)} broad, {len(silent)} silent") \ No newline at end of file diff --git a/scripts/tier2/artifacts/diag_audit_check2.py b/scripts/tier2/artifacts/diag_audit_check2.py new file mode 100644 index 00000000..80230c28 --- /dev/null +++ b/scripts/tier2/artifacts/diag_audit_check2.py @@ -0,0 +1,27 @@ +import json +import subprocess +import sys + +result = subprocess.run( + ["uv", "run", "python", "scripts/audit_exception_handling.py", "--src", "src", "--json"], + capture_output=True, text=True, cwd=r"C:\projects\manual_slop_tier2", +) +data = json.loads(result.stdout) + +# Check all categories for the helpers we care about +gui = [f for f in data.get("files", []) if "gui_2" in f.get("filename", "")][0] +all_findings = gui.get("findings", []) +# Group by context +by_context = {} +for f in all_findings: + ctx = f.get("context", "") + by_context.setdefault(ctx, []).append(f) + +# Check the existing helper +for ctx in ["_tier_stream_scroll_sync_result", "_tier_stream_scroll_sync_result.dispatcher", "_install_default_layout_if_empty", "_install_default_layout_if_empty_result", "render_tier_stream_panel"]: + print(f"=== {ctx} ===") + findings = by_context.get(ctx, []) + for f in findings: + print(f" L{f['line']} [{f['category']}] {f['kind']}: {f.get('context_extra', '')}") + if not findings: + print(" (no findings)") \ No newline at end of file