evalarc / context-controls /protocol /harness /protocol_probe.py
glayguo's picture
Publish verified EvalArc 19ac9d8a5cb8
6c4b387 verified
Raw History Blame Contribute Delete
4.33 kB
"""Exercise two JSONL requests without closing stdin; numerical answers are not graded."""
from __future__ import annotations
import argparse
import json
import os
import select
import subprocess
import sys
import time
from pathlib import Path
class ProtocolFailure(Exception):
pass
def probe(candidate: Path, example: Path, timeout: float = 3.0) -> list:
if not 0 < timeout <= 10:
raise ValueError("response timeout must be between zero and ten seconds")
request = json.loads(example.read_text().strip())
payload = (json.dumps(request, allow_nan=False) + "\n").encode()
if len(payload) > 65536:
raise ValueError("example exceeds the JSONL request limit")
process = subprocess.Popen(
[sys.executable, "-I", "-B", str(candidate.resolve())],
cwd=candidate.resolve().parent,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
responses = []
output_bytes = 0
try:
for stream in (process.stdin, process.stdout, process.stderr):
os.set_blocking(stream.fileno(), False)
streams = [process.stdout, process.stderr]
for _ in range(2):
pending, received = memoryview(payload), bytearray()
deadline = time.monotonic() + timeout
while pending or b"\n" not in received:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise ProtocolFailure(
"response timeout: flush each JSON response before waiting for more input"
)
readable, writable, _ = select.select(
streams, [process.stdin] if pending else [], [], remaining
)
if writable:
try:
pending = pending[os.write(process.stdin.fileno(), pending) :]
except BrokenPipeError as error:
raise ProtocolFailure(
"program closed stdin before both requests"
) from error
for stream in readable:
chunk = os.read(stream.fileno(), 4096)
if not chunk:
streams.remove(stream)
if stream is process.stdout and b"\n" not in received:
raise ProtocolFailure("program ended before returning a JSON line")
continue
output_bytes += len(chunk)
if output_bytes > 16384:
raise ProtocolFailure("program exceeded the 16 KiB diagnostic output limit")
if stream is process.stdout:
received.extend(chunk)
line, _, extra = received.partition(b"\n")
if extra.strip():
raise ProtocolFailure("multiple stdout lines for one request")
try:
response = json.loads(line)
json.dumps(response, allow_nan=False)
except (ValueError, UnicodeDecodeError) as error:
raise ProtocolFailure("response is not finite JSON") from error
responses.append(response)
finally:
if process.poll() is None:
process.kill()
process.wait(timeout=2)
for stream in (process.stdin, process.stdout, process.stderr):
stream.close()
return responses
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--candidate", type=Path, default=Path("main.py"))
parser.add_argument("--example", type=Path, default=Path("example.jsonl"))
parser.add_argument("--timeout", type=float, default=3)
args = parser.parse_args()
try:
responses = probe(args.candidate, args.example, args.timeout)
except (ProtocolFailure, ValueError, OSError) as error:
print(json.dumps({"protocol_ok": False, "error": str(error)}))
return 1
print(
json.dumps(
{
"protocol_ok": True,
"requests": 2,
"responses": responses,
"numerical_correctness": "not_assessed",
}
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())