Skip to content

Commit 1263f7b

Browse files
fix: smoke_test — remove daemon thread HTTP server, mcp_restart, QApplication instantiation (all cause EXCEPTION_ACCESS_VIOLATION in UEFN Slate loop)
1 parent 8965f9d commit 1263f7b

2 files changed

Lines changed: 28 additions & 44 deletions

File tree

Content/Python/UEFN_Toolbelt/smoke_test.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
from __future__ import annotations
2525

26-
import json
2726
import os
2827
import socket
2928
import sys
@@ -143,26 +142,16 @@ def _worker(): flag["ok"] = True
143142
t.start(); t.join(timeout=2)
144143
_record("Layer 1", "Daemon thread", flag["ok"])
145144

146-
# HTTP server round-trip (use a different port to avoid conflicts)
145+
# HTTP server class instantiable (daemon thread round-trip removed — unsafe in UEFN Slate tick)
147146
try:
148147
class _H(BaseHTTPRequestHandler):
149-
def do_GET(self):
150-
self.send_response(200)
151-
self.send_header("Content-Type", "application/json")
152-
self.end_headers()
153-
self.wfile.write(b'{"ok":true}')
148+
def do_GET(self): pass
154149
def log_message(self, *a): pass
155-
156-
srv = HTTPServer(("127.0.0.1", 18765), _H)
157-
th = threading.Thread(target=srv.serve_forever, daemon=True)
158-
th.start()
159-
import urllib.request
160-
resp = urllib.request.urlopen("http://127.0.0.1:18765/", timeout=3)
161-
body = json.loads(resp.read())
162-
srv.shutdown()
163-
_record("Layer 1", "HTTP server round-trip", body.get("ok") is True)
150+
srv = HTTPServer(("127.0.0.1", 0), _H) # port 0 = OS assigns, never serve_forever
151+
srv.server_close()
152+
_record("Layer 1", "HTTPServer instantiable", True)
164153
except Exception as e:
165-
_record("Layer 1", "HTTP server round-trip", False, str(e))
154+
_record("Layer 1", "HTTPServer instantiable", False, str(e))
166155

167156
# File write
168157
try:
@@ -293,7 +282,7 @@ def _layer_toolbelt() -> None:
293282
"config_list",
294283
"config_get",
295284
"mcp_status",
296-
"mcp_restart",
285+
# mcp_restart intentionally excluded — registers Slate tick callbacks mid-test → crash
297286
"plugin_export_manifest",
298287
"plugin_validate_all",
299288
"plugin_list_custom",
@@ -381,12 +370,15 @@ def _layer_dashboard() -> None:
381370
"run: <UE>/Engine/Binaries/ThirdParty/Python3/Win64/python.exe -m pip install PySide6")
382371
return
383372

373+
# QApplication instantiation skipped — creating one mid-tick and letting it
374+
# GC immediately causes EXCEPTION_ACCESS_VIOLATION in UEFN's Slate loop.
384375
try:
385376
from PySide6.QtWidgets import QApplication
386-
app = QApplication.instance() or QApplication(sys.argv)
387-
_record("Layer 5", "QApplication", app is not None)
377+
app = QApplication.instance()
378+
_record("Layer 5", "QApplication (existing)", app is not None,
379+
"None = dashboard not yet launched (normal)" if app is None else "running")
388380
except Exception as e:
389-
_record("Layer 5", "QApplication", False, str(e))
381+
_record("Layer 5", "QApplication check", False, str(e))
390382

391383
try:
392384
from UEFN_Toolbelt.dashboard_pyside6 import ToolbeltDashboard

tests/smoke_test.py

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
from __future__ import annotations
2525

26-
import json
2726
import os
2827
import socket
2928
import sys
@@ -143,26 +142,16 @@ def _worker(): flag["ok"] = True
143142
t.start(); t.join(timeout=2)
144143
_record("Layer 1", "Daemon thread", flag["ok"])
145144

146-
# HTTP server round-trip (use a different port to avoid conflicts)
145+
# HTTP server class instantiable (daemon thread round-trip removed — unsafe in UEFN Slate tick)
147146
try:
148147
class _H(BaseHTTPRequestHandler):
149-
def do_GET(self):
150-
self.send_response(200)
151-
self.send_header("Content-Type", "application/json")
152-
self.end_headers()
153-
self.wfile.write(b'{"ok":true}')
148+
def do_GET(self): pass
154149
def log_message(self, *a): pass
155-
156-
srv = HTTPServer(("127.0.0.1", 18765), _H)
157-
th = threading.Thread(target=srv.serve_forever, daemon=True)
158-
th.start()
159-
import urllib.request
160-
resp = urllib.request.urlopen("http://127.0.0.1:18765/", timeout=3)
161-
body = json.loads(resp.read())
162-
srv.shutdown()
163-
_record("Layer 1", "HTTP server round-trip", body.get("ok") is True)
150+
srv = HTTPServer(("127.0.0.1", 0), _H) # port 0 = OS assigns, never serve_forever
151+
srv.server_close()
152+
_record("Layer 1", "HTTPServer instantiable", True)
164153
except Exception as e:
165-
_record("Layer 1", "HTTP server round-trip", False, str(e))
154+
_record("Layer 1", "HTTPServer instantiable", False, str(e))
166155

167156
# File write
168157
try:
@@ -293,7 +282,7 @@ def _layer_toolbelt() -> None:
293282
"config_list",
294283
"config_get",
295284
"mcp_status",
296-
"mcp_restart",
285+
# mcp_restart intentionally excluded — registers Slate tick callbacks mid-test → crash
297286
"plugin_export_manifest",
298287
"plugin_validate_all",
299288
"plugin_list_custom",
@@ -381,12 +370,15 @@ def _layer_dashboard() -> None:
381370
"run: <UE>/Engine/Binaries/ThirdParty/Python3/Win64/python.exe -m pip install PySide6")
382371
return
383372

373+
# QApplication instantiation skipped — creating one mid-tick and letting it
374+
# GC immediately causes EXCEPTION_ACCESS_VIOLATION in UEFN's Slate loop.
384375
try:
385376
from PySide6.QtWidgets import QApplication
386-
app = QApplication.instance() or QApplication(sys.argv)
387-
_record("Layer 5", "QApplication", app is not None)
377+
app = QApplication.instance()
378+
_record("Layer 5", "QApplication (existing)", app is not None,
379+
"None = dashboard not yet launched (normal)" if app is None else "running")
388380
except Exception as e:
389-
_record("Layer 5", "QApplication", False, str(e))
381+
_record("Layer 5", "QApplication check", False, str(e))
390382

391383
try:
392384
from UEFN_Toolbelt.dashboard_pyside6 import ToolbeltDashboard
@@ -400,9 +392,9 @@ def _layer_dashboard() -> None:
400392
def _layer_verse_book() -> None:
401393
_header("Layer 6 — Verse Book (Spec Reference)")
402394

403-
# Locate verse-book relative to this file (tests/ → project root → verse-book)
395+
# smoke_test.py lives at Content/Python/UEFN_Toolbelt/ — go 3 levels up to project root
404396
here = os.path.dirname(os.path.abspath(__file__))
405-
book_root = os.path.normpath(os.path.join(here, "..", "verse-book"))
397+
book_root = os.path.normpath(os.path.join(here, "..", "..", "..", "verse-book"))
406398
docs_path = os.path.join(book_root, "docs")
407399

408400
# Clone present

0 commit comments

Comments
 (0)