Files
EscapistsReintroduced/tests/test_prison_approach_evidence.py
T

83 lines
5.5 KiB
Python

"""Independent native route policy regression; no input/display operations."""
import copy
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
import prison_approach_evidence as v
def fixture():
samples={}
for i in range(21,36):
moved=min(6,max(0,i-24))*.15
p=[6092.77,211.44,7157.1-moved]
active=25<=i<=29
samples[i]={'sample':i,'real_ms':1000+i*100,
'input':{'origin':p,'forward':1. if active else 0.,'input':[0,0,1 if active else 0],'velocity':[0,0,-1.5 if active else 0]},
'look':{'direction':[0,0,-1]},
'action':{'pressed':0.,'performing':False,'progress':0.},
'geometry':{'body':p,'prison':[6090,211.44,7160],'gate':[6090,211.34,7154],'context':[6092.77,213.22,7154],'gate_forward':[0,0,1],'door_state':0.,'door_control':0.}}
data={'snapshots':samples,'ready':{'sample':20,'real_ms':3000}}
look={'return_sample':20,'finished_monotonic':9.}
proof={'final_sample':35,'requested':True,'completed':True,'nonce':'N','paired_release_errors':[],'unconfirmed_held_key':None,'started_monotonic':10.,'finished_monotonic':12.,'deadline_monotonic':35.,'events':[{'key':'W','down':True,'monotonic':10.5},{'key':'W','down':False,'monotonic':10.8}],'stops':[{'before_sample':24,'after_sample':33}],'motions':[],'samples':[{'sample':i,'real_ms':1000+i*100,'received_monotonic':10+(i-21)*.1} for i in range(21,36)]}
return data,look,proof
class RouteEvidence(unittest.TestCase):
def run_case(self, mutate=None, geometry=True, calibration=True):
d,l,p=fixture()
if mutate:mutate(d,l,p)
with patch.object(v,'join_gate_snapshots',return_value=d),patch.object(v,'validate_gate_geometry',return_value={'verified':geometry,'errors':[]}),patch.object(v,'validate_look_evidence',return_value={'verified':calibration,'errors':[],'response_yaw_degrees':2.}) as validator:
r=v.validate_approach_evidence('raw','N',l,p)
if r['verified']:self.assertEqual(validator.call_args.args[-1],p['events'][:2])
return r
def reject(self, mutation):
r=self.run_case(mutation);self.assertFalse(r['verified'],r);self.assertTrue(r['errors'])
def test_stopped_interior_route_is_limited_not_escape(self):
r=self.run_case();self.assertTrue(r['verified'],r);self.assertAlmostEqual(r['path_metres'],.9);self.assertAlmostEqual(r['final_distance_metres'],.7);self.assertFalse(r['escape_verified']);self.assertFalse(r['held_action_verified'])
def test_no_helper_completion_or_release_shortcut(self):
for field,value in [('completed',False),('nonce','foreign'),('paired_release_errors',['failed']),('unconfirmed_held_key','W')]:
self.reject(lambda d,l,p:p.update({field:value}))
def test_requires_both_independent_raw_gate_and_calibration_verification(self):
self.assertFalse(self.run_case(geometry=False)['verified']);self.assertFalse(self.run_case(calibration=False)['verified'])
def test_actual_held_time_and_key_shape_not_requested_sleep(self):
self.reject(lambda d,l,p:p['events'][1].update(monotonic=11.))
self.reject(lambda d,l,p:p['events'][0].update(key='F'))
self.reject(lambda d,l,p:p['events'][0].update(down=1))
self.reject(lambda d,l,p:p['events'][1].update(monotonic=10.4))
def test_final_coasting_or_wrong_waypoint_not_success(self):
self.reject(lambda d,l,p:d['snapshots'][35]['input'].update(velocity=[0,0,.1]))
self.reject(lambda d,l,p:d['snapshots'][34]['input'].update(velocity=[0,0,.1]))
self.reject(lambda d,l,p:d['snapshots'][35]['input'].update(origin=[6092.77,211.44,7156.4]))
def test_native_forward_input_required(self):
def mutation(d,l,p):
for s in d['snapshots'].values():s['input']['forward']=0
self.reject(mutation)
def test_gate_operation_or_teleport_refused(self):
self.reject(lambda d,l,p:d['snapshots'][27]['geometry'].update(door_state=.1))
self.reject(lambda d,l,p:d['snapshots'][27]['action'].update(pressed=1))
self.reject(lambda d,l,p:d['snapshots'][27]['input'].update(origin=[6092.77,211.44,7153.5]))
self.reject(lambda d,l,p:d['snapshots'][27].update(real_ms=5000))
def test_final_reference_and_unexpected_input_rejected(self):
self.reject(lambda d,l,p:p.update(final_sample=34))
self.reject(lambda d,l,p:d['snapshots'][27]['input'].update(input=[.2,0,1]))
def no_z(d,l,p):
for item in d['snapshots'].values():item['input']['input']=[0,0,0]
self.reject(no_z)
def test_native_clock_and_sample_references_bound(self):
self.reject(lambda d,l,p:d['ready'].update(real_ms=-40000))
self.reject(lambda d,l,p:p['stops'][0].update(before_sample=21))
self.reject(lambda d,l,p:p['samples'].append({'sample':35}))
self.reject(lambda d,l,p:p.update(deadline_monotonic=100.))
def test_helper_native_clocks_and_receipts_cannot_be_rewritten(self):
self.reject(lambda d,l,p:p['samples'][3].update(real_ms=99999))
self.reject(lambda d,l,p:p['samples'][3].update(received_monotonic=11.9))
self.reject(lambda d,l,p:p['events'][0].update(monotonic=10.1))
def test_unacknowledged_or_oversized_mouse_proposals_refused(self):
self.reject(lambda d,l,p:p['motions'].append({'dx':129,'dy':0,'monotonic':10.2,'after_sample':21,'response_sample':23}))
self.reject(lambda d,l,p:p['motions'].append({'dx':24,'dy':0,'monotonic':10.2,'after_sample':21,'response_sample':23}))
if __name__=='__main__':unittest.main()