Checkpoint 2: integrate native Editor, MCP, gameplay builds and standalone export
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Exercise the real headless Editor over newline-delimited MCP, without a GPU."""
|
||||
import json
|
||||
import pathlib
|
||||
import queue
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
|
||||
|
||||
def run(executable, project):
|
||||
process = subprocess.Popen([executable, "--project", str(project), "--mcp"],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE, text=True, encoding="utf-8")
|
||||
output = queue.Queue()
|
||||
def reader():
|
||||
for line in process.stdout:
|
||||
output.put(json.loads(line)) # Any non-JSON stdout fails the test.
|
||||
output.put(None)
|
||||
threading.Thread(target=reader, daemon=True).start()
|
||||
sequence = 0
|
||||
def request(method, params=None):
|
||||
nonlocal sequence
|
||||
sequence += 1
|
||||
message = {"jsonrpc": "2.0", "id": sequence, "method": method}
|
||||
if params is not None:
|
||||
message["params"] = params
|
||||
process.stdin.write(json.dumps(message) + "\n")
|
||||
process.stdin.flush()
|
||||
result = output.get(timeout=20)
|
||||
assert result and result["id"] == sequence, result
|
||||
return result
|
||||
def call(name, arguments=None, error=False):
|
||||
response = request("tools/call", {"name": name, "arguments": arguments or {}})["result"]
|
||||
assert response["isError"] == error, response
|
||||
return response["structuredContent"]
|
||||
try:
|
||||
initialized = request("initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
|
||||
"clientInfo": {"name": "faset-integration-test", "version": "1"}})
|
||||
assert initialized["result"]["serverInfo"]["name"] == "faset-editor"
|
||||
process.stdin.write('{"jsonrpc":"2.0","method":"notifications/initialized"}\n')
|
||||
process.stdin.flush()
|
||||
names = {tool["name"] for tool in request("tools/list")["result"]["tools"]}
|
||||
assert {"faset_scene_edit", "faset_export", "faset_job_cancel", "faset_plugins"} <= names
|
||||
assert "faset_runtime_query" not in names and "faset_editor_capture" not in names
|
||||
if (project / "Scenes/main.scene.json").exists():
|
||||
opened = call("faset_document_open", {"path": "Scenes/main.scene.json"})
|
||||
assert opened["scene"]["entities"][0]["name"] == "Door 世界"
|
||||
return
|
||||
scene = call("faset_document_create", {"name": "MCP integration", "dimension": 3})
|
||||
identity = scene["id"]
|
||||
edit = {"document": identity, "revision": 0,
|
||||
"operations": [{"op": "entity.create", "name": "Door 世界"}],
|
||||
"idempotency_key": "create-door"}
|
||||
changed = call("faset_scene_edit", edit)
|
||||
assert call("faset_scene_edit", edit) == changed
|
||||
conflict = dict(edit)
|
||||
del conflict["idempotency_key"]
|
||||
assert call("faset_scene_edit", conflict, True)["error"]["code"] == "revision.conflict"
|
||||
undone = call("faset_undo", {"document": identity, "revision": 1})
|
||||
assert undone["scene"]["entities"] == []
|
||||
redone = call("faset_redo", {"document": identity, "revision": 2})
|
||||
assert redone["scene"] == changed["scene"]
|
||||
saved = call("faset_document_save", {"document": identity, "path": "Scenes/main.scene.json"})
|
||||
assert not saved["dirty"]
|
||||
call("faset_document_open", {"path": "../outside.json"}, True)
|
||||
assert request("resources/read", {"uri": "faset://documents"})["result"]["contents"]
|
||||
assert request("faset_runtime_query")["error"]["code"] == -32601
|
||||
finally:
|
||||
process.stdin.close()
|
||||
try:
|
||||
code = process.wait(timeout=20)
|
||||
except subprocess.TimeoutExpired:
|
||||
process.kill()
|
||||
process.wait()
|
||||
raise
|
||||
errors = process.stderr.read()
|
||||
assert code == 0, errors
|
||||
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="faset-mcp-stdio-") as temporary:
|
||||
run(sys.argv[1], pathlib.Path(temporary))
|
||||
run(sys.argv[1], pathlib.Path(temporary))
|
||||
print("Real MCP stdio lifecycle, clean stdout, revision conflict, retry, Undo/Redo, disk reopen and process shutdown passed")
|
||||
Reference in New Issue
Block a user