WebHost: Fix generate argparse with --config-override + add autogen unit tests so we can test that (#5541)
* Fix webhost argparse with extra args * accidentally added line * WebHost: fix some typing B64 url conversion is used in test/hosting, so it felt appropriate to include this here. * Test: Hosting: also test autogen * Test: Hosting: simplify stop_* and leave a note about Windows compat * Test: Hosting: fix formatting error * Test: Hosting: add limitted Windows support There are actually some differences with MP on Windows that make it impossible to run this in CI. --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Optional, cast
|
||||
from typing import TYPE_CHECKING, Iterable, Optional, cast
|
||||
|
||||
from WebHostLib import to_python
|
||||
|
||||
@@ -10,6 +14,7 @@ if TYPE_CHECKING:
|
||||
|
||||
__all__ = [
|
||||
"get_app",
|
||||
"generate_remote",
|
||||
"upload_multidata",
|
||||
"create_room",
|
||||
"start_room",
|
||||
@@ -17,6 +22,7 @@ __all__ = [
|
||||
"set_room_timeout",
|
||||
"get_multidata_for_room",
|
||||
"set_multidata_for_room",
|
||||
"stop_autogen",
|
||||
"stop_autohost",
|
||||
]
|
||||
|
||||
@@ -33,10 +39,43 @@ def get_app(tempdir: str) -> "Flask":
|
||||
"TESTING": True,
|
||||
"HOST_ADDRESS": "localhost",
|
||||
"HOSTERS": 1,
|
||||
"GENERATORS": 1,
|
||||
"JOB_THRESHOLD": 1,
|
||||
})
|
||||
return get_app()
|
||||
|
||||
|
||||
def generate_remote(app_client: "FlaskClient", games: Iterable[str]) -> str:
|
||||
data = io.BytesIO()
|
||||
with zipfile.ZipFile(data, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
|
||||
for n, game in enumerate(games, 1):
|
||||
name = f"{n}.yaml"
|
||||
zip_file.writestr(name, json.dumps({
|
||||
"name": f"Player{n}",
|
||||
"game": game,
|
||||
game: {},
|
||||
"description": f"generate_remote slot {n} ('Player{n}'): {game}",
|
||||
}))
|
||||
data.seek(0)
|
||||
response = app_client.post("/generate", content_type="multipart/form-data", data={
|
||||
"file": (data, "yamls.zip"),
|
||||
})
|
||||
assert response.status_code < 400, f"Starting gen failed: status {response.status_code}"
|
||||
assert "Location" in response.headers, f"Starting gen failed: no redirect"
|
||||
location = response.headers["Location"]
|
||||
assert isinstance(location, str)
|
||||
assert location.startswith("/wait/"), f"Starting WebHost gen failed: unexpected redirect to {location}"
|
||||
for attempt in range(10):
|
||||
response = app_client.get(location)
|
||||
if "Location" in response.headers:
|
||||
location = response.headers["Location"]
|
||||
assert isinstance(location, str)
|
||||
assert location.startswith("/seed/"), f"Finishing WebHost gen failed: unexpected redirect to {location}"
|
||||
return location[6:]
|
||||
time.sleep(1)
|
||||
raise TimeoutError("WebHost gen did not finish")
|
||||
|
||||
|
||||
def upload_multidata(app_client: "FlaskClient", multidata: Path) -> str:
|
||||
response = app_client.post("/uploads", data={
|
||||
"file": multidata.open("rb"),
|
||||
@@ -188,7 +227,7 @@ def set_multidata_for_room(webhost_client: "FlaskClient", room_id: str, data: by
|
||||
room.seed.multidata = data
|
||||
|
||||
|
||||
def stop_autohost(graceful: bool = True) -> None:
|
||||
def _stop_webhost_mp(name_filter: str, graceful: bool = True) -> None:
|
||||
import os
|
||||
import signal
|
||||
|
||||
@@ -198,13 +237,30 @@ def stop_autohost(graceful: bool = True) -> None:
|
||||
|
||||
stop()
|
||||
proc: multiprocessing.process.BaseProcess
|
||||
for proc in filter(lambda child: child.name.startswith("MultiHoster"), multiprocessing.active_children()):
|
||||
for proc in filter(lambda child: child.name.startswith(name_filter), multiprocessing.active_children()):
|
||||
# FIXME: graceful currently does not work on Windows because the signals are not properly emulated
|
||||
# and ungraceful may not save the game
|
||||
if proc.pid == os.getpid():
|
||||
continue
|
||||
if graceful and proc.pid:
|
||||
os.kill(proc.pid, getattr(signal, "CTRL_C_EVENT", signal.SIGINT))
|
||||
else:
|
||||
proc.kill()
|
||||
try:
|
||||
proc.join(30)
|
||||
try:
|
||||
proc.join(30)
|
||||
except TimeoutError:
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
# on Windows, the MP exception may be forwarded to the host, so ignore once and retry
|
||||
proc.join(30)
|
||||
except TimeoutError:
|
||||
proc.kill()
|
||||
proc.join()
|
||||
|
||||
def stop_autogen(graceful: bool = True) -> None:
|
||||
# FIXME: this name filter is jank, but there seems to be no way to add a custom prefix for a Pool
|
||||
_stop_webhost_mp("SpawnPoolWorker-", graceful)
|
||||
|
||||
def stop_autohost(graceful: bool = True) -> None:
|
||||
_stop_webhost_mp("MultiHoster", graceful)
|
||||
|
||||
Reference in New Issue
Block a user