130 lines
3.9 KiB
Python
130 lines
3.9 KiB
Python
"""Exercise Rayforge-style async cancellation on the offline JCZ driver."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
RAYFORGE_COMMIT = "c28918c87f8273dd1d244f5a057addc192807fa5"
|
|
|
|
|
|
def _commit_of(source: Path) -> str:
|
|
result = subprocess.run(
|
|
["git", "-C", str(source), "rev-parse", "HEAD"],
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return result.stdout.strip()
|
|
|
|
|
|
def _ops(Ops, line_count: int):
|
|
ops = Ops()
|
|
ops.job_start()
|
|
ops.set_power(0.5)
|
|
ops.set_feed_rate(15_000)
|
|
ops.set_rapid_rate(120_000)
|
|
ops.set_frequency(40_000)
|
|
ops.set_pulse_width(200)
|
|
ops.move_to(50, 50, 0)
|
|
for index in range(line_count):
|
|
ops.line_to(51 + index % 2, 50 + index % 2, 0)
|
|
ops.job_end()
|
|
return ops
|
|
|
|
|
|
async def _exercise(Driver, Ops) -> dict[str, object]:
|
|
machine = MagicMock()
|
|
machine.coordinate_systems = {}
|
|
driver = Driver(MagicMock(), machine)
|
|
encoder = driver.create_encoder(machine)
|
|
|
|
long_ops = _ops(Ops, 260)
|
|
encoded = encoder.encode(long_ops)
|
|
buffered_before_cancel = sum(
|
|
len(packet) for packet in encoded.driver_data["list_buffers"]
|
|
)
|
|
run_task = asyncio.create_task(driver.run(encoded, None, long_ops))
|
|
await asyncio.sleep(0)
|
|
await driver.set_hold(True)
|
|
held_state = driver.offline_session.state.value
|
|
await driver.set_hold(False)
|
|
resumed_state = driver.offline_session.state.value
|
|
await driver.cancel()
|
|
await asyncio.wait_for(run_task, timeout=2)
|
|
|
|
cancelled_state = driver.offline_session.state.value
|
|
buffered_after_cancel = driver.offline_session.buffered_bytes
|
|
restart_rejected = False
|
|
try:
|
|
driver.offline_session.start()
|
|
except RuntimeError:
|
|
restart_rejected = True
|
|
|
|
driver.offline_session.recover()
|
|
recovered_state = driver.offline_session.state.value
|
|
|
|
recovery_ops = _ops(Ops, 2)
|
|
recovery_encoded = encoder.encode(recovery_ops)
|
|
await asyncio.wait_for(
|
|
driver.run(recovery_encoded, None, recovery_ops), timeout=2
|
|
)
|
|
|
|
return {
|
|
"transport": None,
|
|
"long_job_buffer_count": len(encoded.driver_data["list_buffers"]),
|
|
"buffered_bytes_before_cancel": buffered_before_cancel,
|
|
"held_state": held_state,
|
|
"resumed_state": resumed_state,
|
|
"cancelled_state": cancelled_state,
|
|
"buffered_bytes_after_cancel": buffered_after_cancel,
|
|
"restart_without_recovery_rejected": restart_rejected,
|
|
"recovered_state": recovered_state,
|
|
"second_job_state": driver.offline_session.state.value,
|
|
"events": driver.offline_session.events,
|
|
"hardware_commands": [],
|
|
}
|
|
|
|
|
|
def run(source: Path, addon: Path) -> dict[str, object]:
|
|
commit = _commit_of(source)
|
|
if commit != RAYFORGE_COMMIT:
|
|
raise RuntimeError(f"expected Rayforge {RAYFORGE_COMMIT}, found {commit}")
|
|
sys.path.insert(0, str(source))
|
|
sys.path.insert(0, str(addon))
|
|
|
|
from pirate_ray_jcz.worker import PirateRayJCZNoDeviceDriver
|
|
from raygeo.ops import Ops
|
|
|
|
result = asyncio.run(_exercise(PirateRayJCZNoDeviceDriver, Ops))
|
|
result["rayforge_commit"] = commit
|
|
return result
|
|
|
|
|
|
def main() -> None:
|
|
root = Path(__file__).parents[1]
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("source", type=Path, help="pinned Rayforge checkout")
|
|
parser.add_argument(
|
|
"--addon",
|
|
type=Path,
|
|
default=root / "spikes" / "rayforge-jcz-no-device-addon",
|
|
)
|
|
parser.add_argument("--output", type=Path)
|
|
args = parser.parse_args()
|
|
result = run(args.source.resolve(), args.addon.resolve())
|
|
rendered = json.dumps(result, indent=2) + "\n"
|
|
if args.output:
|
|
args.output.write_text(rendered, encoding="utf-8", newline="\n")
|
|
else:
|
|
print(rendered, end="")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|