test descriptions
This commit is contained in:
@@ -40,7 +40,7 @@ def _get_json(url: str) -> dict:
|
||||
|
||||
|
||||
def test_tracker_send_json_ignores_broken_pipe_after_client_disconnect():
|
||||
"""A disconnected client must not dump a BrokenPipe traceback from the tracker."""
|
||||
"A disconnected client must not dump a BrokenPipe traceback from the tracker.\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _TrackerHandler
|
||||
|
||||
class BrokenPipeWriter:
|
||||
@@ -63,6 +63,7 @@ def test_tracker_send_json_ignores_broken_pipe_after_client_disconnect():
|
||||
|
||||
|
||||
def test_tracker_exposes_registered_model_metadata():
|
||||
"Tracker exposes registered model metadata\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
@@ -109,6 +110,7 @@ def test_tracker_exposes_registered_model_metadata():
|
||||
|
||||
|
||||
def test_tracker_lists_recommended_kimi_before_nodes_register():
|
||||
"Tracker lists recommended kimi before nodes register\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
@@ -129,6 +131,7 @@ def test_tracker_lists_recommended_kimi_before_nodes_register():
|
||||
|
||||
|
||||
def test_network_map_exposes_pool_size_and_speed_summary():
|
||||
"Network map exposes pool size and speed summary\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
@@ -170,6 +173,7 @@ def test_network_map_exposes_pool_size_and_speed_summary():
|
||||
|
||||
|
||||
def test_network_map_exposes_served_model_copy_count():
|
||||
"Network map exposes served model copy count\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
@@ -223,6 +227,7 @@ def test_network_map_exposes_served_model_copy_count():
|
||||
|
||||
|
||||
def test_recommended_kimi_becomes_deployable_when_pool_is_large_enough():
|
||||
"Recommended kimi becomes deployable when pool is large enough\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
@@ -251,6 +256,7 @@ def test_recommended_kimi_becomes_deployable_when_pool_is_large_enough():
|
||||
|
||||
|
||||
def test_network_assign_can_start_recommended_kimi_by_short_name():
|
||||
"Network assign can start recommended kimi by short name\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
url = f"http://127.0.0.1:{port}"
|
||||
@@ -269,7 +275,7 @@ def test_network_assign_can_start_recommended_kimi_by_short_name():
|
||||
|
||||
|
||||
def test_tracker_serves_health_while_proxy_request_is_in_flight():
|
||||
"""Long inference proxy requests must not block heartbeats/health checks."""
|
||||
"Long inference proxy requests must not block heartbeats/health checks.\n\nTags: http, routing, tracker"
|
||||
|
||||
class SlowChatHandler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
@@ -332,6 +338,7 @@ def test_tracker_serves_health_while_proxy_request_is_in_flight():
|
||||
|
||||
|
||||
def test_tracker_route_log_counts_proxy_inflight_requests():
|
||||
"Tracker route log counts proxy inflight requests\n\nTags: http, routing, tracker"
|
||||
entered = threading.Event()
|
||||
release = threading.Event()
|
||||
|
||||
@@ -416,6 +423,7 @@ def test_tracker_route_log_counts_proxy_inflight_requests():
|
||||
|
||||
|
||||
def test_tracker_logs_stream_progress_before_request_completes():
|
||||
"Tracker logs stream progress before request completes\n\nTags: http, routing, streaming, tracker"
|
||||
chunk_sent = threading.Event()
|
||||
release = threading.Event()
|
||||
|
||||
@@ -504,6 +512,7 @@ def test_tracker_logs_stream_progress_before_request_completes():
|
||||
|
||||
|
||||
def test_tracker_stream_survives_idle_gap_between_sse_chunks():
|
||||
"Tracker stream survives idle gap between sse chunks\n\nTags: http, routing, streaming, tracker"
|
||||
first_chunk_sent = threading.Event()
|
||||
|
||||
class IdleStreamingChatHandler(http.server.BaseHTTPRequestHandler):
|
||||
@@ -573,6 +582,7 @@ def test_tracker_stream_survives_idle_gap_between_sse_chunks():
|
||||
|
||||
|
||||
def test_tracker_dashboard_can_cancel_inflight_proxy():
|
||||
"Tracker dashboard can cancel inflight proxy\n\nTags: http, routing, tracker"
|
||||
chunk_sent = threading.Event()
|
||||
release = threading.Event()
|
||||
|
||||
@@ -667,7 +677,7 @@ def test_tracker_dashboard_can_cancel_inflight_proxy():
|
||||
|
||||
|
||||
def test_tracker_routes_hf_model_alias_from_quickstart():
|
||||
"""The documented qwen2.5-0.5b alias resolves a full HF repo registration."""
|
||||
"The documented qwen2.5-0.5b alias resolves a full HF repo registration.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -694,7 +704,7 @@ def test_tracker_routes_hf_model_alias_from_quickstart():
|
||||
|
||||
|
||||
def test_tracker_proxy_accepts_hf_model_alias_from_quickstart():
|
||||
"""The tracker proxy accepts the same model alias used by the quickstart curl."""
|
||||
"The tracker proxy accepts the same model alias used by the quickstart curl.\n\nTags: http, routing, tracker"
|
||||
|
||||
class ChatHandler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
@@ -751,7 +761,7 @@ def test_tracker_proxy_accepts_hf_model_alias_from_quickstart():
|
||||
|
||||
|
||||
def test_tracker_proxy_routes_split_preset_nodes_by_alias():
|
||||
"""The chat proxy must build routes from alias-matched preset nodes."""
|
||||
"The chat proxy must build routes from alias-matched preset nodes.\n\nTags: http, routing, tracker"
|
||||
|
||||
class ChatHandler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
@@ -833,6 +843,7 @@ def test_tracker_proxy_routes_split_preset_nodes_by_alias():
|
||||
|
||||
|
||||
def test_tracker_route_endpoint_routes_split_preset_nodes_by_alias():
|
||||
"Tracker route endpoint routes split preset nodes by alias\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"qwen3.6-35b-a3b": {
|
||||
"layers_start": 0,
|
||||
@@ -879,6 +890,7 @@ def test_tracker_route_endpoint_routes_split_preset_nodes_by_alias():
|
||||
|
||||
|
||||
def test_tracker_route_endpoint_ignores_model_case_and_outer_whitespace():
|
||||
"Tracker route endpoint ignores model case and outer whitespace\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"qwen3.6-35b-a3b": {
|
||||
"layers_start": 0,
|
||||
@@ -912,8 +924,7 @@ def test_tracker_route_endpoint_ignores_model_case_and_outer_whitespace():
|
||||
|
||||
|
||||
def test_tracker_route_prefers_distributed_over_single_full_shard():
|
||||
"""When a full 0-39 node and a partial 0-21 head coexist, /v1/route
|
||||
should return both hops — not the full shard alone."""
|
||||
"When a full 0-39 node and a partial 0-21 head coexist, /v1/route\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"qwen3.6-35b-a3b": {
|
||||
"layers_start": 0,
|
||||
@@ -963,6 +974,7 @@ def test_tracker_route_prefers_distributed_over_single_full_shard():
|
||||
|
||||
|
||||
def test_tracker_proxy_ignores_model_case_and_outer_whitespace():
|
||||
"Tracker proxy ignores model case and outer whitespace\n\nTags: http, routing, tracker"
|
||||
class ChatHandler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
pass
|
||||
@@ -1025,6 +1037,7 @@ def test_tracker_proxy_ignores_model_case_and_outer_whitespace():
|
||||
|
||||
|
||||
def test_tracker_registration_node_id_includes_wallet_prefix_and_stable_suffix():
|
||||
"Tracker registration node id includes wallet prefix and stable suffix\n\nTags: http, routing, security, tracker, wallet"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
wallet = "7j77FsPY1evV8tuf4Z73AVrWwxBEW1pvKwi4EvcRD3g"
|
||||
@@ -1065,7 +1078,7 @@ def test_tracker_registration_node_id_includes_wallet_prefix_and_stable_suffix()
|
||||
|
||||
|
||||
def test_tracker_node_registration():
|
||||
"""A node can register with the tracker and receives a node_id."""
|
||||
"A node can register with the tracker and receives a node_id.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1087,7 +1100,7 @@ def test_tracker_node_registration():
|
||||
|
||||
|
||||
def test_tracker_route_selection():
|
||||
"""Tracker returns ordered route when nodes collectively cover all layers."""
|
||||
"Tracker returns ordered route when nodes collectively cover all layers.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1115,7 +1128,7 @@ def test_tracker_route_selection():
|
||||
|
||||
|
||||
def test_tracker_route_error_no_coverage():
|
||||
"""Tracker returns 503 when registered nodes do not cover all required layers."""
|
||||
"Tracker returns 503 when registered nodes do not cover all required layers.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1138,7 +1151,7 @@ def test_tracker_route_error_no_coverage():
|
||||
|
||||
|
||||
def test_tracker_coverage_endpoint_reports_uncovered_ranges():
|
||||
"""Coverage endpoint returns compressed layer ranges with node counts."""
|
||||
"Coverage endpoint returns compressed layer ranges with node counts.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 6,
|
||||
@@ -1183,7 +1196,7 @@ def test_tracker_coverage_endpoint_reports_uncovered_ranges():
|
||||
|
||||
|
||||
def test_tracker_coverage_endpoint_accepts_registered_hf_repo_or_short_name():
|
||||
"""Coverage endpoint supports real HF models registered outside preset catalog."""
|
||||
"Coverage endpoint supports real HF models registered outside preset catalog.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1220,6 +1233,7 @@ def test_tracker_coverage_endpoint_accepts_registered_hf_repo_or_short_name():
|
||||
|
||||
|
||||
def test_tracker_models_endpoint_lists_registered_hf_repo_and_short_name_alias():
|
||||
"Tracker models endpoint lists registered hf repo and short name alias\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1242,7 +1256,7 @@ def test_tracker_models_endpoint_lists_registered_hf_repo_and_short_name_alias()
|
||||
|
||||
|
||||
def test_tracker_auto_assigns_new_node_to_uncovered_range_first():
|
||||
"""Capability-driven registration fills the first uncovered layer gap."""
|
||||
"Capability-driven registration fills the first uncovered layer gap.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 8,
|
||||
@@ -1271,7 +1285,7 @@ def test_tracker_auto_assigns_new_node_to_uncovered_range_first():
|
||||
|
||||
|
||||
def test_tracker_speed_weighted_vram_assignment_covers_model():
|
||||
"""Three auto-assigned nodes reach 100% coverage with widest range on largest VRAM."""
|
||||
"Three auto-assigned nodes reach 100% coverage with widest range on largest VRAM.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 12,
|
||||
@@ -1315,6 +1329,7 @@ def test_tracker_speed_weighted_vram_assignment_covers_model():
|
||||
|
||||
|
||||
def test_tracker_speed_is_primary_when_both_nodes_can_cover_gap():
|
||||
"Tracker speed is primary when both nodes can cover gap\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 12,
|
||||
@@ -1349,6 +1364,7 @@ def test_tracker_speed_is_primary_when_both_nodes_can_cover_gap():
|
||||
|
||||
|
||||
def test_tracker_registration_directive_is_not_replayed_on_heartbeat():
|
||||
"Tracker registration directive is not replayed on heartbeat\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 2,
|
||||
@@ -1372,7 +1388,7 @@ def test_tracker_registration_directive_is_not_replayed_on_heartbeat():
|
||||
|
||||
|
||||
def test_tracker_pool_join_adds_redundant_copy_without_splitting_incumbent():
|
||||
"""A second managed node with capacity for the full model keeps the first copy intact."""
|
||||
"A second managed node with capacity for the full model keeps the first copy intact.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 4,
|
||||
@@ -1410,7 +1426,7 @@ def test_tracker_pool_join_adds_redundant_copy_without_splitting_incumbent():
|
||||
|
||||
|
||||
def test_tracker_explicit_full_copy_join_preserves_existing_serving_node():
|
||||
"""--model style joins with explicit shards add redundancy instead of reshuffling."""
|
||||
"--model style joins with explicit shards add redundancy instead of reshuffling.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(heartbeat_timeout=10.0)
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1449,6 +1465,7 @@ def test_tracker_explicit_full_copy_join_preserves_existing_serving_node():
|
||||
|
||||
|
||||
def test_tracker_periodic_rebalance_purges_expired_nodes_without_requests():
|
||||
"Tracker periodic rebalance purges expired nodes without requests\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(
|
||||
heartbeat_timeout=0.05,
|
||||
rebalance_interval=0.02,
|
||||
@@ -1470,6 +1487,7 @@ def test_tracker_periodic_rebalance_purges_expired_nodes_without_requests():
|
||||
|
||||
|
||||
def test_tracker_faster_node_receives_wider_range_when_capacity_ties():
|
||||
"Tracker faster node receives wider range when capacity ties\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 12,
|
||||
@@ -1500,7 +1518,7 @@ def test_tracker_faster_node_receives_wider_range_when_capacity_ties():
|
||||
|
||||
|
||||
def test_tracker_rebalances_after_middle_range_node_timeout():
|
||||
"""Killing a middle shard queues LOAD_SHARD and restores coverage."""
|
||||
"Killing a middle shard queues LOAD_SHARD and restores coverage.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(
|
||||
heartbeat_timeout=0.15,
|
||||
model_presets={
|
||||
@@ -1548,7 +1566,7 @@ def test_tracker_rebalances_after_middle_range_node_timeout():
|
||||
|
||||
|
||||
def test_tracker_rebalances_managed_hf_node_after_peer_timeout():
|
||||
"""HF nodes auto-assigned by the tracker receive LOAD_SHARD after a peer dies."""
|
||||
"HF nodes auto-assigned by the tracker receive LOAD_SHARD after a peer dies.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(heartbeat_timeout=0.15, rebalance_interval=10.0)
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1584,7 +1602,7 @@ def test_tracker_rebalances_managed_hf_node_after_peer_timeout():
|
||||
|
||||
|
||||
def test_tracker_route_error_no_nodes():
|
||||
"""Tracker returns 503 with clear error when the registry is empty."""
|
||||
"Tracker returns 503 with clear error when the registry is empty.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1600,7 +1618,7 @@ def test_tracker_route_error_no_nodes():
|
||||
|
||||
|
||||
def test_tracker_heartbeat_updates_node():
|
||||
"""Sending a heartbeat for a registered node succeeds."""
|
||||
"Sending a heartbeat for a registered node succeeds.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1620,7 +1638,7 @@ def test_tracker_heartbeat_updates_node():
|
||||
|
||||
|
||||
def test_tracker_heartbeat_stores_current_requests():
|
||||
"""Node-reported in-flight request snapshots appear on the network map."""
|
||||
"Node-reported in-flight request snapshots appear on the network map.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1668,6 +1686,7 @@ def test_tracker_heartbeat_stores_current_requests():
|
||||
|
||||
|
||||
def test_normalize_current_requests_sanitizes_payload():
|
||||
"Normalize current requests sanitizes payload\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _normalize_current_requests
|
||||
|
||||
assert _normalize_current_requests(None) == []
|
||||
@@ -1684,7 +1703,7 @@ def test_normalize_current_requests_sanitizes_payload():
|
||||
|
||||
|
||||
def test_tracker_heartbeat_expiry():
|
||||
"""Nodes that miss their heartbeat window are excluded from routes."""
|
||||
"Nodes that miss their heartbeat window are excluded from routes.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(heartbeat_timeout=0.05) # 50 ms
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1713,7 +1732,7 @@ def test_tracker_heartbeat_expiry():
|
||||
|
||||
|
||||
def test_tracker_heartbeat_expiry_removes_node_from_registry():
|
||||
"""Expired nodes are removed, not merely hidden from route responses."""
|
||||
"Expired nodes are removed, not merely hidden from route responses.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(heartbeat_timeout=0.05)
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1741,7 +1760,7 @@ def test_tracker_heartbeat_expiry_removes_node_from_registry():
|
||||
|
||||
|
||||
def test_tracker_route_rejects_non_extending_overlap():
|
||||
"""Overlapping shards that do not extend coverage cannot form a complete route."""
|
||||
"Overlapping shards that do not extend coverage cannot form a complete route.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1766,6 +1785,7 @@ def test_tracker_route_rejects_non_extending_overlap():
|
||||
|
||||
|
||||
def test_tracker_console_records_model_not_available():
|
||||
"Tracker console records model not available\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1790,7 +1810,7 @@ def test_tracker_console_records_model_not_available():
|
||||
|
||||
|
||||
def test_tracker_registration_rejects_invalid_payload():
|
||||
"""Registration errors return a defined JSON 400 response."""
|
||||
"Registration errors return a defined JSON 400 response.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1810,6 +1830,7 @@ def test_tracker_registration_rejects_invalid_payload():
|
||||
|
||||
|
||||
def test_tracker_registration_rejects_invalid_max_loaded_shards():
|
||||
"Tracker registration rejects invalid max loaded shards\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -1829,6 +1850,7 @@ def test_tracker_registration_rejects_invalid_max_loaded_shards():
|
||||
|
||||
|
||||
def test_network_map_exposes_node_capacity_limits():
|
||||
"Network map exposes node capacity limits\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 12,
|
||||
@@ -1859,7 +1881,7 @@ def test_network_map_exposes_node_capacity_limits():
|
||||
|
||||
|
||||
def test_tracker_capacity_uses_ram_when_node_has_no_vram():
|
||||
"""CPU-only nodes should expose RAM-backed shard capacity, not default GPU capacity."""
|
||||
"CPU-only nodes should expose RAM-backed shard capacity, not default GPU capacity.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 20,
|
||||
@@ -1888,7 +1910,7 @@ def test_tracker_capacity_uses_ram_when_node_has_no_vram():
|
||||
|
||||
|
||||
def test_rebalance_keeps_one_active_range_even_when_multiple_slots_advertised():
|
||||
"""max_loaded_shards is exposed but reserved until node runtime supports multi-range serving."""
|
||||
"max_loaded_shards is exposed but reserved until node runtime supports multi-range serving.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 8,
|
||||
@@ -1915,7 +1937,7 @@ def test_rebalance_keeps_one_active_range_even_when_multiple_slots_advertised():
|
||||
|
||||
|
||||
def test_tracker_routes_only_nodes_for_requested_model():
|
||||
"""A node registered for one model cannot satisfy another model's route."""
|
||||
"A node registered for one model cannot satisfy another model's route.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"model-a": {"layers_start": 0, "layers_end": 31},
|
||||
"model-b": {"layers_start": 0, "layers_end": 31},
|
||||
@@ -1938,7 +1960,7 @@ def test_tracker_routes_only_nodes_for_requested_model():
|
||||
|
||||
|
||||
def test_tracker_assignment_coverage_is_model_scoped():
|
||||
"""Shard assignment gaps are computed only from nodes serving the same model."""
|
||||
"Shard assignment gaps are computed only from nodes serving the same model.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"model-a": {"layers_start": 0, "layers_end": 31},
|
||||
"model-b": {"layers_start": 0, "layers_end": 31},
|
||||
@@ -1959,7 +1981,7 @@ def test_tracker_assignment_coverage_is_model_scoped():
|
||||
|
||||
|
||||
def test_tracker_excludes_banned_wallets_from_routes():
|
||||
"""Tracker refuses route candidates whose wallet is banned on-chain."""
|
||||
"Tracker refuses route candidates whose wallet is banned on-chain.\n\nTags: http, routing, security, tracker, wallet"
|
||||
contracts = LocalSolanaContracts()
|
||||
contracts.registry.submit_stake("wallet-a", 1_000)
|
||||
contracts.registry.submit_stake("wallet-b", 1_000)
|
||||
@@ -1994,7 +2016,7 @@ def test_tracker_excludes_banned_wallets_from_routes():
|
||||
|
||||
|
||||
def test_tracker_rejects_banned_wallet_registration():
|
||||
"""A banned wallet cannot register with the tracker."""
|
||||
"A banned wallet cannot register with the tracker.\n\nTags: http, routing, security, tracker, wallet"
|
||||
contracts = LocalSolanaContracts()
|
||||
contracts.registry.submit_stake("wallet-b", 1_000)
|
||||
contracts.registry.ban_wallet("wallet-b")
|
||||
@@ -2017,7 +2039,7 @@ def test_tracker_rejects_banned_wallet_registration():
|
||||
|
||||
|
||||
def test_tracker_ban_registration_guard_reads_contract_state():
|
||||
"""The tracker registration guard reads ban status from the contract facade."""
|
||||
"The tracker registration guard reads ban status from the contract facade.\n\nTags: http, routing, tracker"
|
||||
contracts = LocalSolanaContracts()
|
||||
contracts.registry.ban_wallet("wallet-b")
|
||||
|
||||
@@ -2027,7 +2049,7 @@ def test_tracker_ban_registration_guard_reads_contract_state():
|
||||
|
||||
|
||||
def test_gateway_route_guard_rejects_banned_wallet_metadata():
|
||||
"""Gateway rejects tracker routes that include a wallet now banned on-chain."""
|
||||
"Gateway rejects tracker routes that include a wallet now banned on-chain.\n\nTags: http, routing, security, tracker, wallet"
|
||||
contracts = LocalSolanaContracts()
|
||||
contracts.registry.ban_wallet("wallet-b")
|
||||
|
||||
@@ -2040,7 +2062,7 @@ def test_gateway_route_guard_rejects_banned_wallet_metadata():
|
||||
|
||||
|
||||
def test_gateway_routes_even_when_compute_node_has_low_stake():
|
||||
"""Routing is not based on a compute node's token balance."""
|
||||
"Routing is not based on a compute node's token balance.\n\nTags: http, routing, tracker"
|
||||
contracts = LocalSolanaContracts()
|
||||
contracts.registry.submit_stake("wallet-a", 1_000)
|
||||
contracts.registry.submit_stake("wallet-b", 5)
|
||||
@@ -2094,7 +2116,7 @@ def test_gateway_routes_even_when_compute_node_has_low_stake():
|
||||
|
||||
|
||||
def test_gateway_records_compute_attribution_after_inference_session():
|
||||
"""Gateway records layer and token attribution for every routed node."""
|
||||
"Gateway records layer and token attribution for every routed node.\n\nTags: http, routing, tracker"
|
||||
contracts = LocalSolanaContracts()
|
||||
contracts.registry.submit_stake("wallet-a", 1_000)
|
||||
contracts.registry.submit_stake("wallet-b", 1_000)
|
||||
@@ -2153,7 +2175,7 @@ def test_gateway_records_compute_attribution_after_inference_session():
|
||||
|
||||
|
||||
def test_gateway_returns_json_503_when_tracker_unavailable():
|
||||
"""Tracker-backed gateway failures are reported as JSON HTTP errors."""
|
||||
"Tracker-backed gateway failures are reported as JSON HTTP errors.\n\nTags: http, routing, tracker"
|
||||
gateway = GatewayServer(tracker_url="http://127.0.0.1:9")
|
||||
gateway_port = gateway.start()
|
||||
try:
|
||||
@@ -2180,7 +2202,7 @@ def test_gateway_returns_json_503_when_tracker_unavailable():
|
||||
|
||||
|
||||
def test_gateway_returns_json_503_for_malformed_tracker_route():
|
||||
"""Malformed tracker route payloads do not drop the gateway connection."""
|
||||
"Malformed tracker route payloads do not drop the gateway connection.\n\nTags: http, routing, tracker"
|
||||
|
||||
class MalformedRouteHandler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
@@ -2226,7 +2248,7 @@ def test_gateway_returns_json_503_for_malformed_tracker_route():
|
||||
|
||||
|
||||
def test_two_node_pipeline_via_tracker():
|
||||
"""End-to-end: nodes register with tracker; gateway discovers route dynamically."""
|
||||
"End-to-end: nodes register with tracker; gateway discovers route dynamically.\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
tracker_url = f"http://127.0.0.1:{tracker_port}"
|
||||
@@ -2283,6 +2305,7 @@ def test_two_node_pipeline_via_tracker():
|
||||
# ---------------------------------------------------------------- US-025 stats
|
||||
|
||||
def test_stats_endpoint_returns_empty_models_initially():
|
||||
"Stats endpoint returns empty models initially\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
port = tracker.start()
|
||||
try:
|
||||
@@ -2294,6 +2317,7 @@ def test_stats_endpoint_returns_empty_models_initially():
|
||||
|
||||
|
||||
def test_stats_rolling_counter_rpm_reflects_recorded_requests():
|
||||
"Stats rolling counter rpm reflects recorded requests\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _RollingCounter
|
||||
|
||||
counter = _RollingCounter(num_buckets=60, bucket_seconds=60)
|
||||
@@ -2306,6 +2330,7 @@ def test_stats_rolling_counter_rpm_reflects_recorded_requests():
|
||||
|
||||
|
||||
def test_stats_collector_records_and_returns_rpms():
|
||||
"Stats collector records and returns rpms\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _StatsCollector
|
||||
|
||||
collector = _StatsCollector()
|
||||
@@ -2321,6 +2346,7 @@ def test_stats_collector_records_and_returns_rpms():
|
||||
|
||||
|
||||
def test_stats_collector_records_node_model_tokens_per_second():
|
||||
"Stats collector records node model tokens per second\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _StatsCollector
|
||||
|
||||
collector = _StatsCollector()
|
||||
@@ -2339,6 +2365,7 @@ def test_stats_collector_records_node_model_tokens_per_second():
|
||||
|
||||
|
||||
def test_stats_collector_merges_peer_rpms_additively():
|
||||
"Stats collector merges peer rpms additively\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _StatsCollector
|
||||
|
||||
collector = _StatsCollector()
|
||||
@@ -2355,6 +2382,7 @@ def test_stats_collector_merges_peer_rpms_additively():
|
||||
|
||||
|
||||
def test_stats_sqlite_persistence_survives_restart(tmp_path):
|
||||
"Stats sqlite persistence survives restart\n\nTags: http, persistence, routing, tracker"
|
||||
from meshnet_tracker.server import _StatsCollector
|
||||
|
||||
db = str(tmp_path / "stats.db")
|
||||
@@ -2375,6 +2403,7 @@ def test_stats_sqlite_persistence_survives_restart(tmp_path):
|
||||
|
||||
|
||||
def test_stats_gossip_endpoint_merges_peer_slice():
|
||||
"Stats gossip endpoint merges peer slice\n\nTags: gossip, http, network, routing, tracker"
|
||||
tracker = TrackerServer(hive_secret=_TEST_HIVE_SECRET)
|
||||
port = tracker.start()
|
||||
try:
|
||||
@@ -2421,9 +2450,7 @@ def _make_node(node_id, shard_start, shard_end, bench=1.0, queue_depth=0, wallet
|
||||
|
||||
|
||||
def test_select_route_overlapping_shards_uses_start_layer_protocol():
|
||||
"""_select_route with overlapping shards: A(0-22) and B(20-24).
|
||||
Route must be [A, B]; B's effective start_layer = 23 (after A ends at 22).
|
||||
"""
|
||||
"_select_route with overlapping shards: A(0-22) and B(20-24).\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
node_a = _make_node("A", 0, 22)
|
||||
@@ -2437,7 +2464,7 @@ def test_select_route_overlapping_shards_uses_start_layer_protocol():
|
||||
|
||||
|
||||
def test_select_route_no_overlap_three_nodes():
|
||||
"""Non-overlapping shards are covered in the right order."""
|
||||
"Non-overlapping shards are covered in the right order.\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
nodes = [_make_node("C", 8, 15), _make_node("A", 0, 7), _make_node("B", 16, 23)]
|
||||
@@ -2448,6 +2475,7 @@ def test_select_route_no_overlap_three_nodes():
|
||||
|
||||
|
||||
def test_select_route_gap_returns_error():
|
||||
"Select route gap returns error\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
nodes = [_make_node("A", 0, 5), _make_node("B", 10, 15)]
|
||||
@@ -2457,7 +2485,7 @@ def test_select_route_gap_returns_error():
|
||||
|
||||
|
||||
def test_select_route_prefers_higher_throughput_node_when_shards_equal():
|
||||
"""When two nodes cover the same interval, prefer the faster / less-loaded one."""
|
||||
"When two nodes cover the same interval, prefer the faster / less-loaded one.\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
fast = _make_node("fast", 0, 11, bench=10.0, queue_depth=0) # effective = 10.0
|
||||
@@ -2470,7 +2498,7 @@ def test_select_route_prefers_higher_throughput_node_when_shards_equal():
|
||||
|
||||
|
||||
def test_select_route_prefers_observed_model_throughput_over_startup_benchmark():
|
||||
"""Observed per-model TPS is the primary routing signal once available."""
|
||||
"Observed per-model TPS is the primary routing signal once available.\n\nTags: http, performance, routing, tracker"
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
high_benchmark = _make_node("bench-fast", 0, 11, bench=100.0, queue_depth=0)
|
||||
@@ -2484,7 +2512,7 @@ def test_select_route_prefers_observed_model_throughput_over_startup_benchmark()
|
||||
|
||||
|
||||
def test_select_route_throughput_accounts_for_queue_depth():
|
||||
"""A nominally faster node with high queue depth loses to a slower idle node."""
|
||||
"A nominally faster node with high queue depth loses to a slower idle node.\n\nTags: http, routing, tracker"
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
busy_fast = _make_node("busy", 0, 11, bench=20.0, queue_depth=9) # effective = 2.0
|
||||
@@ -2501,10 +2529,7 @@ def test_select_route_throughput_accounts_for_queue_depth():
|
||||
|
||||
|
||||
def test_select_route_prefers_higher_reputation_when_throughput_equal():
|
||||
"""AH-009 test-first item 3: among candidates that advance coverage
|
||||
equally with the same effective throughput, the higher-reputation
|
||||
wallet's node wins the tiebreak (ADR-0018 §6 -- earnings scale with
|
||||
tenure/standing)."""
|
||||
"AH-009 test-first item 3: among candidates that advance coverage\n\nTags: http, routing, tracker"
|
||||
from meshnet_contracts import LocalSolanaContracts
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
@@ -2521,9 +2546,7 @@ def test_select_route_prefers_higher_reputation_when_throughput_equal():
|
||||
|
||||
|
||||
def test_select_route_reputation_never_overrides_coverage():
|
||||
"""A lower-reputation node that covers more layers still wins -- the
|
||||
reputation multiplier is a tiebreak among equal-coverage candidates,
|
||||
never a substitute for coverage maximization."""
|
||||
"A lower-reputation node that covers more layers still wins -- the\n\nTags: http, routing, tracker"
|
||||
from meshnet_contracts import LocalSolanaContracts
|
||||
from meshnet_tracker.server import _select_route
|
||||
|
||||
@@ -2541,8 +2564,7 @@ def test_select_route_reputation_never_overrides_coverage():
|
||||
|
||||
|
||||
def test_two_stub_nodes_complete_pipeline_via_tracker():
|
||||
"""Integration: two StubNodeServer instances serving complementary shards
|
||||
produce a full inference response through the tracker route."""
|
||||
"Integration: two StubNodeServer instances serving complementary shards\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"two-node-model": {"layers_start": 0, "layers_end": 3}
|
||||
})
|
||||
@@ -2575,7 +2597,7 @@ def test_two_stub_nodes_complete_pipeline_via_tracker():
|
||||
|
||||
|
||||
def test_route_timeout_config_is_exposed_on_server():
|
||||
"""TorchNodeServer exposes the route_timeout that was configured."""
|
||||
"TorchNodeServer exposes the route_timeout that was configured.\n\nTags: http, routing, tracker"
|
||||
from meshnet_node.torch_server import TorchNodeServer
|
||||
|
||||
class _MinimalBackend:
|
||||
@@ -2592,6 +2614,7 @@ def test_route_timeout_config_is_exposed_on_server():
|
||||
|
||||
|
||||
def test_torch_node_applies_tracker_load_shard_directive(monkeypatch):
|
||||
"Torch node applies tracker load shard directive\n\nTags: http, routing, tracker"
|
||||
from meshnet_node import torch_server
|
||||
from meshnet_node.torch_server import TorchNodeServer
|
||||
|
||||
@@ -2637,15 +2660,7 @@ def test_torch_node_applies_tracker_load_shard_directive(monkeypatch):
|
||||
|
||||
|
||||
def test_shard_heal_cycle_surviving_node_covers_dead_peers_gap(monkeypatch):
|
||||
"""End-to-end heal: kill one managed node, surviving node receives LOAD_SHARD and hot-swaps.
|
||||
|
||||
Cycle:
|
||||
1. Two managed nodes (A: 0-11, B: 12-23) register with tracker.
|
||||
2. Node A stops heartbeating; tracker expires it and triggers rebalance.
|
||||
3. Node B's next heartbeat response contains LOAD_SHARD(0, 23).
|
||||
4. Node B (TorchNodeServer) applies the directive — backend hot-swapped.
|
||||
5. Coverage endpoint confirms full model is covered by Node B alone.
|
||||
"""
|
||||
"End-to-end heal: kill one managed node, surviving node receives LOAD_SHARD and hot-swaps.\n\nTags: http, routing, tracker"
|
||||
from meshnet_node import torch_server
|
||||
from meshnet_node.torch_server import TorchNodeServer
|
||||
|
||||
@@ -2746,6 +2761,7 @@ def test_shard_heal_cycle_surviving_node_covers_dead_peers_gap(monkeypatch):
|
||||
|
||||
|
||||
def test_network_map_exposes_memory_pool():
|
||||
"Network map exposes memory pool\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"tiny-model": {
|
||||
"total_layers": 8,
|
||||
@@ -2774,6 +2790,7 @@ def test_network_map_exposes_memory_pool():
|
||||
|
||||
|
||||
def test_same_endpoint_can_register_multiple_models():
|
||||
"Same endpoint can register multiple models\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
@@ -2803,6 +2820,7 @@ def test_same_endpoint_can_register_multiple_models():
|
||||
|
||||
|
||||
def test_scale_demanded_models_queues_add_shard_on_spare_host():
|
||||
"Scale demanded models queues add shard on spare host\n\nTags: http, routing, tracker"
|
||||
tracker = TrackerServer(model_presets={
|
||||
"model-a": {
|
||||
"total_layers": 4,
|
||||
|
||||
Reference in New Issue
Block a user