File size: 3,591 Bytes
e1acc15 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | """PP4-90f 0132: an HTTP 400 from the served API is never opaque again (CPU only, no host)."""
from __future__ import annotations
import io
import json
import time
import urllib.error
import urllib.request
from email.message import Message
from pathlib import Path
import pytest
from offload.gb10_budget import window_cells as wc
from offload.launch.windows import pp4_admission_requests as api
from offload.prefill_maxlen.evidence import Json
# The image's own 400 body for the 0132 request (sampling_params.py:1110-1115 via error_response.py:41-44).
IMAGE_400 = json.dumps({'error': {'message': 'The min_p and logit_bias sampling parameters are not yet supported '
'with speculative decoding.', 'type': 'BadRequestError', 'param': None, 'code': 400}})
def refuse(request: urllib.request.Request, timeout: float = 0) -> object:
raise urllib.error.HTTPError(request.full_url, 400, 'Bad Request', Message(), io.BytesIO(IMAGE_400.encode()))
def test_post_logs_and_carries_the_server_error_body(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
monkeypatch.setattr(urllib.request, 'urlopen', refuse)
body: dict[str, Json] = {'model': api.MODEL, 'prompt': 'public', 'max_tokens': 8, 'logit_bias': {'5': 100}}
with pytest.raises(api.HttpRefused) as caught:
api.Http('http://fixture.invalid').post('/v1/completions', body)
assert caught.value.status == 400 and caught.value.endpoint == '/v1/completions'
assert 'logit_bias' in caught.value.body and 'speculative decoding' in str(caught.value)
assert isinstance(caught.value, ValueError) # a STOP like every other failed assertion
logged = json.loads(capsys.readouterr().err.strip().splitlines()[-1])
assert logged['http_error'] == 400 and logged['body'] == IMAGE_400
assert logged['request_settings'] == {'model': api.MODEL, 'max_tokens': 8, 'logit_bias': {'5': 100}}
assert 'prompt' not in logged['request_settings']
def test_unreadable_error_body_is_named_never_dropped(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
class Broken(io.BytesIO):
def read(self, size: int | None = -1) -> bytes: raise ConnectionResetError('peer reset')
def opener(request: urllib.request.Request, timeout: float = 0) -> object:
raise urllib.error.HTTPError(request.full_url, 400, 'Bad Request', Message(), Broken())
monkeypatch.setattr(urllib.request, 'urlopen', opener)
with pytest.raises(api.HttpRefused, match='error body unreadable: ConnectionResetError'):
api.Http('http://fixture.invalid').post('/tokenize', {'model': api.MODEL})
assert 'unreadable' in capsys.readouterr().err
def test_cancel_stream_refusal_also_logs_the_body(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
monkeypatch.setattr(urllib.request, 'urlopen', refuse)
class Idle:
def post(self, path: str, data: dict[str, Json]) -> dict[str, Json]: raise AssertionError('no post')
def metrics(self) -> str: return 'vllm:num_requests_running 0\nvllm:num_requests_waiting 0\n'
with pytest.raises(api.HttpRefused, match='HTTP 400 Bad Request on POST /v1/chat/completions'):
api.cancel('http://fixture.invalid', Idle(), time.time()+60)
assert json.loads(capsys.readouterr().err.strip())['body'] == IMAGE_400
def test_main_uses_the_logging_client() -> None:
assert issubclass(api.Http, wc.Http) and api.Http.post is not wc.Http.post
source = Path(api.__file__).read_text()
assert 'wc.Http(wc.URL)' not in source and 'api = Http(wc.URL)' in source
|