"""Bounded functional fixtures; no device, socket, capture or load generation.""" import json import threading import time from queue import Queue, Empty from types import SimpleNamespace import numpy as np import pytest from k1link.sessions.live_planning import PlanningLiveEvent from k1link.missions.live_buffer import LiveCloudBuffer from k1link.missions.live_tests import PlanningLiveTests from k1link.missions.registration_colors import query_colors from k1link.missions.live_scene import scene_bytes from k1link.compute.live_perception import LivePerceptionIngress from k1link.device_plugins.xgrids_k1.planning_live import K1PlanningLiveSource from test_stream_summary import _pose_payload, _pcl_payload def event(kind, *, t=1, p=(0,0,0), points=None, sequence=1): return PlanningLiveEvent('B',2,sequence,int(t*1e9),int(t*1e9),kind,points,p) def test_causal_pose_and_bounded_point_window(): b=LiveCloudBuffer([[10,10,0],[14,10,0]]) cloud=np.array([[1,1,1],[1.01,1,1],[99,1,1],[1,1,20]]) b.ingest(event('points',points=cloud));assert len(b.snapshot()['points'])==0 b.ingest(event('pose',t=2)) b.ingest(event('points',t=1.9,points=cloud));assert len(b.snapshot()['points'])==0 b.ingest(event('points',t=2.6,points=cloud));assert len(b.snapshot()['points'])==0 b.ingest(event('points',t=2.1,points=cloud));assert len(b.snapshot()['points'])==1 # No double pose transform: query remains in its native local K1 frame. assert np.allclose(b.snapshot()['points'][0],[1,1,1]) for i in range(1,80): b.ingest(event('pose',t=3+i,p=(i*.05,0,0))) b.ingest(event('points',t=3.1+i,points=cloud,sequence=i)) assert len(b.chunks)==40 and len(b.events)==40 assert len(b.snapshot()['points'])<=40000 with pytest.raises(ValueError,match='Разрыв координат'):b.ingest(event('pose',t=200,p=(100,0,0))) def test_route_initialization_can_request_an_explicit_wider_k1_scene(): b=LiveCloudBuffer([[0,0,0],[10,0,0]],point_radius_m=80) b.ingest(event('pose',t=1,p=(0,0,0))) b.ingest(event('points',t=1.1,points=np.array([[79.9,0,1],[80.1,0,1]]))) sample=b.snapshot() assert sample['point_radius_m']==80 assert len(sample['points'])==1 def test_only_accepted_correspondences_are_green(): p=np.array([[0,0,0],[0,0,1],[0,0,2]]) green=np.array([154,235,75]) assert not (query_colors(p)==green).all(axis=1).any() assert not (query_colors(p,{'status':'rejected','matched_query_indices':[0,1,2]})==green).all(axis=1).any() c=query_colors(p,{'status':'candidate','matched_query_indices':[1]}) assert (c==green).all(axis=1).tolist()==[False,True,False] def test_plugin_adapter_reads_existing_committed_ingress_only(): ingress=LivePerceptionIngress();adapter=K1PlanningLiveSource(ingress) adapter.open('test');ingress.begin_session('B') assert adapter.take('test').kind=='session-start' ingress.publish(modality='pose',source_id='x/lio_pose',source_sequence=3, captured_at_epoch_ns=4,received_monotonic_ns=5,payload=_pose_payload((5,0,0))) p=adapter.take('test');assert p.position==(5.,0.,0.) and p.generation==1 ingress.publish(modality='lidar',source_id='x/lio_pcl',source_sequence=4, captured_at_epoch_ns=5,received_monotonic_ns=6,payload=_pcl_payload(scaler=1000,point_count=4)) frame=adapter.take('test');assert np.allclose(frame.points[0],[1,-2,.5]) with pytest.raises(RuntimeError):adapter.open('other-profile') adapter.close('test');assert not ingress.snapshot()['consumer_connected'] ingress.close() class Source: def __init__(self):self.state=dict(active=False,session_generation=1,session_id='old');self.queue=Queue();self.owner=None def snapshot(self):return dict(self.state) def open(self,id): if self.owner:raise RuntimeError('busy') self.owner=id def close(self,id):assert self.owner==id;self.owner=None def take(self,id): try:return self.queue.get(timeout=.02) except Empty:return None def fixture_service(tmp_path,monkeypatch): import k1link.missions.live_tests as module draft=dict(id='draft',name='Test route',revision=1,zone=dict(session_id='A',generation='a'), route=dict(length_m=20,start_index=0,end_index=20,points=[dict(position=[i,0,0]) for i in range(21)])) r=np.random.default_rng(19);points=r.normal(size=(1000,3)) sources=SimpleNamespace(store=SimpleNamespace(get_session=lambda _:SimpleNamespace(plugin_id='test-plugin')), reference_map=lambda *args,**kwargs:(points,dict(session_id='A',source_digests={'raw':'a'*64}))) drafts=SimpleNamespace(database=tmp_path/'db',sources=sources,get=lambda _:json.loads(json.dumps(draft))) source=Source();lock=threading.Lock();service=PlanningLiveTests(drafts,{'test-plugin':source},lock) def calculate(directory,ref,query,hint): # Real numeric regression is tested separately; this fixture verifies orchestration. return dict(status='candidate',T_reference_query=hint.tolist(),matched_query_indices=[0], overlap=.9,inlier_rmse_m=.1,reasons=[]) monkeypatch.setattr(module,'run_registration',calculate) from k1link.missions.route_relocalization import ROUTE_RELOCALIZATION_POLICY monkeypatch.setattr(module,'run_route_relocalization', lambda directory,ref,path,query,anchor:dict( status='candidate',T_reference_query=np.eye(4).tolist(), matched_query_indices=[0],overlap=.9,inlier_rmse_m=.1,reasons=[], initialization=dict(complete=True,scope='selected-route', policy=ROUTE_RELOCALIZATION_POLICY,expected_attempts=1,attempts=[{}]))) return service,source,lock,draft def until(fn): deadline=time.monotonic()+5 while time.monotonic()