holy fuking shit

This commit is contained in:
Horldsence
2026-09-12 01:56:34 +08:00
parent 36f354736c
commit d975309a81
83 changed files with 2748 additions and 896 deletions
+25 -23
View File
@@ -1,5 +1,7 @@
"""真实 HTTP 承载测试:接口仅回环、仅窗口期开放、协议边界与官方一致。"""
# pylint: disable=redefined-outer-name # pytest 夹具名必须与测试函数形参同名,非遮蔽缺陷
import http.client
import json
import time
@@ -20,7 +22,7 @@ def wait_until(condition, timeout=5.0, interval=0.02):
@pytest.fixture()
def referee(tmp_path):
def local_referee(tmp_path):
ref = Referee(action_port=0, data_dir=tmp_path / "data")
ActionInterfaceServer(ref)
return ref
@@ -43,20 +45,20 @@ def post(port, path, obj=None, *, headers=None, method="POST", raw=None):
return response.status, parsed
def payload(id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=id, **extra)
def payload(request_id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=request_id, **extra)
def start_open_session(referee, **opts):
def start_open_session(local_referee, **opts):
opts.setdefault("countdown_s", 0.05)
session = referee.start("q3_practice", "2026001", **opts)
session = local_referee.start("q3_practice", "2026001", **opts)
assert wait_until(lambda: session.phase == "open")
return session
def test_binding_is_loopback_only(referee):
session = start_open_session(referee)
server = referee._open_interface.__self__ # ActionInterfaceServer
def test_binding_is_loopback_only(local_referee):
_session = start_open_session(local_referee)
server = local_referee._open_interface.__self__ # ActionInterfaceServer
port = open_port(server)
assert server._httpd.server_address[0] == "127.0.0.1"
status, body = post(port, "/enter", payload("enter"))
@@ -64,17 +66,17 @@ def test_binding_is_loopback_only(referee):
assert body["remaining_real_duration_s"] == 1200
def test_interface_refuses_connections_outside_window(referee):
session = referee.start("q3_practice", "2026001", countdown_s=5)
server = referee._open_interface.__self__
def test_interface_refuses_connections_outside_window(local_referee):
session = local_referee.start("q3_practice", "2026001", countdown_s=5)
server = local_referee._open_interface.__self__
assert server.is_open is False # 倒计时期间未监听
assert wait_until(lambda: session.phase == "open", timeout=6)
assert server.is_open is True # 窗口开启后才绑定端口
def test_full_protocol_round_trip_over_http(referee):
session = start_open_session(referee)
server = referee._open_interface.__self__
def test_full_protocol_round_trip_over_http(local_referee):
session = start_open_session(local_referee)
server = local_referee._open_interface.__self__
port = open_port(server)
status, body = post(port, "/enter", payload("enter-1"))
assert status == 200 and body["accepted"] is True
@@ -94,9 +96,9 @@ def test_full_protocol_round_trip_over_http(referee):
assert session.end_reason == "user_exit"
def test_protocol_error_statuses_over_http(referee):
start_open_session(referee)
server = referee._open_interface.__self__
def test_protocol_error_statuses_over_http(local_referee):
start_open_session(local_referee)
server = local_referee._open_interface.__self__
port = open_port(server)
assert post(port, "/unknown", payload())[0] == 404
assert post(port, "/enter/", payload())[0] == 404
@@ -109,9 +111,9 @@ def test_protocol_error_statuses_over_http(referee):
assert status == 200 and body["accepted"] is True
def test_interface_closes_after_window_timeout(referee):
session = start_open_session(referee, window_s=0.4)
server = referee._open_interface.__self__
def test_interface_closes_after_window_timeout(local_referee):
session = start_open_session(local_referee, window_s=0.4)
server = local_referee._open_interface.__self__
port = open_port(server)
assert wait_until(lambda: session.phase == "finished", timeout=5)
assert session.end_reason == "window_timeout"
@@ -120,9 +122,9 @@ def test_interface_closes_after_window_timeout(referee):
post(port, "/enter", payload("late"))
def test_recorded_feed_contains_every_request(referee):
session = start_open_session(referee)
server = referee._open_interface.__self__
def test_recorded_feed_contains_every_request(local_referee):
session = start_open_session(local_referee)
server = local_referee._open_interface.__self__
port = open_port(server)
post(port, "/enter", payload("enter-1"))
post(port, "/unknown", payload("bad"))
+6 -4
View File
@@ -11,8 +11,8 @@ from referee import LocalSimulator, SimulatorClosed
from referee.interface import handle_raw
def payload(id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=id, **extra)
def payload(request_id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=request_id, **extra)
def post(sim, path, body=None, headers=None, method="POST", target=None):
@@ -60,7 +60,8 @@ def test_raw_size_and_depth_boundaries_are_inclusive():
("PUT", "/clear", None, 405),
("POST", "/enter", [("Content-Type", "text/plain")], 415),
("POST", "/enter", [("Content-Type", "application/json;foo=bar")], 415),
("POST", "/enter", [("Content-Type", "application/json"), ("Content-Type", "application/json")], 415),
("POST", "/enter",
[("Content-Type", "application/json"), ("Content-Type", "application/json")], 415),
("POST", "/enter", [("Content-Type", "application/json"), ("Content-Encoding", "gzip")], 415),
("POST", "/enter", [("content-type", "Application/JSON; charset=UTF-8")], 200),
("POST", "/enter", [("content-type", 'application/json;charset="utf-8"')], 200),
@@ -84,7 +85,8 @@ def test_on_request_callback_receives_parsed_payload():
seen = []
raw = json.dumps(payload("enter-1")).encode()
handle_raw(sim, "POST", "/enter", [("Content-Type", "application/json")], raw,
on_request=lambda path, payload_dict, result: seen.append((path, payload_dict, result)))
on_request=lambda path, payload_dict, result: seen.append(
(path, payload_dict, result)))
assert seen[0][0] == "/enter"
assert seen[0][1]["request_id"] == "enter-1"
assert seen[0][2].status == 200
+2 -1
View File
@@ -20,6 +20,7 @@ HARNESS = REPO / "referee" / "tests" / "js" / "replay_harness.cjs"
@pytest.mark.skipif(shutil.which("node") is None, reason="需要 node 环境运行 JS 桩测试")
def test_replay_player_logic():
result = subprocess.run(["node", str(HARNESS)], capture_output=True, text=True,
env={**os.environ, "REPO": str(REPO)}, timeout=60)
env={**os.environ, "REPO": str(REPO)}, timeout=60,
check=False)
assert result.returncode == 0, f"回放器逻辑测试失败:\n{result.stdout}\n{result.stderr}"
assert "ALL PASS" in result.stdout
+3 -3
View File
@@ -65,7 +65,7 @@ def test_cannot_start_while_running_and_restart_after_finish(tmp_path):
def test_formal_attempts_accounting(tmp_path):
referee = Referee(action_port=0, data_dir=tmp_path)
for _ in range(FORMAL_ATTEMPTS):
session = referee.start("q3_formal", "2026001", countdown_s=0.05)
_session = referee.start("q3_formal", "2026001", countdown_s=0.05)
referee.abort() # 正式中止仍占用机会 [题面 4.5]
with pytest.raises(RefereeError):
referee.start("q3_formal", "2026001", countdown_s=0.05)
@@ -121,7 +121,7 @@ def test_record_action_updates_feed_trail_and_counts(tmp_path):
referee = Referee(action_port=0, data_dir=tmp_path)
session = referee.start("q3_practice", "2026001", countdown_s=0.05, window_s=5)
assert wait_until(lambda: session.phase == "open")
sim = session.simulator
_sim = session.simulator
referee.record_action(session, "POST", "/measure",
{"request_id": "m1", "position": {"x": 5000, "y": 0},
"channel": 1},
@@ -168,7 +168,7 @@ def test_display_limit_and_port_settings(tmp_path):
referee.set_display_limit(50)
with pytest.raises(RefereeError):
referee.set_display_limit(5001)
session = referee.start("q3_practice", "2026001", countdown_s=5)
_session = referee.start("q3_practice", "2026001", countdown_s=5)
with pytest.raises(RefereeError):
referee.set_action_port(8080) # 测试进行中不能改端口
referee.abort()
+23 -14
View File
@@ -1,7 +1,6 @@
"""评测引擎一致性:时限、状态机、观测判定、幂等与量化(移植自参赛队仓库并扩展)。"""
from concurrent.futures import ThreadPoolExecutor
from dataclasses import replace
from threading import Event
import pytest
@@ -9,12 +8,12 @@ import pytest
from referee import FieldSpec, LocalSimulator, SimulatorClosed, Source, Spec
def payload(id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=id, **extra)
def payload(request_id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=request_id, **extra)
def measure(id="m", x=0, y=0, channel=1):
return payload(id, position=dict(x=x, y=y), channel=channel)
def measure(request_id="m", x=0, y=0, channel=1):
return payload(request_id, position=dict(x=x, y=y), channel=channel)
# ---------------------------------------------------------------- 时限 [题面 附件1 2.5]
@@ -92,7 +91,7 @@ def test_error_rejection_does_not_consume_id():
sim = LocalSimulator([])
for bad in ["e\n", "e\u200b", "e\u0085", "e\ud800"]:
assert sim.handle("/enter", payload(bad))[0] == 400
assert sim._idempotency == {}
assert not sim._idempotency
assert sim.handle("/enter", payload())[1]["accepted"]
@@ -181,6 +180,8 @@ def test_huge_numeric_fields_return_bad_request():
(dict(arena_id="other", robot_id="2026001", request_id="e"), False),
(dict(arena_id="default", robot_id="someone-else", request_id="e"), False),
])
# pytest 参数化保留 expect 列(用例矩阵可读性),当前断言恒为 False,故不参与断言
# pylint: disable-next=unused-argument
def test_unknown_fields_and_mismatch_are_business_rejected(body, expect):
sim = LocalSimulator([])
status, response = sim.handle("/enter", body)
@@ -204,12 +205,14 @@ def test_channel_and_position_validation():
]
for extra in cases:
assert sim.handle("/measure", payload("m", **extra))[0] == 400
assert sim.handle("/measure", payload("ok", position=dict(x=0, y=0), channel=1.0))[1]["accepted"]
assert sim.handle("/measure",
payload("ok", position=dict(x=0, y=0), channel=1.0))[1]["accepted"]
def test_actions_before_enter_are_business_rejected():
sim = LocalSimulator([])
for path, body in [("/measure", measure()), ("/clear", payload("c", position=dict(x=0, y=0), channel=1)),
for path, body in [("/measure", measure()),
("/clear", payload("c", position=dict(x=0, y=0), channel=1)),
("/exit", payload("x"))]:
status, response = sim.handle(path, body)
assert status == 200 and response["accepted"] is False
@@ -224,7 +227,8 @@ def test_clear_rules():
assert ok["clear_result"] == "success"
again = sim.handle("/clear", payload("c5b", position=dict(x=10, y=0), channel=5))[1]
assert again["clear_result"] == "no_target_in_range" # 同一源只能清除一次
assert sim.handle("/clear", payload("c6", position=dict(x=15, y=0), channel=6))[1]["clear_result"] == "success"
cleared = sim.handle("/clear", payload("c6", position=dict(x=15, y=0), channel=6))[1]
assert cleared["clear_result"] == "success"
miss = sim.handle("/clear", payload("c9", position=dict(x=0, y=0), channel=9))[1]
assert miss["clear_result"] == "no_target_in_range"
assert sim.channel == 1 # /clear 不切换测向机频道 [题面 附件1 2.3]
@@ -238,15 +242,20 @@ def test_measure_observation_matrix():
sim = LocalSimulator(sources, error_half_deg=0.0)
sim.handle("/enter", payload("enter"))
# 超出有效接收半径
assert sim.handle("/measure", measure("a", -600, 0, channel=1))[1]["measure_result"] == "no_signal"
assert sim.handle("/measure",
measure("a", -600, 0, channel=1))[1]["measure_result"] == "no_signal"
# 接收半径边界:distance == r_eff 仍有信号 [题面 附件2 2.1 "超过"]
assert sim.handle("/measure", measure("b", -500, 0, channel=1))[1]["measure_result"] == "direction"
assert sim.handle("/measure",
measure("b", -500, 0, channel=1))[1]["measure_result"] == "direction"
# 覆盖角边界:源2 朝东,正东方向(bearing=0°)恰在 ±90° 覆盖内
assert sim.handle("/measure", measure("c", 200, 0, channel=2))[1]["measure_result"] == "direction"
assert sim.handle("/measure",
measure("c", 200, 0, channel=2))[1]["measure_result"] == "direction"
# 覆盖角边界:bearing=90°(正北)仍在覆盖内(含边界)[题面 附件2 2.2]
assert sim.handle("/measure", measure("c2", 100, 100, channel=2))[1]["measure_result"] == "direction"
assert sim.handle("/measure",
measure("c2", 100, 100, channel=2))[1]["measure_result"] == "direction"
# 覆盖角外(bearing≈135° > 90°):无信号
assert sim.handle("/measure", measure("d", 90, 10, channel=2))[1]["measure_result"] == "no_signal"
assert sim.handle("/measure",
measure("d", 90, 10, channel=2))[1]["measure_result"] == "no_signal"
# 距离过近:不返回 svd_deg [题面 附件2 2.4]
near = sim.handle("/measure", measure("near", 998, 0, channel=1))[1]
assert near["measure_result"] == "near" and "svd_deg" not in near
+4 -4
View File
@@ -4,12 +4,12 @@ from referee import CostBreakdown, LocalSimulator, Source, clear_cost, measure_c
from referee.timing import move_time
def payload(id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=id, **extra)
def payload(request_id="e", **extra):
return dict(arena_id="default", robot_id="2026001", request_id=request_id, **extra)
def measure(id="m", x=0, y=0, channel=1):
return payload(id, position=dict(x=x, y=y), channel=channel)
def measure(request_id="m", x=0, y=0, channel=1):
return payload(request_id, position=dict(x=x, y=y), channel=channel)
def test_attachment_timing_example():
+21 -19
View File
@@ -1,5 +1,7 @@
"""WebUI API 测试:状态快照、启动/中止/配置、会话列表与回放数据源。"""
# pylint: disable=redefined-outer-name # pytest 夹具名必须与测试函数形参同名,非遮蔽缺陷
import http.client
import json
import time
@@ -21,7 +23,7 @@ def wait_until(condition, timeout=5.0, interval=0.02):
@pytest.fixture()
def ui(tmp_path):
def webui(tmp_path):
referee = Referee(action_port=0, data_dir=tmp_path / "data")
ActionInterfaceServer(referee)
server = WebUIServer(referee, 0)
@@ -43,8 +45,8 @@ def call(port, method, path, obj=None):
return response.status, json.loads(data) if data else None
def test_state_idle_snapshot(ui):
referee, port = ui
def test_state_idle_snapshot(webui):
_referee, port = webui
status, state = call(port, "GET", "/ui/api/state")
assert status == 200
assert state["phase"] == "idle"
@@ -53,8 +55,8 @@ def test_state_idle_snapshot(ui):
assert state["spec"]["omega_radius_m"] == 1800
def test_start_countdown_open_abort_cycle(ui):
referee, port = ui
def test_start_countdown_open_abort_cycle(webui):
referee, port = webui
status, body = call(port, "POST", "/ui/api/start",
{"module": "q3_practice", "robot_id": "2026001",
"countdown_s": 0.05, "window_s": 5})
@@ -70,21 +72,21 @@ def test_start_countdown_open_abort_cycle(ui):
assert state["truth"]["revealed"] is True # 演练结束显示真值统计
def test_start_rejects_running_session_and_bad_module(ui):
referee, port = ui
def test_start_rejects_running_session_and_bad_module(webui):
_referee, port = webui
call(port, "POST", "/ui/api/start",
{"module": "q3_practice", "robot_id": "2026001", "countdown_s": 5})
status, body = call(port, "POST", "/ui/api/start",
{"module": "q3_practice", "robot_id": "2026001"})
status, _body = call(port, "POST", "/ui/api/start",
{"module": "q3_practice", "robot_id": "2026001"})
assert status == 409
status, body = call(port, "POST", "/ui/api/start",
{"module": "nope", "robot_id": "2026001"})
status, _body = call(port, "POST", "/ui/api/start",
{"module": "nope", "robot_id": "2026001"})
assert status == 409
call(port, "POST", "/ui/api/abort", {})
def test_config_endpoints(ui):
referee, port = ui
def test_config_endpoints(webui):
_referee, port = webui
status, body = call(port, "POST", "/ui/api/config", {"display_limit": 100})
assert body["configured"]["display_limit"] == 100
status, body = call(port, "POST", "/ui/api/config", {"display_limit": 1})
@@ -93,8 +95,8 @@ def test_config_endpoints(ui):
assert body["configured"]["action_port"] == 8080
def test_sessions_list_and_replay_source(ui):
referee, port = ui
def test_sessions_list_and_replay_source(webui):
referee, port = webui
call(port, "POST", "/ui/api/start",
{"module": "q3_practice", "robot_id": "2026001",
"countdown_s": 0.05, "window_s": 5, "seed": 11, "source_count": 10})
@@ -125,8 +127,8 @@ def test_sessions_list_and_replay_source(ui):
assert call(port, "GET", "/ui/api/replay?case=../etc")[0] == 400
def test_static_page_served(ui):
_, port = ui
def test_static_page_served(webui):
_, port = webui
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
conn.request("GET", "/")
response = conn.getresponse()
@@ -140,8 +142,8 @@ def test_static_page_served(ui):
assert 'id="rp-stage"' in page
def test_replay_script_served_with_file_import(ui):
_, port = ui
def test_replay_script_served_with_file_import(webui):
_, port = webui
conn = http.client.HTTPConnection("127.0.0.1", port, timeout=5)
conn.request("GET", "/replay.js")
response = conn.getresponse()