"""Full-pipeline integration tests using xpu_server --dry-run.
Validates end-to-end flow: config -> heartbeat -> dispatch -> verify -> cleanup.
Uses port 19094 to avoid conflicts with other test suites.
"""
import base64
import hashlib
import http.client
import json
import os
import shutil
import subprocess
import sys
import tempfile
import time
import numpy as np
import pytest
_INTEGRATION_SYNC_DIR = os.path.join(tempfile.gettempdir(), "ttk_sync_integration")
@pytest.fixture(scope="module")
def xpu_server():
if os.path.exists(_INTEGRATION_SYNC_DIR):
shutil.rmtree(_INTEGRATION_SYNC_DIR, ignore_errors=True)
os.makedirs(_INTEGRATION_SYNC_DIR, exist_ok=True)
import yaml
tmp_dir = tempfile.mkdtemp(prefix="ttk_tmp_integration_")
config_file = os.path.join(tempfile.gettempdir(), "xpu_server_integration.yaml")
config_data = {
"storage": {
"sync_dir": _INTEGRATION_SYNC_DIR,
"tmp_dir": tmp_dir,
},
}
with open(config_file, "w") as f:
yaml.dump(config_data, f)
proc = subprocess.Popen(
[sys.executable, "-m", "server.xpu_server",
"--port", "19094", "--dry-run", "--config", config_file],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
env=os.environ.copy(),
)
for _ in range(20):
time.sleep(0.5)
try:
conn = http.client.HTTPConnection("127.0.0.1", 19094, timeout=1)
conn.request("GET", "/v1/heartbeat")
resp = conn.getresponse()
resp.read()
conn.close()
if resp.status == 200:
break
except (ConnectionRefusedError, OSError):
continue
yield proc
proc.terminate()
proc.wait(timeout=5)
shutil.rmtree(_INTEGRATION_SYNC_DIR, ignore_errors=True)
class TestFullPipeline:
"""End-to-end: config -> heartbeat -> dispatch -> verify -> cleanup."""
def test_api_mode_full_flow(self, xpu_server):
"""API mode: config -> heartbeat -> /run -> verify -> cleanup."""
from ttk.remote.dispatcher import dispatch_to_remote
from ttk.remote.config import RemoteConfig
config = RemoteConfig.from_dict({"endpoints": [{"host": "127.0.0.1", "port": 19094}]})
tenant_id = "integration_test_001"
conn = http.client.HTTPConnection("127.0.0.1", 19094, timeout=5)
conn.request("GET", f"/v1/heartbeat?tenant_id={tenant_id}")
resp = conn.getresponse()
assert resp.status == 200
resp.read()
inputs = [np.random.randn(8, 16).astype(np.float32)]
outputs = dispatch_to_remote(
op_name="softmax_v2",
inputs=inputs,
provider="torch",
attrs={"axis": -1},
endpoint_host="127.0.0.1",
endpoint_port=19094,
tenant_id=tenant_id,
)
assert len(outputs) >= 1
assert isinstance(outputs[0], np.ndarray)
conn.request("DELETE", f"/v1/tenant/{tenant_id}")
resp = conn.getresponse()
assert resp.status == 200
data = json.loads(resp.read())
assert data["cleaned"] is True
conn.close()
def test_multi_input(self, xpu_server):
"""Multiple inputs, verify serialization roundtrip."""
from ttk.remote.dispatcher import dispatch_to_remote
inputs = [
np.random.randn(4, 8).astype(np.float32),
np.random.randn(4, 8).astype(np.float32),
]
outputs = dispatch_to_remote(
op_name="add",
inputs=inputs,
provider="torch",
attrs={},
endpoint_host="127.0.0.1",
endpoint_port=19094,
tenant_id="integration_test_002",
)
assert len(outputs) >= 1
class TestRemoteConfigIntegration:
def test_tenant_id_available(self):
from ttk.remote import get_tenant_id
tid = get_tenant_id()
assert isinstance(tid, str)
assert len(tid) == 12
class TestSyncIntegration:
def test_sync_then_run(self, xpu_server):
"""Sync spec files then run -- dry-run mode ignores spec."""
tenant_id = "integration_sync_001"
content = base64.b64encode(b"# dummy spec").decode()
h = hashlib.sha256(b"# dummy spec").hexdigest()
body = json.dumps({
"files": {"nn/dummy.py": {"content": content, "hash": f"sha256:{h}"}}
})
conn = http.client.HTTPConnection("127.0.0.1", 19094, timeout=5)
conn.request("POST", "/v1/sync", body=body,
headers={"Content-Type": "application/json",
"X-Tenant-ID": tenant_id})
resp = conn.getresponse()
resp.read()
assert resp.status == 200
from ttk.remote.dispatcher import dispatch_to_remote
outputs = dispatch_to_remote(
op_name="dummy",
inputs=[np.array([1.0])],
endpoint_host="127.0.0.1",
endpoint_port=19094,
tenant_id=tenant_id,
)
assert len(outputs) >= 1
conn.close()